10

Run Authorization and Capability Tokens

Authorize one immutable deployment before execution, keep broad credentials out of the runtime, and issue short-lived proof for exactly one downstream action.

The enterprise problem and today’s slice

Enterprise problem: If Northstar starts the HelixWorks Supplier Onboarding Agent from a valid session and gives its runtime reusable connector tokens, one stale membership, prompt injection, or compromised dependency can become broad and persistent enterprise access.

Whole-course context: The previous slice produced Alice’s authenticated principal, current Marketing context, app binding, and separately owned service and personal grants; today consumes those facts at run admission without treating any one of them as sufficient.

Today’s slice: We authorize one immutable deployment, require workload identity as a second key, exchange authority across narrow audiences, mint one action-bound capability token, and bound revocation freshness across provider, runtime, and connector boundaries.

End-of-day evidence: Acme receives an allowed run, denied cross-team run, two-key workload proof, changed-argument and replay denials, and mid-run revocation result with complete immutable evidence.

Still unsolved: Model Context Protocol (MCP) discovery, detailed tool and argument policy, human approval, nested agent delegation, source-data trimming, and production infrastructure remain outside this slice.

Thesis: A run token is proof that one subject may start one deployment; it becomes safe downstream authority only after a trusted workload presents it and a gateway narrows it into a single-use action capability.

Smallest complete model: Admit, bind, narrow. Northstar first admits Alice to the Procurement deployment, binds that decision to the attested Supplier Onboarding workload, then narrows one proposed supplier-system change into proof the connector adapter can verify.

This model prevents two common category errors: run admission is not tool permission, and workload identity is not human delegation. Both are necessary before the gateway can mint a capability for one side effect.

Customer outcome and implementation focus

The customer outcome is a reliable, reviewable implementation of 06 run authorization capability tokens. This day introduces the mechanism before policy detail and evidence review; it does not repeat the same customer stories in prose, tables, and diagrams.

Components in focus

Authorization policy service owns decisions; the connector/runtime gateway owns enforcement. Compute: API and isolated worker processes. Storage: PostgreSQL is authoritative for grants and state; Redis is a versioned cache; vault owns secrets; object storage retains redacted evidence only.

Implement the mechanism

Implement the day’s boundary with a current, explicit decision before privileged compute or a downstream call. Bind every effect to a tenant, subject or workload, deployment, resource, and short-lived evidence ID; a cache or model response never grants authority.

Failure modes, trade-offs, and decision rules

Capability systems fail when a broadly reusable token is renamed “capability” without actually narrowing what the holder can do. Acme must choose freshness and token granularity according to consequence.

Reusable decision rule: Admit one immutable run from current facts, require workload and delegated authority as separate keys, then narrow every trust-boundary hop in audience, resource, arguments, lifetime, and replay semantics.

Failure or trade-offConsequenceReusable decision rule
Run token accepted directly by adapterRun admission becomes arbitrary tool accessExchange at each trust boundary and require exact service audience
Workload identity without delegationAny trusted pod can act for any userRequire workload identity AND current run delegation
Capability omits canonical argument digestRuntime changes channel or body after allowBind normalized arguments and reject any mismatch
“Single-use” check is non-atomicConcurrent replay duplicates side effectStore nonce consumption atomically before provider call; add idempotency key where provider supports it
Revalidate every fact on every low-risk readStrong freshness but high latency and owner-service dependenceUse versioned bounded caches for reads; reload immediately for consequential writes
Cache until token expiry for high-risk writeFast path preserves revoked authorityRisk sets freshness: higher consequence means shorter cache and later final check

Implementation and verification

An apparently valid token chain may still accept the wrong workload, audience, arguments, or replay, so implementation proceeds from durable decision to adapter verification.

  1. Persist the full run intersection and decision ID before allocating the Campaign Launch runtime.
  2. Mint a one-time admission for agent-runtime; verify attestation and deployment/environment binding before exchanging it for tool-gateway delegation.
  3. Keep provider credentials behind the broker and give the runtime no Vault route or minting key.
  4. Canonicalize the Slack intent, persist the tool decision, then mint a seconds-lived capability for exact adapter, connector, channel, digest, nonce, and decision.
  5. Atomically consume the nonce at the adapter before secret retrieval; verify audience, workload channel, digest, expiry, and current revocation state.
  6. Publish versioned revocation events and define measurable freshness objectives for low-risk reads and high-risk writes.

Verify an allow and these independent denials: Finance deployment, copied run token, trusted workload without delegation, wrong capability audience, changed channel, changed text, expired capability, concurrent replay, and membership removal before the next write. Each denial needs a fresh valid positive control.

Where the audience-narrowing hop uses OAuth token exchange, RFC 8693 defines subject-token, actor-token, audience, and act semantics. The single-action adapter capability is a platform security object, so its additional resource, argument-digest, nonce, atomic-consumption, and revocation invariants remain explicit platform policy rather than implied OAuth behavior.

Practical next action and falsifiable evidence

The next useful step is not adding another token type; it is proving that Acme’s existing three-box chain cannot be widened. Run one HelixWorks Supplier Onboarding Agent test that records the observed admission, workload exchange, original Slack post, changed-channel replay, and mid-run membership removal.

The thesis is falsified if compute starts before a durable allow, either key admits alone, any service accepts a token for another audience, changed arguments pass the adapter, a nonce is consumed twice, or Alice posts after revocation beyond the stated objective. Passing evidence shows one run decision narrowed into one workload-bound, action-bound, single-use effect.

HelixWorks repository lab

The run boundary lives in services/run-orchestrator/src/domain/run.ts:

public consume(costUsd: number): void {
  this.assertRunning();
  if (this.#stepsUsed + 1 > this.data.maxSteps ||
      this.#costUsd + costUsd > this.data.maxCostUsd) {
    throw new RunBudgetExceededError('Run budget exceeded');
  }
  this.#stepsUsed += 1;
  this.#costUsd = Math.round((this.#costUsd + costUsd) * 100) / 100;
}

The declared intent is bounded execution, not a reusable bearer credential. Node interprets the aggregate; run counters change; CPU and memory execute the check; the snapshot and rejected command are evidence. The aggregate owns run invariants (SRP), repositories and clocks are injected (IoC/DI), and HTTP and event paths reuse the same budget rule (DRY). PubSub carries completed-run facts but cannot bypass admission.

pnpm --filter @helixworks/run-orchestrator test

Consume the final permitted step, attempt one extra step, cancel, and attempt another consume. Expect one budget denial and one state denial without a connector decision. An extra accepted step or post-cancel side effect falsifies the boundary.