Skip to content

release(0.2.13): attest contract, interview readiness, and the devops/flaky policy rules - #52

Merged
lidge-jun merged 19 commits into
mainfrom
dev
Aug 25, 2026
Merged

release(0.2.13): attest contract, interview readiness, and the devops/flaky policy rules#52
lidge-jun merged 19 commits into
mainfrom
dev

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Promotes the 0.2.13 train from dev. Head: cd342bf0, where CI and Packed install lifecycle are both green.

What lands

The attest gate rejected what its own documentation taught. coerceAttest requires from/to before any other check, and the "Required attest keys" table agents copy named neither — nor planUnit, workPhaseId, or testReceiptPath. A goalplan-bound P>A cost three separate refusals, one turn each. attest JSON missing valid from/to appears 50+ times across four repos since 2026-08-13.

The table now names every key per edge with copy-paste objects, and the refusal names the real edge: to is the verb, from is the session's phase, and only that edge's extra keys are listed. An illegal edge gets its legal routes and no example, because every example would be rejected.

Interview readiness was unreachable. isInterviewReady demanded level max on all four dimensions and no shipped writer could produce it — deriveLevel tops out at high and --dim <d>=max is rejected. Every interview either dead-ended or spent an attested override, which made the override's ledger row meaningless.

Accepting high outright would have been worse: four --known flags reach all-high in one command, measured before the fix was written. So the gate asks the append-only Q&A ledger where a level came from. A dimension counts when a question was asked, answered, and attributed with --map.

DEVOPS-* freeze-gate rules and an elimination-first TEST-FLAKE-* policy with a single canonical owner, both harvested from opencodex's v2.32.1 train.

Verification

  • npm test 1995 pass / 0 fail (was 1961 at the branch point)
  • gate.mjs, inventory.mjs --check, check-versions.mjs 0.2.13 all OK
  • CI and Packed install lifecycle green at cd342bf0

Reviewed

Five independent audit rounds across the train. Three found things self-review did not: a false claim about our own parser, a backtick corrupted while fixing a neighbouring bug, three fabricated citations in the devops rules, a severity class that made a receipt waivable, and — on the release itself — badge drift that would have failed cxc release verify closed at 1961 vs 1995.

Note for tagging

Exact-head receipts are looked up on GITHUB_SHA. The tag must land on a SHA where CI and Packed install lifecycle have both concluded success, so this merge commit needs its own green runs before v0.2.13 is pushed.

wp0 docs-only roadmap unit. The gate requires from/to before any other check (attest.ts:91-96); the skill table agents copy (pabcd/SKILL.md:91-98) names neither, nor planUnit, workPhaseId, or testReceiptPath. 50+ historical failures across four repos.

Audited by an independent grok-4.6 lane: GO-WITH-FIXES, 4 blockers folded. Blocker 1 disproved this plan's own claim that the parser cannot name the edge - argv[0] is the verb and runOrchestrateCli already reads state on that path.
The refusal 'attest JSON missing valid from/to' has 50+ occurrences across
opencodex, ima2-gen, cli-jaw and codexclaw since 2026-08-13, and the cause was
not the gate. coerceAttest rejects an attest without from/to before any other
check runs, while the table agents copy (pabcd/SKILL.md) listed neither those
keys nor planUnit, workPhaseId, or testReceiptPath - so a bound P>A cascaded
through three separate refusals, each costing a turn.

Docs: the attest table now names every key each edge requires, with four
copy-paste objects, under a citable ATTEST-SHAPE-01. The interview override
examples and the doctrine's invalid-JSON snippet carry from/to. loopArmDirective
and cxc orchestrate --help gained the P>A object, a B>C example, and
testReceiptPath; PA_ATTEST_EXAMPLE is one constant so the win32 and posix
branches cannot drift.

Runtime: the null-coerce refusal now names the real edge. An audit disproved the
plan's claim that the parser cannot know it - the verb IS 'to', resolved before
the attest loop, and the error path already reads session state for 'from'. It
forward-declares only the keys for that edge, not a menu, and a malformed-JSON
error keeps its own diagnosis with no misleading example.

