A systems integrator inherits a client's estate, builds inside it for eleven months alongside two other firms, and hands it back. Each workstream ships something agentic. When those agents begin calling each other — the prime's orchestrator invoking a capability the delivery centre built, which spawns a planner nobody scoped, which writes to a ledger the client's own platform team owns — the authority behind each call is whatever credential happened to be in scope. The companion teardown to this piece, Three teams built three agents and nobody owns what happens when they talk, walks the evidence that this is the normal condition rather than a lapse, and that no framework or protocol currently shipping closes it. I am going to take that as established and design the thing that gets handed over.

FIGURE 1 · THE HALF NOBODY SCOPED The contract binds persons who can sign. The run delegates between processes that cannot. WHAT THE CONTRACT NAMES legal persons Client · regulated firm the duty is non-delegable outsourcing does not diminish it master agreement · right to audit liability for subcontractors Prime integrator workstreams 1 and 2 signs, is audited, can be terminated consent to subcontract · notice audit reaching subcontractors Delivery centre · subcontractor workstream 3 named in a schedule, consented to Every edge in this panel is a clause about an entity that can sign, be audited, and be terminated. Two edges. Both of them enforceable. WHAT THE RUN DOES processes client platform agent authorised by a named human at session start no grant object prime orchestrator inherits the ambient session no grant object planning subagent spawned at runtime; nothing records under what no grant object · vendor boundary delivery-centre reconciler authenticates as itself; carries no grant no grant object shared service account → ledger write attributed to the account, not to any principal Four edges. None of them an object. The audit right reaches the subcontractor. It does not reach the subagent the subcontractor spawned. Nothing in either estate records the edge that joins the left panel to the right one.

The deliverable I want is narrow and specific: an explicit delegation contract, produced as an inter-workstream artefact rather than as one vendor's internal control, which the client can verify afterwards without either vendor's cooperation. It has to be implementable inside an estate the delivery team does not own, without a platform migration, and it has to still function when everyone who built it has been rolled off. The reference implementation is the package `@authority/delegation`, and its core is written out below rather than described.

The clause that already exists, and what it does not reach

The reason this lands in the statement of work rather than in an architecture review is that the client cannot move the obligation. The US interagency third-party guidance is explicit in its overview: "Whether activities are performed internally or via a third party, banking organizations are required to operate in a safe and sound manner and in compliance with applicable laws and regulations. A banking organization's use of third parties does not diminish its responsibility to meet these requirements to the same extent as if its activities were performed by the banking organization in-house." The duty does not travel with the work.

Having established that the duty stays put, the same guidance says where the specification goes. Under the contract provision headed "Responsibility for Compliance with Applicable Laws and Regulations": "A banking organization is responsible for conducting its activities in compliance with applicable laws and regulations, including those activities involving third parties. The use of third parties does not abrogate these responsibilities. Therefore, it is important for a contract to specify the obligations of the third party and the banking organization to comply with applicable laws and regulations." The obligation is pushed into contract text — not into the vendor's own supervisor, not into a standards body, not into a platform's default settings.

The nearest thing to an authority-delegation clause is about entities. The guidance's subcontracting provision is the closest US bank supervision comes to governing delegation, and it opens by naming the exact defect: subcontracting "can result in risk due to the absence of a direct relationship between the banking organization and the subcontractor, further lessening the banking organization's direct control of activities." The remedies it then offers are entity remedies. A banking organization "may want to address when and how the third party should notify the banking organization of its use or intent to use a subcontractor and whether specific subcontractors are prohibited"; may consider whether the contract "should prohibit assignment, transfer, or subcontracting of the third party's obligations to another entity without the banking organization's consent"; may include "a provision that states the third party's liability for activities or actions by its subcontractors"; and may "reserve the right to terminate the contract without penalty if the third party's subcontracting arrangements do not comply with contractual obligations." Notify. Consent. Prohibit. Terminate. Every verb presupposes a counterparty that can be notified, can consent, and can be terminated.

The audit right has the same shape and the same reach. The guidance contemplates "provisions for periodic, independent audits of the third party and its relevant subcontractors, consistent with the risk and complexity of the third-party relationship," and reserves "the banking organization's right to conduct its own audits of the third party's activities or to engage an independent party to perform such audits." That is a right that extends past the prime contractor by design. It is also a right that cannot be exercised against a run in which nobody recorded which subagent acted under whose grant. An auditor who arrives with the contractual power to inspect, and finds that every consequential write is attributed to a shared service account, has a right and no object to exercise it on.

India draws the same line in statute rather than guidance, and draws it harder. Section 8(1) of the Digital Personal Data Protection Act, 2023 provides that a Data Fiduciary "shall, irrespective of any agreement to the contrary or failure of a Data Principal to carry out the duties provided under this Act, be responsible for complying with the provisions of this Act and the rules made thereunder in respect of any processing undertaken by it or on its behalf by a Data Processor." Section 8(2) makes the contract mandatory rather than advisable: a Data Fiduciary may involve a processor "only under a valid contract." Section 8(5) extends the same posture to security — the fiduciary must protect personal data "in its possession or under its control, including in respect of any processing undertaken by it or on its behalf by a Data Processor, by taking reasonable security safeguards to prevent personal data breach." A Data Processor, under section 2(k), is "any person who processes personal data on behalf of a Data Fiduciary."

