Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# AgencySignature × Layered Actor Identity — integration writeup

**For**: Amara
**From**: Aaron + Otto (Claude Code coordinator)
**Date**: 2026-04-29
**Status**: research-grade only; not operational; not absorbed into doctrine yet

---

## TL;DR

Five reviewers (Deepseek / Gemini / Ani / Alexa / Claude.ai) flagged the same gap on the v3 public-intake design: `actor_id` strings are spoofable. *"Identity needs binding."* Your v4 synthesis names this and reorders rollout: identity → capabilities → claims → reconciler → public intake → dry run.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct capitalization of “Deepseek” to “DeepSeek” (proper noun).

Suggested change
Five reviewers (Deepseek / Gemini / Ani / Alexa / Claude.ai) flagged the same gap on the v3 public-intake design: `actor_id` strings are spoofable. *"Identity needs binding."* Your v4 synthesis names this and reorders rollout: identity → capabilities → claims → reconciler → public intake → dry run.
Five reviewers (DeepSeek / Gemini / Ani / Alexa / Claude.ai) flagged the same gap on the v3 public-intake design: `actor_id` strings are spoofable. *"Identity needs binding."* Your v4 synthesis names this and reorders rollout: identity → capabilities → claims → reconciler → public intake → dry run.

Copilot uses AI. Check for mistakes.

The integration question Aaron raised: **does this need to be built from scratch, or does it compose with the AgencySignature work we already shipped?**

