fix(lint): the translation rule's remaining collection rungs read what the artifact's packages[] provide - #19625
Conversation
…inst a sibling package Test-first, red before the fix: 18 failed | 114 passed. Every one of the six collection rungs of `translation-target-unknown` builds its universe from the top-level collection alone, so on `os build`'s per-package leg a package translating what a SIBLING package of the same artifact declares is reported as an orphan at `error`. Each rung is pinned in both directions in one describe block — the false orphan that must disappear, and the true orphan that must remain — plus the CONTROL leg (the same bundle judged alone) so "no findings" cannot be confused with the rung going quiet. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
…t the artifact's packages[] provide `translation-target-unknown` built the universe for `views`, `pages`, `actions`, `apps`, `dashboards` and `flows` from the top-level collection alone, while the same file already read `packages[].manifest.…` for `navigationContributions`, `objectExtensions` and `objects`. So on `os build`'s per-package leg a package translating what a SIBLING package of the same artifact declares was told the target does not exist, at `error`, with a remedy that deletes a translation the runtime honours. All six fold RECORDS, for three distinct reasons: `dashboards` / `flows` / `apps` carry a sub-rung derived from the record; the `actions` record is read downstream by `checkActionParams` and carries the owner that keeps a bound action out of `globalActions`; `views` and `pages` have no bundle rung of their own at all and contribute facts under the object they bind to. `apps` is the half #18442 did not cover: that change reads contributions, so a sibling's declaration was invisible whether or not this package contributed into the app. Folding the records before the contributed-only pass also stops such an app acquiring a diagnosis that points at a package in this artifact. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
…ngeset The file header documented the `packages[]` reach for the object ladder only, which left the implementation reaching wider than the declaration — the same mismatch, in the opposite direction, that this rule's object rung was carded for. One paragraph now names the single carrier reader, the disposition table that decides which collections may be asked of it, and why `apps` reads both that carrier and `navigationContributions`. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bd681fbe4436c82020cde74744f885df0c9d100a && git checkout bd681fbe4436c82020cde74744f885df0c9d100a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 744a0a3f1d501e18385d7d9b43aa1792db6e23e3 1ef60553c7f227254a30ad09184d494d6c414254 && git checkout -B drift-repro 744a0a3f1d501e18385d7d9b43aa1792db6e23e3 && git merge --no-ff 1ef60553c7f227254a30ad09184d494d6c414254
node scripts/docs-audit/affected-docs.mjs --json 744a0a3f1d501e18385d7d9b43aa1792db6e23e3 |
|
Contract reviewServed-tier: 204/204
① Derived judgments1 · One reader, seven call sites — RE-DERIVED. 2 · Records, not names, three reasons — RE-DERIVED, and each reason holds for its own level. The mechanical basis is real: 3 · False-negative controls — PRESENT FOR ALL SIX, and they were run. Every one still reports, at exactly the right path: dashboard name, widget id, flow name, screen id, action name, object-bound sibling action routed to its owner rather than made globally addressable, view name, tab name, app name, nav id. Plus precedence (a sibling's widget id still errors under this package's same-named dashboard), #18442-intact, segment-reference-makes-nothing-addressable, and single- 4 · #18442 and 5 · The ablation — ESTABLISHED, by the reviewer's own mutation rather than by the claim's shape. The single anchor was guarded on a private copy so only the six new rungs are neutralised: all six revert to the false orphan, and #19064's object rung still returns 6 · Scope fence — HELD, and the line-drawing was right. 3 files, +628/−22 (GitHub agrees), the 455 test lines additive onto a pre-existing 3045-line file, no probe residue. Not touching 7 · Semver and 9 · NOT-MEASURED declarations — SOUND. Exit 3 is 10 · Self-test-only families — one genuine gap. All five root scripts are 11 · The PR body — every checkable sentence true at head. The merge-base coordinate table ( 12 · The docs sweep — ADEQUATE, and fully reproducible. Under one consistent convention (case-insensitive line hits over ② Semver level
③ Boundary flags⛔ FAIL BASIS — CI was red and incomplete at this head when the review was taken. Latest run per check NAME, 33 runs, all names distinct,
Remedy: no code change. The failing package declares no dependency on SEAT — the state since that reading, and one thing the blast-radius argument does not cover. The seat had reached the same diagnosis independently before this review returned, recorded it at comment Correctable before a maintainer reads it, not fail bases:
Implemented-by: VERDICT: FAIL Generated by Claude Code |
Re-run outcome:
|
Contract reviewServed-tier: 225/225
① Derived judgmentsCI at this head — RE-READ, and the fail basis is DISCHARGED. 34 check runs, all names distinct, latest run per check NAME, by job conclusion:
The flake reading — the reviewer AGREES with the seat's correction, and sharpens it. The mechanism was verified rather than accepted. At head, exactly seven workspace manifests declare So the correction is right, and the first record was incomplete in a way worth naming precisely: the finding that the failing package declares no dependency edge to One refinement the re-run now licenses, by #19631's own stated criterion ("a pass means intermittent, a failure means deterministic at this load"): the re-run passed at the same head with the same diff, so the reading is intermittent, not deterministic. The composite the evidence supports is fragile against a class of diffs that includes this one, and probabilistic rather than deterministic at this load — which is exactly why #19631's refusal to open with a
Everything else from the first record stands, re-derived and unchanged. One reader, seven call sites ( ② Semver level
③ Boundary flagsNo fail basis remains. The sole basis in the first record was the CI state, and it is discharged at the same head by measurement, not by argument. Correctable, none blocking:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19349
translation-target-unknownbuilt the universe for six collection rungs from the top-level collection alone, while the same file already readpackages[].manifest.…for three others. Onos build's per-package leg a package translating what a SIBLING package of the same artifact declares was reported as an orphan aterror— a loud, wrong refusal whose remedy deletes a translation the runtime honours.Clause-②: no
Line numbers, re-derived by SHAPE on my own tree — and the card's divergence resolved
The card records two seats' numbers differing by about 90 lines and deliberately does not guess the cause. Re-derived at merge base
0b4022b41c, by grepping forrecordsOf(stack.CONST)and itsrecordsOf(stack.packages)siblings rather than by coordinate:⭐ The divergence is arithmetic, not a mystery. Every one of the six shifted by exactly +96:
PR #19347's net change to this one file is
+104 / -8= +96, and it insertedartifactProvidedObjectsand its docblock ABOVE every collection rung. So the triage seat measured on2277d1fcd1(before #19347 landed) and the construction seat's numbers were taken with #19347 applied. Neither set was wrong; they named different trees. My tree is post-merge and matches the construction seat exactly.The reproduction — both layers probed first-hand, ⛔ nothing inherited
A throwaway probe (deleted in the same act;
git status --porcelainverified empty, 0 lines) ran every level on the per-package legpackageBodyAsStack(body, entries)builds. Layer A re-reproduced rather than relayed, layer B probed where the card explicitly had not:dashboardserror·translations[0]["zh-CN"].dashboards.crm_overviewflowserror·….flows.lead_conversionglobalActionserror·….globalActions.export_allviewserror·….objects.crm_order._views.boardpageserror·….objects.crm_order._tabs.mineappserror·….apps.crm_app⭐
appsis partly covered, and the card was right to demand that be judged first — but BOTH halves were broken. #18442 closed the contributed-app level, which readsmanifest.navigationContributions: contributions, never declarations. A sibling'sapps[]declaration was invisible either way, so a second probe was run:translation-target-unknowntells a contributor package that the app it ships labels for is "not defined by this stack" —os buildper-package leg, app-name rung #18442, but the owner's own navigation ids are orphans — 1errorat….apps.crm_app.navigation.leads, diagnosed "this stack contributes no such item", advising a move to a package sitting in the same artifact.⇒ #18442 covers neither half of the DECLARATION question. Both are closed here.
viewsandpagesare deliberately bound tocrm_order, the object the TRANSLATING package declares, so these readings do not ride on #19064's object fold. If that rung regressed, the cases would fail for the wrong reason and the_views/_tabsreadings would be worthless.The carrier is proven, not assumed
Every one of the six keys carries disposition
concatinCOMPOSE_KEY_DISPOSITIONS, which is exactly what puts it insideASSEMBLED_PACKAGE_BODY_DISPOSITIONSand so inside an ADR-0130 D4 entry's assembled body — the same proofobjectExtensionsByTarget(#18441) and #19064's fold rest on.objectscarries dispositionobjects, also in that set. ⛔ And there is nostack.manifest.KEYform to read beside it: all six are STACK collections, not manifest keys.artifactProvidedObjectsis generalised toartifactProvidedRecords(stack, collection)— one reader of that carrier for all seven call sites, rather than six copies free to disagree about the entry shape.Records or names — decided PER LEVEL, with three different reasons
⛔ Not one assumption applied six times. All six fold records, but not for the same reason:
dashboards,flows,appsactionUrls; screen node ids and each screen'sconfig.fields[].name; navigation ids). Names alone would resolve the top key and then judge that sub-rung against an EMPTY set — #19064's trap, moved one rung down. Forflowsthe record also carriesotherNodes, which is a DIAGNOSIS ("declares it as a TYPE node, not ascreen") a name-only fold would lose.actionscheckActionParamsjudgesparams.NAMEoff it._actions, neverglobalActions. Registering it globally would make legal a key the resolver never reads.views,pages_views,_sectionsand_tabsFACTS under the object they bind to, so the record is the only thing carrying both the fact and its binding.Precedence follows #19064: where the stack in hand and a sibling declare the same name, the declaration this leg is JUDGING keeps the slot (
ownDeclaration), foractions,dashboardsandflows— the three whose fold carries a value.viewsandpagescontribute only Set adds, so two adds are one add and no precedence rule is needed.appsaccumulates nav ids into one Set per name, which is the merge the declaration loop already performs for two same-named entries and whatconcatcomposition delivers at runtime.apps: the artifact fold runs BEFORE the contributed-only pass, so a sibling-declared app is a DECLARED app and never acquires thecontributedOnlydiagnosis. An app owned OUTSIDE the artifact keeps #18442's wording, pinned below.Both control directions, per level
A false-positive fix without a false-NEGATIVE control is the shape this repo keeps carding, so every acceptance sits beside its control in the same block:
globalActionsstill errors, with its "is bound to object" redirectdefineStackshape is untouched — all six are stack collections with nostack.manifestformOne fixture correction is worth naming: the
record:detailscase first used aregionsspelling the shared page walk does not recognise, so it failed after the fix. That was a wrong FIXTURE, not a wrong fix — corrected to theregions[].components[]shape the file's own section-anchor block uses, and the case then passed in both directions.Reverse verification, from the COMMITTED fix
scripts/ablation-replace.mjs, one anchor, guarding the fold so that ONLY the six new rungs are neutralised and #19064's object fold survives — which makes the reds provably this PR's:translation-target-unknown's OBJECT rung reports a false orphan on the per-package leg — the rule's universe readsstack.objectsonly, while its own docblock claimsartifactProvidedObjectNamesreach #19064 object rung went red, which is the targeting proof.src/, not a staledist/. That is the false-green hazard closed by observation rather than by assertion.git diff HEADis 0 lines andgit status --porcelainis 0 lines.Checks — real exit codes, captured before any pipe
pnpm --filter '@objectstack/lint^...' build(dependency closure)pnpm --filter @objectstack/lint test— 108 files, 4106 passed / 5 skippedpnpm --filter @objectstack/lint typecheck(incl.check:test-typecheck)pnpm lint— repo-wideeslint . --no-inline-config, ⛔ not narrowedscripts/pm/dispatch-gates.mjs --commandsderives for this diffpnpm check:docs-transcript-drift, re-run afterpnpm --filter '@objectstack/lint...' builddispatch-gates --ranreconciles: 59 derived, 56 run, 3 NOT-MEASURED, 0 UNRUN, with every family carrying a recorded exit code so the NOT-MEASURED count is derived rather than claimed. The derivation was re-taken aftergit fetch origin mainand was byte-identical.⊘ NOT MEASURED locally, declared to CI — each exited 3, which these gates define as "nothing was measured", neither a pass nor a finding. All three refuse without a whole-repo
pnpm build:check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt.Acceptance notes
content/docs/protocol/kernel/i18n-standard.mdx's key table describesobjects.{object}as "an object this stack defines, or a platform object". fix(lint): the translation rule's OBJECT rung reads what the artifact's packages[] provide #19347 noted the same row as already understating the universe and named "the next rung card on this rule" as its natural carrier — that is this card, and this change widens the same table's understatement further, now across six more rungs. ⛔ It is still not filed and not fixed here: the declared file surface for this card is this rule, its tests and.changeset/, andcontent/docs/is outside it. Noted for whoever owns that page.packages[]reach for the object ladder only, so the implementation would have reached wider than the declaration — the same mismatch, reversed, that got the object rung carded. One paragraph in the header now names the single carrier reader, the disposition table that decides which collections may be asked of it, and whyappsreads both that carrier andnavigationContributions.🤖 Generated with Claude Code
https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Generated by Claude Code