feat(lane3): merge integrity -- cross-cycle claim preservation on the compound axis (DO NOT MERGE INDIVIDUALLY) - #295
Closed
mosesy5688-cell wants to merge 1 commit into
Closed
Conversation
… compound axis
Preserves cross-cycle conflicting values at the compound deep-merge point.
deepMergeCompound is COMPOSED, never modified: aggregated-deep-merge.js
carries a zero diff, so winner invariance is structural rather than merely
tested. Six frozen top-level keys are emitted, and nothing else.
E-1 split, per path:
inchi_key / smiles_canonical winner IS the previous value
-> preserved_against_null
molecular_formula / iupac_name incoming null wins and the previous value
is destroyed -> competing_claims
New modules (all seven pre-approved filenames):
scripts/factory/lib/merge-claims-canonical.js canonicalisation, equality
scripts/factory/lib/merge-claims-revalidate.js 3h re-validation + 3j pass
scripts/factory/lib/merge-claims-wrapper.js the compound strategy
Wiring, against the frozen ten-step ledger:
aggregated-merger.js 250 -> 249 lines, headroom 1
stage-3-merge.js emits the claims counters and bounded samples; the
only consumer printed a hardcoded key list, so a new
counter would have been silently discarded
recovery-merge-all.js exports a pure compound-recovery merge and wraps
main() in an import.meta.url guard (F-2). Compound
axis only; the drug-label axis stays unwired.
Overflow is fail-soft-LOUD: never throws, never process.exit, never silently
truncates. Admission is skip-and-continue. The three overflow keys are sticky
and compose cross-cycle by F-1: union of fields, per-path MAX of counts.
The pre-existing recovery key-function defect (drug-labels.jsonl has no key
map entry, conditional throw) is REPORTED, NOT FIXED.
Tests: 104 new assertions across four files; full suite 2895 passed / 13
skipped; CES passed; astro build green.
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>
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 3 -- Merge Integrity (independent rebuild)
Preserves cross-cycle conflicting values at the compound deep-merge point.
deepMergeCompoundis composed, never modified, so winner invariance isstructural rather than merely tested.
DO NOT MERGE INDIVIDUALLY. No individual lane PR merges or deploys; the
five-lane composition gate applies all five diffs.
Required attestations
10.18 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 PR is not a green composed state. A re-test gate on the composed branch
is a Founder decision.
10.2
TYPECHECK = NOT CONFIGURED / NOT CLAIMED. Per D-1 no typecheck isrequired, no prep PR and no temporary compiler. Additionally
tsconfig.jsonexcludes
scripts/**, so a typecheck could not have covered this lane's.jsmodules even if one existed -- though it would have covered this lane's
tests/factory/*.test.ts.10.1 Base proof
git ls-remote origin refs/heads/mainwasc88c1f52...before the first edit,immediately before commit, and immediately after push.
10.3 The six key names are absent from
mainat basegit grep -c <name> c88c1f5 -- .returns 0 files for every one ofcompeting_claims,preserved_against_null,field_sources,claim_set_state,claim_overflow_fields,claim_overflow_counts.Greenfield against
main.10.4 Zero diff on the oracle
git diff c88c1f5 -- scripts/factory/lib/aggregated-deep-merge.jsprintsnothing (0 bytes of output). The file is not in the changed-file list.
10.5 The four frozen paths as implemented
Byte-identical to 3a.
idandpubchem_cidare excluded and closed.10.6 The line ledger AS EXECUTED
:127-130) -> 1-linemakeDeepMergeCounters()call:95):97-100)merged_deep_*stats (:169-174) packed into threemerged_deep_claims: deepCounters.claims,let revalidateStats = null;fnamebranch (:227-229)if (bootstrapStats)blockMeasured final:
scripts/factory/lib/aggregated-merger.js= 249 lines,headroom 1. Step 3 costs only one line because
revalidatePrevClaimsisre-exported from
merge-claims-wrapper.js. Step 2 also drops the now-unuseddeepMergeCompoundname from that import (zero line delta). A zero-deltacomment extension on the existing
:225line explains the 3j call in place.10.7 Winner invariance
23 assertions in
tests/factory/merge-claims-differential.test.tsover thefull 4a corpus -- both sides populated and equal; both populated and different;
incoming
null; incoming''; incoming explicitundefined; previousnull;both
null; key missing on either side; a key present oncurrentwith anexplicit
undefined; keys only onprev;prevalready carrying all sixkeys while
currentcarries none; plus the E-1 split per path. Each casestrips the six keys from BOTH sides, then asserts deep equality, key-order
identity and canonical-string identity. All pass.
10.8 E-1 evidence, per path (emitted records)
For the two structural paths the winner IS the previous value, so condition 4
holds and the event is a preservation. For the other two the winner is
null,condition 4 fails, and the previous value becomes a claim -- which is exactly
what happened to it.
10.9 Overflow: no throw, skip-and-continue, 3e shapes, stickiness
Per-field cap counted across both containers (4 claims + 4 preserved on
inchi_key= 8):The sample carries a record
idand an allow-listedpathand no value.Cap-1 / cap / cap+1 are asserted for both caps; the record cap is exercised in
isolation with the path far below the field cap.
F-1 cross-cycle composition -- inherited 3 against a current-merge refusal
of 1 on the SAME path yields
{"molecular_formula":3}. MAX = 3; SUM would give4; overwrite would give 1.
10.10 Inherited re-validation, both containers, and the 3j boundary pass
Dropped: a non-allow-listed path, a wrong shape, a
nullvalue, a non-finitevalue, a dedup collision, a non-allow-listed preserved path, a wrong preserved
shape, and an illegal
SourceRef.preserved_against_nullis validated onshape / allow-list / legality / dedup only -- never re-derived against
today's winner.
3j prev-load-boundary pass over a prev-only record:
It never creates a container key on a record that has none, and never touches
the three sticky overflow keys.
10.11 No container- or record-level source promoted
Every
field_sourcesentry this lane writes is{source: null, status: "unknown"}; thestatus: "known"branch is dead code in schema v1.0 and isnever written. A record carrying
provenance.primary_source: "pubchem"andexternal_ids.sources: ["unichem"]promotes neither.10.12 Recovery path, and the pre-existing defect
scripts/factory/recovery-merge-all.jsnow exports the purerecoveryMergeCompoundRecords(runRecords, accumulatedRecords)and wrapsmain()in animport.meta.url === pathToFileURL(process.argv[1]).hrefguard.Direct script execution behaviour is unchanged. The compound entry is wired
explicitly (
fname === COMPOUND_FILE); the naturalMERGE_STRATEGY_PER_FILE[fname]wiring is NOT used, so the drug-label axisstays unwired. A test asserts that against comment-stripped source.
PRE-EXISTING DEFECT, REPORTED AND NOT FIXED. That file resolves its key
function without a fallback (
KEY_FN_PER_FILE[fname]at what is now:152)while
MERGE_FILEScontainsdrug-labels.jsonl, which the key map lacks. Thethrow is conditional: the loop
continues on a missing object and the keyfunction is only invoked for a non-empty record list, so it fires only when a
historical bundle actually holds a non-empty drug-labels file. The compound
file is first in
MERGE_FILES, so the compound strategy is reached before anysuch throw. Fixing it was NOT necessary to wire the compound strategy.
THE RECOVERY TOOL WAS NOT RUN. R2 WAS NOT ACCESSED. No client was created,
no credential read, no object uploaded and no latest pointer advanced at any
point. All four
R2_*environment variables were unset throughout.10.13
npm run buildastro buildcompleted: 1 page built in 1.38s. It isastro buildover asingle static page and is entirely inert for
scripts/**. Combined withD-1, no gate compiles or type-checks this lane's modules;
npm testis theonly verification.
10.14
npm testZero failures. The four new files:
merge-claims-preservation20 tests,merge-claims-canonical-parity44,merge-claims-differential23,merge-claims-overflow17 -- 104 passed, 0 failed.rc3b-locator-runtimeand
snomed-rehydrateboth pass; thecore.autocrlf=inputworkspace preventedthe CRLF failure entirely.
10.15 CES
New filenames, all from the seven pre-approved names, all clean against the 14
Art 9.1 patterns and none beginning
rights-:10.16 Byte audit (canonical command, section 11)
Command identity verified before use: 813 bytes, sha256
ffdf10c301824adb311ec1454f2bc44d24e58e696db36cc66acb66fd8123ac1a-- matchingthe Founder-mandated value exactly.
Every changed committed blob reports
nul=0 controls=0. The dedup separator iswritten as the escape
'\^@'; there is no literal NUL byte on disk.tests/factory/umls-mrconso-probe.test.tsis NOT in this diff and thereforenever enters the audit.
10.17 Per-file line counts,
splitlines()semantics (neverwc -l)scripts/factory/lib/aggregated-merger.jsscripts/factory/lib/stage-3-merge.jsscripts/factory/recovery-merge-all.jsscripts/factory/lib/merge-claims-canonical.jsscripts/factory/lib/merge-claims-revalidate.jsscripts/factory/lib/merge-claims-wrapper.jstests/factory/merge-claims-differential.test.tstests/factory/merge-claims-preservation.test.tstests/factory/merge-claims-overflow.test.tstests/factory/merge-claims-canonical-parity.test.tsscripts/factory/lib/aggregated-deep-merge.jsWorkspace:
git ls-files --eolreports 781w/lf, 0w/crlf. Disk bytesequal index blob bytes (raw
sha256sumagainstgit cat-file blob) on everyfile sampled, and
sha256(scripts/rc3b-audit/locator-artifact-schema.json)on disk is33e65017a28e77c54e2c819ffb1b7238deaed03aa95be09a75e29d9daabc8837.Design notes the reviewer should check
stage-3-merge.jsnow EMITS the counters. Its per-file printing was theonly consumer of the stats object and printed a hardcoded key list, so a new
merged_deep_claimskey would have been added to an object and then discarded-- a permanent claim loss would have produced no CI output at all. The printing
is extracted into an exported pure
logMergeStatsPerFile(perFile)so a test canassert a non-zero refusal count with no client, no credential and no R2. That
test is green.
The F-1 composition rule is exported as
composeOverflow. The frozen L3-1cases -- inherited 3 / current 2 and inherited 2 / current 3 on the SAME path --
are NOT reachable end to end: under schema v1.0 all four paths are top-level
scalars, so the winner is always one of the two sides' own values and at most
one new candidate per path survives the equal-to-winner filter. A single merge
can therefore refuse at most one item per path. The frozen numbers are
exercised directly against the rule, and end-to-end merges cover the reachable
same-path and disjoint-path cases. Only MAX satisfies all three cases: SUM
fails both same-path cases (5 vs 3), keep-inherited fails the second (2 vs 3),
and overwrite fails the first and third. The disjoint case alone passes under
SUM and keep-inherited, which is why all three are required.
Scope. Zero diff on
aggregated-deep-merge.js; nothing undersrc/**,.github/**,wrangler.toml, or any otherscripts/**path. No thirdstrategy-map key -- the map still has exactly two keys and the compound value
was changed in place. No value-identity assertion on the compound entry was
added. No file whose basename begins
rights-was created.Process-boundary disclosure
During evidence preparation, the executor wrote one temporary diff file to
Git-Bash /tmp/lane3dm.txt instead of the lane-specific scratch directory and
then deleted it. No other lane file was read or modified, and the temporary
file never entered a repository blob or commit. This was a confirmed
scratch-isolation procedure violation. Independent verification found no
effect on the accepted tree.
Accepted implementation judgments
The frozen same-path overflow numbers are not constructible through one current
end-to-end merge because the four governed paths are top-level scalars and the
winner filter admits at most one new refused candidate per path. The production
path uses composeOverflow directly; the frozen max rule is tested at that pure
boundary, while reachable cases remain end-to-end tested.
For testability and the 250-line cap, logMergeStatsPerFile was extracted as a
pure function, three canonicalisation helpers were relocated, and existing
assertions were moved between pre-approved test files. No assertion was removed
and no tested semantics were weakened.