On its behalf is the statutory phrase, and it appears three times. It is precisely the relation that an ambient shared session fails to record. When the delivery centre's reconciler reads a customer record because it inherited a session token, the processing is being done on somebody's behalf as a matter of fact and on nobody's behalf as a matter of evidence. Non-waivable liability plus a mandated contract is a combination that puts the burden of proof on the client and gives the client no artefact with which to discharge it.

The two other instruments a services firm inherits in these markets say the same thing from different directions. The Reserve Bank of India's Master Direction on Outsourcing of Information Technology Services holds that outsourcing does not diminish the regulated entity's obligations, and requires the sub-contracting chain to be contractually visible and liable: prior consent for the service provider's use of sub-contractors, a right to seek information about third parties in the supply chain, clauses making the service provider contractually liable for the performance and risk-management practices of its sub-contractors, and a right of audit reaching the sub-contractors themselves. The Saudi Central Bank's Rules on Outsourcing go further and put a supervisory gate on the delegation step: the contract must prohibit sub-contracting of a material outsourcing without the bank's prior approval and SAMA's no objection, must address the provider's obligations where it subcontracts all or part of the arrangement, and the board retains ultimate responsibility for every outsourcing arrangement. In the Gulf, an unrecorded sub-delegation is not merely an audit gap. It is a step that, had anyone characterised it correctly, would have required a regulator's no objection before it happened.

Whether it should be so characterised is genuinely unresolved, and I am not going to resolve it in an essay. Every subcontracting clause cited above is written about a legal person the prime engages. Nothing in the primary text of the interagency guidance, the RBI Master Direction or the SAMA rules states whether an autonomous subagent spawned mid-run under an inherited credential is a subcontractor for those purposes. Both readings are arguable. What is not arguable is that the question currently has no factual answer either, because nobody is recording the delegation in a form that would let a supervisor ask.

And the framework that would have specified the controls has stepped back. OCC Bulletin 2026-13, "Model Risk Management: Revised Guidance" of 17 April 2026 — issued alongside the parallel Federal Reserve letter SR 26-2, and rescinding OCC 1997-24, 2011-12, 2021-19 and the model-risk-management booklet of the Comptroller's Handbook — is the OCC half of a paired issuance whose Federal Reserve half, SR 26-2, supersedes SR 11-7 and SR 21-8. Those two letters should for that reason no longer be cited as current. The OCC bulletin states in its own text that "Generative AI and agentic AI models are novel and rapidly evolving. As such, they are not within the scope of this guidance," and that "This guidance does not set forth enforceable standards or prescriptive requirements." I read that as a deferral rather than an exemption: every obligation attached to the underlying action — safety and soundness, consumer protection, sectoral duties, third-party risk — is untouched. What was removed is the framework that would have specified the controls.

One more absence belongs here, because it is the strongest thing I know about this problem and it is a negative. No primary publisher — not the banking agencies, not the RBI, not SAMA, not the IETF, not the OpenTelemetry project, not the MCP or A2A projects — publishes any measurement of how attribution accuracy behaves as agent-to-agent handoffs increase. There is no decay curve, no hop-count threshold, no quantified attribution metric anywhere in the primary literature I have been able to read. The sector is signing contracts that assign a duty nobody has measured their ability to discharge. I am not going to supply a number to fill that hole; the honest statement is that the number does not exist, and any figure you are shown for it should be traced to a publisher before it is repeated.

Five premises

Everything below follows from five statements. They are separated out so that a reader who rejects one can say which, rather than arguing with the consequences. The first four are the general design. The fifth is what this sector adds, and it is the one that changes the architecture.

One: authority must be carried, not ambient. A component must act under authority it was handed for the purpose, not authority it happens to possess. NIST's zero-trust tenets state the rule plainly: "Access to individual enterprise resources is granted on a per-session basis... Access should also be granted with the least privileges needed to complete the task... However, authentication and authorization to one resource will not automatically grant access to a different resource." A subagent that can act because a credential was in scope has violated that by construction, and the same document flagged the machine case six years ago: "Artificial intelligence and other software-based agents are being deployed to manage security issues on enterprise networks... How these components authenticate themselves in an enterprise implementing a ZTA is an open issue," with the associated risk being "that an attacker will be able to induce or coerce an NPE to perform some task that the attacker is not privileged to perform." NIST called it open in 2020. Nothing reviewed here has closed it.

Two: every delegation produces a strictly weaker credential that names its parent. If handing work across a workstream boundary produces no new credential, there is no edge to record; the receiving agent simply is the sending one, as far as any resource server can tell. If it produces a credential that does not name its parent, the record has nodes and no edges — a list of grants with no ancestry, which answers who acted and never answers on whose authority. Naming the parent is what turns a pile of credentials into a graph, and the graph is the only object an auditor can walk.

