Skip to content

feat(autonomy): telemetry contract + setup slice, conformance check, live-proven demo (WP2)#343

Merged
kyle-sexton merged 11 commits into
mainfrom
feat/autonomy-telemetry
Jul 18, 2026
Merged

feat(autonomy): telemetry contract + setup slice, conformance check, live-proven demo (WP2)#343
kyle-sexton merged 11 commits into
mainfrom
feat/autonomy-telemetry

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

WP2 (ai-ladder-wp2-telemetry) implementation, phases 1–4 per its approved PLAN (docs/topics/ai-ladder-wp2-telemetry/PLAN.md, #330):

  • Telemetry contract (reference/telemetry.md): OTLP pinned to semconv v1.43.0 with schema_url on contract-authored emissions (native tool output consumed as-is — empirically corrected during the demo); the autonomy.work_item.url join attribute (normalized canonical item URL, selection rule, resource-scope on sessions / span-scope on CI, 1:1-lease granularity guarantee, join-epoch limitations, confidentiality clause, governed autonomy.* extension); one causal tree via W3C context propagation with the interactive exclusion; three sink classes with the file-artifact free default; native-surface principle; telemetry-is-not-return boundary.
  • Setup telemetry slice: existing-stack wiring, free default (pipeline-span JSON-lines writer + ephemeral per-job collector into one artifact directory, interactive coverage via a local collector store), OTEL_RESOURCE_ATTRIBUTES join wiring (vendor honoring verified against the official monitoring doc), paid sinks advisory-first, additive telemetry binding section.
  • Emission-conformance check (scripts/check-emission-conformance.mjs): pinned schemaUrl (OTLP JSON lowerCamelCase), join-attribute presence + normalization — the contract's enforcement surface.
  • Templates, three new eval cases, v0.2.0 manifest + catalog.

Conforming-path demo (executed live)

Trigger stub emitted its own root span and propagated TRACEPARENT → template writer emitted the pipeline span → a real headless agent session inherited the context with the join attribute at resource scope. Results: DuckDB over the single artifact directory shows 1 distinct trace across 4 spans (trigger + pipeline + 2 session spans); claude_code.cost.usage/token.usage carry autonomy.work_item.url string-identical to the pipeline span; conformance check exit 0. Sanitized fixture committed (skills/setup/evals/fixtures/otlp-demo/) as the WP3 demo interface. Demo capture used an equivalent OTLP/HTTP-JSON receiver (same JSON-lines output shape as the template's collector fileexporter) — the adopter template keeps the collector.

Audits

  • Near-duplicate: the slice composes the fleet's existing observability pipeline as one conforming existing-stack instance; the demo exercised the file-artifact class without touching it. No native telemetry surface reimplemented.
  • Gates: validate-plugins, contract validator (fleet + vendor sweeps), catalog check, plugin validate --strict, skill-quality PASS, evals schema ok, run-plugin-tests exit 0, markdownlint/typos clean.

No linked issue

Related

🤖 Generated with Claude Code

PLAN (published at effort-end close-out)

ai-ladder-wp2-telemetry

Brief

TLDR

Telemetry unification contract (T6): every execution context — interactive session, CI,
autonomous runner — emits standard OTLP pinned to the OTel CI/CD + VCS semantic conventions,
plus one contract-owned custom attribute carrying the work-item ID (the return-accounting join
key), with W3C TRACEPARENT propagation forming one causal tree per triggered chain. Sink
binding is deployment-owned and out of the contract. WP2 ships the contract doc and the
telemetry slice of guided setup.

Goal

Any adopting org can reach the emitting state — all three execution contexts producing
semconv-conformant OTLP with the work-item join attribute and propagated trace context — via
guided setup, with zero paid dependencies on the default path, against whatever observability
stack the org already has (or the free file-artifact default when it has none).

Locked decisions

# Decision
D1 Package scope: WP2 ships (i) the telemetry contract doc and (ii) the telemetry slice of the guided-setup capability, both in the capability-distribution home (the ladder plugin per WP1 D4). Fleet dogfood wiring and the native-vs-hook audit execution are work-item backlog, out of package.
D2 Contract pillars imported from T6 unchanged: (1) standard OTLP pinned to OTel CI/CD + VCS semconv; (2) one minimal custom attribute namespace carrying the work-item ID; (3) W3C TRACEPARENT propagation trigger → CI → agent session; sink binding deployment-owned, out of contract.
D3 Work-item attribute lives in a single contract-owned neutral namespace — never per-adopter reverse-DNS (breaks cross-org queryability; forces per-deployment sink-adapter config). Exact token deferred to architect alongside the plugin naming pass. If upstream semconv later ships a work-item/tracker namespace, the contract migrates to it — the no-parallel-schema rule applies to our own addition.
D4 Semconv pin policy: pin an exact semconv release and record schema_url on every emission. RC status accepted; upstream renames are handled as reviewed migrations, never silent. Exact version number resolved at build time against the live releases page.
D5 CI-side emission default class: OTLP-file-as-artifact + query-on-read is the free default (OTLP File Exporter pattern; zero standing infrastructure, zero cost). A network OTLP endpoint (existing stack, self-hosted, or hosted) is an opt-in adapter, never default. Role split per WP1 D3: emission handler logic = CI-orchestration home; enabling settings = settings-as-code home. Exact exporter/action choice at architect, verified live.
D6 Guided-setup telemetry slice is discovery-first per WP1 D7: detect an existing observability stack and wire emission toward it; none found → free file-artifact default; paid/hosted sink → advisory + explicit opt-in with cost surfaced (WP1 D6). All wiring lands as reviewable changes.
D7 Contract carries the native-surface principle: prefer each tool's native telemetry export over reimplementing it; anything reimplementing vendor built-in telemetry is scrapped. The audit applying this to the first adopting instance (local collector/DuckDB pipeline, hook envelope vs native export) files via the work-item flow.

Constraints

  • Any fleet repo name in normative contract text is a defect (WP1 acceptance criterion);
    fleet bindings live only in the binding-seam instance doc.
  • Zero new cost by default; paid sinks advisory + explicit opt-in always.
  • Telemetry is never presented as return — usage measures activity, not return (T5 boundary);
    return fields stay in WP3.
  • Interactive sessions deliberately ignore inbound TRACEPARENT (verified vendor behavior);
    trace-tree joining is a headless/CI/runner property — the contract must not promise it for
    interactive contexts.
  • Boris-alignment is the standing acceptance criterion: step-1 guardrail in its literal form
    (standard OTel into whatever stack exists), no step-skipping, trust before scale.

Acceptance criteria

  • Contract doc names roles and standard vocabulary only; semconv attributes referenced by
    pinned version + schema_url, never hand-copied lists.
  • The work-item attribute is defined precisely enough that the WP3 Brief consumes it as the
    join key without reopening this contract.
  • Guided setup takes an adopter with no observability stack to the emitting state on the free
    default path, as reviewable changes, with no paid dependency.
  • One conforming path demonstrates a single trace tree across trigger → CI → agent session.
  • No capability in the package reimplements a native telemetry surface.

Captured assumptions

  • Semconv RC is stable enough to pin against; rename migrations are manageable review-sized
    changes (evidence: githubreceiver v1.37.0 alignment).
  • Boris artifact live-fetch failed this session (3x boot error); alignment verified against
    same-day verbatim captures in durable homes (Unify session and CI verification telemetry? #247 resolution comment, design-threads
    T5/T6). Re-fetch if any quote beyond those captures becomes load-bearing.
  • Agent-CLI trace export remains beta; span shapes may change — contract pins signals and
    attributes, not beta span trees.
  • All WP2 decisions recommendation-locked under explicit user pre-authorization
    ("go with your recommendation"), each grounded in same-day primary-sourced research.

Out-of-scope (deferred with triggers)

  • Fleet dogfood wiring (settings env blocks, CI workflow edits) — work-item backlog; trigger:
    WP2 build lands.
  • Native-vs-hook telemetry audit execution — work-item backlog (carried seed); trigger: WP2
    contract text exists to audit against.
  • Return-accounting fields, prompts, bands — WP3.
  • Any sink backend recommendation or comparison — deployment-owned; guided setup surfaces
    classes (existing stack / file default / opt-in backend), never vendor picks.

Deferred questions

  • Exact custom-namespace token (with plugin naming pass) — /architect.
  • Exact semconv release number to pin — /architect (verify live at build).
  • CI exporter/action choice + collector config shape — /architect (verify live; community
    actions unofficial).
  • Sink adapter shape catalog (existing-stack / file-default / opt-in-backend interfaces) —
    /architect.

Plan

Interview-locked this round: join-key attribute namespace autonomy.work_item.* (OTel
app-name-prefix form; reverse-DNS structurally excluded by the fleet-name ban; migrates to any
future upstream work-item namespace per D3); join-key value = the item's canonical web URL,
attribute autonomy.work_item.url (globally unique across repos/trackers, derivable on both
sides; native short IDs collide multi-repo).

Prerequisite: WP1 implementation merged (plugins/autonomy/ exists with reference/ docs,
skills/setup/, and the contract-validator fleet-name gate). All phases sit on a fresh branch
cut after that merge; WP3's PR follows this package's PR (its convention doc cites this
contract — dead-link avoidance, same ordering rule as WP1's standards PR).

Phase 1: Telemetry contract doc [DONE]

File Action What changes
plugins/autonomy/reference/telemetry.md Create The T6 contract as tool-agnostic normative text: (1) every execution context class (interactive session, CI, autonomous runner) emits standard OTLP pinned to the OTel CI/CD + VCS semantic conventions — pin recorded as semconv release v1.43.0 with schema_url: https://opentelemetry.io/schemas/1.43.0 on every emission; attributes cited by registry reference, never hand-copied lists; RC caveat + reviewed-migration policy for upstream renames (D4). (2) The work-item join attribute: single custom attribute autonomy.work_item.url, value = the work item's canonical web URL in normalized form (the tracker's own canonical item URL: https scheme, no trailing slash, no query string or fragment) — string equality is the join operation, so the normalization rule is normative. Selection rule: the key is always the WORK ITEM's URL, never a PR/change URL; a change closing N items yields N per-item associations; an agent session keys on its leased item. Attribute SCOPE pinned per context: RESOURCE-scope on agent-session emission (so cost/token metrics and session spans all carry it), span-scope on the CI pipeline/task spans. Granularity guarantee: conforming autonomous dispatch runs ONE leased work item per emitting session/process (the trigger-layer lease contract is the guarantor); multi-item batch sessions get session-granular cost only — stated as a known limitation, not silently misattributed. Known join-epoch limitations stated normatively: repo rename/transfer and tracker migration change the canonical URL; the join is query-time, so sinks MAY remap historical values; a secondary immutable-ID attribute is deferred with trigger (rename churn proves material). Confidentiality clause: the attribute value inherits the confidentiality class of the referenced repo/tracker — any sink or artifact carrying it must enforce access controls at least as strict as the item's home. No other custom namespace is defined by THIS contract; a sibling capability contract in the same home MAY define its own additions under the same autonomy.* prefix and governance (reviewed contract changes, no-parallel-schema rule) — minimality binds this contract, it does not forbid governed extension. No-parallel-schema migration rule if upstream ships a tracker namespace; contract migrations (including the semconv-Stable rename wave when cicd/vcs graduate) are reviewed changes owned by the contract home, trigger recorded in the plugin's trigger register. (3) W3C TRACEPARENT propagation trigger → CI → agent session forms one causal tree; headless/CI-only property — interactive contexts explicitly excluded from inbound trace joining. Sink binding out of contract: three sink classes only (existing observability stack / OTLP-file-artifact free default with query-on-read / opt-in network backend), adapter obligations per class stated as shape, no vendor picks. Native-surface principle (D7): prefer each tool's native telemetry export; reimplementations of native surfaces are non-conforming. Telemetry-is-not-return boundary note (T5 seam). Zero vendor/fleet names — tool-specific mechanics live in SKILL.md.

Sanity Check:

  • grep -c 'opentelemetry.io/schemas/1.43.0' plugins/autonomy/reference/telemetry.md ≥ 1
  • grep -c 'autonomy.work_item.url' plugins/autonomy/reference/telemetry.md ≥ 1
  • Vendor+fleet deny-list grep empty over reference/telemetry.md (same token list as WP1 Phase 3)
  • grep -ci 'traceparent' plugins/autonomy/reference/telemetry.md ≥ 1
  • No hand-copied semconv attribute table: grep -o 'cicd\.[a-z.]*' plugins/autonomy/reference/telemetry.md | wc -l ≤ 4 (attributes named only as illustrative citations, never enumerated)
  • grep -c 'no trailing slash' plugins/autonomy/reference/telemetry.md ≥ 1 (URL normalization rule present)
  • node scripts/validate-plugin-contracts.mjs exit 0; lychee lane passes

Phase 2: Guided-setup telemetry slice [DONE]

Extends the WP1 setup skill (discovery-first per D6/WP1 D7); all wiring lands as reviewable
changes; paid sinks advisory + explicit opt-in with cost surfaced. First work item — fresh-docs
mandate (repo CLAUDE.md): re-fetch the official skills + settings + monitoring docs before
editing SKILL.md or citing vendor env vars.

File Action What changes
plugins/autonomy/skills/setup/SKILL.md Modify Add the telemetry slice to discovery + apply: detect an existing observability stack (config/env interview + repo inspection); wire emission toward it when found (env-block + CI snippet as reviewable changes); none found → free file-artifact default: CI pipeline spans via a minimal OTLP JSON-lines writer snippet AND agent-session signals via an ephemeral per-job collector (single static OSS binary + config template, receives the session's OTLP and writes JSON-lines into the same artifact directory — per-job, no standing infrastructure; this closes the session-capture path the agent CLI's exporter set cannot reach alone). INTERACTIVE sessions get the same free-default coverage (the contract promises the emitting state for all three execution contexts): env block toward the discovered stack when one exists; none → a local collector instance (same binary + config template, file export into a local query-on-read store directory), landed as reviewable settings/config changes like every other wire. No dependency on the stale community actions; the two live third-party actions are endpoint-push and stay named only as opt-in adapter examples. Hosted/paid sink → advisory + explicit opt-in, cost surfaced; free-default cost caveat surfaced consistently: artifact storage and per-job collector runtime are metered on private repos (same framing as the WP3 minutes caveat). Agent-session wiring MUST set the join attribute at resource scope (OTEL_RESOURCE_ATTRIBUTES carrying autonomy.work_item.url for work-item-dispatched sessions — verify the vendor honors it at implementation) alongside TRACEPARENT behavior and beta-trace flags; all vendor env vars documented here, not in reference/. Records the telemetry binding (sink class, endpoint/artifact path, semconv pin) as an ADDITIVE section of the WP1 schema-versioned binding — absent-section tolerance stated; no schema major bump for additive sections.
plugins/autonomy/skills/setup/scripts/check-emission-conformance.* (name/extension per repo script conventions) Create Minimal emission-conformance check adopters (and the Phase 3 demo) run against produced OTLP JSON-lines: the schema URL present and pinned — matched as the schemaUrl key, since the OTLP JSON encoding uses lowerCamelCase field names (resourceSpans, schemaUrl), never the proto snake_case — join attribute present where required, value normalization holds. This is the contract's only enforcement surface — without it adopter drift is undetectable.
plugins/autonomy/skills/setup/evals/evals.json Modify Add telemetry-slice cases: existing-stack wiring path, no-stack file-artifact default, paid-sink advisory/opt-in refusal-to-default, non-interactive argument-supplied run extended with telemetry args.
plugins/autonomy/skills/setup/templates/ (or the skill's existing snippet home from WP1 impl) Create CI emission snippet template(s): pipeline-span OTLP JSON-lines writer + artifact upload step shape, TRACEPARENT injection for headless agent steps. Template is surface-class-parameterized; fleet names banned.
plugins/autonomy/README.md Modify Shipped-capability list grows by the telemetry contract + setup slice; roadmap row flips from deferred.
plugins/autonomy/.claude-plugin/plugin.json Modify Description extends to shipped telemetry capability; minor version bump per repo convention.

Sanity Check:

  • /skill-quality:check + validate-evals pass (skills_root = plugins/autonomy/skills)
  • claude plugin validate --strict exit 0
  • grep -ci 'file-artifact' plugins/autonomy/skills/setup/SKILL.md ≥ 1 and grep -ci 'opt-in' plugins/autonomy/skills/setup/SKILL.md ≥ 1
  • grep -c 'OTEL_RESOURCE_ATTRIBUTES' plugins/autonomy/skills/setup/SKILL.md ≥ 1
  • Fleet-name sweep (validate-plugin-contracts.mjs) exit 0

Phase 3: Conforming-path demonstration [DONE]

Acceptance-criterion probe: one path demonstrates a single trace tree trigger → CI → agent
session on the free default, zero paid dependencies. Scratch consumer repo (NOT this repo),
mirroring WP1's migration-gate step 9: a stub trigger step GENERATES its own root span and
propagates TRACEPARENT (the trigger hop is observed, not seeded); the Phase 2 writer snippet
emits the pipeline span into a single artifact directory; a headless agent session inherits
TRACEPARENT with OTEL_RESOURCE_ATTRIBUTES set, its OTLP captured by the Phase 2 ephemeral
collector into the SAME artifact directory; the Phase 2 conformance check runs against the
combined output; query via the DuckDB otlp extension (live-verified 2026-07-18). A small
sanitized OTLP fixture from this run is COMMITTED (plugin test-fixture home) as the durable
artifact the WP3 demo consumes — the PR-body transcript is evidence, not the interface.

Sink-class note: this demo exercises the FILE-ARTIFACT FREE DEFAULT class end-to-end. The
fleet's claude-ops collector/DuckDB pipeline is the existing-stack class's conforming
instance and is deliberately NOT used here — the scratch repo must not depend on fleet
plugins; no standing capability is created (near-duplicate audit rationale).

Sanity Check:

  • DuckDB query over the single demo artifact directory returns exactly 1 distinct trace_id covering ≥ 3 spans (trigger root + pipeline + agent session)
  • The same dataset shows an agent-session COST/token metric whose resource carries autonomy.work_item.url equal (string-identical) to the pipeline span's attribute value
  • check-emission-conformance exit 0 over the demo output
  • The committed fixture exists and the same DuckDB assertions pass against it
  • Demo transcript + query output attached to the PR body; no paid service touched

Phase 4: Gates [DONE]

Full in-repo gate run (same roster as WP1 Phase 5): scripts/validate-plugins.sh,
scripts/run-plugin-tests.sh, node scripts/validate-plugin-contracts.mjs,
markdown/typos/lychee lanes, claude plugin validate --strict, catalog regen check.
Near-duplicate audit statement: the telemetry slice composes claude-ops observability
(fleet's conforming sink instance) rather than duplicating it.

Sanity Check:

  • All gate scripts exit 0
  • node scripts/generate-catalog.mjs reports in-sync
  • Near-duplicate audit statement present in the PR body

Blast radius

MEDIUM — ~8 files in one plugin, but the contract doc constrains WP3–WP6, every future
adopter, and the join key WP3 depends on; new convention + cross-cutting observability
triggers match. Fully git-revertible; automated gates cover the shared surfaces.

Stress-test summary

Step 3 fresh-context plan review: 11 findings (1 CRITICAL — the cost-join was undemonstrable:
attribute OTel scope unpinned, OTEL_RESOURCE_ATTRIBUTES unwired, demos joined against a
zero-cost pipeline span; all folded). Step 4 /devils-advocate: 3 CRITICAL / 4 HIGH /
4 MEDIUM / 1 LOW — folded: process-granularity guarantee + batch limitation; ephemeral
per-job collector closes the session-capture gap; join-key selection rule +
rename/transfer/tracker-move limitations + deferred immutable-ID trigger (user-locked: keep
URL); emission-conformance check + migration ownership; confidentiality clause;
observed-not-seeded trigger hop; committed durable fixture; consistent private-repo cost
caveats. Residual accepted: OTEL_RESOURCE_ATTRIBUTES reaching the vendor's metric exporter
is verify-at-implementation (divergence escalates); RC semconv pin accepted per D4.

Execution shape

Fully sequential 1 → 2 → 3 → 4 — Phase 2 cites Phase 1's contract; Phase 3 exercises Phase 2's
snippets and conformance check; Phase 4 gates the authored tree. Parallel saving immaterial
(small volume, tightly coupled). Cross-package: this PR merges before the WP3 PR (WP3's doc
cites this contract; WP3's demo consumes this package's committed fixture); both wait on the
WP1 implementation PR.

Phase Surface Basis
1 main-session normative contract authoring, tightly coupled to T6 + interview locks
2 main-session setup-skill + template judgment, vendor-doc re-fetch gate
3 main-session scratch-repo runtime probe with divergence judgment
4 main-session gate runs

Open questions

  • Native-vs-hook telemetry audit + fleet dogfood wiring: backlog items to file via the
    work-items flow after this package lands (out of package per D1; trigger recorded).

Decisions made (gate-passed)

Decision What it changes in the plan Basis (evidence)
Contract doc filename reference/telemetry.md Phase 1 target path WP1 layout convention (concern-named docs, one per capability in reference/) + WP1's three shipped names
Semconv pin recorded as v1.43.0 now, re-verified at build Phase 1 pin + sanity check Live releases API 2026-07-18; D4 makes /architect the arbiter
Free-default CI mechanics: hand-rolled OTLP JSON-lines writer + ephemeral per-job collector for session signals Phase 2 deliverables; no third-party action dependency Live verification: no official OTel action; artifact-pattern actions stale (2022/2023); live actions are endpoint-push; agent CLI has no file exporter
1:1 process-per-item granularity guarantee + batch-session limitation Phase 1 contract clause Stress-test CRITICAL #1; trigger-layer lease contract (T1) is the guarantor
Join-key selection rule (work-item URL, never PR) + join-epoch limitations + deferred immutable-ID trigger Phase 1 contract clause Stress-test CRITICAL #3; user locked keep-URL 2026-07-18
Confidentiality clause on the join attribute Phase 1 contract clause Stress-test HIGH #7
Committed sanitized OTLP fixture as WP3's demo interface Phase 3 output Stress-test MEDIUM #10
Two-PR ordering: WP2 merges before WP3; both after WP1 impl Plan preamble + execution shape WP1's dead-link-avoidance ordering precedent; WP3 cites this contract

[FALLBACK — confirm or override] Emission-conformance check script as a NEW deliverable
(Phase 2) — invented beyond the Brief to close the stress-test governance gap (HIGH #6:
pinned contract with no enforcement surface). Flag if unwanted.

Handoff to implementation

User-approval gates

  • The conformance-check script above is [FALLBACK] — surface before authoring if scope is
    contested.
  • OTEL_RESOURCE_ATTRIBUTES not honored by the vendor's metric exporter at implementation →
    STOP and re-surface (the cost-join design depends on it; do not improvise an alternative).
  • 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; scratch-repo demo per Phase 3; divergence escalation applies to every phase.

Mechanical work

Commit per phase on the implementation branch (suggest feat/autonomy-telemetry); gates re-run
in full at Phase 4; commits via the repo's commit conventions; PR body carries the demo
transcript + near-duplicate audit statement + this PLAN in a <details> block at close-out.

kyle-sexton and others added 6 commits July 18, 2026 14:01
The T6 contract as tool-agnostic normative text: semconv v1.43.0 pin
with schema_url on every emission (registry-cited, never copied), the
autonomy.work_item.url join attribute (normalized canonical item URL,
resource-scoped on sessions / span-scoped on CI, selection rule,
1:1-lease granularity guarantee, join-epoch limitations, deferred
immutable-ID trigger, confidentiality clause, governed autonomy.*
extension), one causal tree via W3C context propagation with the
interactive exclusion, three sink classes with the file-artifact free
default, native-surface principle, telemetry-is-not-return boundary.

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
… phase 2)

Telemetry slice in setup: existing-stack wiring, file-artifact free
default (pipeline-span JSON-lines writer + ephemeral per-job collector
into one artifact directory, interactive coverage via a local collector
store), OTEL_RESOURCE_ATTRIBUTES join-attribute wiring for dispatched
sessions (vendor honoring verified against the official monitoring
doc), paid sinks advisory-first with metered-cost caveats, additive
telemetry section in the schema-versioned binding. Ships the
emission-conformance check (schemaUrl pin + normalized join attribute
over OTLP JSON-lines, lowerCamelCase encoding), surface-class snippet
templates, three new eval cases, v0.2.0 manifest + README.

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
…2 phase 3)

Scratch-repo demo executed the full free-default path: a trigger stub
emitted its own root span and propagated TRACEPARENT (observed hop),
the template writer emitted the pipeline span, and a live headless
agent session inherited the context — DuckDB over the single artifact
directory shows 1 distinct trace across 4 spans, and cost/token metrics
carry autonomy.work_item.url at resource scope string-identical to the
pipeline span. Empirical contract correction from the run: native
agent-session emission declares no schemaUrl, so the pin now binds
contract-authored emissions (native surfaces consumed as-is) and the
conformance check requires at least one pinned declaration per output
set instead of one per line. Sanitized fixture (trigger + pipeline +
scrubbed session cost metrics) committed as the WP3 demo interface.

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
…e 4)

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c4cb15bc7

ℹ️ 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".

Comment thread plugins/autonomy/skills/setup/templates/ci-otlp-artifact.md Outdated
Comment thread plugins/autonomy/skills/setup/scripts/check-emission-conformance.mjs Outdated
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 12s —— View job


PR Review — feat(autonomy): WP2 telemetry contract + setup slice

  • Gather context and understand the PR
  • Review telemetry contract (reference/telemetry.md)
  • Review CI template (templates/ci-otlp-artifact.md)
  • Review conformance check script (scripts/check-emission-conformance.mjs)
  • Review SKILL.md updates and eval fixtures
  • Review manifest / catalog changes
  • Assess Codex review findings (P1/P2 items)
  • Write and post final review

Summary

The telemetry contract is well-designed — the three-pillar structure (standard OTLP, work-item join attribute, W3C causal tree), native-surface principle, and "telemetry is not return" boundary are all sound. The conformance script handles the native-vs-contract-authored distinction correctly (tolerating absent schemaUrl on native emissions while requiring it somewhere in the set). Eval cases 6–8 cover the key telemetry wiring paths well.

Two issues need fixing before this is ready to merge, and two more are worth addressing now.


Critical

1. Template doesn't show how to extract the parent span ID from TRACEPARENT

ci-otlp-artifact.md:16

The pipeline-span writer uses trace_id="<derived-or-generated 32-hex>" but gives no guidance on how to derive it from an inbound TRACEPARENT. The emitted span has no parentSpanId field either. An adopter following this template literally writes a disconnected root span — the trigger→CI link in the causal tree never forms.

The committed fixture (pipeline.jsonl) does have a parentSpanId pointing to the trigger span, which proves the desired chain works — but nothing in the template explains how to establish it. The missing piece is parsing the inbound TRACEPARENT before writing the span:

# Parse inbound trigger context when present
if [ -n "${TRACEPARENT:-}" ]; then
  IFS='-' read -r _ver trace_id trigger_span_id _flags <<< "$TRACEPARENT"
else
  trace_id="<generated 32-hex>"
  trigger_span_id=""
fi
span_id="<generated 16-hex>"

And the JSON shape needs a conditional "parentSpanId":"$trigger_span_id" (or the field omitted when empty). Without this, the acceptance criterion "One conforming path demonstrates a single trace tree across trigger → CI → agent session" (PLAN.md D2/AC4) is satisfied by the fixture but not reproducible from the template.

Fix this →


2. Conformance check validates join attribute globally, not per emission

check-emission-conformance.mjs:100

if (joinAttributeHits === 0 && linesChecked > 0) {
  findings.push(`no occurrence of ${JOIN_ATTRIBUTE} anywhere in the checked output`);
}

This passes when a pipeline span carries autonomy.work_item.url but agent-session metrics do not — exactly the split that breaks return attribution. The contract states resource-scope on all agent-session emissions (cost + token metrics, session spans) and span-scope on CI spans; a single hit anywhere in the artifact set doesn't verify either guarantee. The Codex P1 finding is accurate.

A tighter approach: require the join attribute on every resourceMetrics/resourceLogs resource block (session-class emissions) and on every CI span individually, rather than accepting one global occurrence. At minimum, the check should fail if any resourceMetrics resource block (which the contract assigns to session telemetry) lacks the attribute.

Fix this →


Important

3. Fixture span count doesn't match PR description

session-metrics.jsonl

The PR description states "1 distinct trace across 4 spans (trigger + pipeline + 2 session spans)" and "DuckDB over the single artifact directory." The committed fixtures have the trigger span (1) and pipeline span (1) — but session-metrics.jsonl contains resourceMetrics only, no session spans. That's 2 spans total, not 4. The Codex P2 finding is accurate.

If the session spans were omitted intentionally (e.g., because CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 was not set and traces are beta-gated), that decision should be documented in the fixture's companion note or in the SKILL.md. If they were expected to be present, the fixture is incomplete.

4. OTEL_RESOURCE_ATTRIBUTES clobbers existing values

ci-otlp-artifact.md:68

OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

This assignment silently drops any existing OTEL_RESOURCE_ATTRIBUTES in the environment (e.g., service name, deployment environment attributes set by the CI platform). The OTEL SDK spec allows comma-separated key=value pairs; adopters with an existing observability stack who follow the template will lose their existing resource attributes. The session env block should show how to prepend or append:

OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

Minor

5. No eval case for conformance-check failure

evals.json covers the happy path (evals 6–7) and the paid-sink guard (eval 8), but has no case validating that the skill correctly surfaces a conformance failure and refuses to declare the emitting state reached. Given that the conformance check is the contract's enforcement surface, a test of its failure path would guard against silent regressions.

6. plugin.json keywords don't include telemetry

plugin.json:11

The description now mentions telemetry prominently (WP2's primary deliverable) but the keywords array doesn't include "telemetry" or "otel". Low priority, but worth adding for discoverability.


What works well

  • The three-pillar contract structure is clean and the normative language is precise — the scope, selection rule, normalization rule, and confidentiality clause on the join attribute leave no ambiguity for WP3 to consume.
  • Separating "sink binding — out of contract" is the right call; it prevents vendor lock-in at the contract layer and keeps adopter flexibility.
  • The conformance script correctly distinguishes contract-authored from native emissions: absent schemaUrl on a native line is tolerated, a declared mismatch fails, and the set must have at least one declaration.
  • Resource-scope vs. span-scope join-attribute placement in the demo fixtures is correct per the contract (session-metrics.jsonl at resource level; pipeline.jsonl and trigger.jsonl at span level).
  • Eval cases 6–8 are well-specified with clear expectations that map directly to contract invariants.
  • SKILL.md correctly surfaces the beta-gating caveat on spans and never depends on beta span shapes.

…k, span fixture

The pipeline-span writer now consumes inbound TRACEPARENT for both the
shared trace ID and parentSpanId (joining the trigger's tree instead of
rooting a second one); the conformance check requires the join
attribute on EVERY emission, not once globally; the committed fixture
gains a scrubbed agent-session span line so the >=3-span single-tree
demo is reproducible from the fixture alone (verified: 1 trace, 4
spans, conformance OK). Exec bit set on the conformance script (hygiene
gate).

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 13s —— View job


PR Re-Review — feat(autonomy): WP2 telemetry contract + setup slice (c56e742)


Re-review summary

The three issues flagged as Critical (×2) and Important (#3) in the previous round are all correctly fixed in c56e742. One Important issue (#4) from the previous review remains unaddressed. Two Minor issues remain open but non-blocking.


Previously critical — confirmed fixed

Fix #1: TRACEPARENT propagation ci-otlp-artifact.md:19–28

The shell snippet correctly splits TRACEPARENT on - to extract field 2 (trace ID) and field 3 (parent span ID), sets parent_field="\"parentSpanId\":\"<id>\"," (with trailing comma) when present, and leaves it empty otherwise. The heredoc interpolation "spanId":"$span_id",$parent_field"name": produces valid JSON in both branches — when parent_field is empty, the fixed comma before the variable already acts as the field separator so there's no double-comma. The trigger→pipeline→session tree is now reproducible from the template. ✅

Fix #2: Per-emission join attribute enforcement check-emission-conformance.mjs:93–100

The check now fails any emission line where hits === 0, regardless of what other lines carry. The logic correctly counts resource-scope hits via entry.resource?.attributes and item-scope hits via scope.spans/scopeMetrics/scopeLogs, so a session-metrics line carrying the attribute only at resource scope (as the contract requires) still passes. All four fixture files pass: trigger.jsonl and pipeline.jsonl hit it at span scope, session-spans.jsonl hits at both resource and span scope, session-metrics.jsonl hits at resource scope. ✅

Fix #3: Fixture span count session-spans.jsonl

The fixture now contains a claude_code.interaction span (parentSpanId=d79ce0092ae71d97, the pipeline span) and a claude_code.llm_request span (parentSpanId=b267738f2647e74e, the interaction span). The complete tree across all four fixture files is:

Span ID Parent
demo-trigger e83d2fc23d34ff22 — (root)
demo-pipeline d79ce0092ae71d97 e83d2fc23d34ff22
claude_code.interaction b267738f2647e74e d79ce0092ae71d97
claude_code.llm_request c0f7bc4594ea469b b267738f2647e74e

One trace, four spans, single continuous parent chain. PR description claim now verifiable from the fixture alone. ✅


Still unaddressed — Important

#4OTEL_RESOURCE_ATTRIBUTES plain assignment clobbers existing attributes ci-otlp-artifact.md:77

OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

Any adopter whose CI platform already sets OTEL_RESOURCE_ATTRIBUTES (service name, deployment environment, host attributes) silently loses those values when following this template verbatim. OTEL SDK spec accepts comma-separated key=value pairs, so the safe form is:

OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

This is particularly relevant for teams adopting the "existing-stack" sink class (eval 6), who are exactly the adopters most likely to have an existing OTEL_RESOURCE_ATTRIBUTES set. Not blocking alone, but worth fixing before or shortly after merge to avoid a known footgun in the template.

Fix this →


Still open — Minor

#5 — No conformance-failure eval case

evals.json still has no case verifying that the skill surfaces a conformance failure and refuses to declare the emitting state reached. Given the script is the contract's enforcement surface, a failure-path eval would protect against silent regression. Low priority but the gap exists.

#6plugin.json keywords don't include telemetry or otel

plugin.json:11 — WP2 makes telemetry the plugin's primary new deliverable, but the keywords array (autonomy, adoption, governance, topology, binding, guided-setup) still has no discoverability signal for it.


What's clean

The $parent_field heredoc technique is sound — the comma/empty-string placement is correct in both branches, no shell quoting hazard. The conformance script's native-vs-contract-authored distinction (absent schemaUrl tolerated, declared must match the pin, set must have at least one declaration) is preserved correctly in the rewrite. The fixture join attribute placement correctly differentiates resource scope (session) from span scope (CI) per the contract. The exec-bit fix on the script is a good hygiene touch.

Overall: the two blocking issues from the previous review are resolved. Issue #4 is a real footgun for adopters and worth a follow-up fix before or shortly after merge; #5 and #6 can reasonably live in a follow-up.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c56e742028

ℹ️ 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".

Comment thread plugins/autonomy/skills/setup/scripts/check-emission-conformance.mjs Outdated
Comment thread plugins/autonomy/skills/setup/scripts/check-emission-conformance.mjs Outdated
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 32s —— View job


PR Re-Review — feat(autonomy): WP2 telemetry contract + setup slice (fc1f8ca)

  • Gather context: read current state of key changed files
  • Assess new Codex P1: per-resource join attribute validation
  • Assess new Codex P2: URL parsing/normalization validation
  • Verify status of still-open ci: onboard to the CI platform (ci-workflows + standards) #4: OTEL_RESOURCE_ATTRIBUTES clobber
  • Write and post final review

Re-review summary

The merge commit fc1f8ca brings in main — no functional changes to the PR's deliverables. This pass focuses on the two new Codex findings raised against c56e742 (P1: per-resource join validation; P2: URL normalization) and the still-open Important issue #4 from the previous round. No new blocking issues, but the Codex P1 and issue #4 are both real gaps worth addressing before merge.


New Codex findings — assessed

Codex P1 — Valid, Important

check-emission-conformance.mjs:41–62

The hits counter in checkResourceBlocks is declared once and accumulated across all signal-type keys in the parsed object (resourceSpans, resourceMetrics, resourceLogs). A valid OTLP export batch that mixes signal types in one JSON line — e.g. a joined resourceSpans entry alongside an unjoined resourceMetrics entry — would yield hits > 0 and pass the per-line check, letting the unjoined metrics resource through.

In practice, the file-artifact free default (collector fileexporter) always writes signal-homogeneous export batches, so the issue doesn't arise with any of the committed fixtures. But as the contract's sole enforcement surface, the check should be correct against valid OTLP, not just the nominal output shape. The per-line hits === 0 guard introduced in c56e742 was the right fix for the global-hit problem; this is the next level: the check needs to reset per resource entry, not per line.

Minimal fix — move hits inside the inner loop:

for (const entry of block) {
  const entryHits = { count: 0 };   // per resource-entry, not per line
  checkAttributeList(entry.resource?.attributes, `${where} resource`, entryHits);
  for (const scope of ...) {
    for (const item of ...) {
      checkAttributeList(item.attributes, ..., entryHits);
    }
  }
  if (entryHits.count === 0) {
    findings.push(`${where}: resource entry carries no ${JOIN_ATTRIBUTE} attribute`);
  }
  totalHits += entryHits.count;
}

The return value and the per-line hits === 0 guard in the caller can then be removed (the per-entry push makes them redundant).

Fix this →


Codex P2 — Valid, Minor

check-emission-conformance.mjs:20

const NORMALIZED_URL = /^https:\/\/[^\s?#]+[^\s?#/]$/;

This regex accepts structurally invalid URLs: https:///items/101 (empty host — [^\s?#]+ matches /items/101) and https://host:notaport/path (:notaport is not in the exclusion set). Values come from the adopter's own configuration, so this isn't an injection risk, but the conformance check can silently accept a value that no OTLP backend will join correctly. The fix is to parse via new URL() and assert a non-empty hostname:

function isValidNormalizedUrl(v) {
  try {
    const u = new URL(v);
    return u.protocol === "https:" && u.hostname !== "" &&
           u.search === "" && u.hash === "" && !v.endsWith("/");
  } catch { return false; }
}

Non-blocking, but the regex flaw undermines the normalization guarantee. Worth addressing in this PR or a follow-up.

Fix this →


Carried over — still open

#4OTEL_RESOURCE_ATTRIBUTES plain assignment clobbers existing values ci-otlp-artifact.md:77

OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

Still a plain assignment. Any adopter with an existing OTEL_RESOURCE_ATTRIBUTES (service name, deployment environment) silently loses those values when following this template. Particularly affects existing-stack adopters (eval 6), who are exactly the ones most likely to have the variable set. Safe form:

OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

Fix this →


Previously open — Minor (still open)

#5 — No conformance-failure eval case: evals.json still covers only the happy path and the paid-sink guard. A failure-path test would guard against silent regression in the script.

#6plugin.json keywords missing telemetry/otel: WP2 makes telemetry the primary new deliverable, but the keywords array has no discoverability signal for it.


What looks clean

All three previously-blocking/critical fixes (c56e742) are confirmed clean in the current HEAD. The fixture join-attribute placement is correct: resource-scope on all session emissions (session-metrics.jsonl, session-spans.jsonl) and span-scope on CI emissions (pipeline.jsonl, trigger.jsonl). The TRACEPARENT parsing is sound; the heredoc's $parent_field placement correctly handles both branches without a double-comma. The checkResourceBlocks schemaUrl logic (tolerate absent, fail on declared mismatch, require at least one) is preserved correctly.

Verdict: Codex P1 and issue #4 are both real gaps at the contract enforcement and adoption layers respectively. Neither blocks merge on its own, but two open Important items in the same PR is worth one more fix pass before landing.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc1f8ca846

ℹ️ 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".

Comment thread plugins/autonomy/skills/setup/templates/ci-otlp-artifact.md Outdated
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 10s —— View job


PR Re-Review — feat(autonomy): WP2 telemetry contract + setup slice (ccff787)


Re-review summary

ccff787 is a merge-from-main with no changes to any of the PR's functional files. All findings from the previous review round (fc1f8ca) carry forward unchanged. Three of the four open issues affect either the contract's enforcement surface or the template's operational correctness — one of them silently breaks the entire telemetry binding for adopters following the template verbatim. That one warrants fixing before merge.


Still open — should fix before merge

Session env block variables are not exported ci-otlp-artifact.md:73–77

CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

These five assignments are shell-local. Without export, the Claude Code child process inherits none of them — telemetry is never enabled, the exporter is never pointed at the collector, and the join attribute is never set. Compare line 40 (export TRACEPARENT=...), which is correctly exported. Any adopter following the session env block verbatim will see no session telemetry in the artifact directory, with no error to indicate why.

The OTEL_RESOURCE_ATTRIBUTES line also has the carried-over clobber issue. Both defects are fixed together:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

Fix this →


Still open — Important (can follow up)

Per-resource join attribute validation check-emission-conformance.mjs:42–61

const hits = { count: 0 } is declared once at the top of checkResourceBlocks, then accumulated across all signal keys (resourceSpans, resourceMetrics, resourceLogs) in the parsed line. A valid OTLP export batch that mixes signal types in one JSON line — a joined resourceSpans entry alongside an unjoined resourceMetrics entry — would yield hits > 0 and the unjoined metrics resource passes the per-line guard at line 98. The file-artifact free default always writes signal-homogeneous batches, so no committed fixture triggers this; but the check is the contract's sole enforcement surface and should be correct against valid OTLP, not just the nominal tooling output.

Fix: move const hits inside for (const entry of block) so it resets per resource entry, and push a finding directly when hits.count === 0 for that entry.

Fix this →


Still open — Minor

Codex P2 — URL regex accepts invalid hostnames check-emission-conformance.mjs:20

/^https:\/\/[^\s?#]+[^\s?#/]$/ accepts https:///items/101 (empty host) and https://host:notaport/path (non-numeric port). Replacing the regex test with new URL() parse + hostname and protocol assertion is a one-liner fix that closes the gap.

Issue #5 — No conformance-failure eval case

evals.json still covers only the happy path (6–7) and the paid-sink guard (8). A failure-path test guards against silent regression in the script.

Issue #6plugin.json keywords missing telemetry/otel plugin.json:11

WP2 makes telemetry the primary new deliverable; "keywords" still has no discoverability signal for it.


What looks clean

All three previously-blocking fixes from c56e742 (TRACEPARENT propagation, per-line join attribute enforcement, four-span fixture) are confirmed clean at this HEAD. The contract structure, native-vs-contract-authored schemaUrl distinction, fixture placement (resource-scope on session emissions, span-scope on CI emissions), and heredoc $parent_field correctness are unchanged.

Verdict: One fix before merge (session env block not exported — silently breaks all telemetry for adopters following the template). The per-resource join validation fix is a real correctness gap in the enforcement surface worth addressing soon after.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccff787538

ℹ️ 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".

…ported env

Address the review wave on the conformance checker and CI template:

- Require the join attribute per OTLP resource entry, not per JSON line —
  a joined pipeline entry in an export batch must not vouch for an
  unjoined session entry beside it
- Validate the join value by parsing (WHATWG URL) with a round-trip
  equality requirement, rejecting parser-repaired values like an empty
  host and malformed ports a prefix regex accepted
- Validate scope-level schemaUrl declarations against the pin, not just
  resource-entry ones
- Export the session telemetry variables in the CI template so the
  launched agent process actually inherits them

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 11s —— View job


PR Re-Review — feat(autonomy): WP2 telemetry contract + setup slice (f22220c)


Re-review summary

All four targeted fixes in f22220c ("per-entry join check, URL parsing, scope schemaUrl, exported env") are confirmed correct. The previously-blocking session-env export issue is resolved. One Important carry-forward item (OTEL_RESOURCE_ATTRIBUTES clobber) and two Minor items remain open.


Confirmed fixed

Session env block now exports all variables ci-otlp-artifact.md:77–81

All five variables now carry export. The added inline note ("plain sh assignments stay shell-local and the session would emit nothing") explains why, giving adopters who deviate toward env: maps the right mental model. ✅

Per-entry join attribute validation check-emission-conformance.mjs:85

const hits = { count: 0 } is now declared inside block.forEach((entry, entryIndex) => {...}) — correctly scoped per resource entry. A joined resourceSpans entry in the same export batch no longer vouches for an unjoined resourceMetrics entry beside it. The per-line hits === 0 guard in the caller is gone; per-entry findings replace it cleanly. ✅

URL parsing via new URL() with round-trip check check-emission-conformance.mjs:30–48

isNormalizedCanonicalUrl now uses the WHATWG URL parser and the url.href === value round-trip check — stronger than the originally-suggested form. The round-trip check correctly rejects values the parser silently repairs (https:///items/101https://items/101, hostname becomes items, but url.href !== value), and the parser itself rejects non-numeric ports at construction time. Both Codex P2 cases are closed. ✅

Scope-level schemaUrl validation check-emission-conformance.mjs:89

checkSchemaUrl(scope.schemaUrl, ...) is now called for each scopeSpans/scopeMetrics/scopeLogs block, closing the Codex P2 from fc1f8ca. A contract-authored scope-level declaration that disagrees with the pin now fails. ✅


Still open — Important (carry-forward)

OTEL_RESOURCE_ATTRIBUTES still clobbers existing values ci-otlp-artifact.md:81

export OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

The export keyword was correctly added, fixing the blocking silent-failure from the previous round. But the assignment is still destructive: any existing OTEL_RESOURCE_ATTRIBUTES value (service name, deployment environment attributes set by the platform) is silently dropped. The note at lines 72–74 mentions the platform env: map as an alternative, but that also replaces rather than appends. Adopters on the existing-stack path (eval 6) who already have the variable set are the most likely to hit this.

The safe form:

export OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

Not blocking on its own; carried over from previous rounds.

Fix this →


Still open — Minor

#5 — No conformance-failure eval case

evals.json evals 6–8 cover the happy paths and the paid-sink guard. There is still no eval verifying that the skill surfaces a conformance-check failure and declines to declare the emitting state reached. The conformance script is the contract's enforcement surface; a failure-path test guards against it silently regressing to exit-0.

#6plugin.json keywords missing telemetry/otel

plugin.json:11"keywords": ["autonomy", "adoption", "governance", "topology", "binding", "guided-setup"]. WP2 makes telemetry the primary new deliverable and it appears in the description text, but there's no discoverability signal in the keywords.


What's clean

The checkResourceBlocks return shape ({ hits, entries }) is a clean replacement for the old scalar return — the caller can distinguish "no OTLP entries at all" from "entries present but all unjoined," and the per-entry findings.push is authoritative. The url.href === value round-trip is elegant and covers a class of malformed inputs that explicit hostname checks miss. The scope-level schemaUrl loop placement (inside the signal loop, before the item loop) is correct and symmetric with the resource-level check.

Verdict: All blocking items are now resolved. The remaining OTEL_RESOURCE_ATTRIBUTES clobber is a real adoption footgun worth a follow-up fix, but with the export keyword in place, it no longer silently breaks the entire telemetry pipeline. This PR is mergeable.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f22220cbc7

ℹ️ 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".

Comment thread plugins/autonomy/skills/setup/templates/ci-otlp-artifact.md
Metrics and logs alone leave the session out of the trace tree; the
beta span export needs CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 plus
OTEL_TRACES_EXPORTER=otlp, as the setup slice already documents.

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 28s —— View job


PR Re-Review — feat(autonomy): WP2 telemetry contract + setup slice (6f91836)


Re-review summary

6f91836 ("fix(autonomy): enable beta session-trace export in the CI template") addresses the final Codex P1 raised against f22220c. The fix is correct and complete. All blocking issues across the review series are now resolved. One carried-over Important item and two Minor items remain open, none blocking.


Confirmed fixed

Codex P1 — Beta trace export enabled ci-otlp-artifact.md:80–83

# Session spans are beta: both flags below are required for the session to
# join the trace tree; without them only metrics/logs are emitted.
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_TRACES_EXPORTER=otlp

Both flags are present, exported, and accompanied by an inline comment explaining why both are required. The comment precisely describes the consequence of omitting them ("only metrics/logs are emitted"), giving adopters enough context to make an informed decision if their fleet policy restricts beta surfaces. The complete session env block now has all seven variables exported — every value the agent process needs to participate in the trace tree. ✅


Still open — Important (carry-forward from three rounds)

OTEL_RESOURCE_ATTRIBUTES clobbers existing CI platform values ci-otlp-artifact.md:85

export OTEL_RESOURCE_ATTRIBUTES=autonomy.work_item.url=<canonical-item-url>

The export is correct; the plain assignment is still destructive. Any adopter on the existing-stack path (eval 6) who already has OTEL_RESOURCE_ATTRIBUTES set (service name, deployment environment, host attributes from their CI platform) silently loses those values when following the template. The OTEL spec supports comma-separated key=value pairs; the safe form appends rather than replaces:

export OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}autonomy.work_item.url=<canonical-item-url>"

Fix this →


Still open — Minor

#5 — No conformance-failure eval case

evals.json covers happy paths (6–7) and the paid-sink guard (8). There is still no case verifying that the skill surfaces a conformance-check failure and declines to declare the emitting state reached. The script is the contract's sole enforcement surface; a failure-path eval guards against it silently regressing to exit-0.

#6plugin.json keywords missing telemetry/otel

plugin.json:11"keywords": ["autonomy", "adoption", "governance", "topology", "binding", "guided-setup"]. WP2 makes telemetry the plugin's primary new deliverable; there is no discoverability signal for it in the keywords.


What's clean

The inline comment on the beta flags is exactly right — it explains the consequence, not just the requirement, which is more useful when an adopter's fleet policy gates beta features. The session env block now comprehensively covers all seven variables a conforming headless session requires. All conformance-script fixes from prior rounds (per-entry hits, isNormalizedCanonicalUrl, scope-level schemaUrl check) are confirmed clean and unchanged at this HEAD.

Verdict: All blocking items across the full review series are resolved. This PR is mergeable. The OTEL_RESOURCE_ATTRIBUTES append form is a real adoption footgun worth a follow-up; #5 and #6 can live in a follow-up ticket.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f91836303

ℹ️ 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".

for (const scope of entry.scopeSpans ?? entry.scopeMetrics ?? entry.scopeLogs ?? []) {
checkSchemaUrl(scope.schemaUrl, `${where} scope`, tally);
for (const item of scope.spans ?? scope.metrics ?? scope.logRecords ?? []) {
checkAttributeList(item.attributes, `${where} ${item.name ?? "record"}`, hits);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require resource-scoped joins for agent-session exports

When an agent-session resourceSpans entry has no autonomy.work_item.url on its resource but one individual span has the attribute, this increments the shared hit count and the checker exits successfully. That accepts an export which violates the telemetry contract's resource-scope requirement for agent sessions, leaving other session spans (and any resource-level signals) without the work-item join while setup can still declare the emitting state conformant. Validate the attribute at the required scope for agent-session entries rather than allowing span attributes to satisfy the resource check.

Useful? React with 👍 / 👎.

@kyle-sexton
kyle-sexton merged commit 58c276c into main Jul 18, 2026
14 checks passed
@kyle-sexton
kyle-sexton deleted the feat/autonomy-telemetry branch July 18, 2026 21:18
kyle-sexton added a commit that referenced this pull request Jul 19, 2026
…ven demo (WP3) (#356)

## Summary

WP3 return-accounting package (T5), on the merged WP2 telemetry contract
(#343):

- **Convention doc** `plugins/autonomy/reference/return-accounting.md`:
three-layer data model (machine/deterministic, human-attested,
agent/LLM), the two Boris questions verbatim as the human fields
(counterfactual would-have-done-anyway + manual-effort band), six-band
effort scale `<1h / 1-4h / 4h-1d / 1d-1w / 1w-1mo / >1mo` (user-locked),
capture at the task boundary (work-item close / PR merge) as
tracker-resident marker records, autonomous-class scope only, agents
never estimate the return fields.
- **Guided-setup capture slice**: close-boundary templates +
marker-record shape, idempotent at-close capture, attestation upsert.
- **WP1 PLAN Phase 6 → [DONE]** (fleet binding doc merged in
melodic-software/standards#196).

## Live demo evidence (executed this effort)

Throwaway private repo `kyle-sexton/autonomy-demo-scratch` issue #1:
unattested marker record written at close; a re-fired close stayed
idempotent (count 1, no downgrade); attestation upsert recorded with
platform actor + source citation; DuckDB join against the WP2 committed
OTLP fixture matched the join key string-identically
(`autonomy.work_item.url`).

Near-duplicate audit: the capture slice composes the work-item tracker
seam and the WP2 telemetry contract — no second telemetry or tracker
surface created.

No linked issue

## Related

- #343 (WP2 telemetry — join-key contract this package consumes)
- melodic-software/standards#196 (WP1 Phase 6 fleet binding)
- #330 (WP2+WP3 plans)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 19, 2026
…ils, routines, runner design pack) (#359)

## Summary

Architect-round Plans for the four remaining ai-adoption-ladder work
packages, on the merged WP1-WP3 foundations (#330, #343, #356):

- **WP4 trigger-dispatch**
(`docs/topics/ai-ladder-wp4-trigger-dispatch/PLAN.md`): T1 contract —
four signal-surface classes
(`tracker-vcs-event`/`temporal`/`agent-internal`/`channel-feed`), six
adapter obligations, signal envelope (`signal.*` marker record incl.
`signal.work_class` stamped from the security surface), kick + drain
dispatch through the one work-item queue entrypoint,
`check-signal-envelope.mjs` enforcement surface, scratch-repo demo.
- **WP5 guardrails** (`ai-ladder-wp5-guardrails/PLAN.md`): T2+T3+#241 —
matrix hub + 4 leaves, two-surface binding split (security binding in
settings-as-code home, `executor_class`, admission decision table +
classification rules), `check-security-binding.mjs` with eval-mode
promotion-ceiling semantics, probe-before-bind isolation slice,
free-tier CI positive probe + fail-closed demo.
- **WP6 routines** (`ai-ladder-wp6-routines/PLAN.md`): T7 — catalog hub
(39 classes) + 10 v1 leaves (dead-code sweep excluded per D4
correction), contract-owned catalog-to-matrix mapping rules, setup slice
with detect-diff-reconcile of existing schedulers, novel-class
derivation probe.
- **WP7 runner design pack** (`ai-ladder-wp7-runner/PLAN.md`): T4
graduation — spine + 8 seam specs, lifecycle state model, two-family
stop criteria, terminal-handoff escalation with severity/ack knobs,
topology seam map. ZERO build artifacts; build stays trigger-gated;
birth decisions USER-RESERVED.

Merge order pinned: WP4 before WP5 (absent admission binding fail-closes
to human-gated — no ungoverned window), WP6 after both, WP7 last.

Review: WP4+WP5 stress-tested by a fresh-context reviewer (14 findings,
all folded — headline: work-class classification obligation +
`executor_class` on the security binding). WP6+WP7 stress-tested by a
second fresh-context pass (10 findings, FIX-THEN-SHIP both, all folded —
headline: routine→class mappings land on the WP5 security surface, never
the repo-local agent-writable section; D2 gains a hybrid DET+AGT branch,
a `not-a-routine` catalog flag, and provenance/structural axes reaching
C4/C5; WP7's escalation severity refinement adds the WP5
schema+validator edits it requires as genuinely additive optional keys,
C5→L3 cited from the matrix cell, vendor-hosted merge cap restated on
the cloud-backend path).

No linked issue

## Related

- #330 (contract set + WP1-WP3 plans)
- #343 (WP2 telemetry), #356 (WP3 return accounting)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 19, 2026
…elope check (WP4) (#372)

WP4 trigger-dispatch package (T1), per the merged PLAN (all four phases
complete on this branch).

No linked issue

## Related

- #359 (WP4–WP7 plans, incl. this package's PLAN as merged)
- #356 (WP3 return accounting — the marker-record precedent the envelope
serialization follows)
- #343 (WP2 telemetry — the trace-tree obligation this contract imports)

## What ships

- **`plugins/autonomy/reference/trigger-dispatch.md`** (new contract):
four signal-surface classes (`tracker-vcs-event` / `temporal` /
`agent-internal` / `channel-feed`), two recorded attributes (provenance,
transport incl. `push-lifecycle` expiry fail-close + poll-detector
backstop), six class-generic adapter obligations (normalize+enqueue
only; layered idempotent dedup with create-then-reconcile and a
live-duplicate drain guard; provenance + durable raw link; traceparent
propagation; admission enforcement at the seam with absent-binding
fail-close; closed-loop ack), security-surface work-class stamping with
verified `agent-internal` parent provenance and higher-of class
resolution, the schema-versioned signal envelope (marker `<!--
autonomy:signal:v1 -->`), kick + drain dispatch with the one-entrypoint
invariant and execution-surface attestation, executor surface classes
with security-owned `executor_class`.
- **Setup trigger/dispatch slice** (`skills/setup/SKILL.md`):
discovery-first per-class surface interview, DIY-floor kick + hourly
drain as reviewable changes, plan-gated surfaces advisory with cost,
execution-surface recording with the attestation caveat stated,
fail-closed absent-admission wiring, additive `triggers` binding section
(shared `surfaces` map shape that later scheduling sections reuse).
- **Templates**: `templates/trigger-adapters.md` (per-class adapter
shapes, six obligations inline), `templates/ack-reply.md` (one-line ack,
item URL first).
- **Enforcement**: `scripts/check-signal-envelope.mjs` — validates the
envelope marker record (required keys, enums, W3C traceparent,
agent-internal `signal.parent_item` as normalized canonical URL,
deterministic raw-link origin branching via the binding's surfaces
maps), exit contract 0/1/2 mirroring `check-emission-conformance.mjs`.
- Five new setup evals; plugin 0.4.0; README + catalog.

## Conforming-path demo (scratch repo, live)

Run on `kyle-sexton/autonomy-demo-scratch` (private throwaway) with the
work-items capability's tracker seam installed
(`tools/work-item-tracker/` copied per its documented install path) and
a repo-local binding carrying the `triggers` section. No admission
binding present → the fail-closed path is what the demo proves. No paid
service touched.

1. **Stub signal**: label `autonomy:trigger` applied to source issue
[#2](kyle-sexton/autonomy-demo-scratch#2);
labeled-event id `28172185501` fetched from the issue timeline
(surface-native unique event id → `signal.identity`).
2. **Dedup pre-search** for the identity: `[]` (no existing item).
3. **Enqueue via the seam** (`work-item-tracker.sh create-item`): queue
item [#3](kyle-sexton/autonomy-demo-scratch#3)
created carrying the full envelope — class/transport/provenance,
identity, raw link
(`https://github.com/ghapi/repos/kyle-sexton/autonomy-demo-scratch/issues/events/28172185501`),
traceparent `00-dab5c21d6389b65c950f6b13f14af7e3-de1b63c53960913b-01`
(the trigger-hop trace context observed on the enqueue record), NO
`signal.work_class` (no classification rules bound → unclassified),
label `needs-human`.
4. **Create-then-reconcile re-search**: exactly one item (#3) matches
the identity — no duplicate to close.
5. **Ack echo** on the source surface: [comment on
#2](kyle-sexton/autonomy-demo-scratch#2 (comment))
— `Queued as
kyle-sexton/autonomy-demo-scratch#3 (autonomy:
tracker-vcs-event signal)`.
6. **Envelope conformance**: `check-signal-envelope.mjs <item-3-body>
--binding .claude/autonomy/binding.json` → `Signal-envelope conformance
OK: 1 envelope(s) checked.` exit 0.
7. **Drain claims via the seam lease** (autonomous drain mode, the ONE
entrypoint): `work-item-tracker.sh claim
github:kyle-sexton/autonomy-demo-scratch#3` → lease acquired (`holder:
kyle-sexton`, lease comment `5014153239`), exit 0.
8. **Fail-closed refusal, audited live**: admission check before
execution found NO admission binding → autonomous execution REFUSED,
[refusal comment on
#3](kyle-sexton/autonomy-demo-scratch#3 (comment)),
claim released; final state `OPEN`, labels `["needs-human"]`, assignees
`[]`. This also exercises the unclassified → human-gated path (no
security binding → no `signal.work_class`).

No second dispatch path was exercised anywhere: the event kick and the
drain both funnel into the seam lease.

## Near-duplicate audit

The dispatch entrypoint composes the work-item queue capability's
EXISTING autonomous drain mode and race-safe seam lease — no second
claim or dispatch mechanism was created anywhere in this package. The
adapter templates normalize and enqueue only; the drain-side
live-duplicate guard and the create-then-reconcile rule reuse the
queue's own primitives.

## Gates

- `scripts/validate-plugins.sh` exit 0; `node
scripts/validate-plugin-contracts.mjs` exit 0 (vendor deny-list clean
over `reference/`); `node scripts/generate-catalog.mjs --check` in sync.
- `claude plugin validate plugins/autonomy --strict` pass; skill-quality
`check-skill.sh setup` PASS (2 pre-existing WARNs: line soft-target,
gotchas surface); `evals.json` valid against the bundled schema (ajv
draft2020).
- markdownlint 0 errors; typos clean; lychee (offline) 0 errors. Full
plugin test suite rides CI (local run exceeds the shell timeout;
prior-session finding).
- Checker exit contract verified with fixtures: no-args → 2 + usage;
valid tracker/temporal-local → 0; temporal without `--binding`, ci-cron
origin with `file:` link, unrecorded surface, agent-internal missing
`signal.parent_item` → 1 each.

## Boris check

The 2→3 cell ("break up your work into loops and routines; let Claude
kick off Claude") is instantiated as governed queue + kick; the step-3
trap is honored (audit trail before scale — envelope, ack, lease,
refusal are all tracker-resident audit records; no agent-count
machinery); the step-1 guardrail is untouched; the telemetry trace tree
is the kick-off audit trail (traceparent on the enqueue record).

Fresh-docs mandate: GitHub Actions events reference and the Claude Code
headless reference re-fetched this session before the SKILL.md edit
(cited in the Phase 2 commit).

<details>
<summary>PLAN.md (as merged, tags advanced)</summary>

See `docs/topics/ai-ladder-wp4-trigger-dispatch/PLAN.md` on this branch
— all four phase tags [DONE], decisions table and stress-test summary
carried in the file.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
…#377)

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

- #359 (WP4–WP7 plans, incl. this package's PLAN as merged)
- #372 (WP4 trigger-dispatch — the admission seam this package's policy
content binds; envelope/classification cross-links land here)
- #343 (WP2 telemetry — the promotion-evidence base and trace-tree
payloads)

## What ships

- **Guardrail hub** (`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).
- **Leaves** (`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; caps `autonomous_concurrency: 1`,
`items_per_run: 3`).
- **Security-binding schema**
(`skills/setup/schemas/guardrails-security-binding.schema.json`): draft
2020-12, `additionalProperties: false`, fail-closed semantics in-schema;
`executor_class`, `dispatch_posture`, per-surface `isolation_bindings`
(level → substrate + `probe_evidence` + `runtime_markers`),
merge/verification knobs, `promotion_state`, `escalation_routes`,
`admission` (classification + rules + caps).
- **Semantic validator**
(`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 under
`autonomous-enabled` vs declared verdict under `human-gated-only`,
non-promotable promotion entries rejected, admission weakening requires
`override_justification` (admission-only escape), promotion-gated knobs
require their ratified `promotion_state` entry, 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.
- **Setup guardrail slice** (SKILL.md): two-surface resolution (security
binding in settings-as-code home behind an agent-unwritable
org-policy-home bootstrap; repo-local non-security remaps), substrate
detection per surface, probe-before-bind live validation,
detect-diff-reconcile against existing org guardrail surfaces, security
review folded in (one slice, no near-duplicate capability), paid SKUs
advisory + cost surfaced, fail-closed no-L2. Plus
`templates/isolation-probe.md` and 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 (nested `docker run --network none`
container) and executed the probe recipe INSIDE it, with a
networked-outer-job control step. Transcript from the PR run:

From [run
29675620833](https://github.com/melodic-software/claude-code-plugins/actions/runs/29675620833):

```text
outer-egress: HTTP 200 (networked, as expected)
egress-denial: PASS - external fetch failed inside the boundary (exit 4)
credential-absence: PASS - every tested credential path absent inside the boundary (exit 3)
captured live transcript:
  "schema_version": "1",
  "probed_at": "2026-07-19T05:56:39Z",
  "assertions": {
    "egress_denied": { "host": "example.com", "exit_code": "4", "outcome": "denied" },
    "credentials_absent": { "path": "$HOME/.ssh,$HOME/.netrc,$HOME/.docker/config.json", "exit_code": "3", "outcome": "absent-or-denied" }
  },
  "outer_context_networked": true
Security binding OK: live-evidence/demo-binding.json
- surface "ci-pool-a": C1 eligible, C2 eligible, C3 eligible, C4 eligible, C5 blocked (requires L3, proven L2)
```

The final line is `check-security-binding.mjs` exit 0 against a binding
whose `probe_evidence` is 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.mjs` on the missing-L2
`autonomous-enabled` fixture (exit 1):

```text
- isolation_bindings: no bound surface reaches L2 under dispatch_posture autonomous-enabled — autonomous dispatch is blocked fail-closed; compliant paths: bind an L2-capable substrate (whole-process OS-sandbox wrap, default-deny-egress container) on an execution surface, or declare dispatch_posture human-gated-only
```

**Declared posture** — the same missing-L2 shape under
`human-gated-only` validates (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):

```text
- C2-auto-merge: bound promoted -> effective unpromoted — ceiling lowered by contrary evidence (reverted-merge at 2026-07-10T12:00:00Z) WITHOUT modifying the binding; demotion files an escalation item on route "queue:escalations/demotion" requesting the human-ratified binding update
```

## 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 over `reference/`), catalog `--check` in sync, markdownlint 0
errors, typos clean, lychee offline 0 errors, `claude plugin validate
--strict` pass, skill-quality `check-skill.sh setup` PASS (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.

<details>
<summary>PLAN.md (as merged, tags advanced)</summary>

# 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

| # | Decision |
|---|---|
| D1 | ONE guardrail contract, hub-and-leaves progressive disclosure:
matrix doc = index/spine (table + one-line class/column definitions,
glance layer resolves the common question); leaf docs load on demand —
e.g. isolation-ladder levels, per-class detail + promotion triggers,
security-review policy, admission policy (examples, not a fixed list;
split at architect). Plus the guardrail slice of guided-setup. Both in
the capability-distribution home. Fleet materializations = /work-items
backlog. |
| D2 | Org-binding seam: contract-owned schema, one logical binding, two
governance surfaces split by sensitivity. Security-sensitive axes
(isolation substrates per level, merge policy, per-layer blocking knobs,
promotion-trigger state, escalation routes, admission policy) bind in
the settings-as-code home — outside the blast radius of agents working
in the consuming repo (agent-writable guardrail binding = bypass
channel). Non-security mappings (class→label strings, cost-tier→model
names) may bind repo-locally per the work-item-tracker binding pattern.
Layered resolution: org-policy-home defaults → settings-as-code per-repo
binding → repo-local non-security remaps; contract defines resolution
order, materialization at architect. Fail-closed on absent/invalid
security-sensitive binding; documented defaults only for non-security
axes. |
| D3 | Promotion/demotion discipline: contract defines the trigger SHAPE
— an evidence predicate over queryable telemetry (verification outcomes
per the telemetry contract are the evidence base). Promotion =
human-ratified knob flip, recorded as a reviewable change on the
governance surface — never automatic. Demotion = automatic fail-closed
on contrary evidence (gate failure, human-reverted merge), re-earn from
there. Org binds threshold values; suggested defaults at architect.
Near-term promotable cells: C2 auto-merge, C3 AI-review
advisory→blocking (#241); C4/C5 merge never promotes. |
| D4 | Sandbox-ladder setup slice: detect available substrates per level
per machine surface → bind level→substrate in the security binding →
live-validate BEFORE recording (empirical probe, e.g. denied-egress
smoke test inside the boundary; a binding lands only after the sandbox
provably blocks) → fail-closed verify (no L2 substrate → autonomous
dispatch blocked for that surface, compliant paths named — no silent
degrade). |
| D5 | #241 three-part resolution folds into the matrix structure,
nothing ships separate: security-review policy contract (two layers,
per-layer blocking knob) = verification-column leaf; security-review
setup = part of the one guardrail slice of guided-setup (near-duplicate
capability ban); the adjustment/audit layer generalizes matrix-wide —
guided-setup always detect-diff-reconcile against existing org guardrail
surfaces (sandbox configs, branch protections, review workflows), never
greenfield-assume, never silently overwrite. |
| D6 | Escalation column sharpened from DIRECTIONAL: contract defines
escalation event classes (gate failure, verification divergence,
admission rejection, demotion event, structural-class plan approval,
untrusted-provenance always), routing obligation (org-bound routes in
the security binding), and payload (work-item ref + trace link, one
causal tree). Mechanism reuses the governed queue: escalation lands as a
human-gated work item + optional channel notification via the
trigger-contract acknowledgment symmetry — no second channel. Only
interactive escalation UX stays deferred; trigger: WP7 runner design
pack. |
| D7 | Admission-policy content (the trigger contract's admission seam
enforces it; this package owns it): decision-table shape —
signal-surface class × initiator provenance × work class → disposition
(autonomous-eligible / human-gated / audited rejection) — plus caps
(autonomous concurrency, per-run items). Shipped defaults: C1/C2
autonomous-eligible within caps regardless of provenance (blocking agent
provenance would sever the agent-kicks-off-agent loop), C3 per-item
human admission, C4/C5 human-gated. Provenance is recorded input to
gating, never trusted as isolation; caps bound total autonomous fan-out.
|
| D8 | Permission posture: attended ergonomics (auto-mode classifier
tuning, safe-command allowlist distribution) is NOT a matrix column —
unattended runs replace per-action prompts with the whole-process
boundary; the ladder leaf carries a one-line permission-posture note (L1
= attended ergonomics tier, L2+ = the boundary is the control).
Classifier tuning + allowlist distribution = backlog seeds with trigger,
not silent drops. |

### 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.
- Security-sensitive bindings never live where the agents they govern
can edit them.
- Fail-closed everywhere: unavailable substrate, absent/invalid security
binding, contrary
promotion evidence — block and name the compliant path, never degrade
silently.
- No new cost by default: free-path scanners and substrates default;
entitlement-gated tools
(e.g. paid code-scanning SKUs) are advisory + explicit opt-in with cost
surfaced.
- One queue, one escalation channel (the queue itself); no second claim,
dispatch, or
  escalation mechanism.
- Boris-alignment is the standing acceptance criterion (no
step-skipping, trust before scale).

### Acceptance criteria

- Matrix doc's glance layer alone answers "what governs class X" — leaf
docs only for depth
  (progressive-disclosure convention conformance).
- Binding schema names the two governance surfaces + layered resolution
order; security axes
  fail closed when unbound.
- Promotion is human-ratified + evidence-gated; demotion is automatic;
both leave a reviewable
  audit trail on the governance surface.
- Guided-setup slice detect-diff-reconciles existing guardrail surfaces,
live-validates
isolation before binding, and has zero paid dependencies on its default
path.
- Admission decision-table defaults match the matrix class defaults
verbatim; agent provenance
  is not blocked by default; caps are org-bindable.
- Boris check: step-3 guardrails cell covered (agent sandboxing =
ladder; automatic code +
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.
- Auto-mode classifier tuning + allowlist distribution explicitly
out-of-scope with named
  triggers, not dropped.

### Captured assumptions

- Verification-outcome telemetry (WP2 contract) is queryable at
promotion-evaluation time; until
wired, promotion evaluation is manual over the same evidence definition.
- Free-path deterministic scanners remain available on the default path;
paid code-scanning
SKUs stay entitlement-gated (re-verified: private-repo CodeQL requires a
paid license).
- The work-item-tracker binding precedent (contract-owned schema,
org-supplied values,
  stop-and-report on invalid binding) remains the fleet's binding idiom.

### Out-of-scope (deferred with triggers)

- Interactive escalation UX — trigger: WP7 runner design pack.
- Auto-mode classifier tuning + safe-command allowlist distribution —
trigger: 1→2 residue
  sweep / standards allowlist-distribution work (backlog seeds).
- Cost ENFORCEMENT (hard spend caps) — trigger: 3→4 transition work;
cost tiers here are
  policy vocabulary only.
- Fleet materializations (binding instances, workflow gates, scanner
wiring) — /work-items
  backlog post-graduation.

### Deferred questions

- Exact class/level/column/attribute tokens + binding schema fields —
`/architect` (with the
  plugin naming pass).
- Leaf-doc file split + glance-layer wording — `/architect`.
- Promotion-trigger default values (run counts, windows) + demotion
evidence set — `/architect`.
- Setup-slice probe mechanics per substrate class — `/architect`.
- Admission decision-table serialization format — `/architect`.

## Plan

Recommendation-locked this round under the user's standing
pre-authorization (same basis as
the WP2/WP3 round): hub doc `reference/guardrails.md` with leaf docs
under
`reference/guardrails/` (`isolation-ladder.md`, `work-classes.md`,
`security-review.md`,
`admission-policy.md` — the D1 example split, adopted); class tokens
`C1`–`C5` and level
tokens `L0`–`L3` carried verbatim from T3/T2; security binding = a
contract-owned JSON
document in the settings-as-code home (`schema_version` from `"1.0"`);
non-security remaps =
an ADDITIVE `guardrails` section of the WP1 repo-local binding;
admission decision table
serialized as JSON inside the security binding.

Prerequisite: the WP4 implementation PR merged
(`reference/trigger-dispatch.md` exists — the
admission leaf cites its seam; the interim is safe via WP4's fail-closed
absent-binding
clause).

### Phase 1: Guardrail hub + leaf docs [DONE]

| File | Action | What changes |
|---|---|---|
| `plugins/autonomy/reference/guardrails.md` | Create | The hub/spine
(D1): the five-class × five-column matrix table imported from T3
unchanged (C1 read-only / C2 mechanical / C3 scoped / C4 structural / C5
untrusted-provenance × min-isolation / verification / merge policy /
cost tier / escalation), one-line class and column definitions, and the
glance-layer rule — this table alone answers "what governs class X";
every deeper question routes to a named leaf. Permission-posture note
per D8 (one line: L1 = attended ergonomics tier; L2+ = the boundary is
the control). Escalation event classes per D6 (gate failure,
verification divergence, admission rejection, demotion, structural-class
plan approval, untrusted-provenance always) with the routing obligation
(org-bound routes in the security binding) and payload (work-item ref +
trace link); mechanism = the governed queue itself (human-gated item +
optional channel notification via the trigger contract's acknowledgment
symmetry — no second channel). Boris step-4 sentence cited as the
matrix's source framing. Zero vendor/fleet names. |
| `plugins/autonomy/reference/guardrails/isolation-ladder.md` | Create |
T2 ladder normative text: L0–L3 level definitions, attendance ×
input-provenance axes, the falsified trigger-source axis recorded as
rejected (with reason), L2 unattended floor, fail-closed rule where L2
unavailable (block + name compliant paths, no silent degrade),
permission-posture line per D8. Instances org-supplied; free-path
substrate CLASSES named as marked examples only. |
| `plugins/autonomy/reference/guardrails/work-classes.md` | Create |
Per-class detail: risk-property bundle per class (blast radius,
reversibility, provenance, verifiability) — stating explicitly that C1
"read-only" scopes REPO surfaces and permits governed-queue/tracker
writes (work-item filing, no repo mutation; the WP6 stress-test's F8
disambiguation) and that C5's min-isolation cell is the L3 floor WP7's
C5-dispatch gate cites, per-cell promotion triggers (D3 shape: evidence
predicate over queryable telemetry per the telemetry contract; promotion
= human-ratified knob flip recorded as a reviewable change on the
governance surface; demotion = automatic fail-closed on contrary
evidence, re-earn from there). Suggested default predicates
(org-bindable values): C2 auto-merge — ≥ 20 autonomous C2 completions
over ≥ 14 days with 100% deterministic-gate pass and 0 human-reverted
merges; C3 AI-review advisory→blocking — ≥ 30 advisory reviews with 0
human-confirmed missed-blocking findings. C4/C5 merge never promotes.
Demotion evidence set: any post-merge gate failure, any human-reverted
merge, any verification divergence — one event suffices. |
| `plugins/autonomy/reference/guardrails/security-review.md` | Create |
The #241 security-review policy contract as the verification-column
leaf: two layers (deterministic scanners + AI security review),
per-layer blocking knob (advisory/blocking per class, bound on the
governance surface), free-path scanners default, paid SKUs advisory +
explicit opt-in with cost surfaced. |
| `plugins/autonomy/reference/guardrails/admission-policy.md` | Create |
The D7 admission-policy content the WP4 seam enforces: decision-table
shape (signal-surface class × initiator provenance × work class →
disposition `autonomous-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: the `admission` object of the
security binding (Phase 2 schema). |

**Sanity Check:**

- Hub glance test: `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` ≥ 1
- `grep -ci 'fail-closed'
plugins/autonomy/reference/guardrails/isolation-ladder.md` ≥ 1
- Promotion defaults present: `grep -c '20 autonomous C2 completions'
plugins/autonomy/reference/guardrails/work-classes.md` ≥ 1 and `grep -c
'14 days' plugins/autonomy/reference/guardrails/work-classes.md` ≥ 1
- Vendor+fleet deny-list sweep exit 0; lychee lane passes

### Phase 2: Binding schema (two governance surfaces) [DONE]

| File | Action | What changes |
|---|---|---|
|
`plugins/autonomy/skills/setup/schemas/guardrails-security-binding.schema.json`
| Create | Contract-owned JSON Schema for the security binding document
that lives in the settings-as-code home (outside agent blast radius per
D2): `schema_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`, default `autonomous-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_bindings` keyed 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-rule `override_justification`, and caps
`autonomous_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.mjs` |
Create | Validates a security-binding document against the schema +
semantic rules the schema cannot express: merge-policy caps never exceed
the matrix (C4/C5 human always; `executor_class: vendor-hosted` caps
every class at human-gated); every surface bound under
`dispatch_posture: autonomous-enabled` must declare non-empty
`runtime_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; under
`dispatch_posture: autonomous-enabled` it is an ERROR when NO bound
surface reaches L2 (fail-closed, compliant paths named) — under
`human-gated-only` the binding validates and the verdict reports blocked
autonomous dispatch as the declared posture, not a defect; a
`promotion_state` entry for a non-promotable cell (C4/C5 merge) is
rejected; admission defaults not weaker than shipped defaults without
the rule's `override_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 raw `promotion_state` alone 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.md` | Modify | Document the
two-surface split + layered resolution order (org-policy-home defaults →
settings-as-code per-repo security binding → repo-local non-security
remaps in the WP1 binding's additive `guardrails` section: 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 that `org_policy_home` may 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.mjs` exit 0 on a fixture-valid document, exit
1 on each negative fixture: C4 merge_policy `auto`, `executor_class:
vendor-hosted` with any non-human-gated merge row, `promotion_state`
entry for a C4 cell, missing probe evidence, missing L2 under
`dispatch_posture: autonomous-enabled`, admission weakened without
`override_justification`; exit 0 with a blocked-dispatch verdict on the
missing-L2 + `human-gated-only` fixture (deliberate posture, not a
defect) (fixtures under `evals/fixtures/`)
- Evaluation mode: a promoted-C2 binding + contrary-evidence fixture
resolves the EFFECTIVE state demoted (exit output asserts the ceiling
was lowered without modifying the binding file)
- Schema declares `executor_class` and `override_justification` (`grep
-c 'executor_class' …schema.json` ≥ 1; `grep -c 'override_justification'
…schema.json` ≥ 1)
- Schema `additionalProperties: false` present (`grep -c
'"additionalProperties": false' …schema.json` ≥ 1)
- `claude plugin validate --strict` exit 0

### Phase 3: Guided-setup guardrail slice [DONE]

Extends the `setup` skill: detect → bind → live-validate → fail-closed
(D4), always
detect-diff-reconcile against existing org guardrail surfaces (D5 —
sandbox configs, branch
protections, review workflows), never greenfield-assume, never silently
overwrite.

| File | Action | What changes |
|---|---|---|
| `plugins/autonomy/skills/setup/SKILL.md` | Modify | Guardrail slice:
detect available substrates per level per machine surface; bind
level→substrate into the security binding; LIVE-VALIDATE before
recording — the empirical probe per substrate class: denied-egress smoke
test inside the boundary (a network fetch to a well-known external host
MUST fail) + host-credential-path read attempt (MUST be absent/denied);
a binding lands only after the probe transcript proves the boundary;
probe evidence ref recorded in the binding. Fail-closed verify: no L2
substrate on a surface → autonomous dispatch blocked for that surface,
compliant paths named. Security-review slice folded here per D5 (one
guardrail slice, no separate near-duplicate capability). Paid scanner
SKUs advisory + opt-in with cost surfaced. |
| `plugins/autonomy/skills/setup/templates/isolation-probe.md` | Create
| Probe recipe per substrate class (container / OS-sandbox wrap /
VM-microVM), parameterized, vendor names as marked examples only:
egress-denial probe command shape, credential-absence probe shape,
expected-failure assertions, transcript capture shape. |
| `plugins/autonomy/skills/setup/evals/evals.json` | Modify | Add
guardrail-slice cases: substrate detection interview, probe-before-bind
ordering, fail-closed no-L2 path, detect-diff-reconcile against a
pre-existing branch-protection surface, paid-SKU 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
- `grep -c 'probe'
plugins/autonomy/skills/setup/templates/isolation-probe.md` ≥ 3
- `grep -ci 'detect-diff-reconcile'
plugins/autonomy/skills/setup/SKILL.md` ≥ 1
- Fleet-name sweep exit 0

### Phase 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-enabled` binding with a MISSING L2 entry and
observing the
blocked-dispatch error; the deliberate `human-gated-only` posture
validates separately with
its 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 of
the 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: security
review folded into the one guardrail slice (D5); escalation composes the
governed queue — no
second channel created.

**Sanity Check:**

- Positive probe transcript (provisioned no-network container on the CI
runner: egress denied + credentials absent inside; outer job networked)
attached to the PR body
- Negative path: `check-security-binding.mjs` exit 0 on the demo
binding, exit 1 on the missing-L2 `autonomous-enabled` fixture, exit 0 +
declared blocked-dispatch verdict on the `human-gated-only` fixture
- Demotion evaluation output (effective state lowered, binding file
unchanged) in the demo record
- All gate scripts exit 0; catalog in-sync
- Near-duplicate audit statement present in the PR body

## Blast 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_class` added to the
security schema and
enforced by the check; F9 (MED): `override_justification` was required
by the check but
undeclared 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-promotable
cells 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.classification` schema
content (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).

| Phase | Surface | Basis |
|---|---|---|
| 1 | main-session | normative security-policy authoring, tightly
coupled T2/T3 imports |
| 2 | main-session | schema + semantic-validator judgment |
| 3 | main-session | setup-skill judgment, probe design |
| 4 | main-session | live probe + gate runs |

## Open questions

- Fleet materializations (live org security binding, workflow gates,
scanner wiring) —
/work-items backlog post-merge (Brief out-of-scope, triggers recorded).
- Interactive escalation UX — WP7 runner design pack (Brief trigger).
- D8 backlog seeds (auto-mode classifier tuning + safe-command allowlist
distribution) —
carried in the Brief with named triggers; filed via the work-items flow.

## Decisions made (gate-passed)

| Decision | What it changes in the plan | Basis (evidence) |
|---|---|---|
| Leaf split: 4 leaves under `reference/guardrails/` | Phase 1 file set
| Brief D1 names exactly these four as the example split; each maps to
one matrix column/axis |
| Hub-subdirectory layout (first `reference/` subdir) | Phase 1 paths |
Progressive-disclosure hub needs its leaves grouped; flat names would
couple hub prose to 4 sibling filenames |
| Promotion defaults: C2 ≥20 runs/≥14 days/100% gates/0 reverts; C3 ≥30
advisory/0 missed-blocking | work-classes leaf | D3 requires suggested
defaults at architect; values conservative per Boris trust-before-scale
(org-bindable) |
| Cap defaults `autonomous_concurrency: 1`, `items_per_run: 3` |
admission leaf + schema | D7 requires caps; floor values bound total
fan-out at the trust-earning stage |
| Security binding as JSON + JSON Schema + semantic validator | Phase 2
deliverables | WP1 binding precedent (schema-versioned JSON,
stop-and-report on invalid); schema alone cannot express matrix caps |
| `executor_class` + classification rules live on the security surface;
promotion is eval-time evidence-gated (ceiling semantics) | Phase 2
schema + check | Stress-test F1b/F10: gating inputs must sit outside
agent blast radius; an agent-unwritable binding cannot receive an
automated demotion write |
| Demo pivots to fail-closed path when no container substrate exists |
Phase 4 | D4 makes fail-closed the required behavior — the pivot
demonstrates the contract, not a skipped demo |

[FALLBACK — confirm or override] `check-security-binding.mjs` as 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

- The binding-check script above is [FALLBACK] — surface before
authoring if contested.
- Any change to the shipped admission defaults or matrix cells during
implementation → STOP
  (these are T3/D7 user-locked content, not implementation discretion).
- 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; live probe per Phase 4 with the documented fail-closed
pivot.

### Mechanical work

Commit per phase on the implementation branch (suggest
`feat/autonomy-guardrails`); gates
re-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.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
## 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>
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
…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>
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
…ecord (#699)

## Summary

Executes the WP2-deferred audit (#351) empirically and records the
decision:

- **Live probes on Claude Code 2.1.215** (headless sessions against this
machine's `otelcol-contrib` collector): native emission declares no
`schemaUrl` on any signal; `OTEL_RESOURCE_ATTRIBUTES` injection lands
`autonomy.work_item.url` at RESOURCE scope on native session metrics
(Pillar 2 satisfied natively, no hook code); an inbound `TRACEPARENT`
env value is ignored — fresh root trace (Pillar 3 not satisfiable
natively).
- **Decision — hybrid, native-first**: session signals ride native
emission with the join attribute env-injected by the dispatching
surface; the contract-authored wrapper span keeps owning causal joining;
native session signals join the chain query-side via the Pillar-2
attribute. No hook-emission migration is pending because the conforming
session path was already native.
- Version-pinned load-bearing facts + three revisit triggers recorded;
the ladder hub links the record under a post-delivery decision-records
section.

Acceptance criteria from #351: field-by-field comparison verified
against live session capture ✔; decision recorded in
`docs/topics/ai-adoption-ladder/` with revisit triggers ✔.

## Related

- WP2 delivery PR #343 (contract under audit)
- #352 (fleet dogfood wiring — will use the env-injection mechanism this
audit proved)

Closes #351

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant