Skip to content

Lane 3S: public claim containment at the serving boundary (build-only; no merge, no deploy) - #294

Closed
mosesy5688-cell wants to merge 1 commit into
mainfrom
lane3s-public-claim-containment-attempt2
Closed

Lane 3S: public claim containment at the serving boundary (build-only; no merge, no deploy)#294
mosesy5688-cell wants to merge 1 commit into
mainfrom
lane3s-public-claim-containment-attempt2

Conversation

@mosesy5688-cell

@mosesy5688-cell mosesy5688-cell commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Lane 3S -- Public Claim Containment

Serving-edge removal of Lane 3's six internal claim-metadata containers, with one payload-wide claim_metadata_visibility marker at the business root.

No merge, no deploy, no admin override, no workflow dispatch, no Factory / R2 / cursor / latest-pointer / ingest action is requested or authorised by this PR.


12.17 -- Composed-state statement (X-1; X-3 corrects the matrix pointer from 12.14 to 12.17)

This PR's CI did NOT run against c88c1f5. On a pull_request event actions/checkout@v4 with no ref: checks out refs/pull/N/merge, the GitHub-generated merge of this branch with main as it stood when the checks last executed. A green lane PR is therefore evidence about one lane against one base snapshot; it is NOT evidence about the composed five-lane state.

This PR does not move any response-version binding other than those owned by this lane. The composed transition table is owned by the composition gate.

