Repository navigation
feat(objectql): an object extension's fieldPatches relabels the vendor field on every read, refused at install and os validate when it names nothing (#28565) - #28728
Conversation
…ect read; the field-patch target judge Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
… in mergeObjectDefinitions ADR-0146 D10 second rule: whitelisted display keys and option labels by stored value, after the additive merges; ADR-0029 D9.2a yield; priority order; a patch naming a missing target is skipped and named once at the fold. Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…ing mergeObjectDefinitions Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…field or option its target lacks (step 2e) Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
… its in-stack target lacks; changeset Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…nerated object reference Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…the build/validate parity roster Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…-nye4b2 Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
…-nye4b2 Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 180 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 e7b32dedf15cbda1815aacc8c293620e2cae86e1 && git checkout e7b32dedf15cbda1815aacc8c293620e2cae86e1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 272fec05bdf48e8dd06f2be252ef4aeb67740f41 d4414410c797989ecdf4983e7633b32df94619d3 && git checkout -B drift-repro 272fec05bdf48e8dd06f2be252ef4aeb67740f41 && git merge --no-ff d4414410c797989ecdf4983e7633b32df94619d3
node scripts/docs-audit/affected-docs.mjs --json 272fec05bdf48e8dd06f2be252ef4aeb67740f41
|
…e override write gate's docblock The new step-2e helper sat between evaluateOverrideWriteGate and its own docblock, stacking two docblocks and leaving the gate undocumented. Moved unchanged; no behaviour change. Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt Contract reviewServed-tier: Inputs read: card #28565 body and its three comments (unblock record, claim 6558511726, dev report 6560090896), PR #28728 body, its one bot comment, the 18-file net diff against ① Derived judgments
② Semver level
③ Boundary flagsDev report
④ Design judgment
The design holds. ADR-0146 D10 asks for exactly three things and the tree does each: the patch is a per-field map in the extension body (the slot N-23a declared, carried byte-for-byte as Attempts to break it, and what each found:
Findings:
Check-runs on the head at the time of this record: no failure, no cancellation; the lint, spec-liveness, changeset, queue-guard, docs and type-check source gates are green; the core and UI test shards, workspace type-check and E2E lanes were still in progress. Their conclusions are the gate verdicts and govern over this prose when they land. Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. 席位意见:与 diff 相符。对象扩展里的
|
…-nye4b2 The overlap with the __c extension-field change in packages/cli is resolved by keeping both: os validate runs both ADR-0146 D10 steps in main's order (3a-iv the __c reservation, then 3a-v the field-patch target check), and VALIDATE_ONLY_GATES keeps both rows. Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
… their siblings carry Both cases spawn the real CLI; under a loaded box the 5 s default timed out (5014 ms / 5001 ms on the post-merge run). The sibling validate spawn tests (validate-extension-field-suffix, validate-override-declarations) pass 120_000. Claude-Session: https://claude.ai/code/session_01FEkQ5nSMgkteiemkoN31vf Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt Contract reviewServed-tier: This record is a DELTA of the earlier record on this PR (comment 6560336731, head ① Derived judgments
② Semver levelUnchanged from the earlier record: ③ Boundary flagsThe dev's one new flag, judged against the tree rather than taken on trust:
No other deviation or open question is new. The earlier record's answers to deviations 1–4 stand. ④ Design judgment
Findings on this head:
Check-runs on the head at the time of this record: 34 runs — 11 success (Auto Label, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, PR-body guards, Spec property liveness, filter), 7 skipped, 16 in progress (Build Core, Build Docs, Build UI Deps, Check Changeset, the ESLint / repo / docs / CI-release / scoped lint lanes, the four Type Check lanes, Plan Test Core shards, Live E2E UI), 0 failure, 0 cancelled. Their conclusions are the gate verdicts and govern over this prose when they land. Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt 席位核对 · re-landACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. 席位意见:与上次 ACCEPT(6560471454,head
|
domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt
Requested by Leehom · project thread
Fixes #28565
Clause-②: no
Metadata-protocol: semantic
Skills-docs: follow-up #28730
维护者速读
objectExtensions[].fieldPatches,上一版只声明未生效)现在真的生效了:引擎注册扩展时带上它,注册表合并(mergeObjectDefinitions)在字段合并之后把白名单里的 label、description、帮助文字、占位符和选项 label 写到厂商字段上;字段类型、必填、选项取值不变。补丁指向目标对象没有的字段或选项值时,在市场安装口和os validate拒绝;启动时只记一次日志、不拦启动。fieldPatches的扩展;没写的扩展合并结果逐字节不变。租户自己改过的文字照旧优先(ADR-0029 D9.2a),两个扩展冲突时 priority 高者胜。回滚 = revert 本 PR。packages/objectql/src/registry.ts的patchedField/applyFieldPatches与tenantPatchedDisplayKeys(合并与让位);②packages/metadata-core/src/extension-field-patch-targets.ts(三处共用的目标检查);③packages/cloud-connection/src/marketplace-install-local-plugin.ts新增的 2e 步。pnpm --filter @objectstack/objectql exec vitest run src/registry-extension-field-patches.test.ts,预期 9 个测试全绿。What changes
packages/objectql/src/engine.ts: the manifest loop'sextDefcarriesfieldPatchesbesidefields, so the'extend'registration hands the slot to the registry. Re-measured at2678600d: still the only'extend'registration inobjectql,metadata-protocolandruntime(git grep "'extend'", one call site,engine.ts:8996).packages/objectql/src/registry.ts:mergeObjectDefinitionsgains one step after the scalar overrides:applyFieldPatcheswrites eachBASE_FIELD_DISPLAY_WHITELIST.fieldkey onto the named field and eachBASE_FIELD_DISPLAY_WHITELIST.optionkey onto the option whose storedvaluethe patch names. Keys are read from the constant, so a widened whitelist widens the merge. The type of the slot is the spec's own (ObjectExtensionParsed['fieldPatches']); there is no second patch shape.tenantPatchedDisplayKeyscomputes, once over the fold's base, which patched field/option keys the tenant authored away from the packaged owner's value, with the same two "no opinion" edges astenantAuthoredScalars(no owner; base IS the owner by reference). The comparison isscalarOverridesPackagedBaseitself, called through a one-line adapter because its exported key union names only the object scalars andmessage.priority, so the higher one writes last, exactly as forlabel.reportFieldPatchMissesnames, once per object/package/field/option, a patch the fold could not apply, atwarn, worded as the observed state. Never a refusal: the fold runs on every read while a boot is still registering (AGENTS.md Startup registry reads), and the patch applies on the next read once the field arrives.subtractExtenderContributionsis untouched: patches are idempotent over an already-folded base (the base carries the patched text, reads as authored, and the patch yields to the same value), the way the scalars are. Pinned by the "idempotent over a folded body" assertion.protocol-object-extender-fold.tsalready callsregistry.foldObjectExtendersOnto, which runsfoldExtendersOntoDefinition→mergeObjectDefinitions, the one apply implementation (measured; the order's assumption holds). The code layer (getArtifactItem('object')→resolveOwnerLayer) goes through the same fold.packages/metadata-core/src/extension-field-patch-targets.ts(new):fieldPatchTargetMisses(one extension against one target),fieldPatchTargetRefusals(a manifest'sobjectExtensions, located) andfieldPatchTargetRefusalMessage. Misses: a field the target lacks, an option value its field lacks, and an option patch on a picklist-bound field (its options are written over the field after the fold, so an option patch there could never be served).metadata-coreis the one package all three readers already depend on. One call line each:packages/cloud-connection/src/marketplace-install-local-plugin.ts#handleInstall, new step 2e, right after N-09's override write gate (2d) and before the hot-register (3):400 VALIDATION_ERROR, every refusal inerror.details.refusals, nothing registered, written or synced. Targets: one the same artifact delivers, else the registry's resolved object.packages/cli/src/commands/validate.ts, new step 3a-iv after the override gate, overpackages/cli/src/utils/field-patch-targets.ts(the walk: objects across the composed stack and everypackages[]entry).packages/spec/src/data/object.zod.ts:fieldPatches'.describe()drops[EXPERIMENTAL — not enforced]and citesmergeObjectDefinitions; its TSDoc, the@examplecomment and the guidance code comment follow. No liveness ledger row exists for the slot (grep -l ObjectExtension packages/spec/liveness/*.json: none).content/docs/references/data/object.mdxregenerated (gen:docs, the one artifactcheck:generatednamed stale). The spec test that pinned the marker now pins its absence.content/docs/data-modeling/object-extensions.mdx:fieldPatchesin the contribution table, a "Relabelling a vendor field" section, and the D9.2a callout extended.packages/cli/test/validate-build-gate-parity.test.ts: the new validate gate is classified inVALIDATE_ONLY_GATESwith its reason (the closed roster requires it); the build-side gap is reported below..changeset/28565-field-patch-applied.md: minor onobjectql,metadata-core,cloud-connection,cliandspec(the kind issemantic; each package'sfiles[]shipsdist, which carries the change after a build).Tests
All readings at head
043407e6unless named. Red-on-base means the change's source files restored to2678600dwith the new tests kept.packages/objectql/src/registry-extension-field-patches.test.ts(9)registry.ts+engine.tsat base: 8 red (the ninth is the no-patch control).engine.tsalone at base (carry ablation): 1 red, theObjectQL.registerAppcarry test.reportFieldPatchMissescall removed: 1 red, the warned-once test.packages/metadata-core/src/extension-field-patch-targets.test.ts(10)packages/cloud-connection/src/marketplace-install-local-field-patch-targets.test.ts(5)packages/cli/src/utils/field-patch-targets.test.ts(5)packages/cli/test/validate-field-patch-targets.test.ts(2, spawns the CLI)packages/spec/src/data/field-customization.test.tsEXPERIMENTALmarker's absence)Each ablation was restored and the file's blob checked equal to HEAD before the next one.
Existing extension fold tests stay green: the full suites after the last two main merges (whole-repo
pnpm build, exit 0, then):@objectstack/objectql@objectstack/metadata-core@objectstack/cloud-connection@objectstack/specsrc/data@objectstack/cliunittiertsc --noEmitobjectql, cloud-connection, clieslintover the 15 changed TS filesAfter the final merge (
043407e6, no file overlap with this diff) the five new test files were re-run: all green. One run of the CLI trio during that step exited 1 while other suites ran beside it; three re-runs alone exited 0, and its log was not kept, so it is reported here rather than explained.Derived gates (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived at head: identical list).--ranverdict: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. First pass: 109 exit 0 and five that did not. Each was traced to the checkout, not the diff, and re-run green:test cli unitexit 1:setup-integrations-order.pinread aservice-automationdist from before feat(console): Connector connections page — connection status and Connect / Reconnect / Disconnect for OAuth connector instances #28651 came in with the merge; rebuilt, green.check-plugin-teardown-shape --self-testexit 3: shallow clone; its pinned fixture commit was fetched, then 48 cases passed.check:skill-examples,check:dual-build-cjs-loads,check:i18n-coverageexit 3 (PREREQUISITE NOT MET: built output missing); after a whole-repopnpm build, exit 0 each.Acceptance notes
protocol-package-install.ts#installPackage; measured, its manifest is parsed byPackageInstallManifestSchema(strictManifestSchema), which refusesobjectExtensionsas an unknown key before anything else runs:PackageInstallManifestSchema.safeParse({ id: 'com.acme.ext', name: 'E', type: 'app', version: '1.0.0', objectExtensions: [...] })→false, "Unrecognized key(s) on this package manifest:objectExtensions" (spec dist built from2678600d). A call line there could never fire and could not be ablated, so none was written. The only install door that delivers an extension today isPOST /api/v1/marketplace/install-local(includingos package install <file>, which posts there); the card's "Not here" names the future runtime write door for anobject_extensionitem (metadata-protocol + objectql: an object extension is an authorable item of a writable package — saved and published through the existing doors, registered as 'extend', exported as objectExtensions #28574).os builddoes not run the target check. It still emits an artifact the install door then refuses; recorded as theVALIDATE_ONLY_GATESrow. Wiring it intocompile.tsis outside the card's three doors.finishServedObject), so an option label patched in the fold would be overwritten on every read.ObjectExtensionSchema.guidance.actionsstill reads "the merge carriesfields,label,pluralLabel,description,validationsandindexesonly". It is a refusal sentence aboutactions, so it is left as is under ruling 3 (only the slot's marker moves inpackages/spec); it now omitsfieldPatchesfrom that list.[EXPERIMENTAL — not enforced]; the ruling records the kind assemantic.__cextension fields only in a.custompackage — refused at save/publish/restore,os validate, both install doors and duplicate #28564 (the__cjudge at the same doors) has not landed at the time of this PR; whichever lands second sits its call line beside the other's.Verification
pnpm --filter @objectstack/objectql exec vitest run src/registry-extension-field-patches.test.ts— 9/9 at head043407e6; 8 red withregistry.tsandengine.tsrestored to2678600d(the ninth is the no-patch control).Generated by Claude Code