Task 0: deterministic listing probes for both deployable harnesses - #56
Merged
Conversation
Adds scripts/profiles/{probe_codex,probe_claude}.py, six captured fixtures, and
27 hermetic tests. Suite 313 -> 340. Read-only: both probes re-run on demand, so
staleness is checked rather than assumed.
Four findings change later tasks and are recorded in full in the plan's Task 0
"Executed" block, with the rest:
- Only skill lines are charged against the Codex budget, so every figure this
program has produced overstated by ~2 points. Corrected in spec and plan.
- The alias roots table is a rounded difference of two whole bodies, not a sum
of lines (24 tokens against 65) -- Task 3 must port it, not approximate it.
- Codex has a third degradation tier, whole-skill omission, which does emit a
prompt warning where truncation does not. Task 3 needs three shapes.
- Codex reports resolved paths, so project scope must be compared resolved or
a symlinked root returns a confident zero. Task 4.
Debug fixtures are trimmed to the six skill-resolution lines the parser reads.
The raw --debug-file output is 252 lines and also carries permission allow-rule
lists from both settings scopes, MCP server names, and hook configuration --
machine configuration with no business in a fixture, and hard to remove once it
is in history.
Parity with render.rs is exact: on a truncating capture, entry cost equals
limit minus table cost to the token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught this; local runs could not. `classify` built its plugin and `.system` needles from `Path.home()`, which matched the machine that produced the fixtures and nothing else. On any other host the needles never matched, so plugin detection returned zero and Codex's own bundled skills were filed as dojo-managed -- a confident wrong answer with no error, which is the exact failure mode these probes exist to prevent. The home is now inferred from the listing's own locators; every Codex listing carries its `.system` skills, so one is always present. An undiscoverable home raises rather than classifying, because otherwise every origin falls through silently. Two tests pin it: classification with a deliberately wrong home finds no plugin entries while the inferred one does, and an empty listing refuses. The earlier hermeticity check varied cwd and PATH but not HOME, which is where the coupling was. Verified now under all three: 342 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9cf1539ca9
ℹ️ 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".
All four review findings verified against the artifact and fixed. Three were real breakage; the fourth was a label asserting more than the evidence. P1 -- the Claude parser dropped six entries. `[^:]+?` for the name could not match a bare namespaced entry like `- workflows:brainstorm`, so 75 listed became 69 parsed and every plugin skill vanished. This is the same defect the Codex parser has a dedicated test for, made a second time in the same module. The separator is a colon followed by a space; a bare colon belongs to the name. `parse_request` now raises when parsed count differs from listing-line count, so a drop fails loudly instead of returning a short list that looks whole. P1 -- neither live probe classified. `probe()` and `request()` returned parsed output directly, so every CLI invocation and every downstream caller saw `origin="unknown"` for all entries, which reads as "no plugin entries, no foreign entries". The tests passed because they called `classify` themselves. Both live paths now classify, and a stubbed-subprocess test pins it. P1 -- Claude entries had no scope and no origin resolution. Added, joining listed names against the canonical catalog and the two roots. The listing decides membership; the filesystem only labels entries already listed. P2 -- utilization mixed units. With no resolvable context window, render.rs falls back to an 8,000-character budget while the cost stayed in tokens, displaying 51.6% for a listing whose honest character utilization is 205%. Utilization is now computed in the budget's own unit and refuses an unknown one, and an indeterminate model prints an explicit unsupported warning. Also: the assertion that let the dropped entries through was `len(names) > 10` on a 75-entry listing. Replaced with conservation against the block itself. And the Claude fallback origin is now `unresolved` rather than `harness-bundled` -- Claude's listing carries no locators, so that label asserted an identification the evidence does not support. Reconciling the bucket against probe A's source counts is Task 4's. Suite 342 -> 348. Verified under foreign HOME, stripped PATH, and foreign cwd. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This repository is public and the probe fixtures are verbatim harness output, so they carried absolute paths and one capture from an unrelated project. Removed the viral-rooted Codex capture. It was declared as a pytest fixture, used by no test, and published six skill names with full descriptions belonging to a private project. Captures belong here only when rooted in this repository or in a synthetic directory. Pseudonymised the home directory to /Users/example-dev across the remaining fixtures. The replacement is deliberately the same byte length as the original: the vendor-parity test asserts that a truncating listing fills its budget exactly, so any length change breaks that equality -- a shorter placeholder was tried first and failed the test immediately, which is the assertion working. Scrubbed absolute clone and scratchpad paths from the plan, including a session UUID. Two guard tests so a future capture cannot reintroduce either problem: fixtures must carry no real machine identity, and no fixture may be a capture of another project. Audited what remains and it is all vendor-shipped or already public: dojo's own skills, Codex's bundled entries, Claude Code's bundled skills, and two public marketplace plugins. The entries that looked personal turned out to ship inside the Claude Code binary, which is why the classifier files them as unresolved. Suite 348 -> 350. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
davisbuilds
added a commit
that referenced
this pull request
Aug 3, 2026
#57) Docs only — no code changes. Follows Task 0 (#56). ## The change SC-03 required the fits-proof against **every pair whose limit could be established**, which silently promoted Claude Code at 200k to a gate. The operator runs only 1M sessions. Demand does not move with the window — only the budget does — so the existing measurements rescore with no re-probing needed: | Session | Demand | vs 40,000 (1M) | vs 8,000 (200k) | |---|---|---|---| | ordinary | 16,535 | **41%** | 2.07× | | `viral` | 20,220 | **51%** | 2.53× | | `dojo` | 23,287 | **58%** | 2.91× | Corroborated by the 1M capture already in the fixtures, which emits no budget warning at all. Deployability now turns on whether a pair is **declared in use**. Established-but-undeclared pairs are measured and reported, never gating — so a session that does land at 200k is told it is non-conformant rather than discovering bare skill names on its own. Declaring a pair is a maintainer edit, so the gate cannot widen because someone selected a different model. ## What this does to the argument **Nothing the operator runs is in breach except `viral` at 95% on Codex.** That is the third consecutive revision where better information *reduced* urgency: revision 7 found the measurements 1.78× overstated, revision 10 found two of three Codex breaches were ordinary defects, this one finds the remaining harness conformant in practice. The spec says so plainly rather than preserving a threat that no longer exists. What survives is now the whole case, and it is governance rather than firefighting: - The Claude Code margin is **one model selection wide** — 58% or 291%, same catalog, same day, no announcement either way - The figures have moved 2× in a day, twice, with no distribution decision taken - Cross-machine agreement broke silently within 24 hours of being verified - The curated 31-skill set is installed state, not a reviewable declaration Calibration recorded for later: at 200k, bundled entries are **exempt from stripping** and take 3,774 of the 8,000 budget at full length, leaving ~4,226 against a mean 313 chars/entry — about **13 dojo skills**, which is `core` plus roughly one overlay, matching what SC-03's fits-proof already specifies. ## Plan changes from Task 0 - **Task 3's detector: three shapes → five.** Task 0's read of `render.rs` found Codex's third tier (whole-skill omission, which *does* warn in-prompt) and that its two truncations are different mechanisms — the 1,024-char pre-cap appends `"..."`, budget clipping appends nothing. - **Task 3 must import `probe_codex`'s primitives**, not write second implementations. Two are not what a reasonable reading of the source suggests, and Task 0 got both wrong first. - **Task 3's fit proof: two declared pairs** plus a reported-not-gated case for 200k. Policy files carry explicit `deployable: true|false`. - **Task 10 rescoped.** It existed to adjudicate the 56%/85% vs 50%/75% conflict — already resolved by two retractions, so adjudicating now would re-litigate withdrawn measurements. It emits the first machine-generated position instead. ## Verification - `validate_spec.py` PASS, `validate_plan.py` PASS - `pytest tests/ -q` → 350 passed - No success criterion beyond SC-03, no evaluation scenario, and no authority boundary changed 🤖 Generated with [Claude Code](https://claude.com/claude-code)
davisbuilds
added a commit
that referenced
this pull request
Aug 3, 2026
) Follows Task 0 (#56) and spec revision 11 (#57). Suite **350 → 391**. ## What landed Eight profile definitions (one file each, so a duplicate `name` is a detectable condition rather than a silently last-wins key), `harness-equivalences.yaml`, `scripts/profiles/definitions.py`, and `profiles/README.md`. Overlays are deliberately **3–4 members**, not categories. `core` (8) plus any one overlay lands at 11–12, inside the ~13-skill figure a 200k Claude Code window allows. An overlay of nine would not be deployable at all. | Overlay | Members (⚓ = SC-02 anchor) | |---|---| | engineering | ⚓`create-cli` ⚓`secure-code` `api-design` `repo-hardening` | | research | ⚓`deep-research` ⚓`research-architect` `fetchmd` | | design | ⚓`design-critique` ⚓`web-design-guidelines` `frontend-design` | | knowledge | ⚓`obsidian-markdown` ⚓`session-retro` `compound-docs` | | shipping | ⚓`gh-commit-push-pr` ⚓`vercel-deploy` `vercel-preview-logs` | | skill-authoring | ⚓`skill-creator` ⚓`skill-standardizer` `skill-evals` | Each non-anchor member is a *sibling of an anchor along the axis the anchor sets* — `api-design` is `create-cli` at the service boundary, `repo-hardening` is `secure-code` at repo scope, `frontend-design` is the only producer among two auditors, `vercel-preview-logs` is the only skill that reads back what a deployment did. **21 of 48 catalog skills belong to no overlay**, which is correct rather than a gap. Every member carries an inline reason; every file records what was deliberately excluded. ## The mutation probe found three holes All 27 validation rules were probed by breaking each and confirming a test fails. Three did not: - **`members: core`** — plausible YAML, neither the sentinel nor a list, and it would have been iterated character by character into four one-letter "members". - An unrecognised key was silently accepted. - **`equivalence_identity` without its `sorted()` passed everything**, because every test reached identity through the loader, which sorts first. Order-independence was being provided by the wrong function and the guarantee was untested. All three closed. That third one is the same shape as Task 0's `sent := loaded` hole — a property tested only through a caller that already satisfies it. Also built in, and worth stating because it is invisible otherwise: **the non-`core` member count is checked before the anchors.** Every anchor is itself a non-`core` skill, so an overlay holding its anchors can never fail the count — with the checks reversed, that rule could not fire on any input. ## Review changes **Withdrew the `gpt-imagen` ↔ Codex `imagegen` declaration.** It was the only entry resting on a capability judgement across *differing* names, and the judgement is unverified: dojo's pins `gpt-image-2` and exposes inpainting, masking, and batch output, none of which the bundled description claims. The asymmetry decides it — a wrong declaration silently removes a selected skill, an undeclared collision is *reported* — and it buys nothing today since `gpt-imagen` is in no overlay. Kept as a candidate with the comparison it needs. Two declarations remain, both same-name same-capability: `skill-creator`, `skill-installer`. ## Three things Task 1 falsified in its own inputs 1. **`review-agent` is not a Codex-listed entry.** It exists at `~/.codex/skills/.system/review-agent/` and appears in neither the Task 0 capture nor a live re-probe. Spec and plan both named it as bundled. Only listed entries can displace a member — this is the filesystem-is-not-the-listing rule catching this contract's own prose. 2. **The plan's equivalence seed contradicted its own rule.** It named six Codex skills to seed from, while the next sentence forbids declaring a pair whose dojo member does not exist — and three of the six have no dojo counterpart. Rewritten so the rule governs, with "observed in a listing" made an explicit second condition. 3. **"8 rejection cases" was stale** (27 implemented), and `__init__.py` was listed as `Create` though it shipped in Task 0. ## Verification - `pytest tests/ -q` → **391 passed** - CI-shaped run (`cd /tmp`, `HOME=/tmp/fake-home`, `PATH=/usr/bin:/bin`) → identical - `validate_spec.py` PASS, `validate_plan.py` PASS - Every rejection test asserts the error *message* names the offending profile and member; no test compares against a hardcoded catalog total — `full` resolves against `len(skills.json)` computed at test time 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First task of the distribution-profiles plan: the read-only probes every later task trusts. Task 0 is the plan's stop gate — nothing downstream is worth anything if the listing measurement is subtly wrong.
Adds
scripts/profiles/{probe_codex,probe_claude}.py, six fixtures, and 27 hermetic tests. Suite 313 → 340. No mutation: both probes re-run on demand, so staleness is checked rather than assumed.It works
Four findings that change later tasks
render.rssumsline_costover entries; intro prose and section headers are not counted. Every figure this program has produced charged the whole block and overstated by ~2 points. Corrected in spec and plan: ordinary session 76% (not 78%),viral95% (not 97%).aliased_metadata_overhead_cost, not approximate it.Five more are in the plan's Task 0 "Executed" block, including that the plan's stated reason for reading
messageswas wrong: dojo's## Available Skillsdecoy lives inmessagestoo, so the section doesn't discriminate — only the literal opening sentence does. Right conclusion, wrong reason; the fixture retains both to pin it.Vendor parity is exact, not approximate
On a capture that truncated, the port's entry cost equals
limit − table_costto the token (5,416 = 5,416), becauserender_lines_with_description_budgetspends the description budget down to the last one. An off-by-one anywhere in the byte counting, the newline, or the ceiling division breaks it. Asserted as a test.The mutation probe caught one that review would not have
Five deliberate breakages introduced against the detectors. Four failed immediately. One passed all 23 tests: setting
sent := loaded— the filesystem-versus-context error in a new costume — because the over-budget warning line also carries a skill count and was silently overwritingsent. Sosentwas only ever proven on over-budget captures, which is most of them.Fixed by keeping the two counts separate and cross-checking them, and by capturing a 1M-window session that emits no warning at all, which isolates the
sentpath. That fixture doubles as a direct demonstration of SC-03: identical catalog, same machine, same day — over budget on 200k, silent on 1M.Two portability defects caught before CI
Both would have passed locally and failed every CI run — the
docs/archivefalse-pass shape from R17 again:tmp_pathwith a deliberately dangling symlink, since the target path is what classification compares against and it belongs to the machine that produced the capture.fingerprint()crashed when the harness binary was absent. Now an unknown version, which is a legitimate stale state.Fixture hygiene
The
--debug-filecaptures were committed raw at 252 lines, of which the parser reads 4. The remainder carried permission allow-rule lists from both settings scopes, MCP server names, and hook configuration — machine configuration with no business in a fixture, and hard to remove once in history. Trimmed to the six skill-resolution lines with a header noting what was cut (37 KB → 1.3 KB). The other four fixtures were audited and are clean; theAPI_KEYhits in the Codex captures are env-var names inside skill descriptions, never values.A correction to the spec
Earlier in the day I wrote that no live foreign entry remained and the SC-05 fixture would have to be constructed — reasoning from
microsoft-foundrybeing disabled to a general absence, without running the probe. The probe reportsspreadsheet, listed in every session. Both the error and the correction are recorded in the spec and plan, since inferring an absence one command away from the instrument is this contract's own subject matter.Verification
pytest tests/ -q→ 340 passedvalidate_spec.pyPASS,validate_plan.pyPASS,validate_skill_contract.py --strictPASSPATHstripped of both harness binaries🤖 Generated with Claude Code