Uh oh!
There was an error while loading. Please reload this page.
fix(objectql): relax registerObject packageId to optional (#12623) - #12778
Conversation
Widens `SchemaRegistry.registerObject`'s `packageId` parameter to `packageId?: string` (no default), matching the sibling `registerItem` and the behaviour `applyProtection` already documents as intended: a bare `registerObject(schema)` call passes `packageId: undefined` through, which leaves the item provenance-free rather than stamping `_packageId` / `_provenance`. `ObjectContributor.packageId` widens to `string | undefined` to match — the exact value `registerObject`'s own parameter was called with. Dissolves the 14 ledgered TS2554 in packages/rest's test-typecheck debt (test-typecheck-debt.json shrinks from 20 to 6 entries, deleting 6 files that reach zero). Maintainer ruling: issue #12623 comment 5434929046, Option A.
Pins the exact risk the maintainer ruling names by hand: packageId optional is correct, packageId optional WITH A DEFAULT is the bug wearing the fix's clothes. Asserts key ABSENCE (hasOwnProperty) on a bare call, paired with a positive control that a call WITH a packageId still gets stamped — the ablation for this pin follows in a separate commit per the dispatch's required protocol.
…12623) MUTATION (not yet applied): change registerObject's signature from packageId?: string, to packageId: string = '__runtime__', i.e. give the widened parameter a default — the exact wrong-fix shape the ruling warns about (mirrors engine.ts's facade default). PREDICTION: - "a bare registerObject(schema) call — no packageId — produces a provenance-free item" -> RED. `applyProtection(schema, { packageId: '__runtime__' })` will stamp `_packageId = '__runtime__'` and `_provenance = 'package'` onto the schema (applyProtection stamps whenever ctx.packageId is truthy), so both hasOwnProperty assertions flip to true against an expected false. - "positive control: registerObject(schema, packageId) DOES stamp provenance" -> stays GREEN. An explicit packageId argument shadows the default either way, so this test's behavior is unaffected by the mutation — it is the control precisely because it cannot distinguish "no default routed" from "stamping is broken". Named failing set: 1 of 2 tests in packages/objectql/src/registry-register-object-optional-package-id.test.ts (file's first `it`), 2 assertions.
Observed exactly as predicted in 009fb03: - Mutated registerObject's packageId to `packageId: string = '__runtime__'` (registry.ts line 1590, anchored sed, single-line diff verified before/after via anchored grep and git hash-object). - Pin test run against mutated tree: "a bare registerObject(schema) call ... produces a provenance-free item" -> FAILED (hasOwnProperty '_packageId' flipped true; log line confirms "[Registry] Registered object: bare_fixture (own, priority=100) from __runtime__"). "positive control: registerObject(schema, packageId) DOES stamp provenance" -> PASSED, unaffected, as predicted (1 failed | 1 passed). - Restored via `git checkout HEAD -- packages/objectql/src/registry.ts` inside a `trap ... EXIT INT TERM` (absolute paths throughout). Restore proof: `git diff HEAD` empty, `git hash-object` equals the pre-mutation HEAD blob (01d752d), zero marker residue, pin test re-run green (2 passed) on the restored tree. Script: scratchpad issue-12623/ablation.sh (local, not committed).
minor bump on @objectstack/objectql — public-API accept-set widening, not a behavior fix (the runtime path already treated a missing packageId as undefined wherever no tsc program enforced arity).
os-zhuang
commented
Aug 27, 2026
PM review — ACCEPT on substance; enqueueing on all-greenReviewer of record: The one way this fix goes wrong — checked firstOptional, no default. That was the whole risk: The pin asserts key absence via The ablation is the exact wrong-fix — The |
📓 Docs Drift CheckThis PR changes 2 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 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 36caefc3e040f54e8783a32c6ffc35c373fd8932 && git checkout 36caefc3e040f54e8783a32c6ffc35c373fd8932
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4d5b4f83254ee1b7f53197073cfc6eee3025418a be77dd20f0a90121f6ab4c243b82a8d657d66455 && git checkout -B drift-repro 4d5b4f83254ee1b7f53197073cfc6eee3025418a && git merge --no-ff be77dd20f0a90121f6ab4c243b82a8d657d66455
node scripts/docs-audit/affected-docs.mjs --json 4d5b4f83254ee1b7f53197073cfc6eee3025418a
|
os-zhuang
commented
Aug 27, 2026
PM addendum — the 12 flagged docs pages, disposed ofThe drift bot listed 12 hand-written pages plus 2 release-owned ones. Almost all arrived via None of the 12 hand-written pages documents The single mention is on a release-owned, read-only page, and it owes nothing either. Worth stating why this list was safe to clear quickly while #12774's empty list was not: that change altered behaviour, so a "nothing to list" needed hand-reading. This one changes an arity that no page documents, and the check is a single grep with a control — the flagged rows are noise from ubiquitous tokens, and I can say that as a reading rather than an assumption. CI is still mid-flight (8 jobs), nothing red. Still enqueueing on all-green. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12623
What
Relaxes
SchemaRegistry.registerObject'spackageIdparameter fromstring(required) to
packageId?: string(optional, no default value), matchingthe sibling
registerItemand the behaviourapplyProtectionalreadydocuments as intended.
Clause ② — this is a ruled public-API accept-set widening, not a silent
one. Maintainer ruling: issue #12623 comment
5434929046,
Option A. Widening breaks no existing caller — every call site already
supplied
packageId, or (for the 68 sites with notsctest-layer gate)already ran with
packageId === undefinedat runtime since JS does notenforce TS arity. The 82 single-argument call sites this repo carries stop
being latent type errors, and the 14 ledgered
TS2554inpackages/rest'stest layer dissolve in the same stroke.
The risk the ruling names by hand, and how it's pinned
The one way this fix goes wrong is a default sneaking onto the widened
parameter (mirroring
engine.ts's facade default of'__runtime__') —applyProtectionruns unconditionally on everyregisterObjectcall, so adefaulted
packageIdwould silently stamp_packageId/_provenance: 'package'onto every bare-call fixture, exactly what the helper's owncomment says a package-less registration must avoid.
Pinned in
packages/objectql/src/registry-register-object-optional-package-id.test.ts:a bare
registry.registerObject(schema)call is asserted provenance-freeby key absence (
hasOwnProperty, not=== undefined— a key present-and-undefinedmust still fail), paired with a positive control that a callwith a
packageIdstill gets stamped.Ablation, following the trap-guarded protocol: predicted (committed
before mutating,
009fb03ba) that giving the parameter a default(
packageId: string = '__runtime__') turns the bare-call assertion redwhile the positive control stays green. Mutated
registry.tsline 1590under
trap ... EXIT INT TERMwith absolute paths, confirmed the mutationlanded via anchored grep +
git hash-object, ran the pin (observed exactlyas predicted — 1 failed / 1 passed, log line
"...bare_fixture (own, priority=100) from __runtime__"confirms thedefault routed), then restored and proved it (
git diff HEADempty,git hash-objectequal to the pre-mutation HEAD blob, zero marker residue,pin re-run green). Recorded in
bca89a89c.A necessary, narrowly-scoped consequence
ObjectContributor.packageId(the internal per-contributor bookkeepingrecord, also exported from the package) widens from
stringtostring | undefinedto match — it is the exact valueregisterObject's ownparameter is called with, and TypeScript would otherwise refuse the
contributor construction. This is the only other type touched; every
.packageIdread on it already tolerated the value being effectivelyoptional at runtime, confirmed by a clean
tsc --noEmitacross the wholepackage with zero other ripples.
SchemaRegistry.registerNamespace(a separate, still-required-packageIdmethod) is called from inside
registerObjectonly when anamespaceisalso supplied; since a namespace registration needs an owning package id to
mean anything, that call is now guarded on
packageIdbeing present too(
if (namespace && packageId)) rather than wideningregisterNamespace'sown contract, which is out of scope here.
Explicitly out of scope (per the ruling and the dispatch)
it) — both declined by the ruling.
as any/as nevercasts on the schema argument at several of the82 sites (objectql
registerObject的参数标成 schema 的 OUTPUT 类型(z.infer),合法的作者态字面量一律 TS2740 —— 这是 spec/client 两份测试层欠账的同一根因 #5543 residue, a separate card).packageIddefault (engine.ts) — deliberatethere.
Changeset
minoron@objectstack/objectql— a public-API accept-set widening thatbreaks no caller, but does change the method's declared TypeScript contract,
so it earns a real bump rather than riding as an implicit patch. Full
FROM → TO in the changeset body; ADR-0087 marked
not-required(no key,export or shape removed/renamed — nothing for
objectstack migrate metatorewrite).
Tests
packages/objectql: full suite — 245 files / 4243 tests passed;typecheckclean (tsc --noEmit+tsc --noEmit -p tsconfig.scripts.json).packages/rest:typecheckclean, includingcheck:test-typecheck(ledger re-recorded via
gen:test-typecheck-debt: 20 → 6 errors across3 files, the 6 pre-existing/unrelated
TS2345/TS6133debt untouched);the 6 files whose
TS2554dissolved re-run at the vitest layer — 90tests passed.
See the PR comment for the full structured report (gate results, premise
re-measurement, MCP call count).
Generated by Claude Code