Skip to content

Staff Cut B across three sessions and prepare Cut C for the pre-1.0 corpus (#456) - #489

Merged
pengfei-threemoonslab merged 20 commits into
mainfrom
claude/staff-cut-b-session-split-5e332f
Sep 2, 2026
Merged

Staff Cut B across three sessions and prepare Cut C for the pre-1.0 corpus (#456)#489
pengfei-threemoonslab merged 20 commits into
mainfrom
claude/staff-cut-b-session-split-5e332f

Conversation

@pengfei-threemoonslab

Copy link
Copy Markdown
Contributor

Summary

Cut B of #456, staffed as three parallel sessions coordinating through the strata inventory CSV, plus the Cut C preparation that was to run alongside it. The inventory goes from 26 of 59 slots sourced to 59 of 60: 32 sourced qualifying-origin slots (floor 23), 27 synthetic (ceiling 33), every cell still holding a holdout-eligible slot. The one open slot is langchain_crewai.insufficient_evidence.1, whose lead now records what the re-mine found.

  • Contract first (strata-inventory.md, test_strata_inventory.py): constructions live at benchmark/safety-qualification/constructed/<case>/{CASE.md,base,head} and never under samples/; a constructed_design basis with a guard (the trees differ, CASE.md sits outside both, no engine output inside, no name shared with a sample, a by-name engine_tests detector); one pin form per origin, including closed-unmerged (fork point → refs/pull/N/head) and reverted PRs; and the claim protocol. Perturbation-swept: eight violations fail closed, the two valid shapes pass.
  • Session A — 15 constructions plus 3 reserves, one per synthetic gap, across all seven profiles. Each is a plausible PR in the framework's own idiom with the design record beside the trees.
  • Session B — the first post-Epic: account for every narrowing decision — exclusion ledger, conservation invariant, and skip-requires-evidence #403 sweep, 2026-W36-cutb.*: 912 rows over 21 repositories, including the four n8n repositories this project had never mined and eight unwalked MCP servers. The miner gains --state closed|reverted and --pr N enumeration for the rejected vein. 17 of 18 real-history / rejected-or-reverted slots claimed, six of them rejected_or_reverted from five repositories. Real history enters as sweep-then-miner-label so the slots stay holdout-eligible; the reason is written down in the register.
  • Session C — the five calibration cases (calibration.md, four reserve PRs plus one construction), LABELING.md restructured with a rater-facing four-way rubric on top and the miner process below, the rater packet builder and both session harnesses (benchmark/safety-qualification/rater/), and 39 tests.

Type

  • Check or risk-model change
  • Input adapter change
  • CLI or GitHub Action behavior
  • Report, schema, or SARIF output
  • Benchmark corpus, miner tooling, and documentation only — nothing under src/ changes

Verification

CI is authoritative for python -m ruff check ., python -m compileall -q src tests, and python -m pytest.

Additional local checks run:

  • pytest -n auto -m "not perf" --ignore=tests/test_adapter_static_only.py on the merged branch: exit 0; tests/test_adapter_static_only.py: exit 0; ruff check .: clean.
  • Banned-word sweep over every constructed base/ and head/ tree: zero hits; no engine output inside any tree.
  • Guard perturbation sweep on the new constructed-case rules (identical trees, CASE.md inside a tree, report inside a tree, wrong evidence_ref, underived split, sample_design on a construction, construction named in tests/, name shared with a sample): all fail closed.

Things a reviewer should know

  • LABELING.md is the file Amendment 1 names as the rater input, and this PR changes it. The old stripe/ai#232 worked anchor stated a corpus case's label and verifier verdicts, which condition 2 forbids a rater to see; it is replaced by constructed illustrations, and tests/test_labeling_guide_is_rater_safe.py keeps every inventory candidate and every SHIP- check ID out of the guide. The drafted review_required vs insufficient_evidence rule is a first draft for the calibration round to test.
  • The rater harnesses are only partly verified. The Claude runner's smoke test failed closed on an expired OAuth token (claude login needed); the OpenAI runner is written against codex exec flags from memory because the local codex npm install has an empty vendor binary directory. Packets hand raters the head tree plus the diff as "the pinned repository state"; say if the base tree should ship too.
  • google/adk-python#6605 is engine_tests exposure (it was reduced into a trigger fixture before this cut read it), so google_adk × insufficient_evidence gained a third slot to keep a holdout-eligible case. The four calibration PRs are struck from the reserve: chosen by someone who has read the inventory, they can never be corpus cases.
  • The W36 run is off the *-mined glob on purpose. Its selected-PR mix (closed, reverted, named) would drop the cross-run trigger-skip rate below the noise-bound test; the README run note explains. Its labels are one session's cell-targeting labels, not adjudicated, and the README says so.
  • Engine defects found and deliberately not fixed, because a fix made in response to a candidate is what turns it into tuning material. They belong in issues: a single scan passes a published-but-undeclared write (only verify --base catches it); n8n human_review_nodes are collected and never consumed, so disabling the only approval node is mergeable; SHIP-MCP-PERMISSION-EXPANDED names the sibling whose binding hash moved rather than the added tool; n8n non-GET nodes are forced to external_communication so a declared write lands in insufficient_evidence; every manifest touch is SHIP-VERIFY-TRUST-ROOT-TOUCHED; n8n node names with spaces alone force review_required; init writes CHANGE_ME and scan exits 2 on every TypeScript MCP server; Go servers on mcp-go / go-sdk read 0 tools; SDK example repositories now init_skip where the W24 sweep evaluated them.

Release-readiness notes

  • No user-code import added to default scan paths
  • No network access added to default scan paths (the miner's new enumeration is maintainer tooling under benchmark/, already network-touching by design)
  • New or changed check IDs are documented in docs/checks.md — none changed
  • Report/schema changes are additive or documented in STABILITY.md — none

🤖 Generated with Claude Code

pengfei-threemoonslab and others added 19 commits September 1, 2026 23:40
…pins, and claims (#456)

Cut B is staffed as three parallel sessions coordinating through the
inventory CSV. Before they start, the contract they claim slots against
has to exist: where a corpus-built synthetic lives (never samples/),
what makes it a case (base/ and head/ that differ, CASE.md beside them,
no engine output inside), how real history enters (a sweep, then a miner
label, so the slot stays holdout-eligible), and the pin form for
closed-unmerged and reverted PRs, which the merge-commit convention
cannot express.

The guard gains a constructed_design basis, a by-name engine_tests
detector for constructions, and reads n8n as the top-level section it
is rather than a tool-source type it is not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ner (#456)

The safety-qualification corpus needs a rejected_or_reverted vein that
merged history cannot supply. `mine --state closed` enumerates PRs closed
without merging, pinned at the fork point and the PR head;
`--state reverted` finds merged PRs a later Revert PR undid, pinned like
any merged PR with the revert recorded in notes; `--pr N` mines one named
PR in whichever state it is in. Merged enumeration is untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bulary (#456)

Restructure benchmark/miner/LABELING.md so the top of the file is a
self-contained rater rubric for passed / review_required /
insufficient_evidence / blocked, with a first-draft rule for the
review_required vs insufficient_evidence line, constructed illustrations
that name no corpus case, a mapping to the miner's three labels, and the
JSON output contract. The miner process moves below a 'not a rater input'
heading and the real-PR anchor that stated a corpus case's label and
verifier verdicts is removed.

A guard test keeps the guide free of inventory candidate refs (both
spellings), slot samples, and SHIP- check IDs, and requires the four
decisions as headings above the miner section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or Cut C (#456)

build_packet.py turns an external (clone + base/head SHAs) or constructed
(base/ + head/ trees) case into the three inputs Amendment 1 condition 2
allows: repo/ (the head tree, no .git, verifier output and CASE.md
excluded), diff.patch, a byte copy of LABELING.md, plus a role-specific
TASK.md and a MANIFEST.json hashing every file. It refuses a source tree
that carries the strata inventory.

run_rater.py runs one fresh read-only session per family (claude -p with
Read/Grep/Glob only and no MCP; codex exec --sandbox read-only), archives
the complete transcript content-addressed, and parses the final message
into an IndependentHumanLabelV1 with reviewer_id <family>:<model>:<session>.
Anything but exactly one valid JSON object with a known decision fails
closed. --home-mode isolated runs --bare in an empty HOME (needs
ANTHROPIC_API_KEY); shared keeps the caller's HOME under file-level
memory checks. --dry-run prints the command and environment.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Name the five non-corpus cases for the Amendment 1 calibration round:
four merged reserve PRs pinned as merge commit + first parent from a
clone (mongodb-js/mongodb-mcp-server#1417, awslabs/mcp#4489,
stripe/ai#353, openai/openai-agents-python#3518) and one constructed
blocked-shaped LangChain case under calibration/cal-5/, since no reserve
PR has that shape. Each entry records why the diff tests the guide and
where two raters are expected to diverge; no label or verdict is
recorded. The file discloses that the chooser had read the inventory,
which is why none of these can ever become a corpus case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…esses (#456)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a PR (#456)

aaif-goose/goose has a merged revert whose quoted title carries a
truncated `(#65…` reference; `gh pr view 65` fails and the whole
reverted enumeration aborted. One bad reference now skips that revert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r Cut B (#456)

mcp_export_adds_undeclared_tool: a tool server with a complete root declaration publishes a fourth tool the declaration omits (the #432 shape), built outside samples/ so the slot stays holdout-eligible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Cut B (#456)

sdk_agent_adds_ticket_update_tool adds a scoped Zendesk write with no approval policy; sdk_agent_loads_tools_from_registry replaces the literal tools list with a runtime registry so the bound surface is not enumerable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or Cut B (#456)

crewai_tools_from_factory builds the crew's tools from a profile factory; langchain_agent_adds_refund_tool adds a Stripe refund tool with neither an approval policy nor idempotency evidence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…456)

adk_agent_adds_calendar_toolset adds a domain-scoped calendar write through a resolved McpToolset (the adk-samples#1975 shape); adk_billing_sub_agent_refund puts a Stripe refund on a sub-agent (the adk-samples#1745 shape) with no approval policy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ut B (#456)

Five slot cases on one support-orders workflow family: a POST tool with no approval, a Code Tool whose request target is built at run time, a Call Workflow Tool with an expression target, a DELETE tool behind a public webhook, and a disabled send-and-wait approval node. Two reserves: a read-only GET tool and an allowlisted MCP Client Tool write.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d reserve for Cut B (#456)

A triage root with an Agents SDK handoff to a billing sub-agent: a write added on the sub-agent, handoffs resolved from an environment route list, and an approvals sub-agent that decides the refund requests the root submits. One reserve adds a scoped read on the sub-agent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ntory (#456)

Fifteen synthetic gap slots move to pinned with target_basis constructed_design, exposure none and their CASE.md as evidence; the register gains one row per construction and three reserve rows; the summary tables and the shape prose are recomputed from the CSV.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n for Cut B (#456)

2026-W36-cutb: 912 rows over 21 repositories, the first post-#403 run.
Every n8n repository this project had never mined, eight unwalked MCP
servers, and closed-unmerged / reverted / named PRs from the new miner
states. Labeled from the PR diffs as one session's Cut B cell-targeting
labels, not adjudicated. Named off the *-mined glob because it is a
selected, capability-dense sample (trigger-skip 0.71), not the unselected
history the cross-run noise-bound guard measures; the README says why.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eighteen slots pinned from 2026-W36-cutb: seventeen of the eighteen gaps
Cut B session B owned, plus a third google_adk x insufficient_evidence
slot because adk-python#6605 turned out to be engine_tests exposure.
Every rejected_or_reverted slot is filled from five repositories; n8n has
its first two real-history candidates. The guard gains a `reverted` state
(landed history whose rejection came afterwards) mapping to
rejected_or_reverted only. langchain_crewai.insufficient_evidence.1 stays
a gap with the re-mine's findings as its lead. Summary tables recomputed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Conflicts in the inventory CSV and register resolved by union: each
session's rows taken from the session that claimed them, the summary
tables recomputed from the merged CSV.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…read the shape after Cut B (#456)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@pengfei-threemoonslab pengfei-threemoonslab left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changes requested. I found three failures in the new blind-rater boundary. The targeted miner/rater/inventory tests and Ruff checks pass locally, and the full Agents Shipgate verifier routes the protected-surface changes to human review, but these cases are not covered by the current tests.

  1. [P1] Verify the packet manifest before launching a rater (benchmark/safety-qualification/rater/run_rater.py:421-452). _check_packet() only checks that five paths exist and parses MANIFEST.json; it never calls build_packet.verify_manifest() or otherwise re-hashes the packet. Any edit to repo/, diff.patch, LABELING.md, or TASK.md after packet construction is therefore accepted, while the emitted label records the hash of the stale manifest. This breaks the claimed content binding and can turn a contaminated packet into admissible evidence. Please verify the manifest immediately before constructing the invocation and fail closed on any mismatch.

  2. [P1] Do not reuse the caller's real CODEX_HOME in isolated mode (benchmark/safety-qualification/rater/run_rater.py:252-258). The default OpenAI path sets HOME to a temporary directory but then points CODEX_HOME back at the caller's real profile. Codex reads global AGENTS.md/AGENTS.override.md from CODEX_HOME, and user-level config.toml can configure MCP servers, plugins, and web search. Official references: https://developers.openai.com/codex/guides/agents-md and https://developers.openai.com/codex/config-reference. Consequently this session is not blind and is not limited to the packet, even though the mode is named isolated. Use a fresh Codex home containing only the minimum authentication material (or require OPENAI_API_KEY), explicitly disable inherited MCP/plugins/web search, and use an ephemeral session.

  3. [P1] Reject or contain symlinks in packet source trees (benchmark/safety-qualification/rater/build_packet.py:151-156, :374-382). copy_tree_excluding() preserves symlinks verbatim, while hash_packet_files() deliberately skips them. A repository symlink can therefore point outside the packet, be followed by the rater's read tools, and expose unmanifested host content. This contradicts the "entire world"/exact-input guarantee. Please either reject symlinks, or resolve and copy only targets proven to remain inside the source tree and include their bytes in the manifest.

Once these boundaries are closed, the rest of the reviewed miner/inventory work looks well-covered by the added tests.

Review findings on #489, all in the Cut C rater harness.

1. The packet is re-hashed at launch. _check_packet() proved five paths
   existed and parsed the manifest; it never compared the packet against
   it. Any edit landing after the build -- a note under repo/, a rewritten
   guide, a doctored diff -- reached the rater while the label recorded the
   hash of a manifest describing a packet that no longer existed. prepare()
   now calls verify_manifest() and refuses before a session is launched.

2. Isolated mode gives codex its own home. HOME was replaced and then
   CODEX_HOME pointed back at the caller's real profile, which is where
   codex reads global AGENTS.md, AGENTS.override.md, and a config.toml
   that can mount MCP servers and enable web search -- so the mode named
   isolated was neither blind nor packet-limited. It now builds a fresh
   Codex home per run holding one written config (no MCP servers, web
   search off, read-only sandbox, no approvals, no history) and
   authenticates through OPENAI_API_KEY, symmetric with the Claude path;
   shared mode keeps the real profile only after proving it is silent.

3. Symlinks cannot carry unmanifested content. The copy preserved links
   verbatim and the hasher skipped them, so a link out of the tree was
   content the rater's read tools resolve and the manifest cannot
   describe -- and the packet still verified. A link that escapes the tree
   or dangles now refuses the build; one that stays inside is materialised
   so its bytes are hashed; a link found in a packet is tamper, not a skip.

Each fix is covered by tests that fail without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pengfei-threemoonslab

Copy link
Copy Markdown
Contributor Author

Thanks — all three are real, and each is fixed in f7873b2 with a test that fails without the fix. I checked that by reverting each fix in turn and confirming the new tests go red.

1. Verify the manifest before launching a rater. prepare() now calls build_packet.verify_manifest() and refuses before any session starts, so an edit landing between build and launch stops the run instead of producing a label bound to a stale manifest. Covered for an edited repo/ file, TASK.md, LABELING.md and diff.patch, and for a file added under repo/ (which changes no existing hash). The tests also assert the subprocess was never invoked and no label was written, and one test proves an untouched packet still runs.

2. Do not reuse the caller's real CODEX_HOME in isolated mode. You are right that this made the mode's name false. Isolated mode now builds a fresh Codex home per run containing one written config.toml — no mcp_servers, tools.web_search = false, sandbox_mode = "read-only", approval_policy = "never", history.persistence = "none" — and authenticates through OPENAI_API_KEY, refusing up front when it is unset. That is symmetric with the Claude path, which already required ANTHROPIC_API_KEY under --bare. The home is a per-run temporary directory, so the session is ephemeral by construction as well as by config. Shared mode still borrows the real profile, but only after proving it is silent: it refuses on AGENTS.md, AGENTS.override.md, or a config.toml that declares mcp_servers, enables web_search, or sets experimental_instructions_file.

One caveat I want to keep visible rather than bury: the OpenAI path is still unverified end to end, because the local codex install has an empty vendor binary directory. The config keys come from the reference you linked, and the module docstring now says they must be checked with --dry-run plus one live run before any OpenAI-family label counts.

3. Reject or contain symlinks in packet source trees. Both halves are closed. A link that escapes the source tree or dangles refuses the build outright, with nothing written. A link that resolves inside the tree is materialised — copied as a regular file — so its bytes are in the manifest, and no link survives into a packet. hash_packet_files() no longer skips links; finding one is now an error, which is what makes a planted link tamper rather than an invisible gap. One further case the fix covers: a link pointing at an excluded name inside the tree is dropped rather than resurrecting that content under a different filename.

Full suite and ruff check . are green on the branch.

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