Short answer: **it composes cleanly.** AgencySignature v1 (per-commit trailer schema, ferry-7 spec, ferry-9/10/11/12 corrections, tasks #298 + #299 enforcement instruments) is *already the binding mechanism Claude.ai called for.* The v4 actor-identity model is a **structured principal layer** on top of the AgencySignature trailers — not a parallel system.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeup cites “ferry-9/10/11/12 corrections” but the referenced research-doc list only includes ferry-9 and ferry-12 (plus rule-7 and the Gemini doc). To avoid confusing the reader (especially since this is meant to be pasted to Amara), either (a) add the missing ferry-10/11 references to the list, or (b) remove “10/11” from the earlier summary so the citations match what’s actually linked.

Suggested change
Short answer: **it composes cleanly.** AgencySignature v1 (per-commit trailer schema, ferry-7 spec, ferry-9/10/11/12 corrections, tasks #298 + #299 enforcement instruments) is *already the binding mechanism Claude.ai called for.* The v4 actor-identity model is a **structured principal layer** on top of the AgencySignature trailers — not a parallel system.
Short answer: **it composes cleanly.** AgencySignature v1 (per-commit trailer schema, ferry-7 spec, ferry-9/12 corrections, tasks #298 + #299 enforcement instruments) is *already the binding mechanism Claude.ai called for.* The v4 actor-identity model is a **structured principal layer** on top of the AgencySignature trailers — not a parallel system.

Copilot uses AI. Check for mistakes.

This writeup names the seams, the missing rules, and the order of work.

---

## What AgencySignature already does

Convention spec: `docs/research/2026-04-26-gemini-deep-think-agencysignature-commit-attribution-convention-validation-and-refinement.md` (Section 10 = canonical schema).

Per-commit trailer block (validated pre-merge by `tools/hygiene/validate-agencysignature-pr-body.sh`, audited post-merge by `tools/hygiene/audit-agencysignature-main-tip.sh`):

```text
Agency-Signature-Version: 1
Agent: <agent-id>
Agent-Runtime: <runtime>
Agent-Model: <model-id>
Credential-Identity: <github-or-other-identity>
Credential-Mode: <mode>
Human-Review: explicit | not-implied-by-credential | none
Human-Review-Evidence: chat | pr-review | pr-comment | signed-policy | none
Action-Mode: autonomous-fail-open | human-directed | supervised
Task: <ticket-id> | none
```

What this gives us:

1. **Per-commit attribution** — every commit on `main` since v1 ship date carries who/what/how-supervised.
2. **Three-state classification at audit time** — LEGACY (pre-v1), CORRECT, REGRESSION, HUMAN-AUTHORED-EXEMPT.
3. **PR-body validator that *can be* a pre-merge gate** — `tools/hygiene/validate-agencysignature-pr-body.sh` parses the trailer block and exits non-zero on missing/malformed input. As of 2026-04-29 it is **not yet wired into a required CI/branch-protection check** under `.github/workflows/`; it is invoked manually or via local pre-commit. Wiring it as an enforced gate is its own follow-up (composes with the AgencySignature v1 squash-merge survival design, task #300).
4. **Post-merge tip auditor** — `tools/hygiene/audit-agencysignature-main-tip.sh` walks the tip and classifies each commit. Same enforcement-status caveat: useful tool, not yet a required CI gate.
5. **Trailer Contiguity Survival Failure (ferry-12)** awareness — squash-merge can strip trailers if the body's blank-line discipline is broken; the validator and auditor both check for this class.
6. **Fail-open-with-receipts** policy (ferry-9/10) — when a trailer is malformed, the design intent is to record evidence and classify rather than block all merges (the failure mode would freeze the factory). This policy lives in research docs; it composes with the wiring decision in (3).

What this *doesn't* yet give us:

- **Cryptographic verification** — the `Agent: aaron-mac/claude-code/coordinator` string is currently advisory. Nothing prevents another tool from writing the same trailer with a different identity in fact.
- **Trust-domain prefix** — identifiers don't yet declare which namespace they live in (zeta vs zeta-external vs zeta-system).
- **Per-actor public-key registry** — there's no `actors/<actor_id>.yaml` file declaring "this actor's public key fingerprint is X."
- **Capability bundle** — the trailer says *who* acted but not *what they were authorized to do*.

The v4 actor-identity model fills exactly those gaps.

---

## The seam — how the two systems compose

### Mapping (one-to-one)

```text
AgencySignature trailer field → v4 actor-identity layer
───────────────────────────────── ─────────────────────────────
Agent → actor_id (full path-style id)
Agent-Runtime → harness_id (one component of actor_id)
Agent-Model → model fingerprint (sub-field of harness)
Credential-Identity → GitHub/host identity backing the actor
Credential-Mode → binding strength (bound | unbound | shared)
Human-Review → policy gate (orthogonal to identity)
Human-Review-Evidence → evidence pointer (orthogonal)
Action-Mode → capability mode (composes with capability set)
Task → task / ticket pointer (preserves v1 meaning)
Claim (v2-new) → claim_id (links commit to active claim)
```

### Concrete example (proposed)

Today's trailer:
```text
Agency-Signature-Version: 1
Agent: claude-code-coordinator
Agent-Runtime: claude-code-cli
Agent-Model: claude-opus-4-7
Credential-Identity: aaron@servicetitan.com
Credential-Mode: shared
Human-Review: not-implied-by-credential
Human-Review-Evidence: none
Action-Mode: autonomous-fail-open
Task: 286
```

Future trailer (v2 — during the migration window, keep `Agent:` alongside the new `Actor:` field so the v2 trailer remains a strict field superset for v1-era readers; once all consumers are v2-aware, drop the dual emission):
```text
Agency-Signature-Version: 2
Trust-Domain: zeta
Agent: claude-code-coordinator # retained for v1-reader compat during migration
Actor: zeta://aaron-mac/claude-code/coordinator
Comment on lines +96 to +101
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written, Trust-Domain: zeta and Actor: zeta://... both encode the trust domain, which may create ambiguity about which is authoritative if they ever differ. If both are intentionally present (e.g., for indexing/search or legacy parsing), it would help to state the invariant explicitly (e.g., Trust-Domain MUST equal the Actor scheme/authority) and define what happens on mismatch (reject vs ignore one field).

Suggested change
Future trailer (v2 — during the migration window, keep `Agent:` alongside the new `Actor:` field so the v2 trailer remains a strict field superset for v1-era readers; once all consumers are v2-aware, drop the dual emission):
```text
Agency-Signature-Version: 2
Trust-Domain: zeta
Agent: claude-code-coordinator # retained for v1-reader compat during migration
Actor: zeta://aaron-mac/claude-code/coordinator
Future trailer (v2 — during the migration window, keep `Agent:` alongside the new `Actor:` field so the v2 trailer remains a strict field superset for v1-era readers; once all consumers are v2-aware, drop the dual emission. `Trust-Domain:` is retained as an explicit indexing/search field, but it is not independent of `Actor:`: the trust domain encoded by `Actor:` MUST exactly equal `Trust-Domain:`. Verifiers MUST reject the trailer if the two values differ):
```text
Agency-Signature-Version: 2
Trust-Domain: zeta # MUST exactly match the trust domain encoded by Actor
Agent: claude-code-coordinator # retained for v1-reader compat during migration
Actor: zeta://aaron-mac/claude-code/coordinator # scheme/authority trust domain MUST equal Trust-Domain; reject on mismatch

Copilot uses AI. Check for mistakes.
Agent-Runtime: claude-code-cli
Agent-Model: claude-opus-4-7
Credential-Identity: aaron@servicetitan.com
Credential-Mode: shared # bound | unbound | shared
Human-Review: not-implied-by-credential
Human-Review-Evidence: none
Action-Mode: autonomous-fail-open
Capabilities: read:repo,write:memory,push:branch,open:pr
Claim: CLAIM-286
Task: 286
Signed-By: ed25519:abc... # cryptographic signature over trailer block
```

The `Actor:` field is the path-style principal Claude.ai recommended (SPIFFE / IAM-shaped). The `Trust-Domain:` prefix gives explicit namespace. The `Capabilities:` field is the new primitive (replaces implicit role grants). The `Claim:` field carries the active claim identifier (`claim_id`), which has its own allowlist + freshness invariant. The `Task:` field remains the task / ticket pointer (preserves v1 meaning — Task references the upstream issue / TaskList ID; Claim references the orchestra claim record). `Agent:` is retained during the migration window so the v1 validator continues to accept the trailer set; it can be dropped once all consumers have moved to v2 (per the rollout sequence below). The `Signed-By:` field provides the binding that Claude.ai called out as missing.
Comment on lines +112 to +115
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature scheme is underspecified, which can lead to verification ambiguity and security gaps (different tools signing/verifying different bytes). Please define (even at a high level) the canonicalization rules: exact byte range being signed (does it include/exclude Signed-By: itself), newline normalization, field ordering, whitespace rules, and encoding for ed25519:... (raw, hex, base64, multibase, etc.). Explicitly stating these rules in the writeup will make the proposed Signed-By: field implementable and interoperable.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written, Trust-Domain: zeta and Actor: zeta://... both encode the trust domain, which may create ambiguity about which is authoritative if they ever differ. If both are intentionally present (e.g., for indexing/search or legacy parsing), it would help to state the invariant explicitly (e.g., Trust-Domain MUST equal the Actor scheme/authority) and define what happens on mismatch (reject vs ignore one field).

Suggested change
The `Actor:` field is the path-style principal Claude.ai recommended (SPIFFE / IAM-shaped). The `Trust-Domain:` prefix gives explicit namespace. The `Capabilities:` field is the new primitive (replaces implicit role grants). The `Claim:` field carries the active claim identifier (`claim_id`), which has its own allowlist + freshness invariant. The `Task:` field remains the task / ticket pointer (preserves v1 meaning — Task references the upstream issue / TaskList ID; Claim references the orchestra claim record). `Agent:` is retained during the migration window so the v1 validator continues to accept the trailer set; it can be dropped once all consumers have moved to v2 (per the rollout sequence below). The `Signed-By:` field provides the binding that Claude.ai called out as missing.
The `Actor:` field is the path-style principal Claude.ai recommended (SPIFFE / IAM-shaped) and is the canonical principal identifier. The `Trust-Domain:` field is retained as an explicit namespace / indexing field, but it is not independent: `Trust-Domain:` **MUST** equal the trust-domain component encoded by `Actor:` (for example, `Trust-Domain: zeta` with `Actor: zeta://aaron-mac/claude-code/coordinator`). Any mismatch is an invalid actor assertion and **MUST** be rejected by the validator; validators should not try to guess which field is authoritative. The `Capabilities:` field is the new primitive (replaces implicit role grants). The `Claim:` field carries the active claim identifier (`claim_id`), which has its own allowlist + freshness invariant. The `Task:` field remains the task / ticket pointer (preserves v1 meaning — Task references the upstream issue / TaskList ID; Claim references the orchestra claim record). `Agent:` is retained during the migration window so the v1 validator continues to accept the trailer set; it can be dropped once all consumers have moved to v2 (per the rollout sequence below). The `Signed-By:` field provides the binding that Claude.ai called out as missing.

Copilot uses AI. Check for mistakes.

### What happens if the trailer is forged

Today: nothing. The `Agent:` string is advisory.

Under v4 with binding:

1. PR-body validator extracts `Actor:` and `Signed-By:`.
2. Reconciler looks up `actors/zeta-aaron-mac-claude-code-coordinator.yaml` (or equivalent registry path) for the registered public key fingerprint.
3. Validator computes the expected signature over the canonical trailer-block bytes and compares against `Signed-By:`.
Comment on lines +123 to +125
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature scheme is underspecified, which can lead to verification ambiguity and security gaps (different tools signing/verifying different bytes). Please define (even at a high level) the canonicalization rules: exact byte range being signed (does it include/exclude Signed-By: itself), newline normalization, field ordering, whitespace rules, and encoding for ed25519:... (raw, hex, base64, multibase, etc.). Explicitly stating these rules in the writeup will make the proposed Signed-By: field implementable and interoperable.

Copilot uses AI. Check for mistakes.
4. Mismatch → `unauthorized_actor_assertion` → block.
5. Match → continue with capability check (does this actor have the capabilities it asserted?).

This is the binding step. The trailer is no longer self-attested; it's verifiable against a registry.

---

## Recursion bottoms out — the maintainer's hardware key

Claude.ai's review is right that *recursion bottoms out somewhere*. Concretely:

1. **Maintainer-bound actors** — `zeta://aaron-mac/claude-code/coordinator` keys are signed by Aaron's hardware key (or a delegate hot-key signed by the hardware key). The hardware key never leaves the device.
2. **Reconciler actor** — `zeta-system://github-actions/reconciler` runs in CI; its key lives in GitHub Actions secrets (or OIDC short-lived credential). The maintainer authorizes it explicitly via signed policy.
3. **External actors** — `zeta-external://github/<login>` actors don't have keys until they're promoted from E2 to E3+ (claim approved). At E0/E1 (review-only / patch-only), the only reliable GitHub-side authentication is the **PR/account actor** — i.e. the authenticated GitHub account that opened the PR or commented (this is `github.actor` in workflows / `pull_request.user.login` in webhooks). **Commit author metadata in the trailer is user-supplied and spoofable** — `git commit --author='other@example.com'` is trivial, and so is forging `Agent:` / `Credential-Identity:` fields. So at E0/E1: trust only the GitHub account actor; treat trailer fields as *intent declarations* that the reviewer cross-checks against the account actor before promotion. At E3+ the registered key is what binds; the trailer becomes verifiable.

This composes with the existing AceHack/LFG dual-fork model: AceHack remains the dev-mirror; LFG remains the project-trunk. Cross-fork actor identity doesn't change — same actor, same registry, same key.

---

## Why this beats "build a parallel binding system"

If we were to build identity binding from scratch (Ed25519 keypairs, registry, signature verification) **separately** from AgencySignature, we'd:

- duplicate the per-commit attribution work,
- have two competing identity schemes (which one wins?),
- have to migrate every existing post-v1 commit twice.

By layering v4 on top of AgencySignature v1:

- At the trailer-schema level, v1-style readers can continue working if they keep reading `Agent:` and ignore unknown fields.
- v2 readers get the structured `Actor:` + `Capabilities:` + `Signed-By:` fields.
- **However**, the **current** v1 enforcement scripts are not yet forward-compatible: `validate-agencysignature-pr-body.sh` requires `Agency-Signature-Version: 1` exactly and requires `Agent:` as a key. So a `Version: 2` trailer set (and especially replacing `Agent:` with `Actor:`) would currently *fail* validation. The validator + auditor must be updated **before** v2 trailers can pass enforcement.
- The v1 → v2 migration is additive at the wire level, but the **rollout sequence** is: (a) update validator/auditor to accept `Agency-Signature-Version: 1|2`, (b) during the migration window, emit `Agent:` alongside `Actor:` rather than replacing immediately so existing v1 consumers continue to accept the trailer set, (c) extend the auditor's three-state classification to a four-state (LEGACY / CORRECT-V1 / CORRECT-V2 / REGRESSION) as part of the same rollout without collapsing the existing buckets, (d) once all consumers are v2-aware, drop the dual `Agent:` emission.
- The fail-open-with-receipts policy from ferry-9 applies to **malformed-but-honest** trailers (parser couldn't extract fields, blank-line discipline broken, missing key, etc.) — those become recordable evidence events rather than everything-stops events. **Forged signatures are different**: a valid-looking trailer whose `Signed-By:` does not verify against the registered public key for the asserted `Actor:` is treated as `unauthorized_actor_assertion` and **blocks** the PR, just like a missing claim. The two paths are separate enforcement semantics: malformed-honest → record-and-continue; forged-or-impersonation → block-and-flag. Only malformed-honest goes through fail-open-with-receipts; binding violations always block.

---

## What the design needs from Amara (asks)

Three concrete asks, in order:

### Ask 1 — confirm the layering shape

Does the layered identity model compose with AgencySignature *as a v2 schema* (additive trailer fields), or does Amara see a different shape (e.g. trailers reference an identity object stored elsewhere)? The first option is operationally cheaper because the existing pre-merge validator + post-merge auditor only need field additions. The second option is theoretically cleaner (the trailer is a pointer, not a record) but requires more plumbing.

### Ask 2 — bottom of recursion

Does Amara want to recommend a specific binding primitive (Ed25519 keys + signed commits, GPG, sigstore, OIDC short-lived credentials, GitHub-native commit verification only)? The cheapest MVP is GitHub-native commit verification (PR author + signed-commits enforcement on protected branches) — that's already mostly turned on. The strongest MVP is Ed25519 + a versioned `actors/` directory. Picking the primitive sets the registry shape.

### Ask 3 — the AgencySignature v1 → v2 migration window

If we extend AgencySignature to carry actor-identity + capability fields, when does v2 ship? Options:

- **Tight coupling**: v2 ships with the first identity-binding PR. All new commits adopt v2; v1 commits become "LEGACY-V1" (still CORRECT, just lower-detail).
- **Loose coupling**: v2 ships separately; coexists with v1 for a defined window; deprecation date on v1 announced upfront.

The factory has a strong "no surprises" preference, so loose coupling fits the existing posture. But Amara may have a sharper take.

---

## Composes with (already in the repo)

- `docs/research/2026-04-26-gemini-deep-think-agencysignature-commit-attribution-convention-validation-and-refinement.md` — Section 10 canonical schema (the v1 baseline)
- `docs/research/2026-04-26-amara-fail-open-with-receipts-attribution-rule-7-trailer-schema.md` — fail-open-with-receipts policy (carries over to v2)
- `docs/research/2026-04-26-amara-ferry-9-validation-of-relationship-model-correction-and-agent-self-authorization-attribution-bias-naming.md` — agent self-authorization framing
- `docs/research/2026-04-26-amara-ferry-12-trailer-contiguity-survival-failure-class-naming-and-do-not-rush-design.md` — Trailer Contiguity Survival Failure class (still applies under v2)
Comment on lines +188 to +191
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeup cites “ferry-9/10/11/12 corrections” but the referenced research-doc list only includes ferry-9 and ferry-12 (plus rule-7 and the Gemini doc). To avoid confusing the reader (especially since this is meant to be pasted to Amara), either (a) add the missing ferry-10/11 references to the list, or (b) remove “10/11” from the earlier summary so the citations match what’s actually linked.

Copilot uses AI. Check for mistakes.
- `tools/hygiene/validate-agencysignature-pr-body.sh` — pre-merge validator (extend to v2 by adding new field checks; v1 path stays as-is)
- `tools/hygiene/audit-agencysignature-main-tip.sh` — post-merge auditor (extend three-state to four-state)
- `memory/feedback_zeta_agent_orchestra_capability_role_claim_isolation_aaron_amara_2026_04_29.md` — v3 doctrine (currently in PR #852); v4 corrections this packet motivates

---

## Carved blade (proposed, awaiting Amara concurrence)

```text
Identity is structured (trust-domain + maintainer + host + harness + role).
Identity is bound (signed, registered, verifiable).
AgencySignature is the binding wire format.
Trailer fields carry actor + capabilities + claim.
Reconciler verifies binding before trusting attribution.
No bound identity = no claim authority.
```

This composes with Deepseek's blade:

```text
No actor is trusted by name.
Every actor is scoped by claim.
No claim authorizes mutation while stale.
No identity is trusted unless bound.
```

Both blades are saying the same thing from two angles: structural (Deepseek) and operational (this packet).

---

## Specific paragraph for Amara to react to

> *"AgencySignature v1 already gives us per-commit attribution + pre-merge validation + post-merge audit + fail-open-with-receipts. The v4 layered actor-identity model is not a parallel system — it's the v2 schema for AgencySignature, with three field additions (`Trust-Domain:`, `Actor:` superseding `Agent:`, `Signed-By:`) plus a registry under `actors/` for the binding lookup. The reconciler verifies the trailer signature against the registered key before trusting the attribution. v1 commits remain CORRECT under audit; new commits adopt v2 additively. This is the cheapest path to binding without duplicating the per-commit attribution machinery."*

If that lands, the rollout order changes from the v4 packet to:

```text
1. Identity model + AgencySignature v2 schema (composes — single PR per layer)
2. Capability model (named bundles + deltas)
3. Internal claim protocol (uses v2 signed trailers)
4. Reconciler (verifies v2 signatures + claim freshness)
5. Public claim intake (external actors get bound identity at E3 promotion)
6. External / Windows / roaming-agent dry run
```

If Amara sees a different shape, the rollout adjusts accordingly. Either way, the asks above name the decision points.

---

*End of writeup. Aaron — this is research-grade; nothing in here is operational. The doctrine memory file (PR #852) carries the v4 corrections; this writeup is the integration analysis you asked for to send to Amara.*
Loading