Also fixes the same defect family found while inventorying: buildGoalIdleBlock
emitted 'evidence' where the schema says 'did' (silently, since IDLE>P is
ungated); review-round, plan, metric and divergence rejected --help; and
cxc freeze --help fell through to the real run, writing freeze.json and exiting
0 with nothing to signal the mutation. The freeze test asserts the filesystem,
not the wording, because a help text that still writes would pass a text check.

npm test 1980 pass / 0 fail (was 1961). The new assertions were verified to FAIL
against the pre-fix behavior - the first draft asserted the message 'contains
from and to', which the original bare string already did.
…ghbours

An independent review of 49d90e6 found the first pass had shipped a bug of the
same family it was fixing.

1. The hint printed the current phase as 'from' even when the requested edge was
   illegal, so an agent at P asking for D was handed {"from":"P","to":"D"} - an
   object that clears the coerce gate and is then refused for adjacency. Two
   wrong refusals instead of one. An illegal edge now names its legal routes and
   deliberately shows no example, because every example would be rejected.

2. Renaming buildGoalIdleBlock's 'evidence' key to 'did' had replaced the
   closing backtick with a backslash, so both platform branches emitted an
   unterminated span and a trailing backslash. The test asserts balanced
   backticks and no trailing backslash, not the wording.

3. STOP_NEXT_COMMAND - the block agents copy verbatim - still omitted planUnit
   on P>A, workPhaseId on every gated edge, and testReceiptPath on C>D. Bound
   keys are shown with a marker rather than omitted: a key you must delete is
   cheaper than a key you never knew existed.

4. interview/SKILL.md:147 still taught {"override":true}. It is valid JSON, so
   it fails as missing from/to - the confusing case, at the exact moment a stuck
   agent reaches for the escape hatch.

Also: the bound-session note no longer claims testReceiptPath on A/B/C, two weak
assertions the reviewer flagged as passing pre-fix were made positive, and the
Stop, goal-idle, and arming surfaces gained the coverage they never had.

npm test 1985 pass / 0 fail.
…st drift

Top-level help listed neither receipt, review-round, nor scan while telling
agents to run '<cmd> --help', so the producer of a bound C>D's testReceiptPath
was undiscoverable from the entry point. It now lists all three and states the
attest shape in the agent notes.

orchestrate-grammar's parser carried the old bare string. The chat surface
discards attestError today, so this is not yet user-visible - but two parsers
disagreeing is how the next reader concludes one of them is authoritative.

loop/SKILL.md points at the pabcd table instead of paraphrasing it, per the
canonical-owner rule the repo already applies to DEV-STACK-*.

The drift test is the part that outlives this unit: it parses the attest table's
KEYS cell out of pabcd/SKILL.md and asserts each gated row names what that edge's
gate requires. Its first version read the whole row and passed against injected
drift, because the Notes column happened to mention planUnit - so the capture was
narrowed to the contract cell and re-verified by deleting planUnit from the P>A
row and watching it fail.

npm test 1987 pass / 0 fail.
DEVOPS-RELEASE-PROOF-01 governs the proof bundle for an artifact already
published. It says nothing about the decision to publish, which is where the
v2.32.1 hotfix train actually got into trouble: a freeze audit rejected the first
GO report on three counts, and the operator-visibility train that followed added
two more lessons of the same shape.

New SKILL.md 2.8 (GO/NO-GO decisions, STRICT): FREEZE-SHA (pin the report to the
code SHA its gates describe), GATE-WEAKEN (a red gate is never excused inside the
report it failed), REVIEW-THREADS (unresolved threads on merged PRs block GO,
counted after merge), GATE-OWNER (a gate with no implementing phase is a wish).

ci-cd-deploy.md 6 (evidence mechanics): SUITE-PARTITION (replay CI's real
partition; a one-process run is not the gate), BASELINE-DEFECT (a red test is a
candidate defect until baseline + blast radius + CI job all agree),
VERIFY-INSTRUMENT (do not change the instrument while certifying with it),
EXACT-HEAD (re-read the head; a remembered pass is not evidence),
FLAKE-STABILITY (N greens at one head; declare N).

