Lane 1 -- P0.1 Public Error Contract (typed failure classes, no false promises, no leaks) - #292
Closed
mosesy5688-cell wants to merge 2 commits into
Closed
Lane 1 -- P0.1 Public Error Contract (typed failure classes, no false promises, no leaks)#292mosesy5688-cell wants to merge 2 commits into
mosesy5688-cell wants to merge 2 commits into
Conversation
…alse promises, no leaks)
Stop telling the public WHEN a failure will clear or WHAT future process will
fix it; stop leaking internal identifiers through unexpected errors; keep
telling consumers WHETHER a failure class is of a kind a retry could resolve.
- one exported failure contract (src/worker/lib/failure-contract.ts): nine
classes -> {retryable, default_detail, produced}; every emitting path
constructs from it, so `retryable` is never absent
- classify at the throw site BY TYPE (never by message): r2-fetch.ts gains a
typed R2ReadError with six structural discriminants; five bare producer-data
throws plus a new manifest shape guard become ShardDataInvalidError
- NegShardError extracted to src/worker/lib/neg-shard-error.ts and split by the
type of its cause into shard_read_unavailable / shard_manifest_invalid
- a transport fault is no longer routed into a domain 404 (target route) nor
into a 200 resolved:false MCP payload; every other status and JSON-RPC code
is unchanged
- MCP carriers travel in error.data; the message carries prose only
- the four false "next factory-1 cron will produce it" promises are gone,
including the one served on every tools/list call
- no public body echoes an underlying error message any more
Tests: per-class serialization on every emitting route, a declared-not-produced
guard, the thirteen-surface observer census, MCP error/result/tools-list
parity, a leak sentinel, and an enumerated-file source lint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019c1bKNiDv8zfaPunCXUANq
… contract
The 9b.1 matrix carried 34 route/class pairs and missed two that the code can
genuinely reach, and its completeness assertion could not have noticed:
`covers every LIVE class` compares CASES.map(c => c[0]) -- the CLASS column
only. It proves every class NAME appears somewhere; a missing route/class PAIR
is invisible to it.
- add compound x source_unavailable -> 500. With snapshots/latest.json absent,
loadSnapshotContext runs OUTSIDE loadTier1's try (compound-loader.ts:107), so
R2ReadError('not_found') propagates through the catch-less /compound handler
to json500.
- add xrefs x source_unavailable -> 500. With a VALID pointer on its own date
and the xref index visible to head() but absent from get(), xrefIndexExists
takes the indexed path and loadXrefKind raises R2ReadError('disappeared'),
which reaches json500 through the catch-less /xrefs handler.
- add an EXACT route/class membership oracle. Its 36 pairs are hand-written
from the contract table and the per-route census, deliberately NOT derived
from CASES: an expectation computed from the thing under test holds for
whatever that thing contains and proves nothing. Deleting any single pair
from CASES now fails this assertion -- and only this one.
The pre-existing LIVE-class assertion is retained unchanged; it was never
wrong, only insufficient on its own. All 34 existing cases, both spreads
included, are byte-identical.
source_unavailable now covers all eight routes: 8 + 8 + 8 + 3 + 4 + 1 + 1 + 3
= 36. Both new serialized bodies carry failure_class source_unavailable and
retryable true, no forbidden vocabulary and no retry-after / cache-control
header.
Test-only. No production code, status, message, failure class or existing case
is changed. Cosmetic line compaction inside this same file keeps it within the
CES 250-line monolith cap (Art 5.1); no compacted region changes behaviour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019c1bKNiDv8zfaPunCXUANq
This was referenced Sep 3, 2026
Closed
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>
Owner
Author
|
Closed without merge. This exact lane head was accepted only as an This PR was never merged or deployed independently. Do not reopen, |
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.
Lane 1 -- P0.1 Public Error Contract. Single lane, branched from the frozen base. No merge, no deploy, no workflow dispatch is requested or authorised by this PR.
1. Base proof
One commit. Its parent IS the base SHA -- not a descendant of it.
origin/mainwasc88c1f525d623f0b3a16715551d3a5983438e36aat branch creation, immediately before the push, and immediately after it.2. TYPECHECK
TYPECHECK = NOT CONFIGURED / NOT CLAIMEDTypeScript is absent from
package.json,package-lock.jsonandnode_modules. No typecheck is required, no prep PR was created, and no temporary compiler was downloaded.3. Observer census, independently derived
Enumerated from base-SHA source, not copied from the brief. Every surface that can observe a
SnapshotContractError, and the class it now carries:/bioactivitiesbioactivities.ts:65instanceofsnapshot_contract/false/paperspapers.ts:65instanceofsnapshot_contract/false/trialstrials.ts:65instanceofsnapshot_contract/false/targettarget.ts:98instanceofsnapshot_contract/false/negative-evidencesnapshot_contract/false (typed branch added)/repurposing-evidencesnapshot_contract/false (typed branch added)/compoundjson500snapshot_contract/false (shared classifier)/xrefsjson500snapshot_contract/false (shared classifier)get_target_drugsmcp.tsresidualerror.datasnapshot_contract/falseget_negative_evidenceerror.datasnapshot_contract/falseget_repurposing_evidenceerror.datasnapshot_contract/falseresolve_entityerror.datasnapshot_contract/falsesearcherror.datasnapshot_contract/falseDelta from the brief's 5a: NONE. Thirteen surfaces, same routes, same emitters. All thirteen are asserted by
tests/api/error-contract-routes.test.ts(33 tests, green).The two name-based checks (
entity-resolver.ts:127,repurposing-aggregator.ts:161,err.name === 'SnapshotContractError') are untouched; the typed branches downstream are what fix the classification.Status is preserved everywhere. Surfaces 5-8 previously reached a generic 500 and still return 500; only the class is now correct. Section 6e freezes every existing status with exactly two named exceptions, and neither of these is one -- so the honest fix is the carriers, not the status.
4. The exported failure-contract table
src/worker/lib/failure-contract.ts,FAILURE_CONTRACT:failure_classretryableproducedsource_unavailableshard_read_unavailableNegShardError)parse_failedsnapshot_contractshard_manifest_invaliddata_layer_unconfiguredunclassifiedtimeoutclassifySourceLoadError's message branch (source-load-error.ts:85-86), which section 7d exempts from rewriting. It reaches four public carriers:bioactivities.ts,papers.ts,trials.ts,repurposing-evidence.ts.object_integrityobject_integrityhas zero occurrences across every tracked file at the base -- this contract introduces it. No served path verifies a checksum (the only checksum-verifying readers are imported by tests only;shardHashesis populated by two loaders and never read), and no branch emits an integrity label for a transport fault any more. It is not deleted and no producer is invented; a guard test asserts no emitting path yields it, so the day one does, the test says so.data_layer_unconfiguredis nine paths, not eight: the eight REST 503s plusmcp-handlers.ts:37, which is aToolError(-32603)and would otherwise have left the MCP surface silent.5. Serialized examples -- every LIVE class, REST and MCP
All captured from the real worker entry, not hand-written. Every REST example carries
cache-control=nullandretry-after=null.REST
MCP -- carriers in
error.data, message prose only{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32603, "message": "Data layer not configured (R2 binding missing)", "data": { "failure_class": "data_layer_unconfigured", "retryable": false } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "A sharded negative-evidence read failed. This is a READ failure and NOT a finding that no negative evidence exists.", "data": { "failure_class": "shard_read_unavailable", "retryable": true } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "A sharded negative-evidence read failed. This is a READ failure and NOT a finding that no negative evidence exists.", "data": { "failure_class": "shard_manifest_invalid", "retryable": false } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "An upstream evidence source read failed. This is a SOURCE FAILURE and NOT a finding that no evidence exists.", "data": { "failure_class": "source_unavailable", "retryable": true, "source": "snapshot-pointer" } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "The target index could not be read from the current snapshot. This is a READ failure and NOT a finding that the target is absent.", "data": { "failure_class": "source_unavailable", "retryable": true } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32603, "message": "The snapshot pointer failed contract validation. Nothing is served from an unrecognized contract.", "data": { "failure_class": "snapshot_contract", "retryable": false } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32603, "message": "Internal server error", "data": { "failure_class": "unclassified", "retryable": true } } } { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "An upstream evidence source read failed. This is a SOURCE FAILURE and NOT a finding that no evidence exists.", "data": { "failure_class": "parse_failed", "retryable": false, "source": "papers" } } }Stated precisely: I did not separately capture an MCP
timeoutexample. It travels the identicalSourceLoadErrorpath as the MCPparse_failedexample immediately above -- same tool, same code, same message,data.failure_classreadingtimeoutandretryablereadingtrue. The RESTtimeoutexample above is real and captured.-32602invalid-params carries nodata. Client input is out of scope and is not stamped with carriers; this is asserted.tools/listexcerpt --sciweon_get_target_drugs.description, served on every callBefore (base):
... If the index is not yet built (immediately post-deploy) or the target has no bioactivities in the current snapshot, returns {resolved: false} rather than an error. ...After:
6. Leak sentinel (9b.7)
tests/api/error-contract-routes.test.ts, 22 sentinel assertions, all green. The underlying error's message isread of snapshots/latest.json failed for shard bucket-0007 SENT-<timestamp>-<random>; the token is generated per run, so a stale fixture cannot make it pass. Every one of the eight REST routes and both MCP aliases across all five tools is asserted to contain neither the token, norsnapshots/latest.json, norbucket-0007-- in the serialized body, inerror.message, and inerror.data. A control assertion proves the sentinel is genuinely present in the underlying error, so the suite cannot pass vacuously.No public body echoes an underlying error message any more. At base, five sites did:
worker.ts:142,target.ts:118,negative-evidence.ts:101,repurposing-evidence.ts:88,mcp.ts:138-- each passed the raw message through whenever it was under 200 characters.7. No
Retry-After, no failure-responseCache-ControlConfirmed.
git grep -ci "retry-after" -- src/returns zero at base and after. Every failure case in the matrix assertsres.headers.get('retry-after') === nullandres.headers.get('cache-control') === null. Thecache-controlheaders that remain insrc/worker/**are all on 200 responses.8.
src/worker.tsnet-zero budgetThe grant was used exactly: the error-contract text at
:19-23(five lines, not six),json500at:139-144, and one added import.src/worker.ts:24(*/, the docblock terminator) is preserved verbatim -- it appears on neither side of the diff, proven bygit diff -U0 src/worker.ts | grep -E "^[+-] \*/$"returning nothing. TheEnvinterface, the route registration and dispatch body, the_healthbranch, theASSETSfallthrough, and the version labels at:2/:16/:17are all outside the diff.The funding deletion is
:22(429 -- rate limited (not implemented yet; reserved)), an unearned future-capability claim inside the very block being rewritten to remove unearned claims.9. Import anchor (0b), quoted
One import line, at the mandated anchor, non-adjacent to the end of the block.
10.
classifySourceLoadErrorexemption (7d)source-load-error.ts:72-95classifies by message regex and is the sole producer ofsource_unavailable,parse_failedandtimeout. Rule 4c.1 forbids message inference. The ruling is that 4c.1 binds NEW code and the shard path;classifySourceLoadErroris exempt and is NOT rewritten in this lane. Its status mapping is unchanged. Replacing it would re-classify faults on four loaders and change statuses that 6e freezes, which is a separate lane. Its/short read|etag|...|missing/branch already yieldssource_unavailable/true, which is D-2-consistent, so the exemption does not perpetuate a contradiction. Its behaviour is bit-for-bit unchanged by this PR because everyr2-fetch.tsmessage it sniffs is preserved verbatim (section 12).11. The four couplings (7c) and the three repoints (8a)
Couplings:
tests/factory/neg-shard-roundtrip.test.ts:94asserts the strict decode throws matching/strict/i. The new message keeps "strict" verbatim (Shard decode failure (strict): ...), so the assertion is untouched and green.decompressPayload(x, true)has four callers insrc/:rk16/canonical-reader.ts,rk16/directory-reader.ts,rk16/projection-reader.ts(the three rk16 strict readers) plusneg-evidence-filter.ts:37. The rk16 three inherit the newShardDataInvalidErrortype with behaviour unchanged -- it extendsErrorand preserves the message.neg-evidence-filter.ts:37is the one whose behaviour intentionally changes: a strict decode failure there is now typed, so the neg path classifies it asshard_manifest_invalidinstead of falling to a message sniff.tests/api/target.test.ts:147assertedres.status === 404on the target-index-absent path. Rewritten to 503 plus both carriers.tests/api/mcp-target.test.ts:160-163assertedbody.error === undefinedandpayload.resolved === falseon the false-clean. Rewritten to the error-carrier form (error.code === -32000,error.data === {source_unavailable, true}), and the stale docstring at:5-6went with it.Both of couplings 3 and 4 are behavioural and break independently of any string. Section 9d's "no existing test asserts the old strings" is true (
git grep -i "retry shortly" -- tests/returns zero at base) and, on its own, misleading.Repoints of
NegShardError(extracted tosrc/worker/lib/neg-shard-error.ts), all three:src/worker/api/negative-evidence.ts:17src/worker/lib/mcp-handlers.ts:15tests/worker/neg-stale-manifest-legacy-fallback.test.ts:20(the one test importer)The extraction was required by headroom:
neg-evidence-loader.tswas 249 lines (headroom 1). It is now 245 (headroom 5).SnapshotContractErrorwas NOT given metadata --snapshot-context.tsis at 250 lines, headroom 0, and is untouched.Module organisation, stated because it is a judgment call. Section 8a describes one extracted module carrying
NegShardError, the union, theinstanceofclassifier,ShardDataInvalidErrorandclassifyThrown. Section 8c pre-approves two filenames for it and section 9a requires a single exported contract table, so the content is split across both pre-approved names:neg-shard-error.tsholds the shard error types and the by-type classifier;failure-contract.tsholds the class union, the table, the body/data builders andclassifyThrown. This split is what makes 0a satisfiable --json500needs bothclassifyThrownand the body builder, and it is granted exactly ONE import line, so the two must live in one module. No third filename was created.12.
r2-fetch.ts-- six discriminants, call sites, and the blast-radius confirmationSix throw sites, each now
R2ReadErrorwith a structural discriminant, message text preserved verbatim::41not_foundR2 object not found: ${key}:54disappearedR2 object disappeared between head() and get(): ${key}:57etag_driftR2 etag drifted mid-fetch for ${key} (head=..., get=...):63short_readShort read on ${key}: got N bytes, expected M ...:123range_failedR2 range fetch failed: ${key} [offset, +length):128short_range_readShort range read on ${key}@offset+length: got N bytes ...short_range_readis a SEPARATE discriminant: its message isShort range read on ..., which does not contain the substringShort read, so it matched no consumer regex at base.range_failedandshort_range_readKEEP their generic-500 outcome and are not re-routed -- collapsing:128intoshort_readwould move a route from 500 to 502 against the 6e freeze.Verbatim-message proof: extracting every template literal from the file before and after and diffing shows only additions, all inside the new doc comment -- not one original message literal was changed or removed.
Every call site of the six throws.
r2-fetch.tsis imported by 18 production modules plus one in-srctest file. The throwing functions arefetchR2Object(not_found/disappeared/etag_drift/short_read) andfetchR2RangeBytes(range_failed/short_range_read);fetchR2GunzippedTextandfetchR2JsonTextboth delegate tofetchR2Object. Call sites:Behaviour confirmation for callers that do NOT read the discriminant. Exactly three consumers read
.discriminant--target.ts,negative-evidence.ts,repurposing-evidence.ts. Two more branch oninstanceof R2ReadErrorwithout reading it (mcp-handlers.ts, andclassifyThrowninfailure-contract.ts), and both treat all six discriminants identically. Every other call site above is unaffected, becauseR2ReadError extends Errorand preserves.messagebyte-for-byte:err instanceof Erroranderr.messagereturn exactly what a bareErrorreturned. This was verified by direct assertion at runtime, not by inspection. Concretely, this is also why the exemptclassifySourceLoadErroris unchanged -- it sniffs those same message strings and still sees them.One further typed throw was required and is reported explicitly:
neg-evidence-loader.ts:153(Legacy neg-evidence not found: ${key}) becameR2ReadError('not_found', ...)with its message preserved. Under type-based dispatch that path would otherwise have moved from 404 to 500 -- a third exception to the 6e status freeze. Typing it keeps the 404 exactly where it was.13. HTTP status is not a retryability signal
Stated plainly, as required: in this contract HTTP status is not a retryability signal. 502 carries both retryable classes (
source_unavailable) and non-retryable ones (snapshot_contract); the same class appears at 502 on some routes and 503 on others; and 500 carries bothsnapshot_contract(false) andunclassified(true).retryableis the only retry carrier, and it is never absent.14.
npm run buildWhat this establishes for this lane: nothing.
astro buildcompiles a single static page fromsrc/pages/index.astro. It does not bundle, compile or type-checksrc/worker/**. Combined with the TYPECHECK item above, no gate in this cycle compiles or type-checks this lane's code. The evidence level for this lane is "tests pass" -- not "production is fixed".15.
npm testZero failures. Zero pre-existing failures to report. The same suite was run at the base SHA in this workspace before any edit and was also fully green (
249 passed | 2 skipped (251),2791 passed | 13 skipped (2804)), so the delta is exactly the three new test files and their 104 new tests.rc3b-locator-runtimepasses (thecore.autocrlf=inputworkspace prevents its failure mode) andsnomed-rehydratepasses.Verbatim output for every new test file:
Reading of 9b.1, stated because it is a judgment call. "Every LIVE class on every emitting route" is implemented as: for each LIVE class, exercise it on every route that can actually produce it, and assert the serialized body. A literal "every class on every route" is unsatisfiable for the same structural reason 6b already fixed for the not-produced classes --
/compoundhas no shard path, so it can never construct aNegShardError, and no amount of test fixture can make it. The test additionally asserts that the union of exercised classes equals the set of LIVE classes, so no LIVE class can silently go uncovered.Two test failures occurred during development and are recorded rather than hidden: (a) the
/repurposing-evidencetimeout case initially raced four parallel loaders and reportedsource_unavailable; fixed by serving every layer except the timing-out one; (b) two shard cases returned 200 becauseneg-manifest-loader's per-isolate cache is keyed by snapshot identity and a prior case had populated it; fixed by giving each sharded scenario its own snapshot date. Both were test-fixture defects, not source defects. Both are green.16. CES
Run from the repository root with
python, notnpm run ces(whosepython3resolves to a non-functional stub on this machine; CI is unaffected). Every new filename, all from the pre-approved list in 8c and all clean against the 14 Art 9.1 patterns:No
.mjswas used to gain headroom.tests/worker/mcp.test.ts(250 lines, headroom 0) was not modified -- the MCP parity coverage was split into the newtests/worker/mcp-failure-carriers.test.tsrather than trimmed.17. Byte audit -- canonical command, verbatim output
The command text was reproduced byte-identically before running: 813 B, sha256
ffdf10c301824adb311ec1454f2bc44d24e58e696db36cc66acb66fd8123ac1a-- the Founder-mandated identity. The counting logic was first validated on a known-positive control (nul=1 controls=2) and a known-negative control (nul=0 controls=0), whose expected values differ.27 changed blobs, all
nul=0 controls=0, exit 0.tests/factory/umls-mrconso-probe.test.tsis unmodified and therefore does not enter the audit, so the section-10 STOP condition does not fire and no exemption was created.Line endings:
git ls-files --eolreports 779 tracked files, 779w/lf, 0w/crlfin the worktree after committing.18. Per-file line counts, before and after
Counted with
len(content.splitlines()), neverwc -l-- the counting command was validated on a known-positive and known-negative control whose expected values differ (a file without a trailing newline:splitlines()3 vswc -l2).src/worker.tssrc/worker/api/bioactivities.tssrc/worker/api/compound.tssrc/worker/api/mcp.tssrc/worker/api/negative-evidence.tssrc/worker/api/papers.tssrc/worker/api/repurposing-evidence.tssrc/worker/api/target.tssrc/worker/api/trials.tssrc/worker/api/xrefs.tssrc/worker/lib/failure-contract.tssrc/worker/lib/mcp-handlers.tssrc/worker/lib/mcp-tools.tssrc/worker/lib/neg-evidence-filter.tssrc/worker/lib/neg-evidence-loader.tssrc/worker/lib/neg-manifest-loader.tssrc/worker/lib/neg-shard-error.tssrc/worker/lib/neg-shard-router.tssrc/worker/lib/r2-fetch.tssrc/worker/lib/repurposing-aggregator.tssrc/worker/lib/shard-codec.tstests/api/error-contract-routes.test.tstests/api/mcp-target.test.tstests/api/target.test.tstests/worker/failure-contract.test.tstests/worker/mcp-failure-carriers.test.tstests/worker/neg-stale-manifest-legacy-fallback.test.tsLargest file after the change is
tests/worker/failure-contract.test.tsat 250 (headroom 0; raised from 243 by the section 26 coverage correction, which offset its additions by compacting cosmetic lines in that same file). Next largest issrc/worker/lib/neg-evidence-loader.tsat 245. The cap is> 250.src/worker/lib/snapshot-context.ts(250, headroom 0) andtests/worker/mcp.test.ts(250, headroom 0) are untouched.src/worker/lib/rk15-reader-migration.test.ts(240, headroom 10 -- a.test.tsliving undersrc/worker/lib/, inside this lane's grant and collected by vitest's default include) is untouched.19. Composed-state statement
No CI run in this cycle exercises the state produced by composing Lane 2 -> Lane 1 -> Lane 4 -> Lane 3S -> Lane 3. A green single-lane PR is not a green composed state. A re-test gate on the composed branch is a Founder decision. The PR gate set is four jobs, not two:
CI / test,CI / security-scan,CI / schema-validate,CES Gatekeeper / enforce-compliance; this PR touches none of the schema deny-list paths.20. Response-version bindings
Verified mechanically: none of the seven
x-sciweon-rights-filterlines, none of the six F-3 binding lines (negative-evidence.ts,repurposing-evidence.ts,target.ts,xrefs.tsschema-minor,mcp.tsSERVER_INFO.version,mcp.tsx-sciweon-mcp-version) and noPROTOCOL_VERSIONappears on either side of this diff. Their values are byte-identical to base (rc3a-v1,1.2,1.0,0.6.0,1.1,2025-03-26). Their line numbers shift where surrounding code grew; no value moves.src/worker/lib/source-rights-filter.ts(Lane 3S's) is not modified.src/worker/lib/claim-containment-*.tsandsrc/worker/api/health.tsdo not exist at this base and are not created.21. Known deferral for the composition gate
22. Open PM item -- the enumerated-file source lint (9b.8)
Test 9b.8 lints an enumerated file list, never a directory walk.
src/worker/api/health.tsis excluded because it does not exist at this base. Files that a LATER lane creates undersrc/worker/**will fall outside this lint until it is extended:src/worker/api/health.ts(Lane 2) -- its"available"/"unavailable"literals sit outside this lint once the Lane 2 diff is applied in the composition tree.src/worker/lib/claim-containment-*.ts(Lane 3S).src/worker/lib/source-rights-filter.tsis fenced off from this lane and is deliberately not in the list, so a Lane 3S edit cannot redden a Lane 1 test.The enumeration, not a directory walk, is what makes the lint immune to diff-application order.
23. Ordering language
:17. All Lane 2's, and the Lane 2 diff is applied before the Lane 1 diff in the composition tree.Once the Lane 2 diff is applied in the composition tree, its
"available"/"unavailable"literals sit outside this lint, as an open PM item (section 22).24. Diff stat
Every changed path is inside
src/worker/**, the line-limitedsrc/worker.tsgrant, ortests/**. Nothing underscripts/**,wrangler.tomlor.github/**is touched.25. Actual CI result on this PR (superseded head
a5c770d)Section 26 carries the CI result for the current head. All four gate jobs were green on the superseded head
a5c770d:Per section 19 this is evidence about ONE lane against ONE base snapshot, taken on
refs/pull/292/merge. It is not evidence about the composed five-lane state.26. Test-only coverage correction (head
390a6e4)One new commit on top of
a5c770d. It touches exactly one file,tests/worker/failure-contract.test.ts. No production code, status, message,failure class, existing case, base SHA or workflow file is changed. The base is
still
c88c1f525d623f0b3a16715551d3a5983438e36a.26.1 What was wrong
CASESheld 34 route/class pairs and was missing two the code can genuinelyreach. The completeness assertion could not have caught it:
covers every LIVE classcomparesCASES.map(c => c[0])-- the CLASS column only. It proves everyclass NAME appears somewhere in the matrix; a missing route/class PAIR is
invisible to it.
26.2 The two added pairs
compound x source_unavailable -> 500, withsnapshots/latest.jsonabsent(the existing
emptyBucketfixture).loadSnapshotContextruns OUTSIDEloadTier1's try (src/worker/lib/compound-loader.ts:107; the try opens at:109), soR2ReadError('not_found')propagates throughhandleCompound,which has no catch of its own, to
json500. Status is 500, not 503.xrefs x source_unavailable -> 500, with a VALID pointer on its own date(
2026-07-04, distinct from every other fixture date) and the xref-index objectVISIBLE to
head()but ABSENT fromget().xrefIndexExists(
xref-index-loader.ts:95) sees the object and takes the indexed path;loadXrefKindthen reachesfetchR2Object, whoseget()returns null, raisingR2ReadError('disappeared')(r2-fetch.ts:84). It propagates throughresolveEntityand the catch-lesshandleXrefstojson500.classifyThrownmaps everyR2ReadErrordiscriminant tosource_unavailable(
failure-contract.ts:141), so both land on the same class.source_unavailablenow covers all eight routes.8 + 8 + 8 + 3 + 4 + 1 + 1 + 3 = 36.
26.3 Both new serialized bodies, verbatim
GET /api/v1/compound/CID:2244with an absent latest pointer -- status 500,retry-afternull,cache-controlnull, headers[["content-type","application/json"]]:{"error":"Internal server error","failure_class":"source_unavailable","retryable":true,"detail":"A source object could not be read intact. This is a READ failure and NOT a finding that no data exists."}GET /api/v1/xrefs?id=CHEMBL25with the index seen byhead()and absent fromget()-- status 500,retry-afternull,cache-controlnull, headers[["content-type","application/json"]]:{"error":"Internal server error","failure_class":"source_unavailable","retryable":true,"detail":"A source object could not be read intact. This is a READ failure and NOT a finding that no data exists."}Both carry
failure_class: source_unavailableandretryable: true, which isthe contract table's value for that class. Neither carries a forbidden term
(checked by
assertNoForbiddenTermoverstringValues(body)against theexisting 22-pattern
FORBIDDENlist), and neither carriesretry-afterorcache-control.26.4 The membership oracle, as written
The 36 pairs are hand-written from the contract table and the per-route census.
They are deliberately NOT derived from
CASESor fromROUTES: an expectationcomputed from the thing under test holds for whatever that thing contains and
proves nothing, which is exactly the defect being corrected. The
flatMaponlyexpands hand-authored literal rows.
The pre-existing LIVE-class assertion is retained unchanged; it was never wrong,
only insufficient alone. Both assertions now stand.
Non-vacuousness, demonstrated rather than asserted. On a throwaway copy of
the file with the single line
['source_unavailable', 'xrefs', 500, xrefGone],deleted, the run reported
1 failed | 54 passed (55)-- the only failure beingthe new oracle,
expected [ Array(35) ] to deeply equal [ Array(36) ]. TheLIVE-class assertion passed on that mutant. That is precisely the blindness this
correction removes. The copy was deleted; it was never staged or committed.
26.5 The 34 existing cases are untouched
Both spreads and all 34 literal tuples are byte-identical; the two new tuples
are inserted after the existing
source_unavailableblock and nothing isreordered. The diff for
CASESis three added lines (two cases and onecomment) and zero removed lines.
26.6 Disclosed: cosmetic compaction inside the same file
CES Art 5.1 caps every scanned file at 250 lines and this file was already at
243, leaving 7 lines of headroom against roughly 22 lines of required
additions. Rather than spill into a second file (outside the two permitted
changes), the additions were offset by compacting cosmetic regions of the SAME
permitted file: the header docblock, three comment blocks, the
beforeAllguard, the
assertNoForbiddenTermloop nesting, thebucket()head/getstatement layout, and the
9b.2loop body. Every one is a lexical change only-- no condition, order or value is altered -- and the full file remains green.
The file now sits at exactly 250, the cap being
> 250; three other files inthe repo already sit at 250.
26.7 Verification
Affected file:
56 passed (56), up from53 passed (53)-- the two new casescounted in both
9b.1and9b.2, plus the oracle.Full suite:
2897 passed | 13 skipped, with one failure, reported notsuppressed:
tests/factory/dailymed-adapter-incremental.test.ts > fetchIncremental early-stops on first item older than sinceToken, "Test timedout in 5000ms". It is a pre-existing flake, unrelated to this change: run in
isolation it failed once and then passed
5 passed (5)on the next run with nocode change, and it imports only
scripts/ingestion/adapters/dailymed-adapter.jsanddailymed-fetcher.js--nothing this branch touches. CI's
testjob passed.CES:
[OK] CES CHECK PASSED: System is Compliant.exit 0.Line endings:
git ls-files --eolreports 779 tracked, 779w/lf, 0w/crlf.The committed blob contains 0 CR bytes (
git cat-file blob, per the byte-proofrule).
All four gate jobs green on head
390a6e4:Per section 19 this remains evidence about ONE lane against ONE base snapshot,
taken on
refs/pull/292/merge. It is not evidence about the composed five-lanestate.