Three: attenuation must be monotone. A child can never hold more than its parent. Not as a rule a reviewer checks and not as a policy an engine enforces, but as a property of the only constructor that exists. This is the premise that does the most work in a multi-vendor engagement, because the alternative — a request-and-issue flow in which the child asks for what it wants and something decides — has a failure mode that only has to fire once, and three delivery teams on a follow-the-sun rota over eleven months are a great many opportunities for it to fire.

Four: the chain must verify offline. A verifier that must call an issuer has coupled every request path to a service's availability, and a verifier that must reconstruct the run from telemetry has made an assurance decision depend on a sampled, best-effort stream. Neither survives an estate assembled from three firms' components. Everything a verifier needs must be in what it was handed, plus a long-lived public key it can hold.

Five: the verifier is a third party who was not present. This is the sector premise. The party who most needs to check the chain is the client, checking it after the engagement has closed, with no ability to compel either vendor's cooperation and possibly with one of those vendors no longer in the market. That rules out anything that requires the prime's runtime to be up, anything that requires the delivery centre to answer a question, and anything whose verification logic lives in a system either vendor operates. The artefact has to be self-describing, and the checker has to be small enough that a client platform team will actually run it.

The strongest objection to the whole programme lands on premises three and five together, and it lands hard enough that it belongs before the design rather than after it.

You are asking two competing firms to agree on a credential format during mobilisation, when they have not yet agreed on a ticketing tool, a branch strategy or a definition of done. Then you are asking a client who deliberately bought an outcome rather than a capability to operate a verifier against that format for years after the people who understood it have gone. The first ask fails at the commercial-manager level and the second fails at the first budget cycle. What you will actually get is a schedule annex that nobody implements and a verifier that is run once, during acceptance, by the same team that wrote it.

Most of that objection is correct, and I do not have a clean answer to it. What I have is a claim about which of those failures leaves a trace. An engagement that never adopts the format fails visibly at mobilisation, in a document, in front of a client who can decide whether to care — and the decision not to carry authority becomes a decision somebody made rather than a property nobody noticed. A verifier that is run once and then abandoned still produced one dated, signed statement of what the estate could and could not attribute, which is one more than the alternative. The design cannot make an organisation care. It can make not caring into an event with a date on it, and my argument for the whole approach is that the difference between an event and an absence is most of what assurance is.

There is a second objection worth stating at full strength, which is that this is a control invented by the party who benefits from selling its implementation. That is a fair reading of any architecture proposed by a consultant, and the defence is not rhetorical: every component below is either a published construction that predates the agent era or forty lines of code that a client's own platform team can read in an afternoon. If the deliverable cannot be handed over and maintained without the firm that built it, it has failed premise five and should not be sold.

What the premises force

Take them in order and ask what each rules out, because the design is mostly the residue.

Carried, not ambient rules out the pattern where a subagent shares a process, an environment or an HTTP client with its parent — which is the default in every agent framework I have read, and which is how a shared session becomes a shared identity. The credential has to be a value passed as an argument, which means it has to be serialisable, which means it has to fit in a header. That sets a size budget, and the size budget is why chain length turns up later as an operational cost rather than a footnote.

Names its parent rules out formats where ancestry is optional metadata, and rules out the obvious shortcut of putting the parent's identifier in a field the child could omit or falsify. The link has to be structural: the child's validity must depend on the parent's, cryptographically, so that a child with a forged or absent parent fails to verify rather than merely looking odd in a report.

Monotone rules out the request-and-issue flow entirely. The child does not ask for authority. It names which of the parent's authority to keep, and the constructor computes the intersection. Anything named that the parent did not hold falls out of the result — not refused, not logged as an attempt, simply absent, the way a number you did not add is absent from a sum. There is no code path in which the answer comes back larger, because that function was never written.

Offline rules out an introspection endpoint on the hot path, a policy-decision-point round trip, and any lookup of the parent grant to confirm it existed. It also rules out the design most integrators reach for first, which is a central authority service the prime operates — because at handover the prime switches it off.

A third-party verifier is the premise that reshapes the deliverable rather than the runtime. It forces the record to be an export rather than a query interface; forces the verification logic to be source the client holds rather than a service the client calls; forces the permission vocabulary to be legible to someone who was not in the design workshops; and forces the report to include coverage rather than only verdicts. A verifier that says "all forty chains verified" about an estate that emitted forty chains and eleven thousand unattributed actions has told the client something true and useless.

Where the existing standards stop

Almost every part of this exists somewhere. It has to be assembled because no single standard carries all five premises, and the two nearest ones explicitly decline.