sre-foundations.md 7 (runtime evidence): STALE-PROCESS (prove a live process is
the build under test - a canary once measured the reporter's own pre-fix proxy),
OBS-SIGNAL (add the missing signal; never flip a true status bit to compensate).

Each rule is defined in exactly one file, cited to the devlog that produced it,
and FLAKE-STABILITY points at dev-testing for remediation rather than restating
it - that policy is wp3's.

npm test 1987 pass / 0 fail (docs-only; the assertion is that nothing moved).
The repo believed the right thing in seven places and the wrong thing in three.
dev-testing/SKILL.md:77 said a flake is a bug and green-on-retry is unacceptable;
:220 said never blind-retry; :218 then made the protocol 'detect -> quarantine if
blocking'. An agent could not follow both. ci-pipeline.md repeated the quarantine
protocol as its own section 5 heading, so the router and the deep reference both
claimed to own it - with different strength, the router's being weaker (it had no
removal deadline).

ci-pipeline.md section 5 is now the canonical TEST-FLAKE-* policy, following the
DEV-STACK-* pattern the repo already uses:

- ELIMINATE-01 (STRICT): a flake is closed when the cause is named, not when the
  suite is green. The signal table now names causes to remove, not first-aid.
- RERUN-01 (STRICT): re-running to green is not a fix and is never recorded as
  one; raising a timeout to pass is the same violation. A re-run may measure the
  failure RATE, and the measurement gets written down.
- QUARANTINE-01 (DEFAULT): permitted only when the flake blocks unrelated
  delivery, and only with test name, owner, removal deadline, and suspected cause
  recorded together. The exception is deliberate - a policy with no usable hatch
  relocates the pressure into an untracked .skip(), which is worse.
- ATTRIBUTION-01 (DEFAULT): 'environmental' needs the same triple DEVOPS-BASELINE-
  DEFECT-01 requires. This is the rule the repo did not have: every existing line
  said do not HIDE a flake, none said how to PROVE it is not your defect.

dev-testing/SKILL.md 5.4 and dev-debugging Scenario D are now pointer stubs,
skill-ownership.md gains the row whose absence let the policy drift into two
files, and C10 in the contradiction register no longer offers 'an explicit
timeout' as a candidate fix - RERUN-01 forbids exactly that.

No test was touched. This repo's suite is 1987/0 green and the pabcd-state slice
runs identically twice; there is no flake here to eliminate, and the policy is
written for the repos this skill governs.
An independent review checked every historical claim in 3d5d0d3 against the
opencodex devlogs. Three did not survive, and they were mine, not the sources':

- STALE-PROCESS said a train 'ran a 100-call canary' whose numbers described the
  bug. The devlogs record the opposite: the canary was a GO criterion that got
  REJECTED as the wrong instrument once PID 922 was identified as the reporter's
  pre-fix proxy, and a deterministic mixed-sequence regression replaced it. The
  measurement never happened - which is the better version of the lesson, because
  the identity check came first.
- FLAKE-STABILITY said 'run 1 was green, run 2 failed, run 3 produced the
  finding'. The recorded table is green, green, fail. And 'OpenCodex used three'
  greens was wrong twice over: three was the bar they SET, and they never
  collected it, which is why the PR was deferred.
- SUITE-PARTITION said three files, each in its own job. It is three path
  patterns (seven files) covered by two jobs - storage policy runs six together,
  api usage runs one. That loose wording came from the source report; copying it
  into a STRICT rule made it load-bearing.

Also: the router pointed at ci-cd-deploy.md 9 and sre-foundations.md 6, neither
of which exists - the rules live at 6 and 7, so an agent following the pointer
would have found Anti-Patterns and nothing else. Per-rule line citations added,
the cli-jaw start-time-vs-dist-mtime precedent cross-referenced, /health corrected
to /healthz, and the claim that partitioning 'produced a green result' narrowed:
one api-usage failure remained and was waived on the baseline triple.

