Repository navigation
Description
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
Filing gate: ④ child of epic #28236 (P2, cut 2026-11-01 on the maintainer's 「四项提速措施全部按推荐执行」, schedule reviewed) · plan of record: ADR-0146 Rollout P2 "N-23a/b/c object extension — the field-patch whitelist". Split from the review's one-card N-23b: the __c doors stay #28564. ⛔ Not a claim.
Blocked-by: #28560
Part of #28236 (P2, child N-23d) · governs: ADR-0146 D10 second rule ("mergeObjectDefinitions applies it after the additive merges, and the ADR-0029 D9.2a scalar yield applies to it") · lane: domain:server (packages/objectql, packages/metadata-protocol read fold) · size: S–M.
Business reason. The patch N-23a declares has to reach the screen. "Industry" reads "Sector" on every form, list and API read of HotCRM's account, and the vendor's field otherwise stays as shipped. A patch aimed at a field that does not exist must be named, never silently ignored.
Measured (35b178bd0a).
objectql/src/engine.ts:8815-8833: the manifest loop buildsextDeffrom exactlyfields,label,pluralLabel,description,validationsandindexes, then callsregisterObject(…, 'extend', priority). It is the only'extend'registration inobjectql,metadata-protocolandruntime(git grep).objectql/src/registry.ts:559-601#mergeObjectDefinitions: fields are spread, validations merged by name, indexes concatenated. The three scalars yield whentenantAuthoredholds them (ADR-0029 D9.2a). The fold calls it at about :4140-4170. Removing an extender recomputes throughsubtractExtenderContributions(:5332).metadata-protocol/src/protocol-object-extender-fold.ts: the by-name read folds extenders onto a MetadataService body.- Registration refusals precedent:
ExtensionMembershipKeysRefusedError/ExtensionDefaultRowScopeRefusedErrorinregisterObject(about :3826-3836), before the contributor list opens. content/docs/data-modeling/object-extensions.mdx:65documentsfieldsreplacing a same-named field.
Wanted.
- Carry.
extDefcarries N-23a's slot. The registry fold and the by-name read fold serve it, so the list, the by-name read and the layered read agree. - Apply.
mergeObjectDefinitionsapplies each patch after the additive merges:- whitelisted keys onto the base field;
- option labels onto the option whose stored
valuematches.
The patch yields where D9.2a makes the three scalars yield. Two extensions that patch one key: the higherprioritywins, as for the scalars.
- Target check. A patch naming a field the target does not have, or an option value the field does not have, is refused:
- at both install doors (the installed base is in the registry: ADR-0141 D2 admitted the dependency first);
- at
os validatewhen the target is in the composed stack.
At the boot fold the same patch is not applied and is named once in the log, ⛔ never a boot refusal (AGENTS.md Startup registry reads).
- Who may patch is not narrowed here: any extension may patch display properties.
.customextension fields without__care metadata-protocol + cli + install-local:__cextension fields only in a.custompackage — refused at save/publish/restore,os validate, both install doors and duplicate #28564's refusal. - The slot's
[EXPERIMENTAL — not enforced]marker and anyplannedliveness row flip to live, citingmergeObjectDefinitions.Metadata-protocol: semantic(a declared key gains its effect), minor changeset.object-extensions.mdxgains the patch besidefields.
Tests.
- A patch changes the label, description, help text, placeholder and one option label on all object reads; type,
requiredand the option values are unchanged. - Uninstalling the extender restores the vendor's text.
- The D9.2a yield: a tenant-authored label is kept.
- Priority order between two patching extensions.
- An unknown field or option value is refused at install and validate, and named at boot without failing it.
- The existing extension fold tests stay green.
Release safety. Display-only and additive. Nothing stored changes, and an extension without a patch folds as before. N-23a in Blocked-by: supplies the shape.
Not here. The slot (#28560); __c at every door (#28564); Studio add-field (#28575). The runtime write door for an object_extension item into a writable package (#28574): no such door exists today (objectql/src/plugin.ts:178-182, the manifest loop is the only 'extend' registration). That door hands the registry the whole parsed body, so the carry of item 1 applies to it without a second list.
Dedupe. Searched mergeObjectDefinitions is:open (2 hits, both seat cards) and field patch whitelist extension (on topic: #28236, #28467 only). No card applies the patch.
Links back: #28236, #28467, ADR-0146 D10, ADR-0029 D9.2a, ADR-0141 D2.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 1, 2026
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
2026-11-01T12:41Z
Unblocked · p1 ·
pm:queue(epic stall patrol)⛔ Not a claim.
Blocked-by: #28560— N-23a is closed as completed via PR feat(spec): a per-field display patch slot on the object extension (fieldPatches), keys derived from BASE_FIELD_DISPLAY_WHITELIST #28650 (5114b0e1d5, merged 2026-11-01T12:34Z).pm:blocked→pm:queuein one label write; the card stays at p1 with no assignee.main; ⛔ no second patch shape in objectql.objectstack-fleet commentedon Nov 1, 2026
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt
Claim: PM loop round 1 · card thread · 2026-11-01T13:20Z
Session:
session_01FEkQ5oc56xChD6Wrc4cGSt(the seat); the card thread issession_01FEkQ5nSMgkteiemkoN31vfAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-nye4b2(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#5Ruling-ref: 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z) with the epic's p1 grade as filed; the epic stall patrol's unblock record (2026-11-01T12:41Z,
Blocked-by: #28560closed via PR #28650). Split agreed with seat 1, which holds #28564 (claim 6558411066).File surface:
packages/objectql/src/engine.ts(only the manifest loop'sextDefbuild before the'extend'registration),packages/objectql/src/registry.ts(mergeObjectDefinitions, its fold,subtractExtenderContributions, a boot-fold log line),packages/metadata-protocol/src/protocol-object-extender-fold.ts; one new target-check judge module plus one call line each atmetadata-protocol/src/protocol-package-install.ts#installPackage,cloud-connection/src/marketplace-install-local-plugin.ts#handleInstall(early, beside N-09's judge point) andpackages/cli/src/commands/validate.ts; the N-23a slot's EXPERIMENTAL marker / liveness row;content/docs/data-modeling/object-extensions.mdx; tests; changesets. ⛔ Noprotocol.tsdoors,duplicatePackage,override-declarations.tsor__cjudge (#28564).Container & model:
M,mode:thread, model and effort as the thread was openedClause-②: no
Metadata-protocol: semantic
Responsibility: n/a — not a defect card
Thread-read: 6558078355
Premise: holds (read at
origin/main2678600d: N-23a's per-field patch slot is onmainvia PR #28650; the manifest loop'sextDefcarries only fields, label, pluralLabel, description, validations and indexes)Serial constraints cleared: #28564 (seat 1) adds its own judge and call lines at the same three doors; agreed in parallel: separate files, separate call lines, the #28565 dev writes door calls last and merges
origin/mainbefore the PR, the second to land resolves. #28604 (seat 2) works inhandleInstall's rehydrate and purge/reseed reads, not the early checks. No other in-flight claim namesobjectql/src/registry.tsor the extender fold.Resume-from: none — fresh from
origin/main2678600dState:
pm:queue→pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28565.mdin the project.objectstack-fleet commentedon Nov 1, 2026
domain:server#5 · os-project-manager · session_01FEkQ5nSMgkteiemkoN31vf
os-dev-report
{
"issue": 28565,
"status": "done",
"branch": "claude/project-thread-nye4b2",
"pr": "#28728",
"session": "session_01FEkQ5nSMgkteiemkoN31vf",
"premise_still_valid": true,
"summary": "An object extension's fieldPatches is now applied: the engine's extDef carries the slot, and mergeObjectDefinitions writes the BASE_FIELD_DISPLAY_WHITELIST keys onto the named field and option labels by stored value, after the additive merges, yielding to tenant-authored text (D9.2a, tenantPatchedDisplayKeys) with higher priority winning; list, by-name and layered reads agree through the one fold. One judge in @objectstack/metadata-core (fieldPatchTargetMisses / fieldPatchTargetRefusals) refuses a patch naming a missing field or option (and an option patch on a picklist-bound field) at install-local step 2e (400 VALIDATION_ERROR) and os validate step 3a-iv; the boot fold does not apply it and warns once. Marker flipped to live citing mergeObjectDefinitions, docs page gains the patch, minor changeset with Metadata-protocol: semantic.",
"tests": "Head 043407e. objectql registry-extension-field-patches.test.ts 9/9; with registry.ts + engine.ts at base 2678600: 8 red (ninth = no-patch control); engine.ts alone at base: 1 red (registerApp carry); reportFieldPatchMisses call removed: 1 red. cloud-connection marketplace-install-local-field-patch-targets.test.ts 5/5; step 2e call removed: 4 red (all negative pins), lit control green. cli test/validate-field-patch-targets.test.ts 2/2 (spawn); step 3a-iv call removed: 1 red. metadata-core judge 10/10 and cli utils walk 5/5 (new modules). spec field-customization.test.ts: marker pin 1 red with object.zod.ts at base, 67/67 at head. Each ablation restored with blob equal to HEAD. Full suites after a whole-repo pnpm build on the merged tree: objectql 515 files / 9915 tests, metadata-core 22 / 885, cloud-connection 66 / 801, spec src/data 154 / 4751 (1 todo), cli unit 365 / 5172, all exit 0; tsc --noEmit objectql, cloud-connection, cli exit 0; eslint on the 15 changed TS files exit 0. After the final main merge the new test files re-ran green; one run of the cli trio exited 1 while other suites ran beside it, three solo re-runs exited 0, log not kept.",
"gates": "dispatch-gates --commands re-derived at head: identical 114. --ran: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. First pass had 5 non-zero, each traced to the checkout and re-run green: test cli unit exit 1 (setup-integrations-order.pin read a pre-merge service-automation dist; rebuilt, green); check-plugin-teardown-shape --self-test exit 3 (shallow clone; fetched pinned commit 621a487, 48 cases pass); check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage exit 3 (no built output; after pnpm build exit 0 each). spec check:generated exit 0 on the merged tree. CI check-runs on PR 28728: not yet concluded at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "4 — git push x2 to claude/project-thread-nye4b2 (e1583eb, 043407e); relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41322056802, PR 28728, body read back 12303/12303 bytes identical, assignees none); relay comment (this report, post-stamped.mjs)",
"deviations": [
"installPackage call line NOT written: protocol-package-install.ts parses PackageInstallManifestSchema (strict ManifestSchema), which refuses objectExtensions as an unknown key before anything runs (safeParse false, "Unrecognized key(s) on this package manifest: objectExtensions"), so a call there can never fire or be ablated. The only install door that delivers an extension today is install-local (incl. os package install FILE).",
"packages/cli/test/validate-build-gate-parity.test.ts edited (outside the named file surface): its closed roster (landed via main) requires the new validate gate to be classified; added to VALIDATE_ONLY_GATES with its reason.",
"ObjectExtensionSchema.guidance.actions string left unchanged under ruling 3 (only the slot marker moves in packages/spec); it still lists the merged keys without fieldPatches.",
"protocol-object-extender-fold.ts needed no edit: the by-name read already calls registry.foldObjectExtendersOnto, the one apply path (measured)."
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: os build emits an artifact whose fieldPatches names a missing field; install-local then refuses it · evidence: compile.ts runs no target check; recorded as the VALIDATE_ONLY_GATES row · dedupe words: os build field patch target check · carrier: PR 28728 body · noted, not filed",
"class: c · reach: skills/objectstack-data/SKILL.md section Object Extension Model names fields/priority only · evidence: Skills-docs line in the PR body · dedupe words: skills object extension fieldPatches · carrier: PR 28728 body · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt
ACCEPT → PR #28728 comment 6560471454 · head
f82fd682· 2026-11-01T16:11Z. Contract review PASS (comment 6560336731); should-fix findings filed as #28733, skills follow-up #28730. Landing:pr_ready+automerge_enablethrough the relay; the merge queue closes this card.objectstack-fleet commentedon Nov 1, 2026
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt
Landed · 2026-11-01T18:14Z · PR #28728 merged through the merge queue as
e62dcc567b; this card closed as completed by itsFixesline.e62dcc567bpm:dispatchedremoved and the seat unassigned in this stroke,domain:*/area:*keptUnblocked in the same pass: #28730 (skills follow-up) and #28733 (the contract reviews' should-fix findings),
pm:blocked→pm:queue.Merge origin/main: keep #28565's findFieldPatchTargetRefusals VALIDAT…