You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
objectql: a manifest objectExtensions entry or hydrated row whose extend folds to a legacy key opens a second, owner-less object entry #28858
Filing gate: ① should-fix of the PR #28853 contract review (card #28834), out of that PR's surface by its claim. Carried to its own card by this seat. Filed by domain:server seat 1. ⛔ Not a claim. Graded by this seat p3 under 「定级你可以自己决定」 (Leehom, 2026-11-01): it reaches only a legacy ns__name object key, and no shipped manifest reaches it (measured on #28834).
Reach: a code package whose manifest objectExtensions[] names a short extend that getObject folds to a legacy ns__name key, and a stored object_extension row with such a spelling at boot hydration.
What happens (read by the review at PR #28853's head 461552ac)
PR #28853 makes the object_extension item gate refuse an extend that getObject resolves to a different key. Two other entries still file extend verbatim:
the manifest objectExtensions loop in packages/objectql/src/engine.ts (about L9455);
the at-rest hydration of a stored row.
registerObjectExtension then calls registerObject with computeFQN as the identity. That opens an owner-less entry under the short name, beside the real object, and nothing checks it (assertSingleOwnerPerObject has no source caller).
Wanted
One answer at every entry: an extend that getObject folds to a different key never opens a second entry. Either refuse it with the item gate's sentence, or report it at the fold the way the registry reports a field-patch miss. The route is the dispatching seat's call. Measure first: count stored rows and manifests in the repo and in hotcrm, and say whether a legacy key is still reachable at all.
Dedupe: searched extend getObject fold legacy key manifest objectExtensions. Related: #28834 (the item door half, PR #28853). New.
Claim: PM loop round 1 · card thread · 2026-11-02T01:44Z
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01FEkQ5oTRCUNQ4EA3YV84sZ (the #28412 / #28717 / #28771 / #28809 card thread, continuing)
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-jqgfyc (the card thread's harness-pinned branch, restarted from main after PR #28853; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z); seat 1 filed and graded the card p3. Route (left to the dispatching seat): resolve extend through getObject's fold at registration and report the spelling at the fold; never refuse at boot.
File surface: registerObjectExtension / the manifest objectExtensions loop in packages/objectql and the stored-row hydration path in packages/metadata-protocol, their tests and a patch changeset. ⛔ No packages/spec/**, no change to the #28853 item gate, computeFQN or parseFQN.
Container & model: S, mode:thread, model and effort as the thread was opened
Clause-②: no (expected: a boot no longer opens a phantom entry; the dev measures it)
Metadata-protocol: none (expected)
Responsibility: the code is the extension registration of @objectstack/objectql · the platform path is an owner-less second object entry under a short name · reached by a manifest or stored extend that folds to a legacy ns__name key
Thread-read: none
Premise: unmeasured; step 1 of the order measures reach and may close the card as not reachable
Serial constraints cleared: PR #28853 (#28834, seat 1) must land first; the order says so. #28835 (seat 2) touches the field merge in the same fold; whichever lands second merges origin/main once.
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim.
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
os-dev-report
{
"issue": 28858,
"status": "done",
"branch": "claude/project-thread-jqgfyc",
"pr": "#28887",
"session": "session_01FEkQ5oTRCUNQ4EA3YV84sZ",
"premise_still_valid": true,
"summary": "Premise (step 1): the count is 0 everywhere. In this repo (main 7e60dc2, git grep of extend values over examples, packages, apps and qa, tests and dist excluded) the three non-test extend values (showcase_account, sys_sso_provider, dc_account) are each a declared object exact name, no shipped object or seed name contains a double underscore, and no object_extension row is seeded. In hotcrm (origin/main a598256): 0 objectExtensions entries outside docs, 0 such names. Still reachable by authoring: a probe on the spec dist ObjectSchema accepted crm__account and survey__c, and a stored object row from the FQN era reaches it the same way; the seat kept the card on that ground. Fix: registerObject extend arm (the one place the manifest objectExtensions loop and registerObjectExtension for a stored row both reach), right after the extend refusals, files the contribution under extensionTargetKey(extend). That is extend itself when an entry is registered under it or nothing it folds to is registered, and the getObject fold (resolveObjectKey) only when that names a different key. The folded spelling is named once at warn per spelling, resolved key and package (deduped like reportFieldPatchMisses), with the key to write. No boot refusal, no change to the #28853 item gate, computeFQN and parseFQN untouched, no packages/spec, no new export, gate or error code. Clause-② no: no accept set moves and nothing is exported; for a package with the folded spelling (0 measured) the extension fields now reach the object they name, where on main they reached no read. Metadata-protocol none, patch changeset. Residual: a legacy owner that registers after the extension is not there to fold to yet, so that extension is filed as written, as on main.",
"tests": "objectql registry-extension-legacy-key-fold.test.ts (4, from source): manifest extend case beside owner crm__case lands on crm__case, no case contributors, one object listed, one warn across re-read and re-register; registerObjectExtension answers crm__case, warns once, and the short-name withdrawal removes it; controls: the registered key registers with no warn, and an object registered exactly as case beside crm__case keeps its extension with no warn. Red on main 45efba2: 2 failed | 2 passed. runtime meta-object-extension-item-door-fit.test.ts new case: a stored row respelled at rest to extend case, then loadMetaFromDb lands the field on crm__case, no case entry, one warn. Red on main with objectql rebuilt from base via pnpm --filter (head-only string count 0 in dist): 1 failed | 5 passed, a case contributor present; head rebuilt (count 2): 6/6, the #28853 item-gate pins green in the same file. Ablation via ablation-replace.mjs (the resolution line deleted): both objectql pins red, 2 failed | 2 passed; restore ok, blob == HEAD, git diff HEAD empty. Head 6db3a09 under the verify lock: objectql 524 files, 10030 tests passed; metadata-protocol 297 files passed, 4 skipped, 22093 tests passed, 26 skipped.",
"gates": "dispatch-gates --commands on 6db3a09: 63 families, each run with its exit captured before any pipe, all exit 0; --ran: 63 derived, 63 run, 0 NOT-MEASURED. pnpm typecheck in full (tsc plus check:test-typecheck) exit 0 for objectql and runtime, both ledgers unchanged; eslint --no-inline-config on the 3 touched files exit 0. origin/main moved 7 commits after the gates; git merge-tree is clean. CI on PR #28887 was not awaited.",
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "2 — mcp__github__issue_read x2 (get #28858, get_comments #28858); no write tool",
"api_writes": "3 — plain git push of claude/project-thread-jqgfyc (new branch, 6db3a09); relay fleet-write pr_create (run 41381215089, read-back identical, assignees none, no labels named); this os-dev-report via post-stamped.mjs --comment=28858",
"open_questions": [],
"out_of_scope_findings": []
}
PR #28887 merged by the merge queue as 5e1b9b0a77 on main (Fixes #28858; Clause-②: no, with no contract review owed; ACCEPT 6568374337). The card closed on the merge.
Residual (named in the report): a legacy owner that registers after the extension has nothing to fold to yet, so the extension is filed as written, as on main.
Release: branch claude/project-thread-jqgfyc merged; nothing unpushed. pm:dispatched removed and the card unassigned.
Activity
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Claim: PM loop round 1 · card thread · 2026-11-02T01:44Z
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5oTRCUNQ4EA3YV84sZ(the #28412 / #28717 / #28771 / #28809 card thread, continuing)Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-jqgfyc(the card thread's harness-pinned branch, restarted frommainafter PR #28853; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z); seat 1 filed and graded the card p3. Route (left to the dispatching seat): resolve
extendthroughgetObject's fold at registration and report the spelling at the fold; never refuse at boot.File surface:
registerObjectExtension/ the manifestobjectExtensionsloop inpackages/objectqland the stored-row hydration path inpackages/metadata-protocol, their tests and apatchchangeset. ⛔ Nopackages/spec/**, no change to the #28853 item gate,computeFQNorparseFQN.Container & model:
S,mode:thread, model and effort as the thread was openedClause-②: no (expected: a boot no longer opens a phantom entry; the dev measures it)
Metadata-protocol: none (expected)
Responsibility: the code is the extension registration of
@objectstack/objectql· the platform path is an owner-less second object entry under a short name · reached by a manifest or storedextendthat folds to a legacyns__namekeyThread-read: none
Premise: unmeasured; step 1 of the order measures reach and may close the card as not reachable
Serial constraints cleared: PR #28853 (#28834, seat 1) must land first; the order says so. #28835 (seat 2) touches the field merge in the same fold; whichever lands second merges
origin/mainonce.Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-project-manager, recorded by this claim.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
os-dev-report
{
"issue": 28858,
"status": "done",
"branch": "claude/project-thread-jqgfyc",
"pr": "#28887",
"session": "session_01FEkQ5oTRCUNQ4EA3YV84sZ",
"premise_still_valid": true,
"summary": "Premise (step 1): the count is 0 everywhere. In this repo (main 7e60dc2, git grep of extend values over examples, packages, apps and qa, tests and dist excluded) the three non-test extend values (showcase_account, sys_sso_provider, dc_account) are each a declared object exact name, no shipped object or seed name contains a double underscore, and no object_extension row is seeded. In hotcrm (origin/main a598256): 0 objectExtensions entries outside docs, 0 such names. Still reachable by authoring: a probe on the spec dist ObjectSchema accepted crm__account and survey__c, and a stored object row from the FQN era reaches it the same way; the seat kept the card on that ground. Fix: registerObject extend arm (the one place the manifest objectExtensions loop and registerObjectExtension for a stored row both reach), right after the extend refusals, files the contribution under extensionTargetKey(extend). That is extend itself when an entry is registered under it or nothing it folds to is registered, and the getObject fold (resolveObjectKey) only when that names a different key. The folded spelling is named once at warn per spelling, resolved key and package (deduped like reportFieldPatchMisses), with the key to write. No boot refusal, no change to the #28853 item gate, computeFQN and parseFQN untouched, no packages/spec, no new export, gate or error code. Clause-② no: no accept set moves and nothing is exported; for a package with the folded spelling (0 measured) the extension fields now reach the object they name, where on main they reached no read. Metadata-protocol none, patch changeset. Residual: a legacy owner that registers after the extension is not there to fold to yet, so that extension is filed as written, as on main.",
"tests": "objectql registry-extension-legacy-key-fold.test.ts (4, from source): manifest extend case beside owner crm__case lands on crm__case, no case contributors, one object listed, one warn across re-read and re-register; registerObjectExtension answers crm__case, warns once, and the short-name withdrawal removes it; controls: the registered key registers with no warn, and an object registered exactly as case beside crm__case keeps its extension with no warn. Red on main 45efba2: 2 failed | 2 passed. runtime meta-object-extension-item-door-fit.test.ts new case: a stored row respelled at rest to extend case, then loadMetaFromDb lands the field on crm__case, no case entry, one warn. Red on main with objectql rebuilt from base via pnpm --filter (head-only string count 0 in dist): 1 failed | 5 passed, a case contributor present; head rebuilt (count 2): 6/6, the #28853 item-gate pins green in the same file. Ablation via ablation-replace.mjs (the resolution line deleted): both objectql pins red, 2 failed | 2 passed; restore ok, blob == HEAD, git diff HEAD empty. Head 6db3a09 under the verify lock: objectql 524 files, 10030 tests passed; metadata-protocol 297 files passed, 4 skipped, 22093 tests passed, 26 skipped.",
"gates": "dispatch-gates --commands on 6db3a09: 63 families, each run with its exit captured before any pipe, all exit 0; --ran: 63 derived, 63 run, 0 NOT-MEASURED. pnpm typecheck in full (tsc plus check:test-typecheck) exit 0 for objectql and runtime, both ledgers unchanged; eslint --no-inline-config on the 3 touched files exit 0. origin/main moved 7 commits after the gates; git merge-tree is clean. CI on PR #28887 was not awaited.",
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "2 — mcp__github__issue_read x2 (get #28858, get_comments #28858); no write tool",
"api_writes": "3 — plain git push of claude/project-thread-jqgfyc (new branch, 6db3a09); relay fleet-write pr_create (run 41381215089, read-back identical, assignees none, no labels named); this os-dev-report via post-stamped.mjs --comment=28858",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Landing record · #28858 · PR #28887
PR #28887 merged by the merge queue as
5e1b9b0a77onmain(Fixes #28858;Clause-②: no, with no contract review owed; ACCEPT6568374337). The card closed on the merge.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28887-*'origin/mainby contentgit diff --quiet 5e1b9b0a77 origin/main --the PR's 4 filesgit merge-base --is-ancestor 5e1b9b0a77 origin/mainextendthatgetObjectfolds to a legacyns__namekey is filed under the resolved key and named once atwarn, never refused at boot, so a boot no longer opens an owner-less second entry. The fix(metadata-protocol)!: an object_extension item's refusals fit the item door, and its target is the key the object is registered under (#28834) #28853 item gate,computeFQNandparseFQNare unchanged.@objectstack/objectqlpatch,Metadata-protocol: none.main.Release: branch
claude/project-thread-jqgfycmerged; nothing unpushed.pm:dispatchedremoved and the card unassigned.Generated by Claude Code