RFC 8693 has the vocabulary and disarms the chain. OAuth 2.0 Token Exchange draws exactly the distinction this design needs. Under impersonation, "A is given all the rights that B has within some defined rights context and is indistinguishable from B in that context"; under delegation, "principal A still has its own identity separate from B, and it is explicitly understood that while B may have delegated some of its rights to A, any actions taken are being taken by A representing B." By that definition, a subagent running on an inherited session credential is not delegating at all. It is impersonating its parent, in the RFC's own terms, because nothing distinguishes it. The RFC even standardises the chain: "A chain of delegation can be expressed by nesting one act claim within another. The outermost act claim represents the current actor while nested act claims represent prior actors. The least recent actor is the most deeply nested."

And then it makes that chain non-actionable, in normative language: "For the purpose of applying access control policy, the consumer of a token MUST only consider the token's top-level claims and the party identified as the current actor by the act claim. Prior actors identified by any nested act claims are informational only and are not to be considered in access control decisions." So the delegation chain is standardised — as an audit artefact, explicitly not as an enforceable constraint. The RFC also defines a may_act claim, which "makes a statement that one party is authorized to become the actor and act on behalf of another party." That is the right shape for a delegation edge and it is evaluated at the authorization server rather than at the resource, and says nothing about how much authority may cross.

MCP mandates a fresh token per hop and defines nothing that links it to its parent. The Model Context Protocol's authorization specification, revision 2025-06-18, forbids credential pass-through in normative language: "MCP servers MUST NOT accept or transit any other tokens"; "MCP servers MUST validate that access tokens were issued specifically for them as the intended audience"; and where the server calls an upstream API, "The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server MUST NOT pass through the token it received from the MCP client." It names the failure it is preventing: token passthrough "can potentially cause the 'confused deputy' problem, where the downstream API may incorrectly trust the token as if it came from the MCP server." This is a good rule and it is the right rule. Note what it produces, though. Each hop now holds a fresh, separate, correctly-audienced token — and the spec defines no claim that relates that token to the one that caused it to be minted. The chain is broken deliberately at every boundary, for a sound reason, and nothing puts a labelled edge in its place.

A2A transmits the request and not the grant behind it. The Agent2Agent protocol specification, version 1.0.0, defines no in-protocol identity and no on-behalf-of chain between agents. Credentials are obtained and transmitted out of band through standard transport mechanisms — HTTP headers, gRPC metadata — against schemes the agent card declares, each agent independently authenticates its caller, and servers "MUST return an authorization error when the authenticated client lacks required permissions." That is a coherent design decision and it means cross-vendor agent interoperability, as specified today, carries the request and not the authority behind it. When the prime's orchestrator calls the delivery centre's agent, the receiving agent knows who is calling and has no protocol-level way to learn on whose authority.

The telemetry layer completes the picture, and it is the constraint this design must satisfy rather than a gap it fills. The OpenTelemetry semantic conventions for generative AI model the call graph in detail: the operation name, the provider, the request model, input and output messages, system instructions, tool definitions, the conversation identifier, token usage. They define no attribute recording the authorising principal, the credential, the permission, or the delegation under which a tool call was made, and the GenAI-specific attributes carry Development stability. That is the empirical basis for saying the call graph is recoverable from tracing and the authority graph is not: the telemetry standard never modelled it. The absence is the finding; I have located no statement that the project considered and declined such attributes, and characterising it as a refusal would be wrong. Both A2A and these conventions are moving specifications, and anyone leaning on their absences should re-read them before relying on the claim.

The construction, however, has been published for over a decade. Macaroons, presented at NDSS in 2014, are bearer credentials that "embed caveats that attenuate and contextually confine when, where, by who, and for what purpose" a service authorizes a request, built from "nested, chained MACs (e.g., HMACs)" and supporting "decentralized delegation between principals." The property that matters for an agent run is that attenuation is offline and requires no round trip to the issuer. Biscuit turns the same idea into a specification with monotonicity as a structural property: a token is an authority block that "contains rights given to the token holder" followed by blocks that "contain checks that reduce the token's scope"; the holder "can at any time create a new token by adding a block with more checks, thus restricting the rights of the new token"; blocks cannot be removed "without invalidating the signature"; "an operation must comply with all checks in order to be allowed"; and each block carries "the serialized Datalog, the next public key, and the signature by the previous key." A verifiable chain naming its parent, in which a child cannot hold more than its parent. That is premises two, three and four, published and implemented, waiting for a problem bad enough to justify adopting them.

The delegation contract, precisely

The design splits into two objects that are easy to conflate and must not be. The declared contract is a schedule to the statement of work: which workstreams exist, which principals each may act as, which actions and resource patterns each root grant may carry, and what the coverage threshold is at each milestone. It is written by humans, agreed commercially, and is machine-checkable. The runtime grant is the thing that travels on the wire. The declared contract constrains what root grants may be minted; the runtime chain is the evidence of what actually happened. Confusing them produces the familiar consulting artefact where a beautifully specified control has no relationship to the running system.

