feat(autonomy): guardrail matrix, security binding, setup slice (WP5) - #377
Conversation
Hub carries the five-class x five-column matrix (T3 verbatim, C3 promotable cell now linking the security-review leaf), glance-layer routing, D8 permission-posture note, and D6 escalation event classes over the governed queue. Leaves: isolation ladder (L0-L3, rejected trigger-source axis, fail-closed L2 floor), work classes (risk bundles, promotion/demotion predicates with shipped defaults), security review (two layers, per-class blocking knobs, floors without override escape), admission policy (3-axis decision table, wildcard + most-specific-wins, equal-specificity tie = invalid binding, caps 1/3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or (WP5 Phase 2) Contract-owned JSON Schema (draft 2020-12, additionalProperties false, fail-closed semantics in-schema) for the settings-as-code security binding: executor_class, dispatch_posture, per-surface isolation bindings with runtime_markers + probe evidence, merge/verification knobs, promotion_state, escalation routes, admission object (classification + wildcard decision table + caps). check-security-binding.mjs enforces the semantics the schema cannot: matrix merge caps, vendor-hosted human-gate cap, marker non-emptiness + pairwise joint-satisfiability ambiguity rejection, class-aware per-surface isolation verdicts (C5 requires L3), missing-L2 error under autonomous-enabled vs declared verdict under human-gated-only, non-promotable promotion entries, admission weakening without override_justification, verification floors (admission-only override escape), equal-specificity admission ties invalid, promotion-gated knobs require their ratified promotion_state entry, and an evaluation mode resolving effective promotion state against contrary evidence without writing the binding. 15 fixtures; SKILL.md gains the two-surface resolution section with the agent-unwritable org-policy-home bootstrap rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ect-diff-reconcile, fail-closed (WP5 Phase 3) Setup gains the one guardrail slice (security review folded in, no separate capability): per-surface substrate detection, live probe validation before any binding lands (denied-egress + credential-absence transcripts recorded as probe evidence), reviewable preparation of the agent-unwritable security binding, detect-diff-reconcile against existing org guardrail surfaces, paid SKUs advisory with cost surfaced, fail-closed no-L2 verify. Isolation-probe template per substrate class; five new evals; plugin 0.5.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d before merge) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 30s —— View job PR Review: feat(autonomy): guardrail matrix, security binding, setup slice (WP5)
|
| Rule | Status |
|---|---|
| Repo-agnostic (no hardcoded paths/repos) | ✓ All paths use ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PROJECT_DIR} |
| Configurable without editing the plugin | ✓ Security binding is org-supplied; plugin carries schema + validator only |
| Plugin-form-safe | ✓ No ../ reach-outs; persisted state anchors at ${CLAUDE_PLUGIN_DATA} |
| No PII/secrets | ✓ |
| Versioned | ✓ plugin.json bumped 0.4.0 → 0.5.0 |
| Security-reviewed (per-playbook) | ✓ Near-duplicate audit present; no second egress/escalation channel; no agent-writable security axes |
What looks right
- Two-surface split is the correct architectural choice. Binding security axes (isolation, merge policy, verification knobs, admission rules) in a settings-as-code home outside the agent's blast radius, with a non-forgeable bootstrap for security resolution, is exactly the right model.
- Fail-closed everywhere — absent/invalid binding blocks autonomous dispatch; absent evidence fails promotion cells to unpromoted; unattestable surface fails closed as unattestable. Consistent.
- Probe-before-bind is enforced by making
probe_evidenceschema-required on every level binding, not just advisory. The validator and schema agree. - Pairwise joint-satisfiability check on runtime markers is the right level of rigor — subset/identity checks would miss non-conflicting union matches, which is exactly the ambiguity the dispatch seam can't resolve.
- Evaluation mode (
--evidence) correctly models demotion as a ceiling-lowering read with no write-back — clean separation between the governance surface and runtime evidence. - Fixture coverage — 15 fixtures exercising 14 negative paths (each with exactly one finding) plus the valid baseline and the evaluation-mode demonstration. The specificity counting in
equal-specificity-tie.jsonis correct: rules[5] and rules[6] both have specificity 2 (two non-wildcard axes), producing exactly one tie finding.
One action required before merge: remove wp5-isolation-probe-demo.yml from the branch. Everything else is either a documentation clarification or a nitpick. The core package — guardrail hub, four leaves, security-binding schema, semantic validator, setup skill slice — is ready.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3c218418a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… evidence, promotion epochs, encoding Probe evidence is now VERIFIED, not just present: refs resolve under --probe-evidence-root and must parse as the isolation-probe template's JSON transcript with denied/absent assertions and a networked outer context; an unverifiable L2/L3 entry is UNPROVEN and excluded from isolation eligibility (fail-closed). Contrary promotion evidence is scoped to the current epoch via required ratified_at (pre-epoch events were consumed by the re-earn; unparseable timestamps stay contrary fail-closed). NUL pair-separator replaced (editorconfig binary flag). Demo workflow captures the live JSON transcript and proves the checker passes only against live-captured evidence; credential probe paths via HOME (machine-specific-paths lane). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 14s —— View job Claude finished reviewing @kyle-sexton's updates — Review of d56ffc6 fixes
Blocker still open: demo workflow not removed
Codex P1 fix: probe evidence verified — correct ✓The fix is comprehensive. Under New fixtures cover the path correctly: One observation (non-blocking): Codex P2 fix: promotion epoch scoping — correct ✓The fix is correct.
The fixture pair exercises this cleanly: Remaining open items from prior reviewThe previous review had two additional notes beyond the blocker:
VerdictThe two Codex findings are correctly and thoroughly addressed in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d56ffc60bd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… demo probe assertions Transcript identity must match the citing binding entry (reused evidence for another level/surface/substrate is UNPROVEN, fail-closed); demo workflow checks each credential path independently, asserts exact denial exit codes so container infra errors cannot masquerade as denial, and validates a jq-patched demo binding whose substrate matches the live-captured transcript. Spelling per typos lane. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 18s —— View job Claude finished reviewing @kyle-sexton's updates — Review of ee93774 fixes
Blocker still open: demo workflow not removed (third time flagging)
The file must be deleted before squash-merge. All other items below are non-blocking. Codex P1 fix: surface/level/substrate binding — correct ✓The fix is complete and correctly structured. if (transcript.surface !== surfaceId) → surface mismatch
if (transcript.level !== level) → level mismatch
if (transcript.substrate !== substrate)→ substrate mismatchThe call site at L488 correctly passes Fixture: Demo workflow: The Codex P2 fix: independent credential path checking — correct ✓The demo workflow now checks each credential path independently using a shell loop (L43–L53): for p in "$HOME/.ssh" "$HOME/.netrc" "$HOME/.docker/config.json"; do
if [ -e "$p" ]; then echo "credential path readable: $p"; present=1; fi
done
[ "$present" = "0" ] && exit 3 || exit 0Exit 3 = all paths absent (probe passes); exit 0 = at least one readable (probe fails). The outer check requires exactly exit 3. This correctly catches a partial credential leak that the previous Egress check: Exit code 4 now signals denial (not just non-zero), so a Docker infra error (exit 125/126/127) can no longer masquerade as proven egress denial. Both sentinel exit codes (4 = denied, 3 = all-absent) are documented in the failure message with concrete interpretations for each non-sentinel value. Observation (non-blocking)PR body fixture count is stale. The PR body says "15 fixtures" but the branch now has 18 fixture files (17 negative + What looks right (unchanged from prior reviews)The architecture, fail-closed semantics, probe-before-bind discipline, and evaluation-mode demotion are sound and internally consistent. The prior Claude and Codex reviews' substantive findings are all addressed. The only outstanding item is the workflow file deletion. One action required before merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee937745e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An expected-failure probe recording exit_code 0 is internally inconsistent and proves nothing; such a transcript is UNPROVEN, fail-closed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 19s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 10ae236 fixes
Blocker still open: demo workflow not removed (fourth time flagging)
The file must be deleted before squash-merge. All other items are non-blocking. Codex P1 fix (10ae236): nonzero exit_code validation — correct ✓The fix at L400–L405 is correct. const nonzeroExit = (value) => typeof value === "string" && /^[1-9][0-9]*$/.test(value);The regex Null safety: The two new checks at L401/L404 access Fixture: One observation (non-blocking): there's no fixture testing Remaining open items from prior reviews
Summary10ae236's fix is correct and well-exercised by the new fixture pair. The prior Codex findings (P1 surface/level/substrate binding, P2 promotion epochs, this round's exit_code nonzero check) are all addressed. One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10ae23637a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An assertion without its recorded target (egress host, credential path) could be any failing command; such transcripts are UNPROVEN, fail-closed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 8s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 539c033 fixes
Blocker still open: demo workflow not removed (fifth time flagging)
The file must be deleted before squash-merge. All items below are non-blocking. Codex P1 fix (539c033): probe target field validation — correct ✓The fix at L410–L416 is correct. const egressHost = transcript.assertions.egress_denied.host;
if (typeof egressHost !== "string" || egressHost.length === 0 || /\s/.test(egressHost)) {
return `transcript ${path} records assertions.egress_denied.host ... — requires the probed external host (non-empty, no whitespace)`;
}
if (!isNonEmptyString(transcript.assertions.credentials_absent.path)) {
return `transcript ${path} records assertions.credentials_absent.path ... — requires the probed host-credential path`;
}Both checks are correctly gated after the Backward compatibility verified: Both existing valid transcripts Fixture: One observation (non-blocking): there is no dedicated fixture for the All prior Codex findings addressed
Remaining open items from prior reviews
Summary539c033's fix is correct, backward-compatible, and exercised by the new One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 539c033dfd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rker ambiguity rejected ratified_at and evidence timestamps must be strict ISO 8601 with explicit offset (epoch classification must not depend on evaluator timezone); level entries within one surface must carry mutually conflicting runtime markers so attestation pins the exact level boundary — max-proven-level aggregation is sound only under that condition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 49s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 18228fc fixes
Blocker still open: demo workflow not removed (sixth time flagging)
The file must be deleted before squash-merge. All items below are non-blocking. Codex P1 fix (18228fc): strict ISO promotion timestamps — correct ✓The fix is correct and thorough. const ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
function parseIsoStrict(value) {
return typeof value === "string" && ISO_DATE_TIME.test(value) ? Date.parse(value) : Number.NaN;
}This rejects Fixture: Codex P2 fix (18228fc): same-surface level-marker ambiguity rejected — correct ✓The fix is correct and precisely scoped. The old const sameSurface = a.surfaceId === b.surfaceId;
const pairKey = sameSurface
? JSON.stringify([a.surfaceId, a.level, b.level])
: JSON.stringify([a.surfaceId, b.surfaceId]);The finding message correctly distinguishes the two cases, naming the ambiguous levels and suggesting the fix (conflicting markers on a shared key, or split into distinct surfaces). The comment at L503–L509 now accurately states that max-proven-level aggregation is sound only because the joint-satisfiability check above enforces that same-surface level entries carry conflicting markers — the two checks form a coherent pair. Fixtures:
All Codex findings addressed
Remaining open items from prior reviews
Summary18228fc's two fixes are correct, mutually consistent, and well-covered by the three new fixtures. All eight Codex findings across six review cycles are correctly addressed. The architecture, fail-closed semantics, and fixture discipline remain sound throughout. One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18228fcc9c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…t fake conflicts Object.hasOwn across all parsed-JSON membership checks (20 sites); a marker key like toString no longer reaches Object.prototype and falsely satisfies the conflict requirement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 52s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 8b39025 fixes
Blocker still open: demo workflow not removed (seventh time flagging)
The file must be deleted before squash-merge. All items below are non-blocking. Codex P2 fix (8b39025): own-property marker comparisons — correct ✓The fix is correct. function jointlySatisfiable(markersA, markersB) {
for (const [key, value] of Object.entries(markersA)) {
if (Object.hasOwn(markersB, key) && markersB[key] !== value) return false;
}
return true;
}Why this matters: With the old Fixture: Complete audit: Logic correctness: The one-direction iteration ( All Codex findings addressed
Remaining open items from prior reviews
Summary8b39025's fix is correct, comprehensive (all 20 membership checks audited), and exercised by One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b39025899
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…class Transcript substrate_class is required and validated; an L3 entry accepts only vm-microvm — a relabeled container transcript can no longer unlock C5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 23s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 680b077 fixes
Blocker still open: demo workflow not removed (eighth time flagging)
The file must be deleted before squash-merge. All items below are non-blocking. Codex P1 fix (680b077): L3 substrate_class validation — correct ✓The fix is correctly placed and thorough. const SUBSTRATE_CLASSES = new Set(["container", "os-sandbox", "vm-microvm"]); // L57
// Tier 1: unknown class → UNPROVEN
if (!SUBSTRATE_CLASSES.has(transcript.substrate_class)) {
return `transcript ${path} records substrate_class ... — required, one of container | os-sandbox | vm-microvm`;
}
// Tier 2: L3 floor — only kernel-separated substrates qualify
if (level === "L3" && transcript.substrate_class !== "vm-microvm") {
return `transcript ${path} records substrate_class ... for an L3 entry — L3 requires kernel separation, which only substrate_class "vm-microvm" provides`;
}Sequencing is correct: the Backward compatibility: All 6 probe transcripts have been backfilled with Template updated: New fixtures:
Demo workflow: The live-generated transcript at L62 correctly emits All Codex findings addressed
Remaining open items from prior reviews
Summary680b077's fix is correct, sequenced properly within the verifier, and covered by the One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 680b077be4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
parseIsoStrict derives the epoch from captured components and requires a full round trip — silent normalization (Feb 30, hour 25) fails instead of shifting the promotion epoch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 46s —— View job Claude finished @kyle-sexton's task in 3m 23s —— View job Claude finished reviewing @kyle-sexton's updates — Review of 1a1c4b9 fixes
Blocker still open: demo workflow not removed (ninth time flagging)
The file must be deleted before squash-merge. All items below are non-blocking. Codex P1 fix (1a1c4b9): calendar-valid promotion timestamps — correct ✓The fix is correct and complete. const utc = new Date(Date.UTC(+year, +month - 1, +day, +hour, +minute, +second, ms));
if (
utc.getUTCFullYear() !== +year ||
utc.getUTCMonth() !== +month - 1 ||
utc.getUTCDate() !== +day ||
utc.getUTCHours() !== +hour ||
utc.getUTCMinutes() !== +minute ||
utc.getUTCSeconds() !== +second
) { return Number.NaN; }
Offset math is correct: for Backward compatibility: for any previously-valid, calendar-valid ISO 8601 timestamp, Fixture: One observation (non-blocking): the offset field is validated by the regex's All Codex findings addressed
Remaining open items from prior reviews
Summary1a1c4b9's fix is correct, backward-compatible, and exercised by One action still required before squash-merge: delete |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a1c4b99b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
#549 redesign implemented — landing blocked on concurrent-worker collision (escalating)The deny-by-default redesign from #549 is complete and verified, but I am not pushing it onto this branch this cycle because a second, differently-identified autonomous session (git co-author "Claude Fable 5") is actively extending the superseded point-fix approach on the same branch (commits Redesign preserved on branch
Reconciliation note for whoever lands it: Fable 5's two commits are a mix — the required |
A failed read must be attributable to a SECRET being absent or denied. Recognition is now terminal-form based: the path must end at a well-known concrete secret file (the OpenSSH private-key family, .netrc, credentials, .credentials, .kube/config, .docker/config.json, .config/gh/hosts.yml). A non-secret sibling under a credential directory ($HOME/.ssh/known_hosts) passes the outer existence check and fails the inner read while the actual private key stays exposed, and a bare directory marker ($HOME/.ssh) has ambiguous read-tool semantics and fails everywhere — both now reject. GnuPG drops from the recognized set (no static concrete secret file; key-grip names are arbitrary) and defers to the configured allow-list seam, like org-named /run/secrets entries. Transcripts with terminal directory markers migrate to their concrete files; reject fixtures pin the known_hosts descendant and the bare directory marker; an accept pin covers $HOME/.ssh/id_rsa. Sweep: 8 pass / 78 reject, zero flips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 21s —— View job Claude Code Review —
|
| Excluded pattern | Reason |
|---|---|
.ssh (directory alone) |
Directory, not a file — now rejected. $HOME/.ssh/known_hosts → terminal known_hosts ∉ any set. |
.aws (directory alone) |
Same; $HOME/.aws/credentials still accepted (terminal credentials ∈ TERMINAL_SECRET_FILES). |
.gnupg/* |
GPG key grip filenames are dynamic hex strings — no static terminal form possible. |
.config/gh (pair) |
Elevated to triple .config/gh/hosts.yml; bare directory is now rejected. |
Edge cases verified:
id_rsa.pub→ rejected (.pubsuffix ∉ set). ✓$HOME/.aws/config→ rejected (config∉ set; onlycredentialsis). ✓$HOME/.config/gh(directory) → rejected (terminalgh∉ set; no pair match). ✓$HOME/.config/gh/hosts.yml→ accepted by triple. ✓
New fixtures — correctness
| Fixture | Path | Expected outcome | Logic |
|---|---|---|---|
ci-pool-a-l2-known-hosts.json |
$HOME/.ssh/known_hosts |
UNPROVEN → 1 finding | known_hosts ∉ TERMINAL_SECRET_FILES |
ci-pool-a-l2-ssh-dir.json |
$HOME/.ssh |
UNPROVEN → 1 finding | .ssh ∉ TERMINAL_SECRET_FILES |
ci-pool-a-l2-id-rsa.json |
$HOME/.ssh/id_rsa |
PROVEN → 0 findings | id_rsa ∈ TERMINAL_SECRET_FILES; anchor $HOME; outer_exit_code=0 |
All three are structurally consistent with existing transcript fixtures (schema-valid, outer_context_networked: true, correct transport outcomes). ✓
Updated existing negative fixtures — single-defect invariant preserved
Each existing negative fixture had its path field updated from a directory form to a terminal file form so the ONE intended defect remains the only reason for rejection:
| Fixture | Was (path) | Now (path) | Defect (unchanged) |
|---|---|---|---|
cred-outer-nonzero |
/root/.ssh |
/root/.ssh/id_rsa |
outer_exit_code: 1 |
invented-home-user |
/home/definitely-not-a-host-user/.ssh |
/home/definitely-not-a-host-user/.ssh/id_rsa |
Non-existent user in home path |
dot-traversal |
/root/../var/empty/.ssh |
/root/../var/empty/.ssh/id_rsa |
Dot-segment /../ rejection |
expansion-ephemeral |
$HOME/.ssh → /tmp/.ssh |
$HOME/.ssh/id_rsa → /tmp/.ssh/id_rsa |
Expansion to ephemeral /tmp/ base |
expansion-tail-mismatch |
$HOME/.ssh → /home/runner/.gnupg |
$HOME/.ssh/id_rsa → /home/runner/.gnupg/id_rsa |
Tail /.ssh/id_rsa ≠ /.gnupg/id_rsa |
All five correctly isolate the same single defect after the path update. ✓
The expansion-tail-mismatch case deserves a note: the new host_expanded ends in /id_rsa and the path tail is /.ssh/id_rsa — the suffixes share /id_rsa but the tail mismatch is still detected because the full 12-char tail /.ssh/id_rsa ≠ any suffix of /.gnupg/id_rsa. ✓
Template update
isolation-probe.md now reads: "ending at a concrete secret file rather than a directory or an arbitrary file beneath one". This accurately documents the new requirement for operators writing probe transcripts. ✓
No issues found
The fix is minimal, targeted, and complete. All 54 prior Codex findings remain addressed; this commit resolves the final P1 (credential directory bypass). The single-defect-per-fixture invariant is maintained across all eight modified fixtures. The terminal-form enumeration is correct for the SSH, AWS, kubeconfig, Docker, and GitHub CLI credential families.
This PR is ready to squash-merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 474b63a7a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The substrate class was accepted solely from the probe transcript, so doctoring an otherwise-valid L2 container capture to level L3 with substrate_class vm-microvm reported C5 eligible although the two probe assertions cannot establish kernel separation. Each level binding now carries a required human-ratified substrate_class on the agent-unwritable surface; eligibility keys off that ratified class (an L3 entry ratifying a non-kernel-separated class is an invalid binding, fail-closed), and the transcript's recorded class is capture evidence that must equal it — so a doctored capture mismatches, and upgrading the class itself requires a human change on the governance surface. The dispatch seam attests runner identity via runtime_markers; the class rides the ratified binding, never the capture. All binding fixtures migrated with ratified classes matching their transcripts; new fixtures pin the doctored-capture mismatch, the non-kernel-separated L3 ratification, and the missing-class shape. Sweep: 8 pass / 81 reject, zero flips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 56s —— View job Review — commit
|
| Fixture | Scenario | Expected outcome |
|---|---|---|
l3-ratified-class-container.json |
L3 binding with substrate_class: "container" |
Structural finding — ratified class is not kernel-separated |
missing-substrate-class.json |
L2 binding with no substrate_class field |
Structural finding — required field absent |
probe-evidence-ratified-class-mismatch.json |
L3 binding with substrate_class: "vm-microvm" but transcript records a container |
Semantic finding — transcript proves a different substrate than ratified |
All three fixtures are well-formed in every other dimension (admission policy, escalation routes, merge policy) so only the targeted finding fires.
One minor observation (non-blocking)
In verifyProbeTranscript, the ratified-vs-transcript class mismatch check (transcript.substrate_class !== substrateClass) fires before the recognized-class check (!SUBSTRATE_CLASSES.has(transcript.substrate_class)). If the transcript carries a completely unrecognized class, the returned error says "mismatch" rather than "unrecognized class" — slightly less precise. Both paths return non-null (transcript unverified), so the fail-closed guarantee holds. Worth a note in the comment block for future readers, but no code change needed.
Verdict
The attack vector is fully closed. The fix places the eligibility decision exclusively on the human-ratified governance surface, validates that surface structurally before any transcript is read, and then cross-checks the transcript's capture against the ratified assertion. The three fixture files cover the three distinct failure modes introduced by the new requirement.
LGTM — ready to squash-merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 918a8e5337
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ions A recognized secret-file basename at arbitrary depth under a credential anchor let a probe pick a benign existing descendant ($HOME/scratch/ credentials, /etc/example/credentials) whose outer existence check passes while real host credentials stay exposed. Recognition is now exact-location: home-anchored relative forms (ssh key family, .netrc, .git-credentials, .aws/credentials, .kube/config, .docker/config.json, .config/gh/hosts.yml) under a home env token or /root, plus fixed system secret files (/etc/ssh host keys, /run/secrets/credentials) matched in full. The vague .credentials basename is gone (no tool owns it); fixtures probe $HOME/.netrc. Two new reject fixtures cover anchor-descendant entries. Sweep: 8 pass / 83 reject, zero flips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 7m 11s —— View job PR #377 — Security review:
|
| Entry | Old result | New result | Correct? |
|---|---|---|---|
$HOME/scratch/credentials |
ACCEPTED (basename credentials matched) |
REJECTED (["scratch","credentials"] ∉ HOME_SECRET_RELATIVE_FORMS) |
✅ |
/etc/example/credentials |
ACCEPTED (etc anchor + credentials basename) |
REJECTED (["etc","example","credentials"] ∉ FIXED_SECRET_PATHS) |
✅ |
$HOME/.ssh/id_rsa |
ACCEPTED | ACCEPTED ([".ssh","id_rsa"] ∈ forms) |
✅ |
$HOME/.aws/credentials |
ACCEPTED (bare credentials basename) |
ACCEPTED ([".aws","credentials"] ∈ forms, explicit) |
✅ |
/root/.ssh/id_rsa |
ACCEPTED | ACCEPTED (segments[0]==="root", relativeForm=[".ssh","id_rsa"]) |
✅ |
/etc/ssh/ssh_host_rsa_key |
REJECTED (not in old terminal files) | ACCEPTED (new FIXED_SECRET_PATHS) |
✅ new |
$HOME/.git-credentials |
REJECTED (not in old terminal files) | ACCEPTED (new HOME_SECRET_RELATIVE_FORMS) |
✅ new |
$HOME/.credentials |
ACCEPTED | REJECTED (removed — not a well-known tool file) | ✅ |
$HOME/id_rsa |
ACCEPTED (id_rsa basename + home anchor) |
REJECTED (["id_rsa"] ∉ forms — must be under .ssh/) |
✅ |
/root/$HOME/.ssh/id_rsa |
ACCEPTED (dot-traversal gate aside) | REJECTED (["$home",".ssh","id_rsa"] ∉ relative forms) |
✅ |
Corner cases confirmed correct:
- UNC-form rejection (
normalized.startsWith("//")) at L214 fires before segment matching - Dot-segment rejection at L208 fires before segment matching
- Ephemeral segment rejection at L223 fires before segment matching
- Bare
$HOME(single segment) routes through the env-token branch at L199–L202, not recognized → REJECTED ✅ %USERPROFILE%\.ssh\id_rsa— afterreplaceAll("\\","/")→%userprofile%/.ssh/id_rsa,isHomeEnvTokenmatches →relativeForm=[".ssh","id_rsa"]→ ACCEPTED ✅
Fixture integrity verified:
- probe-evidence-etc-descendant-credpath.json: probes
/etc/example/credentials— not inFIXED_SECRET_PATHS→ 1 finding ✅ - probe-evidence-home-descendant-credpath.json: probes
$HOME/scratch/credentials—["scratch","credentials"]∉HOME_SECRET_RELATIVE_FORMS→ 1 finding ✅
Backfill verified (~40 transcripts): Every transcript that previously used $HOME/.credentials (no longer in the recognized set) was updated to $HOME/.netrc (still recognized as [".netrc"]). Each fixture's intended single defect — egress host classification, exit code validation, etc. — is preserved because the credential path change is neutral to those defects.
Error message updated at L1093 to enumerate exact required forms rather than basenames, eliminating the old confusing "probe genuine host credential locations ending at…" phrasing that implied basename matching was sufficient.
Non-blocking observation
Fixture corpus has no automated CI driver. The 93 binding fixtures in evals/fixtures/security-binding/ are not exercised by CI. scripts/run-plugin-tests.sh runs *.test.sh files; there are none for the autonomy plugin. The fixtures are invaluable as regression documentation and can be run manually against the checker, but a future logic regression in check-security-binding.mjs will not be caught by a CI lane. Suggest adding a check-security-binding.test.sh that runs the checker against every fixture and asserts exit-code 1 for negatives and exit-code 0 for positives (the valid/multi-surface fixtures). Not a blocker for this PR — the current fixture set is correct — but worth tracking.
Overall assessment
The fix is correct and complete. The exact-path matching approach eliminates the entire class of anchor-descendant attacks by construction, the new credential forms are well-chosen (system SSH host keys, .git-credentials, .aws/credentials explicit pairing), the removed forms ($HOME/.credentials, bare basename under /etc) are correctly de-listed, and the 40-transcript backfill preserves each fixture's single intended defect.
All 55+ prior findings from this review cycle remain addressed. The schema (substrate_class as a required binding-side assertion), the validator semantics (fail-closed, probe-before-bind, egress-host classification, credential-path exactness), and the guardrail reference documents are consistent with each other.
Approved.
…ice (WP6) (#600) WP6 standing-routines package (T7), per the merged PLAN. All four phases complete; the novel-class derivation probe and near-duplicate audit statement are recorded below. No linked issue ## Related - #359 (WP4–WP7 plans, incl. this package's PLAN as merged) - #242 (standing scheduled routines — the decision node this package graduates: which maintenance classes, hosted where) - #239 (WP map: AI adoption ladder, step-3 unlock) - #372 (WP4 trigger-dispatch — routines are scheduled `temporal` adapters behind its queue contract) - PR #377 (WP5 guardrail matrix + security binding — the matrix the mapping rules derive into; the `admission.classification.temporal` home the routine slice binds) ## What ships - **Routine catalog hub** (`reference/routines.md`): routine = scheduled trigger adapter behind the governed queue, never a private execution/merge path; two-family repetition language (session-scoped: loop, goal, batch, dynamic workflow; standing: schedule, routine — with the corrected batch definition); schedule/event/continuous trigger taxonomy; output contract (report OR governed-queue work item; direct change only through the matrix merge policy); the contract-owned catalog-to-matrix mapping rules including the refined axes (DET → plain cron, `not-a-routine`; hybrid DET-detect + AGT-judgment split; AGT rules to C1–C3; structural/config-surface direct change → C4; attacker-writable external input → C5; compose-to-highest; access axis → prerequisites); 40-row glance catalog — ten `v1` rows, seven `not-a-routine` rows, named join triggers on every deferred row. - **Ten v1 definition leaves** (`reference/routines/`): uniform shape (purpose, trigger and cadence, access scope, output contract, derived guardrail row, admission and escalation, precedent). Every guardrail row is derived step-by-step through the mapping rules, none hand-assigned: six pure-advisory classes at C1/L2 floor; `doc-freshness-sweep`'s optional gated docs-change portion C3; `dependency-update-wave` hybrid with two explicit postures (mechanical-only C2; changelog-reading posture provenance-composed to C5); `tech-debt-sweep` hybrid with human-gated prioritization; `ci-health-review`'s optional CI-config change portion C4 (structural surface). Cadence defaults org-bindable; governance imported by citation only. - **Guided-setup routine slice** (`skills/setup/SKILL.md`): discovery-first surface interview + budget posture; free defaults wired as reviewable changes across role homes; paid/preview surfaces advisory with cost surfaced; detect-diff-reconcile of existing schedulers and bots (a live judgment-bearing bot IS a catalog-class instance — recorded under its class token, never duplicated; DET stays cron through reconciliation). Binding-home split by governance sensitivity: the routine→work-class mapping is admission data and lands only in the security binding's `admission.classification.temporal` home (settings-as-code, agent-unwritable, reconciled bots included); the repo-local `routines` section carries cadence/enablement/surface only, reusing the envelope checker's `surfaces` map shape. - **`templates/routine-definitions.md`**: per-surface-class wiring shapes (CI-cron / local scheduler / self-run / vendor-preview as marked examples), every shape enqueuing through the trigger contract's `temporal` adapter — no direct execution path. - **Protected routine identity (review wave 1)**: routines bind under a routine identity (`<class-token>` or `<class-token>/<posture-token>`; multi-posture classes bind per posture, never the bare token); `admission.classification.temporal` entries are object form `{class, source_surface}` — the identity↔surface association on the security binding, one identity per emitting surface; the temporal envelope carries a required `signal.routine` claim admission validates before stamping (`check-security-binding.mjs` + `check-signal-envelope.mjs` enforce; five new fixtures; sweep 9 pass / 89 reject). - **Evals**: five routine-slice cases (surface interview first, free-default wiring home split, existing-bot reconciliation record-not-duplicate, paid-surface advisory refusal-to-default, selector-is-a-claim protected association). Plugin 0.5.0 → 0.6.0. ## Novel-class derivation probe (Phase 4 acceptance) Classifying a class the catalog does not contain, using only the shipped mapping rules — an untrusted-provenance case, exercising the C5 reach: **Candidate class: community-forum feedback triage** — a standing sweep that reads public community-forum threads about the product, clusters recurring complaints, and files candidate work items. 1. **Judgment axis**: semantic clustering, salience ranking, and routing are agent judgment → `AGT` (not `DET`; no deterministic detection half worth a hybrid split — a keyword pre-filter could route to plain cron, leaving the judgment portion as the routine, per the hybrid branch). 2. **Output axis**: report + work items into the governed queue, no repository mutation → `C1` base (`AGT` + report → `C1`; `AGT` + work item → `C1`). 3. **Direct-change axes**: no direct change → the `C2`/`C3` mechanical-checkability rule and the `C4` structural-surface rule do not lift the class. 4. **Input-provenance axis**: public forum posts are attacker-writable external content → derives `C5` (untrusted provenance). 5. **Composition**: multiple matches compose to the highest-risk class — `C5` > `C1` → the class is **C5**. 6. **Access axis → prerequisites**: `ext` access → connector prerequisite + entitlement resolved in the org binding, join trigger "named intel need"; the `C5` matrix row then fixes the floor — L3 isolation, full gates + zero secret exposure, human merge always, escalation always. The read-only class's exfiltration-surface caveat arrives as the class outcome, not a footnote. End-to-end: axes → guardrail row → prerequisites, with zero contract changes. The acceptance criterion holds, including the C5 reach the plan's stress-test added the provenance axis for. ## Near-duplicate audit Routines compose the WP4 `temporal` adapter and the WP5 guardrail matrix; deterministic checks stay plain cron filing work items through the same trigger adapters; no second scheduling, execution, or merge path is created by this package. ## Plan deviation (recorded) The PLAN's Phase-1 row-count gate said 39 (one row per research-taxonomy class). The research table contains only 9 of the 10 locked v1 classes — `pr-queue-tending` has no research row (the nearest research row, stale grooming, is pure-DET and locked `not-a-routine`). The catalog therefore ships 40 rows = 39 research rows + the fleet-proven `pr-queue-tending`. The D2 mapping rules, the seven `not-a-routine` rows, and the ten-class v1 roster are untouched. ## Gates validate-plugins.sh, validate-plugin-contracts.mjs (33 setup skills / 1777 plugin files), markdownlint-cli2, typos, `claude plugin validate --strict`, catalog regen in-sync — all green at head. Reference-doc vendor-name sweep: zero hits (hub + all ten leaves). run-plugin-tests.sh and lychee validate in CI. <details> <summary>WP6 PLAN (full)</summary> # ai-ladder-wp6-routines ## Brief ### TLDR Standing-routines package (T7): catalog doc as a progressive-disclosure hub (class table = glance layer), contract-tier definition docs for the v1 subset only, contract-owned catalog-to-matrix mapping rules, and the routine slice of guided-setup. A routine stays a scheduled trigger adapter behind the governed queue — never a private execution or merge path. ### Goal Any adopting org can stand up governed background maintenance — the step-3 unlock: "maintenance and cleanup that used to wait for someone to find the time now runs continuously in the background" — with every routine's guardrail row derived from catalog axes, free hosting defaults, and existing org schedulers reconciled rather than duplicated. ### Locked decisions | # | Decision | |---|---| | D1 | Scope: catalog doc (hub-and-leaves — class table with judgment/output/access scores = glance layer) + per-class definition leaf docs for the v1 subset ONLY (non-v1 classes stay table rows with precedent pointers until their join trigger fires) + catalog-to-matrix mapping rules + routine slice of guided-setup. All capability-distribution home. v1 definitions are contract-tier tool-agnostic docs; vendor-binding capability templates are build-stage artifacts. Fleet routine stand-up = /work-items backlog. | | D2 | Catalog-to-matrix mapping rules are contract-owned (orgs classify new routine classes without republication): judgment DET → not a routine — plain cron, zero agent tokens, failures file work items through trigger adapters; AGT + report → C1; AGT + work-item output → C1 (governed-queue write, no repo mutation); AGT + direct change → C2 where truth is mechanically checkable, C3 otherwise; AGT/HUM → agent prepares, disposition human-gated always. Access axis → prerequisites: repo-scoped = L2 unattended floor; prod/product/org/ext = connector prerequisite + entitlement resolved in the org binding; external-watch classes read attacker-writable content — the read-only class's exfiltration-surface caveat applies; unattended GUI actuation = highest isolation tier. *REFINED 2026-07-18 (WP6/WP7 stress-test F3+F4): (a) hybrid branch — a DET-detect + AGT-judgment class (catalog rows 7/14/16/28, incl. v1 dependency-update-wave) splits: the detection portion routes to plain cron, the judgment portion IS the routine and derives through the AGT rules; the binary DET/AGT reading could not classify hybrids. (b) Pure-DET classes carry an explicit `not-a-routine` catalog flag (rows 2/12/20/22/25/27/34) instead of silent exclusion. (c) Two axes added so derivation reaches the full matrix: input provenance — a routine consuming attacker-writable external content derives C5 (untrusted provenance), upgrading the external-watch caveat to a class outcome; structural blast radius — direct change to structural/config surfaces derives C4. Overlapping matches compose to the highest-risk class (C5 > C4 > C3 > C2 > C1). Without them derivation topped out at C3 and the novel-class acceptance criterion was unmeetable for C4/C5 cases.* | | D3 | v1 definition doc shape (each leaf): purpose (toil addressed), trigger-taxonomy slot with suggested cadence (org binds values), access scope, output contract (report shape / work-item filing / gated change), guardrail row derived via D2, admission + escalation notes imported from the guardrail contract, precedent pointer. No vendor scheduling detail baked in — surfaces are preview-stage moving targets; setup researches them live. | | D4 | v1 subset (repo-scoped, agent-judgment, proven manual patterns): issue triage sweep, duplicate-detection sweep, backlog readiness check, PR-queue tending, doc-freshness sweep, dependency update wave, advisory/CVE triage, tech-debt sweep, ~~dead-code sweep~~ eng-metrics digest, CI health review. *CORRECTED 2026-07-18 (review evidence, RESEARCH-routine-catalog row 25): dead-code sweep is classified DET detect — per D2's rule it is not a routine; detection routes to plain cron filing work items through trigger adapters, and any judgment-bearing triage of its findings belongs to the tech-debt sweep. The WP6 architect round confirms the final v1 roster.* Deferred classes keep T7's named join triggers (prod/product/org/ext access, GUI actuation). | | D5 | Routine slice of guided-setup is discovery-first: interview the org for scheduling surfaces (CI cron, dev machine, self-run infra, vendor-hosted preview) and budget posture; wire free defaults as reviewable changes (CI-cron handler = CI-orchestration home, enabling settings = settings-as-code home); advise paid/preview surfaces with cost surfaced; detect-diff-reconcile EXISTING org schedulers and bots — a live dependency bot or stale bot IS a catalog-class instance: record it in the binding, never stand up a second mechanism for the same concern. | | D6 | Ubiquitous language imported from the routine contract thread with one correction: batch = session-scoped parallel fan-out over decomposed units (vendor command verified against the official commands doc; research had conflated it with bulk-inference Batch APIs). Contract carries the two-family distinction: session-scoped (loop, goal, batch, dynamic workflow) vs standing (schedule, routine). Routine output = advisory report OR work item into the governed queue; direct change only through the guardrail matrix's merge policy. | | D7 | Hosting stance imported unchanged (deployment-owned binding): contract fixes invariants only — queue contract, per-class isolation floor, merge-policy caps including hosted-stays-human-gated, cost surfaced before any paid binding. Profiles are non-normative examples; no machine/org-size/budget assumption. | ### Constraints - Any fleet repo or vendor name in normative contract text is a defect; vendor names appear only as marked examples and in binding docs. - No second execution, merge, or scheduling path: routines enter work through the trigger contract's queue and are governed by the guardrail matrix from there. - Deterministic checks are never routines — no agent tokens on script work. - No new cost by default; paid/preview scheduling surfaces are advisory + explicit opt-in. - Boris-alignment is the standing acceptance criterion (no step-skipping, trust before scale). ### Acceptance criteria - Catalog glance layer alone answers "what classes exist, what governs each" — leaf docs only for v1 depth (progressive-disclosure conformance). - Every v1 leaf derives its guardrail row through the D2 rules; no row is hand-assigned outside them. - Mapping rules let an org classify a novel routine class end-to-end (axes → guardrail row → prerequisites) without contract changes. - Setup slice wires only reviewable changes, reconciles existing schedulers instead of duplicating them, and has zero paid dependencies on its default path. - Research gaps carried visibly: dependency-update-wave precedent detail rests on official docs whose page bodies were not all fetched in full (re-verify at architect); support→bug conversion class remains emerging/unverified (not in v1); one vendor goal-primitive claim unconfirmed (not load-bearing for v1). - Boris check: step-3 unlock sentence instantiated by the v1 subset running as governed background work; "break up your work into loops and routines" cell covered with the session-scoped vs standing distinction; step-3 trap honored (catalog-now, instantiate-on-trust — v1 dispatch gated by the admission policy and promotion discipline); token-efficiency bottleneck addressed (deterministic-stays-cron + cost tiers); step-1 guardrail untouched. ### Captured assumptions - The v1 classes' manual patterns remain proven in the first adopting instance (fleet dogfood evidence: recurring manual sweeps exist for each). - Vendor scheduling surfaces remain preview-stage moving targets; contract-tier docs stay binding-free and setup researches surfaces live (re-verify at setup time, not bake-in). - The guardrail matrix (WP5) and trigger contract (WP4) land before or with any v1 routine instantiation — routines have no governance path without them. ### Out-of-scope (deferred with triggers) - Non-v1 class definition leaves — trigger: T7's named join triggers per group (telemetry connector, analytics/feedback connected, org systems connected, named intel need, GUI-only system + isolation tier). - Vendor-binding capability templates (routine/workflow files) — build stage, post-architect. - Fleet routine stand-up + existing-scheduler reconciliation execution — /work-items backlog post-graduation. - Interactive escalation UX for routine-filed items — WP7 (inherited from guardrail package). ### Deferred questions - Exact class tokens + catalog table serialization — `/architect` (with plugin naming pass). - Suggested cadence defaults + per-class output templates — `/architect`. - Leaf-doc file split + glance wording — `/architect`. - Adapter shapes per scheduling-surface class — `/architect` (live-verified then). ## Plan Recommendation-locked this round under the user's standing pre-authorization: catalog hub `reference/routines.md` with v1 definition leaves under `reference/routines/` (one file per v1 class, kebab-case class tokens below); class tokens for the v1 roster — `issue-triage-sweep`, `duplicate-detection-sweep`, `backlog-readiness-check`, `pr-queue-tending`, `doc-freshness-sweep`, `dependency-update-wave`, `advisory-cve-triage`, `tech-debt-sweep`, `eng-metrics-digest`, `ci-health-review` (ten classes; dead-code sweep stays off the v1 roster per the D4 correction — the architect-round confirmation the correction asked for — and appears in the catalog as a `not-a-routine` row; hybrid v1 classes, e.g. `dependency-update-wave`, classify through the D2 hybrid branch: detection portion cron, judgment portion the routine); catalog table serialization = one markdown table (class token × judgment × output × access × derived guardrail row × v1/deferred + join trigger). Prerequisites: WP4 implementation merged (`reference/trigger-dispatch.md` — routines are scheduled trigger adapters behind its queue contract) and WP5 implementation merged (`reference/guardrails.md` — the D2 mapping rules derive rows into its matrix). This package merges after both. ### Phase 1: Catalog hub + mapping rules [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/reference/routines.md` | Create | The T7 routine contract as the catalog hub: routine definition (a scheduled trigger adapter behind the governed queue — never a private execution/merge path); the two-family ubiquitous language imported per D6 (session-scoped: loop, goal, batch, dynamic workflow; standing: schedule, routine — with the corrected batch definition); trigger taxonomy (schedule / event / continuous); output contract (advisory report OR work item into the governed queue; direct change only through the matrix merge policy); the D2 catalog-to-matrix mapping rules verbatim, INCLUDING the refined axes (DET → plain cron, never a routine — flagged `not-a-routine` in the catalog; hybrid DET-detect + AGT-judgment → split: detection portion cron, judgment portion IS the routine, derived through the AGT rules; AGT+report → C1; AGT+work-item → C1; AGT+direct-change → C2 where mechanically checkable else C3; structural/config-surface direct change → C4; input provenance: attacker-writable external content → C5, the untrusted-provenance class, not a caveat; when multiple rules match, the derivation COMPOSES TO THE HIGHEST-RISK class (C5 > C4 > C3 > C2 > C1 — a structural change driven by attacker-writable input derives C5 and its L3 floor, never C4's lower floor); AGT/HUM → human-gated disposition; access axis → prerequisites incl. L2 floor for repo-scoped, connector+entitlement for prod/product/org/ext, highest tier for unattended GUI actuation — without the C4/C5 axes the Phase 4 untrusted-provenance derivation could not run from the shipped contract); the glance-layer catalog table: all 39 classes as rows (class token, judgment, output, access, derived row, and a status flag — `v1`, a named join trigger, or `not-a-routine` for the seven pure-DET rows incl. dead-code sweep, which stay as rows with their cron/tooling precedent pointers rather than silent exclusions); hybrid rows show the D2 split (detection portion cron, judgment portion the routine); non-v1 rows carry precedent pointers only, no leaves; hosting stance per D7 (invariants only, profiles as non-normative examples). Zero vendor/fleet names in normative text. | **Sanity Check:** - `grep -c 'DET' plugins/autonomy/reference/routines.md` ≥ 1 and mapping rules present (`grep -ci 'plain cron' …/routines.md` ≥ 1) - Catalog rows: table row count = 39 (`grep -cE '^\| [a-z][a-z0-9-]+ \|' plugins/autonomy/reference/routines.md` = 39) - v1 rows = 10 (`grep -c '| v1 |' plugins/autonomy/reference/routines.md` = 10, or the chosen flag token) - `not-a-routine` rows = 7 (`grep -c 'not-a-routine' plugins/autonomy/reference/routines.md` ≥ 7); the dead-code-sweep row carries the flag (`grep 'dead-code' plugins/autonomy/reference/routines.md | grep -c 'not-a-routine'` = 1 — flagged, never a silent exclusion, and never a v1 leaf) - Vendor+fleet deny-list sweep exit 0; lychee lane passes ### Phase 2: v1 definition leaves [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/reference/routines/<class-token>.md` × 10 | Create | One leaf per v1 class, uniform D3 shape: purpose (toil addressed); trigger-taxonomy slot + suggested cadence default (issue-triage-sweep, duplicate-detection-sweep, backlog-readiness-check, pr-queue-tending: daily; doc-freshness-sweep, tech-debt-sweep, ci-health-review: weekly; dependency-update-wave, advisory-cve-triage: weekly with event-riding on advisories; eng-metrics-digest: weekly — all org-bindable values); access scope (repo); output contract (report vs work-item filing per class); guardrail row DERIVED via the D2 rules with the derivation shown (no hand-assigned rows); admission + escalation notes imported by citation from the guardrail contract; precedent pointer (the proven manual pattern). No vendor scheduling detail. | **Sanity Check:** - `ls plugins/autonomy/reference/routines/*.md | wc -l` = 10 - Every leaf shows its derivation: `grep -lc 'derived' plugins/autonomy/reference/routines/*.md | wc -l` = 10 - Every leaf names a cadence default: `grep -lc 'cadence' plugins/autonomy/reference/routines/*.md | wc -l` = 10 - Vendor+fleet deny-list sweep exit 0 ### Phase 3: Guided-setup routine slice [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/skills/setup/SKILL.md` | Modify | Routine slice (D5): interview scheduling surfaces (CI cron, dev machine, self-run infra, vendor-hosted preview) + budget posture; wire free defaults as reviewable changes (CI-cron handler shape = CI-orchestration home role, enabling settings = settings-as-code home role); advise paid/preview surfaces with cost surfaced; DETECT-DIFF-RECONCILE existing org schedulers and bots — a live dependency/stale bot IS a catalog-class instance: record it in the binding (class token + surface), never stand up a second mechanism. Binding home split by governance sensitivity (stress-test F1, the WP5 D2 split): the routine→work-class mapping each temporal signal's `signal.work_class` stamp derives from is ADMISSION data — it lands in the WP5 security binding's `admission.classification` (settings-as-code home, reviewable change, agent-unwritable), for reconciled bots too; only non-security keys (cadence, enablement, surface choice) land as the additive repo-local `routines` section of the schema-versioned binding. A repo-local class source would be the exact agent-writable bypass WP4's classification obligation forbids. Research scheduling surfaces live at setup time (preview-stage moving targets), never from this doc. | | `plugins/autonomy/skills/setup/templates/routine-definitions.md` | Create | Per-surface-class routine wiring shapes (CI-cron / local scheduler / self-run / vendor-preview as marked examples), parameterized by class token + cadence; every shape enqueues through the trigger contract's temporal adapter — no direct execution path. | | `plugins/autonomy/skills/setup/evals/evals.json` | Modify | Routine-slice cases: surface interview, free-default wiring, existing-bot reconciliation (record-don't-duplicate), paid-surface advisory refusal-to-default. | | `plugins/autonomy/README.md` + `plugins/autonomy/.claude-plugin/plugin.json` | Modify | Capability list + description + minor version bump. | **Sanity Check:** - `/skill-quality:check` + `validate-evals` pass; `claude plugin validate --strict` exit 0 - `grep -ci 'detect-diff-reconcile' plugins/autonomy/skills/setup/SKILL.md` ≥ 2 (guardrail slice + routine slice each state it) - `grep -c 'temporal' plugins/autonomy/skills/setup/templates/routine-definitions.md` ≥ 1 (queue-entry via the trigger contract) - Fleet-name sweep exit 0 ### Phase 4: Derivation demonstration + gates [DONE] Acceptance probe: classify one NOVEL routine class end-to-end using only the shipped mapping rules (axes → guardrail row → prerequisites) — the acceptance criterion that an org can classify without contract changes; the chosen novel class MUST be an untrusted-provenance case (attacker-writable external input) so the probe exercises the D2 provenance axis and the C5 reach the stress-test found missing, not just the easy C1–C3 path; record the worked example in the PR body (not in the contract — it is evidence, not normative text). Then the full gate roster (`validate-plugins.sh`, `run-plugin-tests.sh`, `validate-plugin-contracts.mjs`, markdown/typos/lychee, `claude plugin validate --strict`, catalog regen). Near-duplicate audit statement: routines compose the WP4 temporal adapter + WP5 matrix; deterministic checks stay plain cron; no second scheduling or merge path created. **Sanity Check:** - Worked novel-class derivation present in the PR body - All gate scripts exit 0; catalog in-sync - Near-duplicate audit statement present in the PR body ## Blast radius MEDIUM — one plugin's files; the catalog constrains WP7 and every adopting org's background maintenance, but all governance content is imported from WP4/WP5 by citation, not redefined. Fully git-revertible. ## Stress-test summary Fresh-context plan review (WP6+WP7 batch): 10 findings, verdict FIX-THEN-SHIP, WP6's share folded — F1 (HIGH, cross-package): the routine→class mapping had no security-surface home; the only binding home named was the repo-local additive-section idiom, which is exactly the agent-writable class source WP4's classification obligation forbids → the mapping lands in the WP5 security binding's `admission.classification` (reconciled bots included); only cadence/enablement/surface stay repo-local. F3 (MED-HIGH): binary DET/AGT mapping could not classify hybrid classes (a v1 class among them), and two Phase-1 gates were mutually unsatisfiable (39 rows vs dead-code = 0) → D2 hybrid branch + explicit `not-a-routine` catalog flag for the seven pure-DET rows; gates reconciled. F4 (MED-HIGH): derivation reached only C1–C3, silently breaking the novel-class acceptance criterion for C4/C5 → provenance and structural-blast axes added; the Phase 4 probe now must exercise an untrusted-provenance case. F8 (LOW) routes to WP5's work-classes leaf (C1 permits governed-queue writes, no repo mutation). ## Execution shape Fully sequential 1 → 2 → 3 → 4 — leaves derive through Phase 1's rules; the slice wires Phase 2's classes; Phase 4 proves Phase 1's rules on a novel class. Cross-package: after the WP4 and WP5 implementation PRs. | Phase | Surface | Basis | |---|---|---| | 1 | main-session | normative catalog + mapping-rule authoring | | 2 | main-session | ten uniform leaves, derivation judgment per class | | 3 | main-session | setup-skill judgment | | 4 | main-session | derivation probe + gate runs | ## Open questions - Fleet routine stand-up + existing-scheduler reconciliation execution — /work-items backlog post-merge (Brief out-of-scope, trigger recorded). ## Decisions made (gate-passed) | Decision | What it changes in the plan | Basis (evidence) | |---|---|---| | v1 roster confirmed at 10 classes (dead-code sweep stays out) | Phase 1 table + Phase 2 leaf count | D4's correction is evidence-backed (RESEARCH-routine-catalog row 25: DET detect); D2's rule excludes DET mechanically | | Class tokens = kebab-case descriptive names (list in preamble) | Phases 1–2 | Matches every shipped contract token set; one token per catalog row | | Hub-and-leaves under `reference/routines/` | Phase 1–2 paths | Same layout decision as WP5's guardrail leaves (subdir per hub) | | Cadence defaults: daily for queue-tending sweeps, weekly for the rest | Phase 2 leaves | Proven manual patterns' observed frequency; org-bindable values, not contract | | Novel-class derivation as the Phase 4 acceptance probe | Phase 4 | Brief acceptance criterion stated verbatim; cheapest mechanical proof of the mapping rules | ## Handoff to implementation ### User-approval gates - Any change to the D2 mapping rules or the v1 roster during implementation → STOP (user-locked content). - Any scope expansion beyond the four phases re-enters `/architect review`. ### Execution shape ([EXEC-SHAPE] tagged) Sequential 1→4, all main-session (table above). PLAN.md phase tags advance in the same commit as each phase. ### Mechanical work Commit per phase on the implementation branch (suggest `feat/autonomy-routines`); gates re-run in full at Phase 4; PR body carries the derivation example + near-duplicate audit statement + this PLAN in a `<details>` block at close-out. </details> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary WP7 of the AI-adoption-ladder effort: the **autonomous-runner design pack** — the architect-ready design contract the T4 charter graduates into, as a progressive-disclosure hub (`plugins/autonomy/reference/runner.md`) with four leaves under `reference/runner/`. Design only: **zero build artifacts, the runner-execution home stays unborn**, both T4 build triggers are restated and neither is assumed fired. - **Hub + seams (phase 1):** T4 charter imported verbatim in substance (queue-contract split, both build triggers, self-run-primary substrate stance with the vendor-hosted human-merge-gate cap, inherited constraints, anti-goals) with an explicit no-build clause; the two-layer composition spine (normative shape in seam vocabulary; adopt-first binding stance with reimplement-the-pattern as the named fallback, no library pinned); the 8 seams as the spine's complete interface set, each citing its shipped owning contract or marked runner-new; envelope fields `stop_reason` / `outcome` / `evidence` / `resume_handle` resolved. - **Lifecycle + escalation + topology (phase 2):** linear state model `leased → executing → verifying → disposing → (escalated | complete)` with per-transition telemetry cited from the telemetry contract; thin per-item-PR launch disposition with the vendor-hosted cap restated on the disposition path; batched gated-merge serialization as a growth stage on its evidence trigger, binding a platform-native merge-queue facility, never reimplemented. Terminal-handoff escalation: four terminal outcomes (`success` never files escalation work), the two-family stop-criteria taxonomy (`runner-owned` / `agent-signaled`), the deterministic two-step mapping (stop reason → outcome → event class), two additively registered runner event classes (`runner-needs-human`, `runner-cap-exceeded`), contract-default severities for all eight classes on the `notice`/`attention`/`urgent` axis, the urgent stop-reason override, 72h/one-bump acknowledgment knobs, deferred mid-run interrupt, and four research gaps carried as UNVERIFIED. Topology: four ownership homes, the launch backend set (one free self-run L2 container-class backend; L3 deferred with C5 dispatch blocked until bound, the floor cited from the work-classes cell; cloud backends advisory and vendor-hosted-capped), birth-time decisions USER-RESERVED. - **Escalation-severity join + setup surface (phase 3):** the security binding gains three genuinely additive optional keys beside an untouched `escalation_routes` — `escalation_severity` (event class → severity), `escalation_severity_routes` (severity → route, personal-push legal at any tier, severity-first resolution with the per-event route as fallback), `escalation_ack` (staleness window, re-escalation cap). The event-class registry grows additively: `escalation_routes` keeps requiring only the six inherited guardrail classes (existing bindings validate unchanged) while the two runner classes are legal optional route keys. `check-security-binding.mjs` mirrors the shapes and adds the one semantic rule the schema cannot express (an unroutable severity-bound event class fails closed). SKILL.md gains the short runner note (427/500 lines), evals.json case 29 covers the slice, plugin 0.6.0 → 0.7.0, catalog regenerated. ## Zero-build audit (phase 4) Branch diff vs base: exactly 17 paths, all inside the four allowed surfaces (reference/ pack, skills/setup/, the two READMEs, the plugin manifest) plus the PLAN's own phase tags. No new `.sh`/`.mjs`/`.py` files. `grep -rci 'git init\|create the repo'` over `reference/runner*` = 0. No sentence in the pack reads as a build commitment; birth-time decisions carry arbiter USER-RESERVED. ## Additivity proof (schema) Full security-binding fixture sweep before/after: 10 pass / 92 reject → 11 pass / 95 reject with **zero flips** among the 102 pre-WP7 fixtures — the delta is exactly the four new escalation fixtures (valid binding passes; bad severity token, unroutable event class, and non-positive ack values each reject with their own finding). A runner class bound through an optional `escalation_routes` entry alone is routable, matching the escalation contract. ## Near-duplicate audit The pack cites the shipped contracts for every inherited obligation — matrix cells, isolation ladder levels, queue/lease dispatch, telemetry emission and join attribute, return-accounting capture, executor surface classes — and defines only runner-new content (envelope fields, state model, stop-criteria taxonomy, severity mapping, ownership seams). The two deliberate restatements are the stress-test F6 items: the vendor-hosted human-gate cap on the lifecycle disposition path and on the topology cloud-backend path. ## Gates `validate-plugins.sh`, `generate-catalog --check`, `validate-plugin-contracts.mjs` (vendor deny-list over reference/), `run-plugin-tests.sh` (all pass or skip), `claude plugin validate --strict`, markdownlint, typos, lychee over the five-file pack (66 links, 0 errors) — all green locally. ## Related No linked issue (the T4 charter decision node #244 is already closed; this package delivers the design pack it chartered). - #359 (WP4–WP7 plans, incl. this package's PLAN as merged) - #244 (T4 autonomous-runner charter — the decision node this package graduates) - #239 (WP map: AI adoption ladder) - PR #600 (WP6 routines — the routine-filed items that drain through this runner's queue) - PR #377 (WP5 guardrail matrix + security binding — the escalation_routes home this PR extends additively) <details> <summary>PLAN</summary> # ai-ladder-wp7-runner ## Brief ### TLDR Runner design pack (T4 charter graduation): architect-ready design contract for the autonomous-drain runner — spine shape + 8 seam specs, lifecycle state model, two-family stop-criteria taxonomy with terminal-handoff escalation, matrix-derived backend set, and the topology seam map. Design only: build stays gated on the T4 triggers; the runner-execution home stays unborn. ### Goal When the T4 build trigger fires, `/architect` and the build start from a complete, Boris-aligned design contract instead of a cold charter: every design decision T4 graduated (composition spine, isolation backends, lifecycle depth, escalation UX, topology) is resolved in contract vocabulary, every inherited constraint is imported unchanged, and nothing in the package commits, implies, or starts a build before the trigger earns it. ### Locked decisions | # | Decision | |---|---| | D1 | Scope: architect-ready design contract docs ONLY — charter import (T4 scope boundary, build triggers, substrate stance, inherited constraints, anti-goals) + the five design decisions T4 graduated (composition spine, multi-backend isolation, lifecycle depth, escalation UX, topology) resolved + seam specifications. Capability-distribution home. No code, no skeleton repo, no capability templates; runner-execution home stays unborn until the T4 build trigger fires (WP1 D1); empirical spine validation waits for the trigger. | | D2 | Composition spine, two-layer: normative contract fixes the spine SHAPE in seam vocabulary — minimal composable orchestration, pluggable sandbox-provider seam × pluggable agent-adapter seam, autonomous/interactive split, the 8 seams as its interface set (absorb discipline, no vendor names). Binding layer records adopt-first: adopt the qualifying spine library as a build-stage dependency, re-verified at trigger time (maintenance, license, seam fit); reimplement-the-pattern is the named fallback on failed re-verification. | | D3 | Isolation backends: the sandbox-provider seam is the normative requirement; the launch backend set derives from matrix coverage of trigger-admitted work — one free self-run L2 container-class backend (default-deny egress) at launch; an L3 backend is deferred with trigger = first C5-class work admitted to autonomous drain (fail-closed blocks C5 dispatch until an L3 binding exists); paid/cloud backends advisory + explicit opt-in. | | D4 | Lifecycle: full state model in the contract — lease-claim → execute in isolation → verification gates (matrix column) → disposition per merge policy → escalate or complete, every transition emitting telemetry per the telemetry contract. Launch disposition is thin: per-item PR through the platform's native flow. Batched gated merge serialization is a named growth stage with an evidence trigger (observed concurrent auto-merge collisions), binding to a platform-native merge-queue facility where one exists (availability verified at binding time); never reimplement a built-in. | | D5 | Escalation UX (WP5 D6 remainder, evidence: RESEARCH-escalation-observability.md): terminal-handoff at launch — every stop is a terminal state (success / gate-failed / needs-human / cap-exceeded); the runner files a human-gated work item carrying the evidence bundle (failure summary, run-transcript link, cost, trace link, session-resume handle; human takeover = resume the persisted session). Contract carries the two-family stop-criteria taxonomy: deterministic runner-owned (turn/budget/wall-clock caps, execution error after retries, refusal, verification-gate failure, isolation violation) and judgment agent-signaled through the structured-output envelope (ambiguity, design decision needing human judgment, security/data-integrity event, unresolvable blocker, no-progress); transient-recoverable retries with backoff, never escalates. Escalation event classes gain a severity axis mapped to org-bound notification fan-out (personal-push tier included as an org-bindable route); acknowledgment on the item + stale-unacked re-escalation (severity bump, capped) are org-bindable knobs. Mid-run interrupt (pause-for-human) deferred with trigger: evidence that kill-and-resume loses material cost/context; first-party pause/resume mechanisms verified available, so later adoption needs no contract change. Escalation telemetry rides the telemetry contract's custom namespace (no standard escalation signal exists; candidate upstream contribution). | | D6 | Topology: ownership seams pre-committed, shape at birth. Design-pack docs stay in the capability-distribution home; at trigger fire the runner-execution home is born owning the runner implementation + its build/release toolchain, consuming the contract docs (never duplicating them). Security-sensitive runner bindings (level→substrate, merge policy, escalation routes, admission) live in the settings-as-code home — the runner reads its governance, never writes it (the agent-writable-binding bypass-channel rationale generalizes to the runner itself); non-security operational config is deployment-owned per the hosting stance. Repo count, name, and implementation language resolve at birth via the naming pass + the re-verified spine choice. Absent settings-as-code home in an adopting org: layered resolution + fail-closed on absent security binding applies; guided-setup names the compliant path. | | D7 | Imports, all unchanged: T4 charter core (queue-contract split, both build triggers, self-run-primary substrate stance with hosted human-gate cap, anti-goals); WP4 D6 executor surface classes — the runner is one executor behind the invocation-adapter seam, adapters untouched on swap; WP5 matrix columns + admission decision-table + caps, promotion human-ratified / demotion automatic, L2+ fail-closed floor; WP6 routine-filed items drain through the same queue (no routine-special path); telemetry contract + trace propagation on every lifecycle transition; return-accounting capture hooks at the runner's task boundary; queue + lease reused from the work-item capability — no second claim mechanism; the 8 seams as the spine's interface set. | ### Constraints - Any fleet repo or vendor name in normative contract text is a defect; vendor names appear only as marked examples and in binding docs. - Zero build artifacts anywhere in the package: no code, no repo, no templates; no sentence that reads as a build commitment. - One queue, one lease, one escalation channel (the queue itself); no second claim, dispatch, or escalation mechanism. - Security-sensitive runner bindings never live where the runner (or the agents it runs) can edit them. - Fail-closed everywhere: absent L2 substrate, absent security binding, unbound C5 work — block and name the compliant path, never degrade silently. - No new cost by default; paid backends/surfaces advisory + explicit opt-in with cost surfaced. - Boris-alignment is the standing acceptance criterion (no step-skipping, trust before scale). ### Acceptance criteria - Design pack delivers, in contract vocabulary only: spine shape + 8 seam specifications, lifecycle state model, two-family stop-criteria taxonomy, severity/ack/re-escalation knob set, launch backend derivation, topology seam map. - Zero build artifacts; runner-execution home unborn; T4 build triggers restated verbatim; no build commitment anywhere in the text. - WP5 D6's escalation-UX deferral — the last WP5-inherited cross-package deferral — is resolved by this package (mid-run interrupt stays deferred with its own evidence trigger; this pack resolves the inherited deferral, it does not claim nothing remains deferred). - Research gaps carried visibly, not laundered: CI-action-class failure-reporting UNVERIFIED; cross-vendor agent-needs-human signaling (agent-protocol / agent-instruction-file guidance) UNVERIFIED; managed-agent event names drift between stream and webhook surfaces — bind exact names at build from live docs; the deprecated approval-SDK is never cited as living precedent. - Boris check: step-3 trap honored (charter-not-build, trust before scale); the 2→3 "let Claude kick off Claude" cell stays inside the governed-queue audit trail; the step-4 cell is chartered, not built — closed loop via self-run substrate, programmatic-scheduling surface class, cost controls as caps/budget knobs, model selection as cost tiers; monitor-by-exception instantiated as terminal-handoff escalation; the thread-post return metric is fed by return-accounting capture at the runner boundary. ### Captured assumptions - The qualifying spine library remains maintained and license-compatible at trigger time — re-verify at trigger; reimplement-the-pattern is the named fallback (D2). - First-party pause/resume mechanisms (SDK callback pause + defer/resume, managed idled webhook) remain available — beta/moving surfaces; re-verify at build before relying on them for the mid-run-interrupt growth stage. - The work-item capability's race-safe lease + autonomous drain mode remain the dispatch entrypoint (live-verified in a prior session). - Personal-push notification routes presuppose org-bound channel adapters (WP4) at binding time; absent adapters, severity fan-out degrades to tracker-item-only — never silently drops the escalation itself. ### Out-of-scope (deferred with triggers) - Runner build itself — trigger: either T4 build trigger fires. - L3 backend — trigger: first C5-class work admitted to autonomous drain. - Batched gated merge serialization — trigger: observed concurrent auto-merge collisions. - Mid-run interrupt escalation — trigger: evidence that kill-and-resume loses material cost/context. - Fleet materializations (runner stand-up, binding instances) — /work-items backlog post-graduation. ### Deferred questions - Exact seam interface tokens, envelope field names, and lifecycle state tokens — `/architect` (with the plugin naming pass). - Severity level names + default staleness window and re-escalation cap values — `/architect`. - Setup/probe mechanics per backend class (joins the WP5 D4 slice) — `/architect`. - Birth-time decisions: repo count/name/implementation language, spine re-verification outcome, exact managed-event name bindings — build stage, gated on the T4 trigger; **arbiter: USER-RESERVED** (trigger firing is a user-ratified event). ## Plan Recommendation-locked this round under the user's standing pre-authorization. Token sets (D-deferred, resolved here): lifecycle state tokens `leased` / `executing` / `verifying` / `disposing` / `escalated` / `complete` (terminal: `escalated`, `complete`); terminal-outcome tokens `success` / `gate-failed` / `needs-human` / `cap-exceeded`; stop-criteria family tokens `runner-owned` (deterministic) and `agent-signaled` (judgment, via the structured-output envelope field `stop_reason`); severity levels `notice` / `attention` / `urgent` (three levels — mapped to org-bound notification fan-out; tracker item always, channel notification per org route, personal-push tier org-bindable at `urgent`); default staleness window 72h with one re-escalation (severity bump, cap 1) — org-bindable knobs. Design-pack docs live under `reference/runner/` (hub `reference/runner.md`). Prerequisites: WP4 + WP5 + WP6 implementation PRs merged (this pack imports their contracts by citation). Design only — zero build artifacts; the runner-execution home stays unborn. ### Phase 1: Charter import + spine and seam specs [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/reference/runner.md` | Create | Design-pack hub: T4 charter imported verbatim-in-substance (queue-contract split interactive-upstream / autonomous-downstream; BOTH build triggers restated verbatim; self-run-primary substrate stance with the hosted human-merge-gate cap; anti-goals) with an explicit no-build-commitment clause; the two-layer composition spine per D2 — normative spine SHAPE in seam vocabulary (minimal composable orchestration, pluggable sandbox-provider seam × pluggable agent-adapter seam, autonomous/interactive split) with the 8 seams as its interface set; adopt-first binding stance recorded (qualifying spine library re-verified at trigger time; reimplement-the-pattern the named fallback); glance rule routing depth to the leaves. | | `plugins/autonomy/reference/runner/seams.md` | Create | The 8 seam specifications in contract vocabulary (invocation adapter, structured-output envelope, queue+lease, isolation policy, outcome-verification gate, merge-policy toggle, observability+cost, session/resume+caps): each seam's obligation set, its already-shipped owning contract where one exists (queue+lease → trigger-dispatch; isolation policy → guardrails; observability+cost → telemetry; capture at task boundary → return accounting) cited never restated, and the runner-side interface tokens. Envelope field names resolved: `stop_reason`, `outcome`, `evidence` (bundle ref), `resume_handle`. | **Sanity Check:** - `grep -ci 'build trigger' plugins/autonomy/reference/runner.md` ≥ 2 (both triggers restated) - `grep -c 'no build' plugins/autonomy/reference/runner.md` ≥ 1 (commitment clause present) - Seam count: `grep -cE '^## ' plugins/autonomy/reference/runner/seams.md` = 8 - Vendor+fleet deny-list sweep exit 0; lychee lane passes ### Phase 2: Lifecycle + stop-criteria + escalation leaves [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/reference/runner/lifecycle.md` | Create | Full state model per D4: `leased → executing → verifying → disposing → (escalated \| complete)`, every transition emitting telemetry per the telemetry contract (trace-linked); launch disposition thin (per-item PR through the platform's native flow, merge disposition governed by the WP5 matrix row — including the vendor-hosted human-gate cap whenever the executing backend is vendor-hosted, restated here rather than inherited silently); batched gated-merge serialization named as a growth stage with its evidence trigger (observed concurrent auto-merge collisions) binding to a platform-native merge-queue facility — never reimplemented. | | `plugins/autonomy/reference/runner/escalation.md` | Create | D5 resolved: terminal-handoff at launch — every stop resolves to a terminal outcome (`success`/`gate-failed`/`needs-human`/`cap-exceeded`); the NON-SUCCESS outcomes file the human-gated work item with the evidence bundle (failure summary, run-transcript link, cost, trace link, `resume_handle`; human takeover = resume the persisted session), while `success` completes through the normal disposition + return-accounting path — no escalation item, no ack/re-escalation obligations, else every healthy run would generate false escalation work (PR-review finding); two-family stop-criteria taxonomy verbatim (runner-owned: turn/budget/wall-clock caps, execution error after retries, refusal, verification-gate failure, isolation violation; agent-signaled via `stop_reason`: ambiguity, design decision, security/data-integrity event, unresolvable blocker, no-progress); transient-recoverable retries with backoff, never escalates; severity axis (`notice`/`attention`/`urgent`) → org-bound fan-out with personal-push as an org-bindable route; an EXPLICIT two-step mapping so severity resolution is deterministic for every non-success stop (PR-review findings — the severity join keys on event class, which runner outcomes alone never named, and stop reasons in turn never named their outcome): STEP ONE, stop reason → terminal outcome — runner-owned: verification-gate failure → `gate-failed`; turn/budget/wall-clock cap → `cap-exceeded`; execution error after retries, refusal, and isolation violation → `needs-human`; agent-signaled (every `stop_reason` value: ambiguity, design decision, security/data-integrity event, unresolvable blocker, no-progress) → `needs-human`; STEP TWO, outcome → event class: `gate-failed` → the guardrail contract's gate-failure event class; `needs-human` and `cap-exceeded` → two runner escalation event classes with the tokens `runner-needs-human` and `runner-cap-exceeded`, registered ADDITIVELY alongside the guardrail contract's set — the Phase 3 schema edit extends the event-class registry (the enum `escalation_routes` and `escalation_severity` keys validate against) with these two tokens, and `check-security-binding.mjs` accepts route/severity bindings for them like any guardrail class (default severities `attention` and `notice`, org-bindable), and contract default severities for EVERY inherited guardrail event class so severity fan-out is defined with no `escalation_severity` binding at all (gate failure `attention`, verification divergence `attention`, admission rejection `notice`, demotion event `attention`, structural-class plan approval `attention`, untrusted-provenance `urgent` — all org-bindable overrides); an isolation violation or a `security/data-integrity` stop reason carries an `urgent` severity override regardless of its event class's default; the filed escalation item's envelope records the resolved event class AND the originating stop reason; acknowledgment-on-item + stale-unacked re-escalation (default 72h window, one severity-bump re-escalation, both org-bindable); mid-run interrupt deferred with its evidence trigger; escalation telemetry rides the telemetry contract's custom-namespace mechanism — the exact namespace token is read from the shipped telemetry contract at implementation, not pinned here (candidate upstream contribution noted). Research gaps carried verbatim (CI-action-class failure-reporting UNVERIFIED; cross-vendor needs-human signaling UNVERIFIED; managed-agent event-name drift — bind at build; deprecated approval-SDK never cited as living precedent). | | `plugins/autonomy/reference/runner/topology.md` | Create | D6: ownership seam map — design pack in the capability-distribution home; at trigger fire the runner-execution home is born owning implementation + build/release toolchain, consuming contracts never duplicating; security-sensitive runner bindings in the settings-as-code home (runner reads its governance, never writes it); non-security operational config deployment-owned; launch backend set per D3 (one free self-run L2 container-class backend; L3 deferred with the first-C5 trigger, fail-closed until bound — the C5→L3 floor CITED from the WP5 work-classes matrix cell, never asserted independently here, per stress-test F5; paid/cloud advisory + opt-in, and any cloud backend IS a vendor-hosted executor: it forces the security binding's `executor_class: vendor-hosted`, capping every merge row at human-gated, per F6); birth-time decisions listed with arbiter USER-RESERVED (repo count/name/language, spine re-verification, exact managed-event names). | **Sanity Check:** - State tokens present: `grep -c 'disposing' plugins/autonomy/reference/runner/lifecycle.md` ≥ 1 - `grep -c 'cap-exceeded' plugins/autonomy/reference/runner/escalation.md` ≥ 1 and `grep -c 'stop_reason' …/escalation.md` ≥ 1 - `grep -ci 'UNVERIFIED' plugins/autonomy/reference/runner/escalation.md` ≥ 2 - `grep -c 'USER-RESERVED' plugins/autonomy/reference/runner/topology.md` ≥ 1 - Vendor+fleet deny-list sweep exit 0 ### Phase 3: Setup note + WP5 escalation-route join [DONE] | File | Action | What changes | |---|---|---| | `plugins/autonomy/skills/setup/SKILL.md` | Modify | A short runner note only: the design pack is bindable-when-born; setup records NOTHING runner-specific until the trigger fires except escalation notification routes (severity axis + personal-push tier as route options). No probe, no wiring, no binding section for the unborn home. | | `plugins/autonomy/skills/setup/schemas/guardrails-security-binding.schema.json` | Modify | The severity refinement is GENUINELY additive (stress-test F2 — the schema is `additionalProperties: false`, so an undeclared shape would fail every severity-tiered binding): `escalation_routes` keeps its existing event-class→route entries unchanged; three new OPTIONAL top-level keys land beside it, modeling the event/severity join explicitly — `escalation_severity` (event class → severity token, the join: which severity each event class escalates at; contract defaults per event class, org-bindable), `escalation_severity_routes` (severity token → route, `notice`/`attention`/`urgent`, personal-push a legal route value at any tier; route resolution = event class → its severity → that severity's route, falling back to the event class's own `escalation_routes` entry when no severity route is bound), and `escalation_ack` (`staleness_window`, default 72h; `reescalation_cap`, default 1). Old bindings validate unchanged — no major bump; that resolution is now in-plan, not asserted. | | `plugins/autonomy/skills/setup/scripts/check-security-binding.mjs` | Modify | Semantic checks for the new keys: severity tokens ∈ the three-level set; `escalation_ack` values positive; routability per event class matches the resolution rule — an event class keyed in `escalation_severity` is routable when its severity has an `escalation_severity_routes` entry OR the event class has its own `escalation_routes` entry (severity-only fan-out is legal, matching severity-route-first resolution; a legacy per-event route is the fallback, never a requirement), and only an event class with NEITHER is flagged unroutable. | | `plugins/autonomy/skills/setup/evals/evals.json` | Modify | Escalation-route slice case (severity + personal-push binding recorded; unborn-home refusal restated) — stress-test F7: every other WP adds eval coverage for its SKILL.md change; no exemption here. | | `plugins/autonomy/README.md` + `plugins/autonomy/.claude-plugin/plugin.json` | Modify | Capability list gains the runner design pack; the roadmap row stays trigger-gated (build), now pointing at the pack; minor version bump. | **Sanity Check:** - `grep -ci 'unborn' plugins/autonomy/skills/setup/SKILL.md` ≥ 1 - README roadmap still carries the build trigger row (`grep -c 'build trigger' plugins/autonomy/README.md` ≥ 1) - Schema additivity: `grep -c 'escalation_severity_routes' …schema.json` ≥ 1 and a pre-WP7 fixture binding still passes `check-security-binding.mjs` unchanged (no major bump proven, not asserted) - `/skill-quality:check` + `validate-evals` pass; `claude plugin validate --strict` exit 0 ### Phase 4: Zero-build audit + gates [DONE] Acceptance probe (mechanical): the package introduces no executable/runtime artifact — the diff contains no new files outside `reference/`, `skills/setup/`, README, and plugin manifest; no new scripts; no repo-creation instruction anywhere. Then the full gate roster (`validate-plugins.sh`, `run-plugin-tests.sh`, `validate-plugin-contracts.mjs`, markdown/typos/lychee, `claude plugin validate --strict`, catalog regen). Near-duplicate audit statement: the pack cites the shipped contracts for every inherited obligation and defines only runner-new content. **Sanity Check:** - `git diff --name-only <base>` contains no path outside the four allowed surfaces; no `*.sh`/`*.mjs` additions - `grep -rci 'git init\|create the repo' plugins/autonomy/reference/runner*` = 0 - All gate scripts exit 0; catalog in-sync; near-duplicate audit statement in the PR body ## Blast radius MEDIUM — docs-only within one plugin, but the pack pre-commits topology and escalation contracts the eventual build must honor; WP5's last cross-package deferral resolves here. Fully git-revertible; zero runtime surface by design. ## Stress-test summary Fresh-context plan review (WP6+WP7 batch): 10 findings, verdict FIX-THEN-SHIP, WP7's share folded — F2 (HIGH): the escalation-route severity refinement touched the WP5 security-binding schema (`additionalProperties: false`) but Phase 3 listed neither the schema nor the validator, leaving the flagship severity/ack knob set unbindable, and "additive, no major bump" was asserted over what read as a value-shape change → both files added to Phase 3 and the refinement modeled as genuinely additive optional keys (`escalation_severity_routes`, `escalation_ack`), with a pre-WP7 fixture-passes gate proving no major bump. F5 (MED): the C5→L3 fail-closed gate now CITES the WP5 work-classes matrix cell (which imports T3's C5/L3 row) instead of asserting the value. F6 (LOW-MED): the vendor-hosted human-gate merge cap is restated on the cloud-backend and disposition paths instead of silently inherited. F7 (LOW): the Phase 3 SKILL.md change gains eval coverage like every other WP. F9 (LOW): the "last cross-package deferral" claim scoped to the WP5-inherited deferral. F10 (LOW): the escalation-telemetry namespace token is read from the shipped telemetry contract at implementation, not pinned in the plan. Scope discipline (zero build artifacts, unborn runner-execution home, USER-RESERVED birth decisions) audited clean. ## Execution shape Fully sequential 1 → 2 → 3 → 4 — leaves depend on the hub's imported charter; the setup note cites Phase 2's escalation severity axis; Phase 4 audits the authored tree. Cross-package: after WP4+WP5+WP6 implementation PRs. | Phase | Surface | Basis | |---|---|---| | 1 | main-session | charter import fidelity + seam-spec judgment | | 2 | main-session | lifecycle/escalation normative authoring | | 3 | main-session | minimal setup-skill touch | | 4 | main-session | mechanical audit + gate runs | ## Open questions - Birth-time decisions (repo count/name/language, spine re-verification, managed-event names) — USER-RESERVED at trigger fire, restated in topology.md. ## Decisions made (gate-passed) | Decision | What it changes in the plan | Basis (evidence) | |---|---|---| | Lifecycle tokens `leased/executing/verifying/disposing/escalated/complete` | Phase 2 leaf | D4's state model named each stage; kebab/lowercase matches shipped token sets | | Terminal outcomes `success/gate-failed/needs-human/cap-exceeded` | Phase 2 leaf | D5 names exactly these four | | Severity set `notice/attention/urgent`; 72h staleness, one re-escalation | Phase 2 leaf | D5 requires a severity axis + ack/re-escalation knobs with suggested defaults; three tiers is the smallest set covering tracker-only / channel / push fan-out | | Envelope fields `stop_reason/outcome/evidence/resume_handle` | Phases 1–2 | D5's evidence-bundle + resume requirements; smallest field set carrying them | | Hub-and-leaves under `reference/runner/` | Phase 1–2 paths | Same layout as WP5/WP6 hubs | | Escalation-route severity refinement = new OPTIONAL schema keys (`escalation_severity` as the explicit event→severity join, `escalation_severity_routes`, `escalation_ack`) beside an untouched `escalation_routes`; schema + validator edits in Phase 3 | Phase 3 | WP5 D6 routes live there; stress-test F2 — only genuinely additive keys avoid a major bump under `additionalProperties: false`, proven by the pre-WP7 fixture gate; the join key answers the PR-review finding that severity→route alone left route selection non-deterministic per event class | ## Handoff to implementation ### User-approval gates - Anything that reads as a build commitment or creates a runtime artifact → STOP (D1 user-locked; the zero-build audit is the backstop). - Birth-time decisions stay USER-RESERVED — never resolved by implementation. - Any scope expansion beyond the four phases re-enters `/architect review`. ### Execution shape ([EXEC-SHAPE] tagged) Sequential 1→4, all main-session (table above). PLAN.md phase tags advance in the same commit as each phase. ### Mechanical work Commit per phase on the implementation branch (suggest `docs/autonomy-runner-design-pack` — docs-type: the package is normative text only); gates re-run in full at Phase 4; PR body carries the zero-build audit output + near-duplicate audit statement + this PLAN in a `<details>` block at close-out. </details> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary Effort-end housekeeping for the AI-adoption-ladder effort: all seven work packages are merged, so the per-WP topic slices come out per the close-out convention (publish the PLAN, then prune the slice). - WP4–WP7 PLANs were already spliced into their PR bodies at each close-out (#372, #377, #600, #676). - WP1–WP3 PLANs are now published the same way into #333, #343, and #356 (each merged PR body gained a `PLAN (published at effort-end close-out)` details block), so every slice's content has a durable home on its merged PR before the directory is removed. - The WP map in `docs/topics/ai-adoption-ladder/index.md` now records delivery PRs instead of live slugs. The effort hub itself (index, design evidence, research docs) stays. No content is lost: every deleted PLAN.md is byte-recoverable from git history and readable in its merged PR's details block. ## Related No linked issue (effort-end housekeeping; the WP map issue #239 is already closed). - #239 (WP map: AI adoption ladder — the effort this closes out) - #333 / #343 / #356 (WP1–WP3 delivery PRs, PLANs published into their bodies in this close-out) - #372 / #377 / #600 / #676 (WP4–WP7 delivery PRs, PLANs published at their own close-outs) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ger row (#698) ## Summary D1 sweep for #353: the WP1–WP7 PLAN out-of-package notes (mined from the merged PR bodies #333/#343/#356/#372/#377/#600/#676) contained 31 deferred notes; every note that named no tracking issue and had no contract-recorded trigger now has a durable home: - **README roadmap** gains four trigger-gated rows: fleet guardrail materializations (WP5), fleet routine stand-up + existing-scheduler reconciliation (WP6), vendor-binding capability templates (WP6), cost enforcement / hard spend caps (WP5, 3→4 trigger). - **Trigger register** gains the second-binding-consumer cross-repo drift check (WP1). - **`reference/return-accounting.md`** records the per-work-class precision-graduation deferral beside its band-stability rule (WP3). - **CHANGELOG.md created** for the autonomy plugin (first entry; 0.1.0–0.7.0 history stays in the WP PR bodies) + version bump to 0.7.1 — starts the CHANGELOG-parity posture #663 gates on. All other mined notes were already covered: tracked issues (#351, #352, #694–#697), contract-recorded triggers (telemetry immutable-ID, return-accounting expansion/revisit), delivered sibling WPs, resolved `/architect` questions, or the user-held dormant triggers (runner build T4, L3 backend, merge serialization, mid-run interrupt, org-enablement, cross-team). Full disposition table lands as a comment on #353 at close. Documentation only — no contract semantics change. ## Related - #685 (effort-end prune that moved the PLAN records into the merged PR bodies) - #663 (CHANGELOG-parity gate this PR's new CHANGELOG.md starts satisfying) - WP delivery PRs mined: #333, #343, #356, #372, #377, #600, #676 Closes #353 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary The security-binding checker (`plugins/autonomy/skills/setup/scripts/check-security-binding.mjs`) recognized a probed host-credential path by **static structural shape** — exact home-anchored / fixed-system forms. A static checker cannot know an org's real credential locations, so for any such recognizer an adversarial reviewer can always craft a plausible-but-invented path (an invented home user, a mount that need not exist) whose failing read proves nothing while real host credentials go unrecognized. That is the non-convergence #549 documents. This implements the operator-ratified **Option A** (deny-by-default; issue [comment](#549 (comment))): a new `--credential-roots <path,path,...>` flag mirroring the already-landed `--egress-hosts` seam, with unconfigured default = deny-all. **Credential side only** — the egress seam (`--egress-hosts`, `isNonExternalEgressHost`, special-use ranges) is untouched. ## Fix - **New `--credential-roots` flag**, parsed exactly like `--egress-hosts` (comma-separated, trimmed; roots are not case-folded at parse time — `normalizeHostPath` folds case consistently at containment time, since the checker never touches the probing host's filesystem and POSIX case-sensitivity is not observable here). - **Deny-by-default containment replaces the structural recognizer.** A filesystem credential entry counts as credential-absence evidence only when its recorded host-side expansion resolves — **lexically, `..`-safe, filesystem-independent** (`normalizeHostPath` / `pathUnderConfiguredRoot`) — under one of the configured trusted roots. With **no roots configured, every filesystem credential entry is untrusted and the level fails closed.** Membership under a configured root is the sole test, dissolving the open-ended enumeration. - **Bounded closed sets kept, needing no allowlist:** cloud-metadata-endpoint routes (`isMetadataEndpoint`) and single-segment well-known credential env tokens (`$GITHUB_TOKEN`, `$GH_TOKEN`). The expansion-coherence guard (`credentialExpansionProblem`: rooted, non-UNC, ephemeral-free, tail-consistent) is retained and now runs for every entry kind, so a concrete entry's `host_expanded` must repeat it verbatim. - **Segment-boundary containment** (never a bare prefix — `/home/run` does not accept `/home/runner-x`); UNC / `//`-prefixed forms are refused so `//etc/credentials` cannot collapse onto a configured `/etc` root. - **Docs updated** to the enforced behavior (no false guarantees): `SKILL.md` and `templates/isolation-probe.md` describe credential = deny-by-default while leaving the egress seam's accurate allowlist-with-fallback description intact; `CHANGELOG.md` + `plugin.json` bumped `0.8.0 → 0.9.0`. - **Fixtures:** pruned the corpus that tested the removed structural discrimination (which files/depths/anchors count); kept `invented-home-user`, `host-home-mount`, and `dot-traversal` as not-under-configured-root regressions; added unconfigured-roots (fail-closed), UNC-rejection, and metadata-expansion-mismatch regressions. An independent security review (fresh-context subagent, rationale withheld) surfaced a UNC-collapse containment bypass and a concrete-entry coherence gap; both are fixed in the second commit with regression fixtures. ## Verification `plugins/autonomy/skills/setup/scripts/check-security-binding.fixtures.test.sh` (the repo's graded-fixture harness): ``` All 357 checks passed (100 fixtures graded, 0 quarantined). ``` Baseline before the change was `All 394 checks passed (109 fixtures graded)`; the delta is the pruned structural-discrimination corpus (−12 fixtures + transcripts) plus 3 new deny-by-default / hardening fixtures. North-star behavior verified directly against the checker: - **(a) unconfigured** — real `$HOME/.ssh/id_rsa` → `/home/runner/.ssh/id_rsa` with no `--credential-roots` ⇒ UNPROVEN, level fails closed (`no --credential-roots configured`). - **(b) under root** — same path with `--credential-roots /home/runner` ⇒ `Security binding OK` (C1–C4 eligible). - **(c) not under root** — `/home/definitely-not-a-host-user/.ssh/id_rsa` with `--credential-roots /home/runner` ⇒ `does not resolve under any configured --credential-roots`. No egress-side fixture changed behavior. Closes #549 ## Related - #377 — merged carrier that landed the `--egress-hosts` seam this mirrors - #375 — decide-and-defer playbook - #440 — report-only precedent - Ratifying decision: #549 (comment) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

WP5 guardrail-enforcement package (T2 + T3 + the security-review policy resolution), per the merged PLAN. Phases 1–3 complete; Phase 4's positive probe runs in this PR's CI (transcript below after the run).
No linked issue
Related
What ships
reference/guardrails.md): five-class × five-column matrix (T3 verbatim), glance-layer routing to four leaves, D8 permission-posture note, escalation event classes + org-bound routing + work-item/trace payload over the governed queue (no second channel).reference/guardrails/):isolation-ladder.md(L0–L3, attendance × input-provenance axes, rejected trigger-source axis with falsification reason, L2 unattended floor, fail-closed no-silent-degrade),work-classes.md(risk bundles; C1 repo-read-only with queue writes permitted; C5→L3 floor; promotion/demotion discipline with shipped default predicates — C2 auto-merge ≥20 completions/≥14 days/100% gates/0 reverts, C3 ai-review ≥30 advisory/0 missed-blocking; demotion one-event-suffices),security-review.md(two layers, per-class blocking knobs as floors — no override escape on verification),admission-policy.md(3-axis decision table with"*"wildcard + most-specific-wins, equal-specificity tie = invalid binding, shipped defaults C1/C2 autonomous within caps regardless of provenance, C3 per-item human, C4/C5 human-gated; capsautonomous_concurrency: 1,items_per_run: 3).skills/setup/schemas/guardrails-security-binding.schema.json): draft 2020-12,additionalProperties: false, fail-closed semantics in-schema;executor_class,dispatch_posture, per-surfaceisolation_bindings(level → substrate +probe_evidence+runtime_markers), merge/verification knobs,promotion_state,escalation_routes,admission(classification + rules + caps).skills/setup/scripts/check-security-binding.mjs, exit 0/1/2): matrix merge caps, vendor-hosted human-gate cap, marker non-emptiness + pairwise joint-satisfiability ambiguity rejection, class-aware per-surface isolation verdicts (C5 requires L3), missing-L2 error underautonomous-enabledvs declared verdict underhuman-gated-only, non-promotable promotion entries rejected, admission weakening requiresoverride_justification(admission-only escape), promotion-gated knobs require their ratifiedpromotion_stateentry, equal-specificity ties invalid, evaluation mode resolving EFFECTIVE promotion state against contrary evidence without writing the binding. 91 binding fixtures plus 67 transcript and 2 evidence files, each negative producing exactly one finding. Note: egress-probe targets must be multi-label DNS names or public literal IPs — encoded loopback/private forms are normalized and rejected.templates/isolation-probe.mdand five new evals; plugin 0.5.0.Live-validation demonstration (Phase 4)
Positive probe — this PR carried a one-off workflow (
wp5-isolation-probe-demo.yml, since removed per the PLAN; evidence persists in the run log below) that PROVISIONED a genuine egress-denied boundary on the hosted runner (nesteddocker run --network nonecontainer) and executed the probe recipe INSIDE it, with a networked-outer-job control step. Transcript from the PR run:From run 29675620833:
The final line is
check-security-binding.mjsexit 0 against a binding whoseprobe_evidenceis the transcript captured seconds earlier in the same job — the checker passes only against live-captured, identity-matched evidence. (The recorded transcript predates the capture-contract fields that later review hardening added to the checker — per-target credential exit codes, host-side expansions, transport outcomes, and the outer-context reachability/existence codes on both assertions; it is preserved verbatim as the run's historical record.)Negative path —
check-security-binding.mjson the missing-L2autonomous-enabledfixture (exit 1):Declared posture — the same missing-L2 shape under
human-gated-onlyvalidates (exit 0) with the blocked-dispatch verdict reported as the DECLARED posture, not a defect.Demotion evaluation — promoted-C2 binding + contrary evidence (exit 0, binding file unchanged):
Near-duplicate audit
Security review is folded into the ONE guardrail slice of guided-setup (no separate security-review capability); escalation composes the governed queue itself via the trigger contract's acknowledgment symmetry — no second claim, dispatch, or escalation channel was created anywhere in this package.
Gates
validate-plugins.sh,validate-plugin-contracts.mjs(vendor deny-list clean overreference/), catalog--checkin sync, markdownlint 0 errors, typos clean, lychee offline 0 errors,claude plugin validate --strictpass, skill-qualitycheck-skill.sh setupPASS (2 pre-existing WARNs), evals.json schema-valid (ajv draft2020), actionlint clean on the demo workflow. Full plugin test suite rides CI. Validator exercised over the full fixture set with expected exit codes and single-finding assertions.Boris check
Step-3 guardrails cell covered (agent sandboxing = isolation ladder; automatic code + security review = verification layers; token/model management = cost tiers); the step-4 sentence ("enforcing the right guardrails for each type of work") is instantiated as the matrix; the step-3 trap honored (human-ratified promotion, automatic evaluation-time demotion); step-1 guardrail untouched. Auto-mode classifier tuning + allowlist distribution explicitly deferred with named triggers.
PLAN.md (as merged, tags advanced)
ai-ladder-wp5-guardrails
Brief
TLDR
Guardrail-enforcement package (T2 + T3 + #241 instance): one guardrail contract shaped as a
progressive-disclosure hub (matrix doc as index/spine) with on-demand leaf docs, an org-binding
seam split by governance sensitivity, evidence-gated promotion with automatic demotion, and the
guardrail slice of guided-setup (detect → bind → live-validate → fail-closed).
Goal
Any adopting org can bind the five-class guardrail matrix to its own instances and get
per-work-class enforcement — isolation floor, verification layers, merge policy, cost tier,
escalation — with the security-sensitive bindings outside agent blast radius, promotion earned
on queryable evidence, and no silent degrade anywhere.
Locked decisions
Constraints
as marked examples and in binding docs.
promotion evidence — block and name the compliant path, never degrade silently.
(e.g. paid code-scanning SKUs) are advisory + explicit opt-in with cost surfaced.
escalation mechanism.
Acceptance criteria
(progressive-disclosure convention conformance).
fail closed when unbound.
audit trail on the governance surface.
isolation before binding, and has zero paid dependencies on its default path.
is not blocked by default; caps are org-bindable.
security review = verification layers; token/model management = cost tiers); step-4 sentence
("enforcing the right guardrails for each type of work") instantiated as the matrix; step-3
trap honored (human-ratified promotion, automatic demotion); step-1 guardrail untouched.
triggers, not dropped.
Captured assumptions
wired, promotion evaluation is manual over the same evidence definition.
SKUs stay entitlement-gated (re-verified: private-repo CodeQL requires a paid license).
stop-and-report on invalid binding) remains the fleet's binding idiom.
Out-of-scope (deferred with triggers)
sweep / standards allowlist-distribution work (backlog seeds).
policy vocabulary only.
backlog post-graduation.
Deferred questions
/architect(with theplugin naming pass).
/architect./architect./architect./architect.Plan
Recommendation-locked this round under the user's standing pre-authorization (same basis as
the WP2/WP3 round): hub doc
reference/guardrails.mdwith leaf docs underreference/guardrails/(isolation-ladder.md,work-classes.md,security-review.md,admission-policy.md— the D1 example split, adopted); class tokensC1–C5and leveltokens
L0–L3carried verbatim from T3/T2; security binding = a contract-owned JSONdocument in the settings-as-code home (
schema_versionfrom"1.0"); non-security remaps =an ADDITIVE
guardrailssection of the WP1 repo-local binding; admission decision tableserialized as JSON inside the security binding.
Prerequisite: the WP4 implementation PR merged (
reference/trigger-dispatch.mdexists — theadmission leaf cites its seam; the interim is safe via WP4's fail-closed absent-binding
clause).
Phase 1: Guardrail hub + leaf docs [DONE]
plugins/autonomy/reference/guardrails.mdplugins/autonomy/reference/guardrails/isolation-ladder.mdplugins/autonomy/reference/guardrails/work-classes.mdplugins/autonomy/reference/guardrails/security-review.mdplugins/autonomy/reference/guardrails/admission-policy.mdautonomous-eligible/human-gated/audited-rejection) + caps (autonomous_concurrency,items_per_run). Shipped defaults verbatim from D7: C1/C2 autonomous-eligible within caps regardless of provenance; C3 per-item human admission; C4/C5 human-gated; provenance recorded, never trusted as isolation. Default cap values (org-bindable):autonomous_concurrency: 1,items_per_run: 3— conservative Boris trust-before-scale floor. Serialization: theadmissionobject of the security binding (Phase 2 schema).Sanity Check:
grep -c '| C[1-5] |' plugins/autonomy/reference/guardrails.md= 5 (matrix rows present) and hub contains zero level-definition prose (grep -c 'microVM' plugins/autonomy/reference/guardrails.md= 0 — depth lives in leaves)grep -c 'audited-rejection' plugins/autonomy/reference/guardrails/admission-policy.md≥ 1grep -ci 'fail-closed' plugins/autonomy/reference/guardrails/isolation-ladder.md≥ 1grep -c '20 autonomous C2 completions' plugins/autonomy/reference/guardrails/work-classes.md≥ 1 andgrep -c '14 days' plugins/autonomy/reference/guardrails/work-classes.md≥ 1Phase 2: Binding schema (two governance surfaces) [DONE]
plugins/autonomy/skills/setup/schemas/guardrails-security-binding.schema.jsonschema_version(const "1.0"),executor_class(self-operated|vendor-hosted— the merge-policy gating input, security-surface data per the trigger contract),dispatch_posture(autonomous-enabled|human-gated-only, defaultautonomous-enabled— an org may intentionally record its security settings while keeping every dispatch human-gated; the PR-review finding that a missing L2 entry must not conflate that deliberate posture with an invalid binding),isolation_bindingskeyed by EXECUTION SURFACE (org-named surface id — e.g. the CI pool, a self-run host class; the PR-review finding that a flat level→substrate map lets one surface's L2 binding satisfy the check while a different dispatch surface has no boundary at all) → level token → substrate instance id + probe evidence ref +runtime_markers(the per-surface TRUSTED identifying markers the trigger contract's dispatch seam matches platform-attested runner context against — a key/value set drawn from metadata the executing workload cannot forge, e.g. attested runner-pool/label identity; serialized HERE, on the agent-unwritable security surface, so surface attestation never falls back to the repo-local recorded id),merge_policy(per class, capped per matrix),verification_blocking(per layer per class),promotion_state(per PROMOTABLE cell only: state + ratifying change ref + evidence window),escalation_routes(event class → org route),admission(classification rules — the signal→work-class mappings the trigger contract's adapters stamp from, e.g. label→class for tracker-vcs-event — plus decision-table rules with a"*"wildcard per axis and most-specific-wins precedence (full triple > two axes > one axis > default), optional per-ruleoverride_justification, and capsautonomous_concurrency/items_per_run).additionalProperties: false; fail-closed semantics documented in-schema (absent/invalid security binding blocks autonomous dispatch).plugins/autonomy/skills/setup/scripts/check-security-binding.mjsexecutor_class: vendor-hostedcaps every class at human-gated); every surface bound underdispatch_posture: autonomous-enabledmust declare non-emptyruntime_markers(else the dispatch seam could never attest the actual surface and every dispatch would fail closed as unattestable), and marker sets must be PAIRWISE UNAMBIGUOUS under joint satisfiability — two conjunctive predicates are compatible (and the binding REJECTED) unless they require CONFLICTING values for at least one shared key, since a runtime context carrying the union of two non-conflicting predicates matches both (subset/identity checks alone miss e.g.{pool: blue, region: us}vs{pool: blue, os: linux}); the dispatch seam still requires EXACTLY ONE matching surface at runtime, failing closed on zero or multiple matches — the validator-level joint-satisfiability rejection is what keeps that runtime rule from turning validly bound fleets into ambiguity outages; isolation verdicts are PER SURFACE and CLASS-AWARE: each bound surface resolves, per work class, eligible (its bound level meets that class's min-isolation matrix cell — L2 is the floor for any autonomous dispatch, C5 requires L3) or blocked — admission/executor resolution must name the surface a dispatch would run on AND the item's work class, and verifies the surface meets the class's minimum at dispatch time, so an L2-only surface is never selected for a C5 item (even after human admission or a later policy change) and an unbound surface is blocked even when a sibling surface is bound; underdispatch_posture: autonomous-enabledit is an ERROR when NO bound surface reaches L2 (fail-closed, compliant paths named) — underhuman-gated-onlythe binding validates and the verdict reports blocked autonomous dispatch as the declared posture, not a defect; apromotion_stateentry for a non-promotable cell (C4/C5 merge) is rejected; admission defaults not weaker than shipped defaults without the rule'soverride_justification(declared in the schema, so a justified override validates); every bound isolation level carries probe evidence. Plus an EVALUATION mode: given a binding + an evidence source, resolves the EFFECTIVE promotion state — the bound knob is a ceiling; live contrary evidence (gate failure, reverted merge, verification divergence) lowers it at evaluation time without writing the binding (automatic demotion has no write-back actor by design: the binding is agent-unwritable; the demotion event additionally files an escalation item requesting the human-ratified binding update). This evaluation is a LIVE-PATH OBLIGATION, not a demo artifact: the admission seam and the merge disposition MUST resolve the effective state (querying the promotion-evidence telemetry) before every autonomous dispatch/merge decision — reading the rawpromotion_statealone is non-conforming, and unavailable evidence telemetry fail-closes to the unpromoted default — the checker's evaluation mode exists to exercise the same resolution mechanically. Exit contract 0/1/2.plugins/autonomy/skills/setup/SKILL.mdguardrailssection: class→label strings, cost-tier→model names) — INCLUDING the security binding's resolvable locator: the org-policy home carries the repo→security-binding-document registry the dispatch seam resolves the binding through when settings-as-code is a separate repository — and for SECURITY resolution the org-policy-home identity itself must come from an agent-unwritable bootstrap, never the repo-local binding (the binding seam's known limitation thatorg_policy_homemay persist repo-locally is tolerable for non-security defaults only; a repo-writable pointer would let an agent redirect the whole chain to a forged policy repository with a forged registry, binding, and matching runtime markers): the seam pins the org-policy-home identity from org-level platform configuration outside repo blast radius (an org-level setting/variable repo agents cannot write) or the executor's trusted deployment config, and any security resolution that would depend on a repo-writable pointer — or an unresolvable locator — fail-closes autonomous dispatch with the compliant path named. Fail-closed on absent/invalid security binding; documented defaults only for non-security axes.Sanity Check:
check-security-binding.mjsexit 0 on a fixture-valid document, exit 1 on each negative fixture: C4 merge_policyauto,executor_class: vendor-hostedwith any non-human-gated merge row,promotion_stateentry for a C4 cell, missing probe evidence, missing L2 underdispatch_posture: autonomous-enabled, admission weakened withoutoverride_justification; exit 0 with a blocked-dispatch verdict on the missing-L2 +human-gated-onlyfixture (deliberate posture, not a defect) (fixtures underevals/fixtures/)executor_classandoverride_justification(grep -c 'executor_class' …schema.json≥ 1;grep -c 'override_justification' …schema.json≥ 1)additionalProperties: falsepresent (grep -c '"additionalProperties": false' …schema.json≥ 1)claude plugin validate --strictexit 0Phase 3: Guided-setup guardrail slice [DONE]
Extends the
setupskill: detect → bind → live-validate → fail-closed (D4), alwaysdetect-diff-reconcile against existing org guardrail surfaces (D5 — sandbox configs, branch
protections, review workflows), never greenfield-assume, never silently overwrite.
plugins/autonomy/skills/setup/SKILL.mdplugins/autonomy/skills/setup/templates/isolation-probe.mdplugins/autonomy/skills/setup/evals/evals.jsonplugins/autonomy/README.md+plugins/autonomy/.claude-plugin/plugin.jsonSanity Check:
/skill-quality:check+validate-evalspassgrep -c 'probe' plugins/autonomy/skills/setup/templates/isolation-probe.md≥ 3grep -ci 'detect-diff-reconcile' plugins/autonomy/skills/setup/SKILL.md≥ 1Phase 4: Live-validation demonstration + gates [DONE]
Acceptance probe, BOTH paths mandatory: (positive) run the Phase 3 probe recipe against a
REAL, PROVISIONED egress-denied boundary — the CI job on this public repo (free minutes)
launches a nested no-network container via the container runtime present on the hosted
Linux runner image (network mode none / internal-only, no host env or secrets passed in)
and executes the probe INSIDE it; the outer job keeps normal networking, so the probe's
egress-denial assertion is satisfied by a genuine boundary the job itself created — never
by the bare runner (which has outbound access and would fail the assertion) and never by a
hardcoded "denied" — transcript shows denied egress + absent credentials inside the
boundary, proving the probe DETECTS a genuine boundary; (negative) the fail-closed path is demonstrated by
validating an
autonomous-enabledbinding with a MISSING L2 entry and observing theblocked-dispatch error; the deliberate
human-gated-onlyposture validates separately withits blocked-dispatch verdict reported as declared, not as a defect. A
fixture security binding referencing the positive probe's evidence passes
check-security-binding.mjs; the demotion evaluation fixture (Phase 2) runs here as part ofthe demo record. Then the full gate roster (WP2 Phase 4):
validate-plugins.sh,run-plugin-tests.sh,validate-plugin-contracts.mjs, markdown/typos/lychee,claude plugin validate --strict, catalog regen. Near-duplicate audit statement: securityreview folded into the one guardrail slice (D5); escalation composes the governed queue — no
second channel created.
Sanity Check:
check-security-binding.mjsexit 0 on the demo binding, exit 1 on the missing-L2autonomous-enabledfixture, exit 0 + declared blocked-dispatch verdict on thehuman-gated-onlyfixtureBlast radius
MEDIUM-HIGH — one plugin's files, but this contract governs every autonomous dispatch
decision (admission), every isolation binding, and the promotion path to the closed loop;
security-posture content raises the review bar. Fully git-revertible; the security binding
itself ships as schema + fixtures only (no live org binding lands in this package).
Stress-test summary
Fresh-context plan review (WP4+WP5 batch): 14 findings, verdict FIX-THEN-SHIP, all folded.
WP5's share — F1b (HIGH, cross-package): the hosted human-merge-gate cap keyed on an
executor class the schema never carried →
executor_classadded to the security schema andenforced by the check; F9 (MED):
override_justificationwas required by the check butundeclared under
additionalProperties: false→ declared in the admission-rule sub-schema;F10 (MED): automatic demotion had no write-back actor against an agent-unwritable binding →
eval-time evidence-gating (bound state is a ceiling; live contrary evidence lowers the
effective state; the demotion event files an escalation item requesting the human-ratified
update), with an evaluation-mode fixture in the check and the demo; F11 (MED): the
fail-closed pivot alone never proved the probe detects a real boundary → positive probe
mandatory against a free-tier CI container, negative path demonstrated separately; F12
(LOW): fragile
grep -c '20'→ full-phrase grep; F13 (LOW): 3-D admission table gains a"*"wildcard + most-specific-wins precedence, and promotion entries for non-promotablecells are rejected; F14 (LOW, symmetry): the D8 backlog seeds are echoed in Open questions.
The classification-rules half of F1a lands here as the
admission.classificationschemacontent (the WP4 contract's adapters stamp from it).
Execution shape
Fully sequential 1 → 2 → 3 → 4 — Phase 2's schema serializes Phase 1's admission leaf;
Phase 3 wires Phase 2's binding; Phase 4 probes Phase 3's recipe. Cross-package: after the
WP4 implementation PR (admission seam exists to cite).
Open questions
/work-items backlog post-merge (Brief out-of-scope, triggers recorded).
carried in the Brief with named triggers; filed via the work-items flow.
Decisions made (gate-passed)
reference/guardrails/reference/subdir)autonomous_concurrency: 1,items_per_run: 3executor_class+ classification rules live on the security surface; promotion is eval-time evidence-gated (ceiling semantics)[FALLBACK — confirm or override]
check-security-binding.mjsas a NEW deliverable (Phase 2)— invented beyond the Brief on the WP2/WP4 enforcement-surface precedent. Flag if unwanted.
Handoff to implementation
User-approval gates
(these are T3/D7 user-locked content, not implementation discretion).
/architect review.Execution shape ([EXEC-SHAPE] tagged)
Sequential 1→4, all main-session (table above). PLAN.md phase tags advance in the same commit
as each phase; live probe per Phase 4 with the documented fail-closed pivot.
Mechanical work
Commit per phase on the implementation branch (suggest
feat/autonomy-guardrails); gatesre-run in full at Phase 4; PR body carries the probe transcript + near-duplicate audit
statement + this PLAN in a
<details>block at close-out.🤖 Generated with Claude Code