Four AI-tell sentences removed ('Every rule here was paid for', 'reads as
pedantic until it bites', and two more).

npm test 1987 pass / 0 fail.
Three claims in the first dev-devops draft were fabricated detail around real lessons. The rules survived; the evidence did not. Recorded in 020 so the next reader sees the correction, not just the corrected text.
An independent review found no blocker that reopened C1-C6, but two real holes
in how the new rules interact with the repo's own severity classes.

QUARANTINE-01 was DEFAULT end to end, which meant the four required fields -
the entire receipt - were waivable with a stated reason. The permission to defer
stays DEFAULT; the four fields are now STRICT. And "blocks unrelated delivery"
was unfalsifiable, since any red CI can be called blocking; it now requires
naming which delivery and why it is independent of the code under test.

ATTRIBUTION-01 (DEFAULT) mirrors DEVOPS-BASELINE-DEFECT-01 (STRICT) on the same
triple, so an agent who loaded only dev-testing could state "no CI access", waive
the DEFAULT rule, and skip the freeze-SHA check the STRICT one requires. The text
now says a recorded gap is not a waiver, and that the STRICT rule governs when
both apply.

Also: ELIMINATE-01 is marked explicitly as a CLOSURE rule so a four-field
quarantine is not read as violating it; the .skip() red flag now names the one
quarantine form that is not a red flag; TEST-ANTI-FLAKE-01 got the pointer 030
promised; the dropped "passes locally, fails in CI" row is back as a cause;
skill-ownership points at ci-cd-deploy 6.2/6.5 rather than dev-devops 6, which
never mentions the family; and the router stub stopped restating all four
payloads - restating canonical content is how the original contradiction drifted.

Two test comments taught what the new policy forbids. mcp.test.ts justified 30s
as absorbing jitter; it is now named as a hang detector, with the unfixed
contention tracked as C10 and the honest fixes listed. hook-e2e.test.mjs said
"skip rather than flake"; it now says the skip is a missing-fixture precondition,
not flake avoidance. Neither behavior changed - the reasoning did, because a
comment that teaches a forbidden move is guidance whether or not it is in a skill.

npm test 1987 pass / 0 fail.
Zero blockers, but two severity-class holes: a DEFAULT rule wrapping a receipt that should be STRICT, and the same triple at two severities letting an agent reach the weaker class by choosing which skill to load. Also recorded that scoping test files OUT was wrong in one direction - two comments taught what the policy forbids.
3ae3847 used 'git add devlog' and swept in two untracked directories belonging to the user's own in-progress work: 260722_repo-governance-config and 260814_fix-main-ci-windows-worktree. The files are unchanged on disk; this only restores their untracked status. Staging by directory instead of by path is how it happened.
Four work-phases, twelve commits, three independent audits. 1961/0 to 1987/0. Records what each audit caught that self-review did not: a false claim about our own parser, a backtick I corrupted while fixing a neighbouring bug, three fabricated citations, and a severity class that made a receipt waivable.
The closeout quoted wp1's numbers. Re-run at wp4, neutering the hint fails 5 of 13 rather than 3, because coverage added in later phases depends on the same function. A number carried forward from an earlier phase is a remembered pass.
438ddd3 fixed this once and 260e2b4 reintroduced it, because I reached for 'git add devlog' a second time. The files were never modified on disk; only their untracked status was. Recorded rather than quietly repaired: a mistake that recurs after being fixed is a habit, and the fix is to stage explicit paths, never a directory that also contains someone else's work.
…rting

isInterviewReady demanded level "max" on all four dimensions and no shipped
writer could produce it: deriveLevel tops out at "high" and --dim <d>=max is
rejected by the parser. So every HITL interview either dead-ended or spent an
attested override - and once the hatch is the only door, its ledger row stops
distinguishing anything. It recorded "bypassed the gate" for the thorough
interview and the skipped one alike.

The naive fix is worse than the bug. Before writing it down I ran the writer:

  cxc scan record --session s --known goal=x --known constraint=x \
                  --known success=x --known ontology=x
  -> all four dimensions "high", scanRounds 1, in one command

--known is free text an agent types, so "accept high" would trade a gate nobody
can pass for a gate nobody can fail.

What distinguishes a real interview is not the level - it is where the level came
from. --derive reads the Q&A ledger written from real request_user_input traffic,
skips unmapped questions rather than guessing, and pins a dimension at "mid" while
any asked question is unanswered.

So provenance is the gate. isInterviewReady stays pure and becomes the SHAPE half
(high or max, contradictions empty, assumptions recorded, scanRounds >= 1).
evaluateInterviewGate composes it with evidence from dimensionsBackedByAnswers,
which re-reads the append-only ledger and returns dimensions holding an asked +
answered + mapped question. scan_completed events now carry their --map so a
later reader can resolve the attribution.

A tracker field would have been simpler and does not work: reconstructScore and
normalizeInterview whitelist four keys, so a derived flag is stripped on write -
measured, not assumed. Widening the fail-closed reconstruct would also make the
flag hand-editable, which is the objection the design exists to answer.

Not tamper-proof: whoever can write session state can append to the ledger. This
closes the accidental path and leaves forgery a deliberate act, which is the right
bar for a soft-gate whose honest bypass is one attested command away.

The human free-pass path keeps the shape-only check - it has no cwd, and a human
is the authority the provenance check approximates.

npm test 1995 pass / 0 fail (was 1987). The load-bearing test is the trivial-path
one: without it this change is indistinguishable from lowering the bar.
… and policy work

Twelve version surfaces plus the inventory. check-versions.mjs 0.2.13 OK, inventory --check OK, gate.mjs OK, npm test 1995/0.
…old rule

A release-readiness audit caught both of these before the tag, which is the point
of running one: 'cxc release verify' compares the published badge count against
the suite it measures, and would have failed closed with

  published tests=1961 but the measured suite reported 1995

The badge is the number the README promises, so drift there is a release claiming
a suite it does not have. inventory --write --tests 1995 updates inventory.json
and all three READMEs together.

Second: the shipped interview skill still documented isInterviewReady as "all
dimensions at max" - the rule 964669d replaced. An agent reading it would keep
spending overrides for interviews that now pass honestly. It now describes both
halves, shape and provenance, and says plainly that --known alone never opens
I->P.

Also from the audit: the CHANGELOG now says what upgrading does to an in-flight
session. Not breaking - an interview that passes today still passes - but a
session already at all-high becomes shape-ready, so flags.interview, freeze, and
the human free-pass will treat it as ready. That unsticks stranded interviews
rather than closing open ones, and a reader deserves to know which.

Stale comments in interview.ts and scan-cli.test.ts corrected the same way.

gate OK, inventory --check OK, check-versions 0.2.13 OK, npm test 1995/0.

@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: cd342bf0b7

ℹ️ 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 on lines +193 to +195
if (o.event === "answer_recorded" && typeof o.questionId === "string") {
if (Array.isArray(o.answers) && o.answers.some((a) => typeof a === "string" && a.trim().length > 0)) {
answered.add(o.questionId);

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 Pair answer provenance with the originating turn

When a questionId is reused across turns, these sets treat an answer from an earlier turn as the answer to a later question with the same ID. This reuse is explicitly supported in rescan-coordinator.ts, which matches (turnId, questionId) because the second question may still be pending. After an answered question is followed by an unanswered clarification with the same ID, scan --derive retains high, this function reports the dimension as backed, and the I→P gate returns ready despite the pending clarification. Key both asked/answered evidence by turn and question rather than questionId alone.

Useful? React with 👍 / 👎.

Comment thread bin/codexclaw.mjs
" plan init <slug> [--phases N] scaffold the devlog/_plan unit the P>A gate verifies",
" receipt test -- <command> produce the test receipt a bound C>D requires",
" review-round open|show|abort the opt-in A-gate plan-audit round",
" scan record|show record interview coverage and contradiction scans",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the nonexistent scan show verb from help

The new top-level help advertises cxc scan show, but the repository-wide command search finds no show handler and parseScanCliArgs accepts only record plus help aliases. Users following this entry therefore receive unknown scan action 'show'; either implement the read-only verb or list only the supported record command.

Useful? React with 👍 / 👎.

@lidge-jun
lidge-jun merged commit 35d7aba into main Aug 25, 2026
21 checks passed
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