Three properties of the runtime model are load-bearing and easy to miss.

  1. The constructor computes a meet. attenuate takes a parent grant and a restriction request and returns the intersection. Nothing in the package produces a grant with more authority than its input. Widening is not refused; it does not exist as an operation.
  2. The seal is keyed by the parent's seal. This is the macaroon construction. The holder of a child has the terminal MAC value and cannot invert it to recover its parent's, so it can extend the chain downward and cannot climb it. A vendor holding a narrow grant cannot manufacture the broad one it descended from.
  3. The verifier recomputes rather than reads. The authority recorded on a grant is a convenience for the holder and for the report. The verifier walks from the root, recomputes the running meet itself, and evaluates a grant on what the chain actually permits rather than on what the grant claims. Without this the invariant rests on every minting party being honest, and in this setting the minting parties are three firms with different incentives and one of them is running inside a model's tool loop.
Configuration

The delegation contract — reference implementation

Four files from @authority/delegation. The types and the constructor; the offline verifier; the handover audit the client runs alone; and the property test that is the reason to believe any of it. Cryptography, identifier generation and resource-pattern containment are all injected, because a deliverable that hard-codes a client's key management or parses a client's resource namespace will not survive contact with the second engagement.

Note what is absent: no widen(), no merge(), no way to construct a non-root Grant except through attenuate(). The engagement and workstream identifiers are carried on every grant so that a chain can be read against the statement of work it was authorised under, which is the difference between an audit artefact and a debugging aid.

src/contract.ts
/** Opaque principal identifier. SPIFFE-shaped by convention; this package never parses it. */
export type PrincipalId = string;
/** The engagement this chain belongs to. Two engagements never form one chain. */
export type EngagementId = string;
/** The workstream as named in the statement of work, carried so a chain reads against the SOW. */
export type WorkstreamId = string;

export interface Permission {
  /** Verb in the resource server's namespace, e.g. "ledger:post". Compared, never parsed. */
  readonly action: string;
  /** Resource pattern in the resource server's grammar. Compared via an injected predicate. */
  readonly resource: string;
}

export type Caveat =
  | { readonly kind: "expires-at"; readonly epochMillis: number }
  | { readonly kind: "engagement"; readonly engagement: EngagementId }
  | { readonly kind: "workstream"; readonly workstream: WorkstreamId }
  | { readonly kind: "audience"; readonly system: string }
  | { readonly kind: "max-invocations"; readonly count: number }
  /** Discharged out of band by a named approver; the reference is the client's own ticket id. */
  | { readonly kind: "human-approval"; readonly approver: PrincipalId; readonly reference: string };

export interface Authority {
  readonly permissions: readonly Permission[];
  readonly caveats: readonly Caveat[];
}

export interface Grant {
  readonly id: string;
  /** null only for a root grant. A child's seal depends on its parent's, so this cannot lie. */
  readonly parentId: string | null;
  readonly engagement: EngagementId;
  readonly workstream: WorkstreamId;
  readonly issuer: PrincipalId;
  readonly subject: PrincipalId;
  /** Convenience for the holder and the report. The verifier recomputes instead of trusting. */
  readonly authority: Authority;
  readonly issuedAt: number;
  /** Terminal MAC of the chain, encoded by the injected Mac. */
  readonly seal: string;
}

/** Resource-pattern containment. Injected because the grammar belongs to the resource server. */
export type Covers = (parentPattern: string, childPattern: string) => boolean;

/** Safe default for callers with no pattern grammar: exact match only. Narrower than most
 *  callers want, which is the correct direction for a default to be wrong in. */
export const exactMatch: Covers = (parent, child) => parent === child;

export interface Mac {
  /** Keyed MAC. The previous seal is the key for the next message — the macaroon construction. */
  readonly compute: (key: Uint8Array, message: Uint8Array) => Uint8Array;
  readonly encode: (bytes: Uint8Array) => string;
  readonly decode: (text: string) => Uint8Array;
}

export interface AttenuationContext {
  readonly mac: Mac;
  readonly covers: Covers;
  readonly newId: () => string;
  readonly now: () => number;
}

export interface AttenuationRequest {
  readonly subject: PrincipalId;
  readonly workstream: WorkstreamId;
  /** Which of the parent's permissions to keep. Anything the parent lacks falls out. */
  readonly keep: readonly Permission[];
  readonly addCaveats?: readonly Caveat[];
}

/** Deterministic serialisation. Two implementations must agree byte for byte or a chain minted
 *  by one vendor will not verify at another, so key order is fixed here rather than left to a
 *  runtime's JSON ordering. */
export const canonical = (value: unknown): string => {
  if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
  if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
  const entries = Object.entries(value as Record<string, unknown>)
    .filter(([, v]) => v !== undefined)
    .sort(([a], [b]) => (a < b ? -1 : 1));
  return "{" + entries.map(([k, v]) => JSON.stringify(k) + ":" + canonical(v)).join(",") + "}";
};

const utf8 = (text: string): Uint8Array => new TextEncoder().encode(text);

/** Everything the seal covers: a grant minus its own seal. */
export type GrantBody = Omit<Grant, "seal">;

export const bodyOf = (grant: Grant): GrantBody => {
  const { seal: _seal, ...body } = grant;
  return body;
};

