feat(org): platform seams — JSON receipts, presented identity, org-mcp, operator context.d - #263
Conversation
|
@claude please review. Stacked on #262 (review that first). This adds the platform seams. Worth your attention:
|
|
Claude finished @itsHabib's task in 4m 46s —— View job Review of PR #263 — platform seams (feat/org-platform-seams)Reviewed at SHA Todo
FindingsP2 —
|
| Invariant | Verdict |
|---|---|
| Allowlist: structure verbs unreachable via verb table | ✅ — verbs slice is the only dispatch path; lookupVerb + dispatch are the only routes; "tools/call" resolves through lookupVerb and nothing else |
| recharter pinned in allowlist test | ❌ — missing from banned list in TestAllowlistExcludesStructureVerbs |
Refusal mapping: exit 1 → {code:"refused", reason} |
✅ — invoke() checks err != nil before the switch, so exec failure never enters exit-1 branch |
exec failure → {code:"exec_failed"} |
✅ — Shell returns (nil, nil, 0, err) for non-exit errors; invoke gates on err != nil first |
exit ≥2 → {code:"error"} |
✅ — default branch |
Identity: -incarnation/ORG_INCARNATION propagated |
✅ — appendAndReport sets d.Incarnation = s.incarnation when empty |
-strict blocks non-minting writes without incarnation |
✅ — correct logic in appendAndReport |
| Minting kinds (charter/attach/takeover) exempt from strict | ✅ — minted() covers all three; cmdAttach bypasses appendAndReport entirely |
| context.d: sorted by name | ✅ — os.ReadDir returns sorted entries |
| context.d: byte-capped on text path | ✅ — contextText enforces -context-bytes |
| context.d: truncation names the directory | ✅ — dir arg from h.ContextDir(...) |
| context.d: dotfiles skipped | ✅ — strings.HasPrefix(e.Name(), ".") |
| context.d: subdirs skipped | ✅ — e.IsDir() |
| context.d: no traversal | ✅ — os.ReadDir returns filenames only; no path separator possible (symlinks not filtered, but operator-controlled dir) |
| JSON receipts on all write verbs | ✅ — all routes via appendAndReport; cmdAttach has its own inline path |
| Receipt fields from folded state after append | ✅ — h.Append returns next (result of org.Advance); all receipt construction uses this next |
cmdAttach receipt field completeness |
❌ — Active/Dangling absent |
Overall
The core architecture is clean: verb table = allowlist is the right invariant and it's airtight, the exit-code tripartite split correctly separates exec failure from refusal, and the context.d mechanism is deliberately minimal. Two P2s (test gap and attach receipt fields) that should be fixed before merging; the rest are informational.
The brief's §6 note said dossier task_list omits notes and no CLI verb reads one. Re-verified against the installed binary and source: refuted — task_list returns structured notes, so the write tier can read conclusions back. The correction names the two arguments that leaned on the false premise (hooks #43's substrate-coupling rationale, store-decision finding 2) rather than silently rewriting them. where-this-stands gains a build update: the re-entry slice is no longer a claim — cmd/org and cmd/org-mcp shipped CI-green in PRs #262/#263, with the deliberate deviations recorded where they were made. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@claude re-review on the current head. Round-1 fixes: both P2s (org_recharter added to the banned-list pin; attach receipt now carries Active/Dangling — matters after a revoke orphans a claim) and all three P3s (refusalReason prefix fallback; -strict/-incarnation test covering exempt minting kinds + pre-append strict refusal + stale_incarnation on a wrong id; minted/mints deduped into exported home.MintsIdentity). Also merged in the base branch's round-1 fixes (hook fail-open, cmdBlob message). |
|
Claude finished @itsHabib's task in 3m 51s —— View job Re-review of PR #263 — platform seams (round-1 fixes verified)Reviewed at SHA Todo
Round-1 fix verificationAll five findings addressed correctly. Detailed checklist: P2:
|
| Invariant | Round 1 | Round 2 |
|---|---|---|
| Allowlist: structure verbs unreachable via verb table | ✅ | ✅ |
| recharter pinned in allowlist test | ❌ | ✅ fixed |
Refusal mapping: exit 1 → {code:"refused", reason} |
✅ | ✅ |
exec failure → {code:"exec_failed"}, never refusal |
✅ | ✅ |
exit ≥2 → {code:"error"} |
✅ | ✅ |
| refusalReason: prefix fallback for seq-less messages | ❌ | ✅ fixed |
Identity: -incarnation propagated; strict pre-append gate |
✅ | ✅ |
-strict test covering minting exemptions + stale id |
❌ | ✅ fixed |
| Minting kind list: single definition, no drift risk | ❌ | ✅ fixed |
cmdAttach receipt: Active/Dangling included |
❌ | ✅ fixed |
| context.d: sorted, byte-capped, truncation named, no traversal | ✅ | ✅ |
| Hook fail-open on malformed stdin | ❌ (base) | ✅ fixed |
Overall
All P2s and P3s from round 1 are correctly addressed. The attach receipt, allowlist test, refusal fallback, identity policy test, and MintsIdentity deduplication are all clean. The base-branch merge picks up hook fail-open and cmdBlob correctly. No new issues introduced.
Ready to merge.
…p, operator context Four seams that turn the home from a demo into a surface other things compose on: Receipts: every write verb takes -json and emits the machine half of the exit-code seam — kind, seq, digest, phase, tip, holder, active, dangling, held, fence. status/verify/boot speak JSON too. Identity: -incarnation (ORG_INCARNATION) presents the id attach minted, letting the kernel's stale_incarnation law actually bite; -strict (ORG_STRICT) refuses the single-operator write-as-holder default. The minting-kind list lives once, exported as home.MintsIdentity. org-mcp: a stdio MCP server exposing the org verbs as native agent tools. It shells the org binary and composes on its exit codes — never imports the home — and its verb table is the allowlist: charter, takeover, revoke, retire, recharter and delegate have no entry, so the org's structure cannot be reshaped over MCP. A kernel refusal surfaces as an isError result carrying the reason id for the agent to branch on. Operator context: files dropped in <role>/context.d/ ride the boot output, sorted, byte-capped, truncating with a pointer to the directory. The dumbest mechanism that works — writing a file is publishing, deleting it is revocation. Includes review round-1 fixes: org_recharter pinned in the allowlist test, attach receipts carrying Active/Dangling, a refusalReason prefix fallback, and a test for the -strict/-incarnation identity policy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dc71783 to
b0fa010
Compare
|
@claude review this PR and publish your findings as a FORMAL GitHub review pinned to head b0fa010 — not an issue comment. Run exactly: gh pr review 263 --repo itsHabib/workbench --comment --body "<your review, stating the head SHA>" Context: the branch was rebuilt onto main as a single commit after #262 squash-merged (same content you already reviewed clean in round 2, plus the round-1 fixes). Re-verify the platform seams: org-mcp allowlist excludes every structure verb, refusal mapping (exit 1 → refused+reason, exec failure ≠ refusal), -strict/-incarnation identity policy with minting kinds exempt, context.d ordering/cap/traversal, and JSON receipts reflecting post-append state. gate cannot count an issue comment: classifyPanel requires User.Type=Bot, a COMMENTED/APPROVED/CHANGES_REQUESTED state, and commit_id == the judged head. If the review command fails, say so plainly. |
|
Claude finished @itsHabib's task in 6m 3s —— View job Re-review of PR #263 — platform seams (rebuilt onto main)Todo
Formal review posted pinned to head Allowlist — structure verbs unreachable over MCP ✅
Refusal mapping — tripartite exit-code split ✅
Identity —
|
| Invariant | Result |
|---|---|
| Allowlist: structure verbs absent from verb table | ✅ |
org_recharter pinned in banned-list test |
✅ |
exit 1 → {code:"refused", reason, detail} |
✅ |
exec failure → "exec_failed", never enters exit-code switch |
✅ |
exit ≥2 → {code:"error"} |
✅ |
| refusalReason: seq-clause primary + prefix fallback | ✅ |
-incarnation/ORG_INCARNATION propagated to draft |
✅ |
-strict pre-append guard; non-minting kinds only |
✅ |
| Minting kinds (charter/attach/takeover) exempt | ✅ |
home.MintsIdentity: single exported definition |
✅ |
-strict/-incarnation test: 4 cases covered |
✅ |
| context.d: sorted, byte-capped, truncation names directory | ✅ |
| context.d: dotfiles/subdirs skipped, no traversal | ✅ |
| JSON receipts on all write verbs | ✅ |
| Receipt fields from post-append state | ✅ |
cmdAttach receipt: all 10 fields including Active/Dangling |
✅ |
All invariants verified clean against head b0fa010329f60690c6cb5616b5f296b8d99fa73f. No findings. Ready to merge.
There was a problem hiding this comment.
Reviewed at SHA b0fa010 (single squash commit, rebuilt onto main after #262 merged).
Re-verification of all platform seams against the stated invariants.
Allowlist — structure verbs unreachable over MCP ✅
verbs.go:127 — the verbs slice is the only dispatch registry. The sole resolution path is handleMessage → dispatch → handleToolsCall → lookupVerb → v.args → invoke. lookupVerb does a linear scan of this slice and nothing else; there is no second dispatch table or special-case route. The banned six have no entry: org_charter, org_takeover, org_revoke, org_retire, org_recharter, org_delegate — all absent from the slice. The tools/call handler cannot reach them.
server_test.go:111 pins all six banned names, including org_recharter, plus asserts lookupVerb("org_charter") returns false. The pin is complete and matches the stated surface contract in server.go:13 and cmd/org-mcp/CLAUDE.md.
Refusal mapping — tripartite exit-code split ✅
server.go:176–192. The Shell runner (server.go:41–56) returns (nil, nil, 0, err) for non-ExitError failures and (stdout, stderr, code, nil) for all exit-code cases — the error channel and the code channel are mutually exclusive. invoke checks err != nil first, before entering the switch, so exec failure is classified "exec_failed" and never enters the exit-1 branch. Exit 1 → {code:"refused", reason, detail}. Exit ≥2 → {code:"error", exit, detail}. The classification is airtight.
TestRefusalSurfacesReason (server_test.go:61) drives exit-1 through the full serve loop and asserts isError=true, code="refused", reason="dangling_claim". TestMissingArgumentIsToolError (server_test.go:84) confirms a pre-flight validation error never reaches the process boundary.
refusalReason (server.go:203–211) — two-pattern fallback: primary matches <reason> at seq N; fallback matches ^org: <reason>: for seq-less messages. The ^ anchor on the fallback is correct — kernel stderr opens with "org: ". A message matching neither degrades to reason:"" with full stderr in detail, which is still machine-inspectable.
Identity — -strict/-incarnation, minting kinds exempt ✅
newScope (main.go:138–149) wires -incarnation defaulting to ORG_INCARNATION and -strict defaulting to ORG_STRICT != "".
appendAndReport (main.go:209–234):
- Pre-append strict guard:
if s.strict && s.incarnation == "" && !home.MintsIdentity(d.Kind)→ returnscodeErrorwith"strict mode"before any append. - Incarnation propagation:
d.Incarnation = s.incarnationwhen the draft carries none — the kernel'sstale_incarnationlaw bites on a wrong presented id.
home.MintsIdentity (home.go:204) — single exported definition: KindCharter || KindAttach || KindTakeover. Used by appendAndReport for the strict guard and by home.draft for the incarnation-stamping decision. No local copy in main.go; no drift risk.
cmdAttach bypasses appendAndReport entirely (calls h.Append directly at main.go:282) — correct because attach IS the identity-minting event; the strict check does not apply by definition.
TestStrictIdentityPolicy (main_test.go:112–133) covers four cases:
charter -strict→ exit 0: minting kind, exemption fires.attach -strict→ exit 0: minting kind, cmdAttach bypasses the guard.assign -strict(no incarnation) →codeError+"strict mode": pre-append guard fires.assign -incarnation sha256:000…(stale, no strict) →codeRefused+"stale_incarnation": kernel law fires independently.
takeover minting-kind exemption is not tested end-to-end (requires a held-state chain), but is covered by MintsIdentity's own return value and appendAndReport's call to it — the gap is informational.
context.d — ordering, byte cap, truncation label, no traversal ✅
home.Context (home.go:248–269):
os.ReadDirreturns entries sorted by name — sorted ordering requirement met.e.IsDir()guard skips subdirectories.strings.HasPrefix(e.Name(), ".")skips dotfiles.os.ReadFile(filepath.Join(dir, e.Name()))— filenames fromReadDircontain no path separator, so traversal outsidecontext.dis structurally impossible (symlinks not filtered, but the directory is operator-controlled).
contextText (main.go:479–498):
if spent+len(entry) > budgetenforces the byte cap before writing each entry.- Truncation message:
"… context truncated at %d bytes — read the rest in %s\n"names the directory (dirarg fromh.ContextDir).
TestBootInjectsOperatorContext (main_test.go:80–107) exercises sorted ordering, presence, and truncation with a 40-byte cap — all three branches confirmed by assertion.
JSON receipts — all write verbs, post-append state ✅
home.Append (home.go:123–149) returns (r, next, nil) where next is org.Advance(state, r) — the state after the admitted record.
appendAndReport (main.go:209–234) constructs the receipt from r (the appended record) and state (the post-advance return value). The Phase, Tip, Holder, Active, Dangling, Held, Fence fields reflect the folded state after the append, not before.
cmdAttach (main.go:291–295) constructs its receipt from the same h.Append return values with all ten fields — Kind, Seq, Digest, Phase, Tip, Holder, Active, Dangling, Held, Fence — byte-identical to appendAndReport's path.
All write verbs route through either appendAndReport or cmdAttach's inline path; both honor -json. Read verbs (status, verify, boot) construct their own JSON shapes correctly.
Invariant checklist
| Invariant | Result |
|---|---|
| Allowlist: structure verbs absent from verb table | ✅ |
org_recharter pinned in banned-list test |
✅ |
Refusal: exit 1 → {code:"refused", reason, detail} |
✅ |
exec failure → {code:"exec_failed"}, never enters exit-code switch |
✅ |
exit ≥2 → {code:"error"} |
✅ |
| refusalReason: seq-clause primary + prefix fallback | ✅ |
-incarnation/ORG_INCARNATION propagated to draft |
✅ |
-strict pre-append guard; non-minting kinds only |
✅ |
| Minting kinds (charter/attach/takeover) exempt from strict | ✅ |
home.MintsIdentity: single exported definition, no drift |
✅ |
-strict/-incarnation test: 4 cases covered |
✅ |
| context.d: sorted by name | ✅ |
| context.d: byte-capped before each entry | ✅ |
| context.d: truncation names the directory | ✅ |
| context.d: dotfiles and subdirs skipped | ✅ |
| context.d: no traversal outside the directory | ✅ |
| JSON receipts on all write verbs (-json honored) | ✅ |
| Receipt fields from folded state after append | ✅ |
cmdAttach receipt: all 10 fields including Active/Dangling |
✅ |
All invariants verified clean against head b0fa010. No findings.
🤖 Generated with Claude Code
…collide cmd/org and cmd/org-mcp are on main (#262, #263). The re-entry surface the reconciliation brief called the closest-to-buildable gap is built: charter/attach/assign/claim/yield/checkpoint/boot plus sessionstart-boot and stop-mark hook scripts. It is not wired — settings.json still has only PreToolUse and PostToolUse. That makes the brief's §5 stale and surfaces something sharper. There are now TWO Stop hooks, neither wired, built days apart by different sessions with no reference to each other: cmd/org/hooks/stop-mark.sh appends a mechanical mark to the role's chain, and hooks PR #42's stop-discharge.sh appends what a session did to the dossier tasks it touched. Both refuse to ask the model, and their headers reach that conclusion independently in different words — which is the strongest signal in this corpus and also a duplication that will double-write if both are installed. The handoff leads with that as the deliverable rather than the vocabulary question. It also carries the two corrections this session earned: the notes error (a skip_serializing_if field sampled at .[0] reads exactly like a field that does not exist) and the mktemp BSD/GNU divergence that passed every local run and failed every CI one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cisions to fold Independent reconciliation of the whole ownership-continuity lineage at 2026-08-26 heads: what #245 designed, what #246/#248/#262/#263 shipped through the governed path, what #265/#266 measure, what hooks#42/#43 and drive#47 add, what cc-skills#29 proves in fixture, and what remains hypothesis. Every major claim classified on the honest rung ladder; duplications and missing joins named; D1-D10 written to be accepted, amended, or struck in place. Indexed from vision.md's document table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Stacked on #262. Turns the Baton home into a surface other tools and agents compose on — the platform-team slice: machine receipts, real identity presentation, an MCP surface, and operator-defined boot context.
What this adds
-jsonemits{kind, seq, digest, phase, tip, holder, active, dangling, held, fence};status/verify/boottoo. The JSON half of the exit-code seam.-incarnation/ORG_INCARNATIONpresents the idattachminted, so the kernel'sstale_incarnationlaw bites;-strict/ORG_STRICTdisables the single-operator write-as-holder default.cmd/org-mcp— stdio MCP server exposing org verbs as native agent tools (org_boot,org_claim,org_yield,org_checkpoint, …). Shells the org binary (boundary law — same posture as console/escalate over gate); verb table IS the allowlist — charter/takeover/revoke/retire/delegate unreachable over MCP; kernel refusals surface asisErrorresults carrying the reason id.$ORG_STATE/<tenant>/<role>/context.d/*ridesorg boot(sorted,-context-bytescap, truncation names the directory). The /continue-style "inject what I want the next session to see" mechanism, as plain files.Validation
gofmt/go vet/golangci-lintclean;go test ./cmd/org/... ./cmd/org-mcp/...green (CLI translation, refusal→reason mapping, allowlist pinning, handshake framing, context ordering + truncation).org_statusreturns the board;org_claimon an unheld role returns{code:"refused", reason:"incarnation_missing"}for the agent to correct. Real boot forlead:agentic-developmentnow carries an operator context.d note.🤖 Generated with Claude Code