/** * Turning GTM's two most misleading write errors into answers. * * Google reports both of the failures below in terms that name neither the * cause nor the fix, and the model reading them cannot recover in one step: * * 404 "Not found or permission denied." on a delete * 400 "Found entity with duplicate name." on a variable create * * The first is the worse of the two, because "or permission denied" sends the * reader to OAuth scopes and container access when the real cause is usually an * id that belongs to a different entity type. GTM numbers tags, triggers and * variables in separate spaces, so trigger 3 and tag 3 are unrelated, and a * caller holding a list of triggers can delete "3" believing it is a tag. That * is not hypothetical: on 2026-08-11 one session listed a workspace, saw no * tags, trigger 3 and variable 4, then deleted tagId 3, triggerId 4 and * variableId 5 in fourteen seconds. All three 404'd, each after seconds of * retries, and the transcript reads like a permissions outage. * * The second is invisible for a specific reason: enabled built-in variables own * their names, but `variables_list` returns only user-defined variables, so a * caller that checks for a clash before creating finds nothing and creates * "Page URL" anyway. In the same session, `built_in_variables_enable` ran five * seconds before the create that collided with it, and the `variables_list` * that followed came back empty. * * Both helpers run ONLY after the API has already refused the write. The * success path is untouched and costs nothing extra: no pre-flight list, no * added latency on the calls that work. Diagnosis is paid for by the request * that already failed. */ import type { GtmClient } from './gtmClient.js'; export interface WorkspaceScope { accountId: string; containerId: string; workspaceId: string; } /** The entity kinds that have their own id space, and therefore this failure mode. */ export type EntityKind = 'tag' | 'trigger' | 'variable'; /** * HTTP status carried by a googleapis error, when it has one. * * Checked before diagnosing so a network fault or an expired token is not * described as a missing entity. Both `code` and `response.status` are read * because gaxios populates them inconsistently across error paths. */ export declare function googleErrorStatus(err: unknown): number | undefined; /** True when Google refused a write because something already owns the name. */ export declare function isDuplicateNameError(err: unknown): boolean; /** * Explain a 404 from a delete, by finding out what the id actually refers to. * * The cross-type check is the point. Reporting "no tag 3 exists" would be true * and would still leave the caller guessing; naming trigger 3 as the thing that * does exist turns a repeated failure into a single correction. * * Falls back to the original error whenever the lookup itself fails, so a * genuine permission problem still reads as one rather than being buried under * a diagnosis that could not be completed. */ export declare function explainMissingEntity(client: GtmClient, scope: WorkspaceScope, kind: EntityKind, id: string, originalError: unknown): Promise; /** * Explain a duplicate-name rejection by naming what already holds the name. * * Built-ins are checked first because they are the invisible case: they own * names, and the list a caller would naturally check does not include them. */ export declare function explainDuplicateName(client: GtmClient, scope: WorkspaceScope, name: string, originalError: unknown): Promise; //# sourceMappingURL=writeDiagnostics.d.ts.map