export const sealOf = (mac: Mac, key: Uint8Array, body: GrantBody): string =>
  mac.encode(mac.compute(key, utf8(canonical(body))));

/** The meet. A permission survives only if the parent already held it; caveats accumulate.
 *  This is the whole monotonicity argument: the result is built by filtering the request
 *  against the parent, so it cannot contain anything the parent did not contain. */
export const meet = (
  parent: Authority,
  keep: readonly Permission[],
  addCaveats: readonly Caveat[],
  covers: Covers,
): Authority => ({
  permissions: keep.filter((wanted) =>
    parent.permissions.some(
      (held) => held.action === wanted.action && covers(held.resource, wanted.resource),
    ),
  ),
  caveats: [...parent.caveats, ...addCaveats],
});

export interface RootRequest {
  readonly engagement: EngagementId;
  readonly workstream: WorkstreamId;
  readonly issuer: PrincipalId;
  readonly subject: PrincipalId;
  readonly authority: Authority;
}

/** Minted once, by the client, against the declared contract in the SOW schedule. */
export const rootGrant = (
  request: RootRequest,
  rootKey: Uint8Array,
  ctx: AttenuationContext,
): Grant => {
  const body: GrantBody = {
    id: ctx.newId(),
    parentId: null,
    engagement: request.engagement,
    workstream: request.workstream,
    issuer: request.issuer,
    subject: request.subject,
    authority: request.authority,
    issuedAt: ctx.now(),
  };
  return { ...body, seal: sealOf(ctx.mac, rootKey, body) };
};

/** The only constructor for a non-root grant. There is no counterpart that widens. */
export const attenuate = (
  parent: Grant,
  request: AttenuationRequest,
  ctx: AttenuationContext,
): Grant => {
  const body: GrantBody = {
    id: ctx.newId(),
    parentId: parent.id,
    engagement: parent.engagement,
    workstream: request.workstream,
    issuer: parent.subject,
    subject: request.subject,
    authority: meet(parent.authority, request.keep, request.addCaveats ?? [], ctx.covers),
    issuedAt: ctx.now(),
  };
  // The parent's seal is the key for the child's. A holder of the child has a MAC output and
  // cannot invert it, so the chain extends downward only.
  return { ...body, seal: sealOf(ctx.mac, ctx.mac.decode(parent.seal), body) };
};

What this code does not do: it does not revoke, does not bind a grant to proof of possession of a key, does not implement third-party caveats, and does not interpret a resource pattern. Those are deliberate omissions discussed below, not oversights — each of them costs something that a first engagement should not spend.

The control path

The runtime path is short, which is the main argument for it being adoptable across firms who disagree about everything else.

  1. At mobilisation, the client mints a root grant per workstream against the declared contract in the SOW schedule. The root key never leaves the client. The root grants do.
  2. At session start, the workstream's entry point holds a root grant naming a human or a scheduled authoriser as issuer. Nothing downstream is permitted to mint a root.
  3. At every handoff — a subagent spawn, a tool call, a cross-vendor invocation — the caller calls attenuate with the subset of its own authority the callee needs, and passes the resulting grant beside the request. The token minted for that hop is minted as the protocol requires; the grant is additional to it, not a replacement for it.
  4. At every boundary, the receiver verifies the chain offline against the root public key it holds, evaluates the caveats it can evaluate, and refuses anything the recomputed authority does not permit. A boundary that cannot verify records the action as uncovered rather than assuming.
  5. At the resource server, the consequential action is recorded with the leaf grant identifier alongside whatever the server already logs. This is one column in an existing table, which is why it does not require a migration.
  6. At every milestone, the bundle is exported: the grants, the root key identifier, the engagement. Exported to the client, not stored for the client by the vendor.
  7. At handover, the client runs the audit against its own action log and gets a coverage report. That report is an acceptance artefact, and the coverage threshold in it is a commercial term.
FIGURE 2 · REFERENCE ARCHITECTURE Five layers, four of them additive. The right-hand column is the one that decides whether it survives. LAYER WHO HOLDS IT AFTER HANDOVER LAYER 5 · ASSURANCE SURFACE The verifier the client runs alone offline chain verification · recomputes the meet from the root coverage report: which actions had a chain, which did not conformance test annexed to the statement of work The client, alone Needs neither vendor. Inputs: the export bundle and one public key. LAYER 4 · RECORD Append-only grant log and export bundle one row per grant · parent id, subject, authority, seal per-engagement export, produced at every milestone span links are advisory; verification never reads them The client Exported on a milestone cadence, not at closure. A bundle that arrives once arrives untested. LAYER 3 · BOUNDARY ADAPTERS Where the grant is attached and checked outbound HTTP header · MCP server-side check · agent-to-agent call site each hop mints a fresh token; the grant travels beside it legacy escape hatch: the chain terminates here, coverage marked partial Per system Whoever owns the system owns its adapter. This is the layer that rots first. LAYER 2 · GRANT LIBRARY attenuate() and verify(), and nothing that widens canonical serialisation · injected MAC · injected resource predicate the meet is the only constructor for a non-root grant no network calls, no hosted dependency, no runtime of its own The client, as source Vendored into the repo, not consumed as a service from a departing vendor. LAYER 1 · EXISTING ESTATE Unchanged. No migration. workload identity · secret store · resource servers · existing log pipeline the design assumes these belong to someone else and cannot be replaced The client, already Nothing here changes, which is the whole point. Every layer above the first is additive. Layer 5 runs on an export bundle and one public key.