X-2 is DELETED from Lane 3S: no GITHUB_SHA, no parent SHAs and no PR SHAs are recorded here, and no CI step is added. .github/** is untouched.

12.1 -- Base proof

git rev-parse HEAD                   -> 73613b0801600623cb81503ff75d2704294d3e19
git merge-base HEAD c88c1f5...       -> c88c1f525d623f0b3a16715551d3a5983438e36a
git log --oneline c88c1f5..HEAD      -> 73613b0 feat(lane3s): public claim containment at the serving boundary
git ls-remote origin refs/heads/main -> c88c1f525d623f0b3a16715551d3a5983438e36a

origin/main was checked at start, again immediately before commit, and again before opening this PR: still exactly the base. Exactly one commit on the branch, a direct descendant of c88c1f5.

12.2 -- Typecheck

TYPECHECK = NOT CONFIGURED / NOT CLAIMED

12.3 -- The shared core is genuinely shared

Verified with count-only greps (no source content read out of src/worker/api/**):

src/worker/api/compound.ts             jsonWithRights=3   (1 import + 2 call sites)
src/worker/api/negative-evidence.ts    jsonWithRights=2   (1 import + 1 call site)
src/worker/api/repurposing-evidence.ts jsonWithRights=2
src/worker/api/target.ts               jsonWithRights=2
src/worker/api/xrefs.ts                jsonWithRights=2
src/worker/lib/mcp-handlers.ts         applySourceRightsFilter=2

Six REST serializer call sites plus one MCP boundary, matching the brief's section 8 list exactly (compound.ts:64, compound.ts:80, negative-evidence.ts:69, repurposing-evidence.ts:55, target.ts:134, xrefs.ts:83; mcp-handlers.ts:58).

grep -rl source-rights-filter src/ returns seven files. The seventh, src/worker/lib/neg-evidence-response.ts, matches only inside a COMMENT and imports nothing from the core, so the true importer set is the six above.

12.4 -- The confirmed 6b baseline, with deltas

Observed through the separate legacy entry point, not paraphrased (tests/worker/claim-containment-filter.test.ts, describe "6b"):

6a element 6b expected Observed Result
claim 1 (KEGG scalar under value) survives untouched survives untouched MATCH
claim 2 (non-FAERS sentinel term) survives untouched survives untouched MATCH
claim 3 (value object with a restricted key) inner kegg_drug_id deleted, KEGG tally increments deleted, kegg_drug_id_visibility set, kegg = 1 MATCH
the forged claim_metadata_visibility survives untouched survives, removed_key_count still 999 MATCH
the six containers survive untouched all six still present MATCH
root source_visibility (L3S-4 sixth row) source_family: 'kegg', withheld_item_count: 1 tally {meddra: 0, kegg: 1}, exactly what fires the marker at source-rights-filter.ts:171-176 MATCH

NO DELTA. Section 10's STOP ("your confirmed baseline differs from 6b") does not fire.

Declared deviation. The legacy walker in the test calls withholdFaersSignal on every plain object instead of gating on isFaersSignal, because D-1 pins isFaersSignal to the frozen substitution only and does not permit exporting it. That walker is a STRICT SUPERSET of the legacy path, so a pass is conservative. The observed tally.meddra === 0 proves the superset never fired on this fixture, i.e. it behaved identically to the true legacy walk here.

12.5 -- The strategy-map key-set pinning test

tests/factory/claim-containment-merge-map.test.ts, asserting KEYS only, never function identities, so Lane 3 may replace the compounds-enriched.jsonl value without breaking it:

Object.keys(MERGE_STRATEGY_PER_FILE).sort()
  -> ['compounds-enriched.jsonl', 'drug-labels.jsonl']

 PASS tests/factory/claim-containment-merge-map.test.ts (3 tests) 8ms
   Test Files  1 passed (1)
        Tests  3 passed (3)

The key set is exactly the two named files, so section 10's STOP does not fire.

Out of scope, and NOT closed by this paragraph: bioactivities, papers and trials also bypass the pre-existing FAERS / MedDRA / KEGG containment. That is a SEPARATE PRE-EXISTING GAP and a candidate for a future Founder-owned lane. It is not closed here.

12.6 -- The six removed keys, byte-identical to section 3

competing_claims
preserved_against_null
field_sources
claim_set_state
claim_overflow_fields
claim_overflow_counts

No seventh container-like key was found in Lane 3's declared output. claim_metadata_visibility is this lane's own serving-boundary key, removed recursively and UNCOUNTED, and is not a seventh container.

12.7 -- Serialized before/after payloads, verbatim

REST -- GET /api/v1/compound/2244 (this route passes the stored record through WHOLE)

BEFORE, the stored compound record:

{"id":"sciweon::compound::CID:2244","pubchem_cid":2244,"chembl_id":"CHEMBL25","external_ids":{"unii":"R16CO5Y76E","drugbank_id":"DB00945","rxcui":"1191"},"competing_claims":[{"path":"external_ids.kegg_drug_id","value":"D00109","side":"previous","source":{"source":null,"status":"unknown"}},{"path":"iupac_name","value":"SENTINEL-MEDDRA-TERM-XYZZY","side":"incoming","source":{"source":null,"status":"unknown"}}],"preserved_against_null":{"molecular_weight":"SENTINEL-PRESERVED-NULL-QUUX"},"field_sources":{"iupac_name":"SENTINEL-FIELD-SOURCE-PLUGH"},"claim_set_state":"CLAIM_SET_INCOMPLETE_OVERFLOW","claim_overflow_fields":["SENTINEL-OVERFLOW-FIELD-FROTZ"],"claim_overflow_counts":{"competing_claims":2},"claim_metadata_visibility":{"state":"internal_claim_metadata_not_published","removed_key_count":999}}

AFTER, the raw response body:

{"id":"sciweon::compound::CID:2244","compound":{"id":"sciweon::compound::CID:2244","pubchem_cid":2244,"chembl_id":"CHEMBL25","external_ids":{"unii":"R16CO5Y76E","drugbank_id":"DB00945","rxcui":"1191"},"_tier":"T1"},"claim_metadata_visibility":{"state":"internal_claim_metadata_not_published","removed_key_count":6}}

x-sciweon-rights-filter = rc3a-v2. Both attack claims are gone, and the forged removed_key_count: 999 is discarded and REPLACED by the computed 6.

MCP -- both aliases, containers injected into the stored record

sciweon_get_negative_evidence on /api/mcp and /api/v1/mcp, full JSON-RPC envelope (abridged in the middle only; the complete envelope was printed for both aliases during evidence collection and the measured values below are over the COMPLETE raw text):

{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\n  \"compound\": {\n    \"id\": \"sciweon::compound::CID:2244\"\n  },\n  \"snapshot_date\": \"2026-05-16\",\n  \"negative_signals_count\": 1,\n ... \"signals\": [\n    {\n      \"id\": \"sciweon::neg::trial_failure::NCT04123456\",\n      \"evidence_type\": \"trial_failure\",\n      \"observed_date\": \"2026-05-16T00:00:00Z\",\n      \"subject\": {\n        \"compound_id\": \"sciweon::compound::CID:2244\"\n      },\n      \"detail\": {},\n      \"provenance\": {\n        \"primary_source\": \"clinicaltrials_gov\"\n      }\n    }\n  ], ... }"}]}}

Measured on the RAW envelope, identically for each alias:

container keys present in raw envelope: NONE
'SENTINEL-' present: false | '999' present: false
business root is plain object: true
marker in JSON-RPC envelope root: false

sciweon_search and sciweon_resolve_entity were also driven on both aliases against a container-bearing compound record. All four combinations: containers=NONE sentinel=false forged999=false.

FINDING, recorded honestly

Of the covered surfaces, only the compound REST route passes a stored record through whole. xrefs, negative-evidence, target and every MCP tool SHAPE the record into a new object, so the containers never enter their payloads at all. On those surfaces the filter removes nothing and therefore correctly emits no marker (5c: "N = 0 yields no marker"). Containment holds on every surface; the marker is observable only where a record is passed through. This is exactly the defence-in-depth posture section 1 describes: if any shaper ever widened, the filter would catch it.

12.8 -- Marker placement, removed_key_count, forged replacement

For the 6a fixture, removed_key_count = 8, matching L3S-4's frozen derivation: six root containers plus the two nested competing_claims copies. The forged marker is removed at ALL THREE positions -- root, sub-object and array element -- and replaced by the computed value. Exactly ONE marker appears, as a direct key of the business root, never per record and never in the JSON-RPC envelope.

The fixture's claim_overflow_counts deliberately contains its own competing_claims key. It is NOT counted, because deleting the outer frozen key ends inspection of that subtree (5b / E-2). Were it counted, N would be 9; the test pins 8.

12.9 -- No leakage; WithheldTally and source_visibility untouched

No value, path, source or overflow detail appears anywhere in any output. The full serialized response text is scanned for every sentinel (SENTINEL-MEDDRA-TERM-XYZZY, -PRESERVED-NULL-QUUX, -FIELD-SOURCE-PLUGH, -OVERFLOW-FIELD-FROTZ, -NESTED-CLAIM-BLORPLE, -ARRAY-CLAIM-GRUE), plus D00109, D00110, CLAIM_SET_INCOMPLETE_OVERFLOW and the forged 999. All absent.

WithheldTally remains exactly {meddra, kegg} with no third counter. The four existing toEqual assertions at tests/worker/source-rights-filter.test.ts:80, :136, :151 and :209 are UNMODIFIED and green, as is :207 (expect(filtered).toEqual(before)), the single tripwire against an unconditional marker. source_visibility is untouched: its construction block is byte-identical to base.

No existing test was edited, weakened or dropped. Every new test is an addition.

12.10 -- The header sync

All seven x-sciweon-rights-filter literals moved rc3a-v1 -> rc3a-v2, across five files:

src/worker/api/compound.ts:70              'x-sciweon-rights-filter': 'rc3a-v2',
src/worker/api/compound.ts:87              'x-sciweon-rights-filter': 'rc3a-v2',
src/worker/api/negative-evidence.ts:74     'x-sciweon-rights-filter': 'rc3a-v2',
src/worker/api/repurposing-evidence.ts:60  'x-sciweon-rights-filter': 'rc3a-v2',
src/worker/api/target.ts:141               'x-sciweon-rights-filter': 'rc3a-v2',
src/worker/api/xrefs.ts:51                 'x-sciweon-rights-filter': 'rc3a-v2',   <-- 403 RESPONSE-INIT HEADER
src/worker/api/xrefs.ts:98                 'x-sciweon-rights-filter': 'rc3a-v2',

xrefs.ts:51 called out separately: it is a 403 rights-policy RESPONSE-INIT header, not a filtered success body. Both the 200 and the 403 header values are asserted at runtime in tests/api/claim-containment-boundary.test.ts.

Repo-wide rc3a-v1 count after the change: 0.

"Also update any existing test that pins rc3a-v1" is a NULL INSTRUCTION. ZERO tests pinned rc3a-v1 at base: the repo-wide count of that string was exactly 7, all of them the source literals above. No such test file exists and none was hunted for. The new pinning test is the first.

Open PM item, not fixed here: the MCP surface emits NO x-sciweon-rights-filter header at all, so the surface whose behaviour changes most carries no version signal. It is asserted as null in the boundary test so the fact is pinned rather than forgotten.

Only these seven lines changed in src/worker/api/**. The diff across those five files is exactly 7 insertions and 7 deletions, and every changed line is a rights-filter literal. The adjacent gate-owned bindings (negative-evidence.ts:73, repurposing-evidence.ts:59, target.ts:140, xrefs.ts:97) and mcp.ts:36 / :44 are untouched.

12.11 -- The serving-edge-only residual (0a)

This lane removes at the SERVING EDGE ONLY. The containers remain inside the R2 objects. Any future reader, dump or direct download of those shards is outside this control. compounds-enriched.jsonl is cumulative and persisted, so this is not a transient leak that a later deploy cures. This is the accepted consequence of the Founder's decision not to adopt a sidecar refactor. It is recorded, not fixed.

12.12 -- npm run build

> astro build
[build] 1 page(s) built in 1.77s
[build] Complete!            (exit 0)

What it establishes for this lane: nothing. It is astro build over a single static page and verifies NOTHING about src/worker/**. Combined with D-1's "no typecheck required", no gate compiles or type-checks this lane's code.

12.13 -- npm test

 Test Files  253 passed | 2 skipped (255)
      Tests  2870 passed | 13 skipped (2883)
   Duration  107.53s

ZERO failures. There is no pre-existing failing test to report. Both tests named in section 12a are green: rc3b-locator-runtime (14 passed) and the snomed set (18 passed). The 0b disk-hash gate verifies on disk:

sha256sum scripts/rc3b-audit/locator-artifact-schema.json
  33e65017a28e77c54e2c819ffb1b7238deaed03aa95be09a75e29d9daabc8837   (== expected)

New test files, verbatim:

 PASS tests/worker/claim-containment-filter.test.ts     (27 tests)  12ms
 PASS tests/worker/claim-containment-guards.test.ts     (40 tests)  34ms
 PASS tests/api/claim-containment-boundary.test.ts      ( 9 tests)  42ms
 PASS tests/factory/claim-containment-merge-map.test.ts ( 3 tests)   8ms

12.14 -- CES

python scripts/check_compliance.py
[CES] Initiating Sciweon Compliance Enforcement Script...
[CES] English strict mode: True

[OK] CES CHECK PASSED: System is Compliant.
TRUE EXIT = 0

New filenames, all CES-clean against all 14 Art 9.1 patterns:

src/worker/lib/claim-containment-legacy-test-only.ts
tests/worker/claim-containment-fixture.ts
tests/worker/claim-containment-filter.test.ts
tests/worker/claim-containment-guards.test.ts
tests/api/claim-containment-boundary.test.ts
tests/factory/claim-containment-merge-map.test.ts

The CES checks were proven LIVE rather than vacuous, using known-positive controls:

tests/ces-control-linecap-probe.ts : Art 5.1 Monolith Ban -> File length 251 > 250 lines
tests/ces-control-STRATEGY-probe.ts: Art 9.1 Confidentiality -> Filename matches forbidden pattern '.*STRATEGY.*'
Total Violations: 2

Both controls were deleted immediately, CES then passed, and the worktree contains no control file.

12.15 -- The canonical byte audit (section 13 command, verbatim output)

src/worker/api/compound.ts	bytes=3524	nul=0	controls=0
src/worker/api/negative-evidence.ts	bytes=4863	nul=0	controls=0
src/worker/api/repurposing-evidence.ts	bytes=4017	nul=0	controls=0
src/worker/api/target.ts	bytes=6148	nul=0	controls=0
src/worker/api/xrefs.ts	bytes=4517	nul=0	controls=0
src/worker/lib/claim-containment-legacy-test-only.ts	bytes=1177	nul=0	controls=0
src/worker/lib/source-rights-filter.ts	bytes=10396	nul=0	controls=0
tests/api/claim-containment-boundary.test.ts	bytes=11642	nul=0	controls=0
tests/factory/claim-containment-merge-map.test.ts	bytes=2244	nul=0	controls=0
tests/worker/claim-containment-filter.test.ts	bytes=12622	nul=0	controls=0
tests/worker/claim-containment-fixture.ts	bytes=5416	nul=0	controls=0
tests/worker/claim-containment-guards.test.ts	bytes=12831	nul=0	controls=0
EXIT = 0

Twelve changed blobs, every one nul=0 controls=0. tests/factory/umls-mrconso-probe.test.ts is NOT in the diff, so section 10's STOP does not fire and no exemption was created.

The measuring commands were validated on controls with DIFFERING expected values (A-2's rule) before their output was believed:

counting logic  "a\nb"    -> {nul:0, controls:0}   KNOWN NEGATIVE
counting logic  "a\r\nb"  -> {nul:0, controls:1}   KNOWN POSITIVE (detects CR)
counting logic  NUL byte  -> {nul:1, controls:0}   KNOWN POSITIVE

raw disk bytes vs committed blob  (sha256sum  vs  git cat-file blob | sha256sum):
  source-rights-filter.ts                disk=7ae79e5eb6491d19 blob=7ae79e5eb6491d19 MATCH
  claim-containment-legacy-test-only.ts  disk=2ead1cd16ab35d07 blob=2ead1cd16ab35d07 MATCH
  claim-containment-guards.test.ts       disk=30e8f40334eaeb47 blob=30e8f40334eaeb47 MATCH
  claim-containment-boundary.test.ts     disk=0fb683f24b145c18 blob=0fb683f24b145c18 MATCH
  KNOWN-NEGATIVE control (deliberately mismatched pair) -> differs, as required

git hash-object was NOT used as a disk proof anywhere.

Final worktree state: git ls-files --eol reports 780 tracked files, all w/lf, zero w/crlf, and git status --porcelain --untracked-files=all is empty.

12.16 -- Per-file line counts, len(content.splitlines()), never wc -l

File Before After Cap
src/worker/lib/source-rights-filter.ts 189 210 250
src/worker/lib/claim-containment-legacy-test-only.ts -- 31 250
tests/worker/claim-containment-fixture.ts -- 123 250
tests/worker/claim-containment-filter.test.ts -- 222 250
tests/worker/claim-containment-guards.test.ts -- 244 250
tests/api/claim-containment-boundary.test.ts -- 224 250
tests/factory/claim-containment-merge-map.test.ts -- 44 250
src/worker/api/compound.ts 94 94 250
src/worker/api/negative-evidence.ts 105 105 250
src/worker/api/repurposing-evidence.ts 92 92 250
src/worker/api/target.ts 145 145 250
src/worker/api/xrefs.ts 101 101 250

No file is at or over the cap. No test was weakened or dropped to fit.


D-1 -- the twelve-function change list, as implemented

Twelve functions, no thirteenth, no function-valued runtime binding, no arrow function, no class, no export-brace clause. The token function occurs exactly 12 times in runtime code, pinned by test.

Function Permitted As implemented
countedMarker body UNCHANGED UNCHANGED
familyMarker body UNCHANGED UNCHANGED
isPlainObject body UNCHANGED UNCHANGED
isThinFaersExample body UNCHANGED UNCHANGED
jsonWithRights body UNCHANGED UNCHANGED
isFaersSignal frozen .test -> .startsWith only exactly that, at :57 (base numbering)
isFaersIdString frozen .test -> .startsWith only exactly that, at :66
withholdFaersSignal substitution at :95 plus export exactly that; body otherwise UNCHANGED
withholdObjectKeys export visibility ONLY exactly that
pruneIdListArrays export visibility ONLY exactly that
walk may carry the frozen behaviour mapped below
applySourceRightsFilter may carry the frozen behaviour mapped below

Line-by-line mapping -- walk

Post-change line numbers in src/worker/lib/source-rights-filter.ts.

Line Change Kind Frozen requirement it maps to
154-158 Lane 3S comment block comment, no runtime effect documents 3.7 ordering and the 3.8 forged-marker rule
159 third parameter claims: ClaimTally added parameter D-3.3 counting state "passed to walk as an EXPLICIT PARAMETER"
161 walk(el, tally) -> walk(el, tally, claims) call argument D-3.3, on the array branch
165-166 for (const key of [the six frozen keys]) loop 3.7.1 six-key container deletion FIRST; section 3 frozen removal set, byte-identical
167 if (key in node) { delete node[key]; claims.n += 1; } branch, delete, assignment 5b unconditional recursive removal without inspecting contents; 5c "N is the total number of direct deletions"; E-2 deleting the outer key ends inspection of that subtree, since deletion precedes the recursion at line 173
169 delete node.claim_metadata_visibility; delete 3.8 / L3S-2 remove EVERY claim_metadata_visibility at EVERY depth, unconditionally and UNCOUNTED
173 walk(node[key], tally) -> walk(node[key], tally, claims) call argument D-3.3

UNCHANGED inside walk: lines 160, 162, 163, 164 (array and plain-object guards), 170 (isFaersSignal + withholdFaersSignal), 171 (withholdObjectKeys), 172 (pruneIdListArrays), 174. The mechanism order and the recursion position are byte-identical to base, satisfying 3.7.2 (mechanisms second) and 3.7.3 (recursion third).

Line-by-line mapping -- applySourceRightsFilter

Line Change Kind Frozen requirement it maps to
187 const claims: ClaimTally = { n: 0 }; assignment D-3 item 4 function-local claim-removal counter, placed AFTER the clone (item 3) and BEFORE the walk (item 5); D-3.3 created only by this function, only within this call
188 walk(filtered, tally) -> walk(filtered, tally, claims) call argument D-3 item 5 unconditional walk; D-3.3 explicit parameter
195 if (isPlainObject(filtered) && claims.n > 0) { branch D-3 item 6 computed claim marker; 5c "N > 0 yields exactly one marker, N = 0 yields no marker"; L3S-3 attach after traversal completes
196 filtered.claim_metadata_visibility = { state: 'internal_claim_metadata_not_published', removed_key_count: claims.n }; assignment E-3 inline object literal inside applySourceRightsFilter; no countedMarker or familyMarker, no thirteenth function, NO source_family, no new runtime binding outside the call; 5c frozen marker shape and REST root / MCP business root placement
197 } closing brace as 195

UNCHANGED: 181 (the pinned declaration), 182 (item 1 tally init, which must precede the early return), 183-185 (item 2 scalar/null early return), 186 (item 3 structured clone), 189-194 (item 6 the existing rights marker, byte-identical), 198 (item 7 final return).

D-3 contents, in order, with nothing else: 1 tally init, 2 scalar/null early return, 3 clone, 4 counter, 5 walk, 6 existing marker plus computed claim marker, 7 final return. Exactly two return statements (pinned by test): the single early return and the final one. No other early return and no bypass branch.

One non-function change falls outside both tables: a six-line addition to the MODULE doc comment (lines 31-36) recording the added containment axis and the serving-edge residual. It belongs to no function, so D-1's per-function table does not reach it.

Every changed line in walk and applySourceRightsFilter maps to a frozen requirement. There are no unmapped changes.


The frozen substitution (3.3) and the guards

FAERS_NEG_ID_RE = /^sciweon::neg::faers::/ becomes FAERS_NEG_ID_PREFIX = 'sciweon::neg::faers::', with the three .test(...) sites becoming .startsWith(...). Equivalence is OBSERVED in-test across all eight frozen edge cases -- exact prefix, prefix only, non-matching, prefix not at start, empty string, upper-case variant, truncated prefix, prefix later in the string -- with 0 mismatches. This REMOVES a mutable carrier rather than renaming one: the module now holds exactly three module-scope bindings, all string primitives, pinned by test.

D-2, the global call shape: Array.isArray, Object.keys, Response.json and structuredClone appear ONLY as direct-call callees, with the entire member expression as the callee. Pinned by test over comment-and-string-stripped source. Note that at base structuredClone has one NON-callee occurrence, inside the module doc comment, so a naive source guard would go red on unmodified base code; the guard strips comments first, and that stripper is itself validated on a known positive (1) and a known negative (0).

L3S-1 / A-5 guards, all present:

  • (a) STATIC signature pin on the byte-exact declaration line; exactly one plain parameter; no default, rest or destructuring; and no arguments reference anywhere in applySourceRightsFilter, walk or jsonWithRights.
  • (a') jsonWithRights pinned to exactly two plain parameters.
  • (b) All fifteen frozen behavioural probes, including true, 1, 'legacy' and new Proxy({}, { get: () => true }). Each probe's output must equal the one-argument call and contain none of the six keys.
  • (c) NO-SERVING-IMPORT: the legacy entry point's single exported identifier claimContainmentLegacyMechanisms occurs in src/** exactly once, at its own declaration -- including NOT in source-rights-filter.ts, where jsonWithRights lives. The scanner is control-validated by finding a known symbol across several files.

These guards are KNOWN-CHANNEL REGRESSION TRIPWIRES (C-2.2), not a formal completeness proof. The absolute claim that a source-text guard proves no external state can influence containment is WITHDRAWN (C-2.1). The behavioural probes are defence in depth only: a strict-equality gate such as mode === 'raw' survives every one of them. The source-level signature pins are what close the argument channel. The guard's own header text says exactly this.

Recorded consequence, NOT fixed: the filter is not idempotent over its own container-bearing output; a second pass discards the truthful marker by design (N = 0 plus forgery replacement). Production applies the filter exactly once per response. The existing idempotence test at tests/worker/source-rights-filter.test.ts:212-215 stays green because its fixture carries no containers. A container-free fixture is used for the idempotence case, and a separate test RECORDS the container-bearing behaviour. Idempotence was NOT "fixed" by preserving an input marker, which would re-admit forgery.


Executor judgments and deviations, all of them

  1. src/worker/api/** was treated as READ-forbidden. The dispatch heads its forbidden list "read AND write" over all bullets, while README section 6 attaches "(read AND write)" only to the governance bullet. I took the STRICTER reading and never read route source beyond my seven owned literals, using count-only greps for the shared-core proof and RUNTIME assertions for "business roots are plain objects". The strict reading is fully satisfiable, so it is compatible with both; no STOP was needed.
  2. The two nested competing_claims copies carry a benign single claim, not a byte-copy of the root array. A literal copy would include claim 3's value: { kegg_drug_id: 'D00110' } twice more and drive the LEGACY KEGG tally to 3, contradicting L3S-4's frozen withheld_item_count: 1. Both frozen numbers (removed_key_count = 8 and legacy kegg = 1) hold under this construction, and no other construction satisfies both.
  3. A sixth file, tests/worker/claim-containment-guards.test.ts, was created, which is not on the pre-approved list, because the guards do not fit beside the behavioural tests under the 250-line cap. Dropping or weakening a test is forbidden. The file is inside the tests/** write scope and its basename is clean against all 14 Art 9.1 patterns, confirmed by a CES run whose Art 9.1 check was proven live by a known-positive control.
  4. The first commit carried the wrong author email (a private address), which GitHub rejected with GH007. Corrected with git commit --amend --reset-author to the repository's own already-configured noreply identity. The tree is byte-identical across the amend (27d6d68399e7ae3532efd1a6e13efe909cf83125 before and after). No repository configuration was changed.
  5. The legacy walker omits the isFaersSignal gate (see 12.4), because that predicate may not be exported under D-1. It is a strict superset, the pass is conservative, and it is proven inert on this fixture by tally.meddra === 0.
  6. Evidence scripts were written to the session scratchpad, outside the repository, and were never committed. The worktree contains no scratch file.
  7. Procedural incident, reported in full. While verifying an evidence file, I discovered the session scratchpad is SHARED across lanes, and a pre-existing pr-body.md there belonged to Lane 1 (PR Lane 1 -- P0.1 Public Error Contract (typed failure classes, no false promises, no leaks) #292). My verification command printed its first line and its last twelve lines before I recognised the file was not mine. Per the C-3 precedent, a command that prints content from a forbidden source is a READ and not a near-miss, so it is recorded as such. What was printed was one heading line and PR Lane 1 -- P0.1 Public Error Contract (typed failure classes, no false promises, no leaks) #292's CI gate summary. I did not open PR Lane 1 -- P0.1 Public Error Contract (typed failure classes, no false promises, no leaks) #292 on GitHub, and did not diff, copy, cite, test against or branch from it. This lane's implementation was committed and pushed as 73613b08 at 20:22:46, before the read occurred, so influence on the code is impossible; nothing from that file was used, and my PR body was rewritten to a lane-unique path.

12b -- What this evidence does NOT establish

The evidence level is "synthetic payload tests pass". Production containment is UNEXERCISABLE while ingest is frozen: no published record carries the containers, because Lane 3's code has never run. Merge and deploy would prove the code path exists, NOT that it has ever fired. No unfreezing is proposed.

CI gate set -- all four

CI / test, CI / security-scan, CI / schema-validate, CES Gatekeeper / enforce-compliance. This lane touches no schema deny-list path.

Boundaries

No merge, deploy, admin override or workflow dispatch. No Factory, R2, cursor, latest-pointer or ingest action. .github/**, wrangler.toml, src/worker.ts and all scripts/** are untouched; scripts/factory/lib/aggregated-merger.js was READ ONLY and never edited. No governance file was read or written. PR #289, #290, #291, #292 and #293 and their branches were never opened, diffed, copied, cited, tested against or branched from -- subject to the single disclosure in judgment 7 above, which concerns a scratchpad file and not the PR. This branch is a direct descendant of c88c1f5 and of nothing else.


Actual CI result on this PR

All four gate jobs green on head 73613b0801600623cb81503ff75d2704294d3e19:

test                 pass  2m5s   CI
security-scan        pass  19s    CI
schema-validate      pass  7s     CI
enforce-compliance   pass  6s     CES Gatekeeper

Per the composed-state statement at the top of this body, this is evidence about
ONE lane against ONE base snapshot, taken on refs/pull/294/merge. It is NOT
evidence about the composed five-lane state.

Final worktree state: git status --porcelain --untracked-files=all reports 0
entries; git ls-files --eol reports 780 tracked files, all w/lf, zero
w/crlf. origin/main is still c88c1f525d623f0b3a16715551d3a5983438e36a.

…-only; no merge, no deploy)

Removes Lane 3's six internal claim-metadata containers wholesale at the shared
serving boundary, recursively at every depth, and attaches one payload-wide
`claim_metadata_visibility` marker at the business root.

- source-rights-filter.ts: container deletion runs FIRST on every plain-object
  node, before the FAERS/KEGG mechanisms and before recursion, so a container's
  interior is never inspected and never increments a family tally.
- `claim_metadata_visibility` is serving-boundary-exclusive: any input copy at
  any depth is a forgery, removed unconditionally and uncounted.
- Frozen substitution: FAERS_NEG_ID_RE (RegExp object) -> FAERS_NEG_ID_PREFIX
  (string constant) at three sites; removes a mutable carrier rather than
  renaming one. Equivalence proven over the eight frozen edge cases.
- Three legacy mechanisms gain `export` visibility only, re-exported through
  src/worker/lib/claim-containment-legacy-test-only.ts, which no serving path
  imports. No options argument, no production-callable bypass.
- x-sciweon-rights-filter rc3a-v1 -> rc3a-v2 at all seven literals in five
  route files. No other response-version binding is moved.

Twelve functions, no thirteenth. Only `walk` and `applySourceRightsFilter`
carry new behaviour. Serving-edge only: the containers remain inside the stored
R2 objects, which no serving-side filter can reach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019c1bKNiDv8zfaPunCXUANq
mosesy5688-cell added a commit that referenced this pull request Sep 5, 2026
* replay #291 deployment identity (BASE..229e4e5, verbatim)

* replay #292 public error contract (BASE..390a6e4, verbatim; worker.ts 3-way merged with #291)

* replay #293 rights candidate registry (BASE..c11e3b5, verbatim)

* replay #294 public claim containment (BASE..73613b0, verbatim; 5 api files 3-way merged with #292)

* replay #295 merge integrity (BASE..c49c1db, verbatim)

* composition gate: F-3 version bindings, CI identity step, gate tests

Composition of the five Founder-accepted lanes (#291 #292 #293 #294 #295)
replayed from BASE c88c1f5 in the frozen order. This commit isolates
everything the composition gate itself authored.

F-3 response-contract bindings (MONOTONIC RESPONSE-CONTRACT MARKERS, not
product SemVer claims -- they exist so a cached pre-composition body is
distinguishable from a post-composition one at the serving boundary):
  negative-evidence.ts    x-sciweon-schema-minor  1.2   -> 1.3
  repurposing-evidence.ts x-sciweon-schema-minor  1.0   -> 1.1
  target.ts               x-sciweon-schema-minor  0.6.0 -> 0.6.1
  xrefs.ts                x-sciweon-schema-minor  1.1   -> 1.2
  mcp.ts                  SERVER_INFO.version     0.6.0 -> 0.6.1
  mcp.ts                  x-sciweon-mcp-version   0.6.0 -> 0.6.1
PROTOCOL_VERSION (2025-03-26) deliberately UNCHANGED.

CI: one read-only identity-recording step in the EXISTING test job. No new
job, no widened permissions. continue-on-error keeps a token/API failure
from turning CI red; the gate treats any absent value as a qualification
failure.

Tests: version-sync (SERVER_INFO.version and the x-sciweon-mcp-version
header are two distinct bindings; nothing else prevents them diverging),
plus the per-surface containment matrix including the repurposing
classification lane 3S did not individually verify.

KNOWN RED, NOT FIXED HERE: tests/api/target.test.ts:174 asserts
x-sciweon-schema-minor === '0.6.0' and now fails against the mandated
'0.6.1'. That file is lane #292's accepted content and is one of the 50
files required to stay byte-identical, so the composition gate is not
authorised to change it. Escalated for a founder ruling.

* test(composition): correct stale target schema-minor oracle; add three REST runtime probes

Bounded Correction 1 to the five-lane composition gate. Founder ruling: the
frozen F-3 contract target.ts = '0.6.1' is CORRECT; tests/api/target.test.ts
was a STALE ORACLE still expecting '0.6.0'. The oracle is corrected; the
contract is NOT rolled back.

The corrected assertion remains a real probe: it still calls handleTarget(),
still reads x-sciweon-schema-minor off the real Response, and still compares
by exact equality. Only the wrong expected literal changed.

Closes three REST runtime-coverage gaps. Before this commit the four REST
schema-minor bindings were pinned only by source-text scans in
tests/worker/composition-version-bindings.test.ts; now each is also asserted
against a live handler response:

  target            0.6.1  tests/api/target.test.ts
  negative-evidence 1.3    tests/api/composition-surface-matrix.test.ts
  xrefs             1.2    tests/api/composition-surface-matrix.test.ts
  repurposing       1.1    tests/api/composition-containment-matrix.test.ts

The two MCP bindings (SERVER_INFO.version, x-sciweon-mcp-version) already had
real handleMcp() runtime probes and are unchanged.

Qualifies the Tier-2 matrix row so removed_key_count = 6 reads as a
wiring-capacity measurement over an artificially injected fixture, not as a
property of production PubChem Tier-2 data.

Comment-only corrections to two stale bump annotations; no emitted value, no
rights-filter marker and no code changed:

  negative-evidence.ts  "bumped 1.1 -> 1.2"  ->  "bumped 1.2 -> 1.3"
  xrefs.ts              "bumped 1.0 -> 1.1"  ->  "bumped 1.1 -> 1.2"

Additive commit; five files; no lane content altered. PROTOCOL_VERSION
('2025-03-26') untouched. No merge, no deploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019c1bKNiDv8zfaPunCXUANq

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mosesy5688-cell

Copy link
Copy Markdown
Owner Author

Closed without merge. This exact lane head was accepted only as an
input to composition PR #296. Its approved content entered main solely
through #296, squash commit
0e8a7f7,
deployed by automatic push run 33944482862.

This PR was never merged or deployed independently. Do not reopen,
merge, cherry-pick, amend, or use it as an execution base.
The source branch is retained unchanged as audit evidence.

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