The architecture has five layers and only one of them touches the existing estate, which is the constraint the sector imposes. Layer one is what is already there: workload identity, secret storage, the resource servers, the log pipeline. The design assumes all of it belongs to someone else, was chosen before the engagement started, and cannot be replaced. Layer two is the grant library, which has no runtime of its own and makes no network calls. Layer three is the boundary adapters, which is where the real work is and where the estate's variety bites. Layer four is the record. Layer five is the verifier, and layer five is the deliverable — the others are how it becomes possible.

FIGURE 3 · ONE CHAIN, TWO BOUNDARIES Authority narrows at every hop, or the hop did not happen. ROOT GRANT named authoriser client, workstream sponsor read: ledger read: counterparty propose: adjustment post: adjustment (limited) caveats: expiry, audience parent: none seal: MAC(root key, body) attenuate keep 3 HOP 1 · PRIME orchestrator prime integrator, workstream 1 read: ledger read: counterparty propose: adjustment post: dropped at this hop caveats: inherited parent: root seal: MAC(root seal, body) attenuate vendor boundary HOP 2 · DELIVERY CENTRE reconciler subcontractor, workstream 3 read: ledger read: counterparty dropped propose: dropped caveats: + engagement, + 15 min parent: hop 1 seal: MAC(hop 1 seal, body) attenuate asks for post HOP 3 · SUBAGENT spawned at runtime read: ledger post: requested, not in the parent, so absent from the result — not denied parent: hop 2 seal: MAC(hop 2 seal, ...) WHAT THE CLIENT CHECKS, ALONE 1 · Recompute every seal from its parent’s. A forged or absent parent does not verify. 2 · Recompute the authority from the root. What a grant states about itself is not evidence. 3 · For every recorded action, find a chain whose recomputed authority permits it. 4 · Report coverage: how many recorded actions had no chain at all. Inputs: the export bundle and one public key. Neither vendor needs to be in the room, in business, or reachable. Step 4 is the one that matters at handover. A verifier that reports only pass or fail on the chains it can see will pass an estate in which almost nothing was instrumented.

Failure modes of this design

A design piece with no honest failure analysis is marketing. These are the ways I expect this to break, in roughly the order I expect to encounter them.

The legacy escape hatch is the first and worst. Some system in every estate accepts only a shared service account and cannot be changed within the engagement — a mainframe adapter, a vendor product with a fixed integration model, a file drop. The chain terminates at the last component that speaks the protocol. Everything past that point is attributed to the adapter, and the design's honest output is a coverage report showing a hole rather than a verdict pretending there is none. This is not a defect I can engineer away, and any consultant claiming full attribution across a real estate in one engagement is describing a greenfield.

Narrower is not always a smaller blast radius. The mental model that attenuation reduces risk monotonically is wrong. A grant narrowed to exactly one high-consequence action is narrower and more dangerous than a broad read-only grant. Monotonicity constrains the shape of the authority graph; it says nothing about the consequence of the leaf. A team that measures its control by counting permissions dropped per hop will optimise itself into a chain of tightly-scoped, catastrophic capabilities.

These are bearer credentials. As written, a grant that leaks is a grant that works, for anyone holding it, until its caveats bite. Binding a grant to proof of possession of a subject key closes that and costs a key-management story at every hop across three firms' infrastructure. I have deliberately left it out of the first build, and a client whose threat model includes credential exfiltration from a delivery partner's environment should put it back in — and should understand that doing so is the point at which this stops being a small deliverable.

Revocation and offline verification pull in opposite directions. Verifying without contacting anyone is exactly what makes a revocation list necessary and awkward. Short expiry caveats are the cheap mitigation, and they trade against long-running agent sessions, which are common. There is no elegant answer here; there is a dial between how fresh a revocation must be and how independent a verifier can be, and the engagement has to set it consciously rather than discover where it landed.

The root grant will be widened at three in the morning. When an incident is running and the agent keeps failing on a permission it legitimately needs, someone will mint a broader root. This design does not prevent that and should not claim to. What it changes is the residue: in an ambient-authority estate the widening is a config change discoverable only by reading deployment history, and here it is a signed, dated, attributed root grant sitting at the head of every chain issued afterwards. The control is not prevention. It is that the decision leaves a body.

A verifier nobody runs is a document. Premise five says the client can verify without either vendor. It does not say the client will. The most likely outcome of this entire design, honestly assessed, is a coverage report produced at acceptance and never again. The mitigations are commercial rather than technical — the coverage threshold as an acceptance criterion, the export produced at every milestone so that running it becomes routine before it becomes final, and the verifier running in the client's pipeline from the first sprint rather than being handed over at the end. All three are things a client can decline.

Partial adoption produces a chain with a hole in the middle. If the prime instruments and the delivery centre does not, you get chains that verify beautifully up to a boundary and then stop, and a report that must distinguish "verified" from "verified as far as it goes". This is why coverage is a first-class output rather than a footnote, and it is also the reason the format must be trivial: a specification that costs a second firm two sprints to adopt will not be adopted by a second firm.

The permission vocabulary ages faster than the estate. Action and resource strings are compared, never parsed, which keeps the library out of the client's namespace and means the library cannot notice when that namespace changes. Rename a resource pattern during a platform upgrade and every chain minted before the rename verifies vacuously — structurally sound, semantically detached. The mitigation is that the declared contract in the SOW is versioned and re-agreed at each milestone, which is an operational burden falling on exactly the person least likely to want it.

It records authority, not correctness. A perfectly formed chain can authorise a disastrous action. Nothing here evaluates whether the reconciliation the agent posted was right, whether the plan it followed was sane, or whether the human at the root understood what they were authorising. This design answers exactly one question — on whose authority, and within what bounds — and a governance programme that treats a green coverage report as an assurance of behaviour has misread the artefact it bought.

What it costs

Five costs, and I am going to be explicit about which of them I have measured.

Latency: unmeasured, and the measurement is designed rather than performed. Per-hop cost is one canonical serialisation and one MAC over a small body, plus the same work again at each verifying boundary, multiplied by chain depth. I have not measured it in a representative estate and will not publish a figure I have not produced. The measurement I intend to run is stated so it can be criticised before it is trusted: wall-clock at the boundary adapter with and without the grant attached, at chain depths one through eight, over a fixed workload, reporting the distribution rather than a mean, with the serialisation and MAC costs separated from transport. Until that runs, the honest statement is that the arithmetic is small and the systems cost is unknown.

Record volume: one row per grant, and grants are minted per hop. A run with a deep agent loop mints grants at a rate closer to span volume than to transaction volume. That is a storage and retention decision the client's existing pipeline will absorb or will not, and it is worth sizing during mobilisation rather than discovering at month nine. It is also the reason the export is per engagement and per milestone rather than a live query surface.

Engineering burden falls almost entirely on layer three. Layers two, four and five are small and are written once. Layer three is per integration point, and its cost is a function of how many distinct call mechanisms the estate contains rather than of how many agents it runs. An estate with three call patterns is cheap; an estate with fourteen is not, and the fourteen are not visible from the architecture diagram. This is the number to establish before quoting the work, and I would treat any estimate produced before that inventory exists as fiction.

The contractual cost is real and lands at the wrong time. The declared contract has to be drafted during mobilisation, when nobody knows what the agents will do, by people whose incentive is to keep the schedule generic. Drafted late it documents what was built; drafted early it constrains it. Both are useful and only the second is worth much. There is no way to make this cheap, and a firm that positions this as a handover artefact rather than a mobilisation artefact will produce the version that documents.

Migration difficulty: deliberately near zero, and that is the design's main commercial claim. Nothing in layer one changes. There is no new identity provider, no new policy engine, no central authority service, no platform to migrate onto, and no hosted dependency on the firm that built it. The grant travels in a header beside credentials the protocol already requires; the record is a column beside logs the resource servers already write; the verifier is a program the client holds as source. That is what makes it implementable inside an inherited estate — and it is also, honestly, what makes it easy for a client to quietly stop maintaining.

What to build first, given one week

Given a week inside a live engagement, this is the order, and most of the architecture above is deliberately not in it.

  1. Days one and two — the library and the property test. The types, the meet, attenuate, verifyChain, and the property test that no chain widens. Vendored into the client's repository from the first commit, with the test running in the client's pipeline rather than the delivery team's. If this is the only thing that survives the engagement, the client still holds something they can read.
  2. Day three — one boundary, chosen by consequence. Pick the single highest-consequence action in the estate — the write that would be quoted in an incident report — and instrument only its call path. One adapter, one resource server column, one root grant. Resist the instinct to cover the whole estate; a thin chain on the action that matters beats a broad chain on the actions that do not.
  3. Day four — the export and the audit. The bundle writer and auditHandover, run against a real day of the client's own action log. The first coverage number will be embarrassing, and the embarrassing number is the deliverable: it is the first time anyone in the programme has seen what fraction of consequential actions can be attributed to a named authorisation.
  4. Day five — the schedule text. Two pages annexed to the statement of work: the declared contract for the instrumented path, the coverage threshold at the next milestone, and the requirement that the export is produced at every milestone rather than at closure. This is the part that makes it an inter-workstream deliverable instead of one team's control, and it is the part a technical team will skip.

What that week deliberately omits: revocation, proof-of-possession binding, third-party caveats, any policy language, any user interface, any coverage of the second and third vendors, and any attempt to instrument the legacy escape hatches. Each is defensible later. None of them is what stands between the engagement and being able to answer, for one action that matters, on whose authority it happened.