Repository navigation
fix(spec): one reader decides whether an external object's owner is provable, and record sharing and lint both read it - #27794
Conversation
…rovable isOwnerAnchorUnprovable (and its columnMap reader externalColumnMapTargets) join the injected-column provenance module; plugin-sharing's hasPhantomOwnerAnchor and lint's hasUnprovableOwnerAnchor read it instead of each restating the external.columnMap arm. resolveInjectedColumnProvenance is unchanged, so lint's write rule keeps its mapped-but-undeclared findings. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2eCEdjSZBk7DGrPPFg
📓 Docs Drift CheckThis PR changes 3 package(s): 32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 167 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 f1a9e40104a4bab3ea971a244a7cdaaf948af1ac && git checkout f1a9e40104a4bab3ea971a244a7cdaaf948af1ac
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e5cce0961ee9fb8c7962d55008943020d3a93cdb db96a59d16c7e63634b621d07e257c9aaf20d719 && git checkout -B drift-repro e5cce0961ee9fb8c7962d55008943020d3a93cdb && git merge --no-ff db96a59d16c7e63634b621d07e257c9aaf20d719
node scripts/docs-audit/affected-docs.mjs --json e5cce0961ee9fb8c7962d55008943020d3a93cdb
|
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV The seat's at-tier review call was refused by the model quota, and per contract-review.md the tier is not lowered. Owed on this head: one same-shape |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: Generated by Claude Code |
…agnostic, read from the spec The write-deny diagnostic that landed on main reads isFederatedObject from federated-phantom-anchors.ts; it now answers the spec's platformProvisionsStorage instead of restating the external check. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2eCEdjSZBk7DGrPPFg
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV For the contract review claimed on
|
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Inputs: card #27661 (body, comments 6509266755 and 6510961760), PR #27794 (body, 8-file list, net diff from the files endpoint), the 54 check-runs on the head, and the live tree at the head, at the PR base and at the merge-base ① Derived judgments
② Semver levelChangeset ③ Boundary flags
④ Design judgment
Must-fix 1 (FAIL). The head does not build against its base. This module's own doc (line 262) names Should-fix 1. Should-fix 2. Implemented-by: VERDICT: FAIL Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: Generated by Claude Code |
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV One more commit is coming: the two doc-only should-fixes from 6511199047 (the |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta review on record 6511199047 (head What moved, separated: ① Derived judgmentsItems 1, 2, 4, 5 of record 6511199047 stand as judged there: the move touches none of the files they read (the hits file, the lint rule, the test file and the two spec registers are byte-identical between 3, re-judged. 3a, new. The restored 6, re-judged. The convergence map's #7858 row (hits file ② Semver levelStands as judged in record 6511199047. The changeset, ③ Boundary flags
④ Design judgment
Must-fix: none. Record 6511199047's must-fix 1 is discharged on the tree: the head builds against its base and against today's Should-fix 1 (carried from 6511199047, open). Hits file Should-fix 2 (carried from 6511199047, open). Hits file Should-fix 3 (new). The PR body's two stale sentences: the "What changes" bullet that says Implemented-by: VERDICT: PASS Generated by Claude Code |
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV 维护者速读
席位意见:ACCEPT。契约复核 PASS 在案(分诊 6511315513, 席位核对
|
Requested by 田騏 · project thread
Fixes #27661
Clause-②: yes
Metadata-protocol: none
Fable review owed. This PR touches
packages/spec/src/**and carriesClause-②: yes; it was developed on Opus while the Fable quota is out, and it does not land until aCONTRACT_REVIEW_TIERcontract review record with a PASS verdict is on its current head.维护者速读
resolveInjectedColumnProvenance、plugin-sharing 的hasPhantomOwnerAnchor、lint 的hasUnprovableOwnerAnchor加columnMapTargets)。现在 spec 新增一个导出isOwnerAnchorUnprovable(provenance 判定 +external.columnMap映到owner_id这一臂)和它用的externalColumnMapTargets,plugin-sharing 与 lint 都改成直接读它,删掉各自的columnMap读取和身份比较副本;spec 里 plugin-sharing lowers__readScopeown/unit to anowner_idpredicate on federated objects, whereowner_idis a phantom column #7858 那一行收敛表按现状重写。privateexternal object whose driver servesowner_idwithout declaring it infieldsgives every member every row —hasPhantomOwnerAnchorreadsfieldsonly, so the read filter goes unscoped (epic #27261, found by #27332's dev) #27425 只在 plugin-sharing 里加了columnMap分支,spec 的 helper 不认、收敛表也没记;下一个按收敛表对齐 helper 的消费方会悄悄放宽或收窄这类对象。resolveInjectedColumnProvenance本身不动(lint 写规则靠它区分「只映射未声明」)。已注册对象上读/写范围、共享拒绝和 lint 结论都不变;两处只在手搓文档上才会动的边角读法写在 Acceptance notes,两边都 fail closed。回滚即 revert 本 PR。packages/spec/src/data/injected-system-column-provenance.ts(新增的两个函数与收敛表 plugin-sharing lowers__readScopeown/unit to anowner_idpredicate on federated objects, whereowner_idis a phantom column #7858 行);packages/plugins/plugin-sharing/src/federated-phantom-anchors.ts(hasPhantomOwnerAnchor变成一行);packages/lint/src/validate-external-owner-sharing.ts。pnpm --filter @objectstack/plugin-sharing exec vitest run src/external-owner-anchor-sql-read.test.ts src/federated-phantom-owner-scoping.test.ts,预期全部通过。What changes
packages/spec/src/data/injected-system-column-provenance.tsgains two exports on@objectstack/spec/data:externalColumnMapTargets(def, field): doesexternal.columnMap(remote column to local field) map some remote column ontofield? Tolerant of un-parsed records; a non-objectexternalorcolumnMapanswersfalse.isOwnerAnchorUnprovable(def):trueexactly whenresolveInjectedColumnProvenance(def, 'owner_id') === 'injected-unprovisioned'andexternalColumnMapTargets(def, 'owner_id')isfalse. The one reader of "is an owner provable from declared metadata".resolveInjectedColumnProvenanceis unchanged, on purpose: acolumnMapentry does not change who wrote theowner_idfield, and lint'ssecurity-external-owner-unmappedneeds that distinction (on a plugin driver the map is not applied, so only a declared field lets a member write). Folding the arm into the provenance verdict would have dropped that rule's mapped-but-undeclared findings. The new function's doc says so.__readScopeown/unit to anowner_idpredicate on federated objects, whereowner_idis a phantom column #7858 row now reads the head: plugin-sharing converges onisOwnerAnchorUnprovable(provenance plus thecolumnMaparm), and lint reads the same function.packages/plugins/plugin-sharing/src/federated-phantom-anchors.ts:hasPhantomOwnerAnchor(schema)returnsisOwnerAnchorUnprovable(schema). The module's ownreadFieldDef,equalsShippedDef,isFederatedObjectandmapsRemoteColumnOntoOwnercopies are deleted (none was exported from the package entry;git grepfinds no other importer), and the module doc's convergence paragraph is rewritten.sharing-service.tsis untouched.packages/lint/src/validate-external-owner-sharing.ts:hasUnprovableOwnerAnchor(obj)returnsisOwnerAnchorUnprovable(obj); the privatecolumnMapTargetsis replaced byexternalColumnMapTargetsinexternalOwnershipGap. Messages and hints are untouched.packages/plugins/plugin-sharing/vitest.config.ts: themetadata-corealias comment no longer says the guard compares againstOWNER_FIELD_DEF(only the scoping test's fixture imports it now).packages/spec/api-surface/data.jsonandexport-origins/data.jsonregenerated (two new names each)..changeset/27661-owner-anchor-one-reader.md: patch on@objectstack/spec,@objectstack/plugin-sharing,@objectstack/lint.Tests
On the final head
e402956d(fe45a398plus the merge oforigin/main79d78b4e), heavy runs underscripts/pm/os-verify-lock.sh:pnpm --filter @objectstack/spec test: Test Files 754 passed (754), Tests 22410 passed, 1 todo (22411); exit 0. Taken onfe45a398; the merge brought no change topackages/spec/src/data/.pnpm --filter @objectstack/spec exec vitest run src/data/injected-system-column-provenance.test.ts: 10 passed, 4 of them new (the unprovable verdict in both document stages and bothfieldsshapes; thecolumnMaparm with provenance stayinginjected-unprovisioned; declared, local and owner-less tiers;columnMapread as values only, tolerant of bad shapes).packages/plugins/plugin-sharingvitest run: Test Files 58 passed (58), Tests 1290 passed (1290), one402956d. The federated phantom-scoping suites andexternal-owner-anchor-sql-read.test.tsare unchanged and green.packages/lintvitest run: Test Files 178 passed (178), Tests 7623 passed (7623), one402956d.validate-external-owner-sharing.test.tsis unchanged and green, so the rule's findings and hints keep their bytes.pnpm --filter @objectstack/metadata-core test(the producer's provenance matrix, through the re-export): Test Files 21 passed (21), Tests 873 passed (873).pnpm --filter @objectstack/spec typecheck,pnpm --filter @objectstack/lint typecheck,pnpm --filter @objectstack/plugin-sharing typecheck: exit 0.Ablation: both consumers really read the spec reader
Committed head first; the mutation deletes the
columnMapguard line fromisOwnerAnchorUnprovableinpackages/spec/src/data/injected-system-column-provenance.ts(grep -cof the guard line: 0 mutated, 1 restored, as atHEAD), thenpnpm --filter @objectstack/spec build, because plugin-sharing and lint resolve@objectstack/spec/datathroughdist/.external-owner-anchor-sql-read.test.ts+federated-phantom-owner-scoping.test.ts3 failed of 55 (thecolumnMaptoowner_idarms: "own rows only"); lintvalidate-external-owner-sharing.test.ts+validate-security-posture.test.ts1 failed of 202 ("keeps the private recommendation where the owner is provable").git checkout HEAD,git diff HEADempty, spec rebuilt: 55 of 55 and 202 of 202 pass.Acceptance notes
owner_idkey at all, on anownership: 'user'object, used to answerfalsein plugin-sharing (its copy needed the key present) and now answerstrue(the spec treats a pre-injection document as carrying the injected anchor, as lint already did). EveryhasPhantomOwnerAnchorcaller insharing-service.tseither checkshasOwnerField(schema)first (buildReadFilter,buildWriteFilter,inertGrantReason) or is the verdict-free write-deny diagnostic, and the registry always injectsowner_idon such an object, so no registered schema reaches it.owner_id(ownership: 'none' | 'org' | 'business_unit',systemFields: false) but which declares a field byte-identical toOWNER_FIELD_DEFused to read as phantom in plugin-sharing (deny-all beloworg) and now reads as the author's field (the spec's existing rule: with no injected definition, any declaration is the author's), so ordinary owner scoping applies. On a remote that lacks the column that predicate matches no row. Agreed with the epic PM before the edit.externalvalues (rejected by Zod, so only on un-parsed lint input): lint'shasUnprovableOwnerAnchorused to answerfalsefor them and now follows the spec'sexternal != nullstorage predicate, as plugin-sharing already did.packages/qa/dogfood/test/federated-phantom-share-grant.dogfood.test.tsL251-L256 still describeshasPhantomOwnerAnchoras hand-rolled. It is a historical test comment outside this card's file surface; noted, not filed (carrier: none).sharing-service.ts; this PR does not touch that file, so the two do not overlap.Verification
Local gates on
e402956d, every exit code written to a file before any pipe.node scripts/pm/dispatch-gates.mjs --commandsderived 86 families (the same 86 before and after the merge), every one was run after a fullpnpm build:check:api-surface("public API surface + factory signatures unchanged"),check:export-origins("5651 exports across 19 entry points resolve exactly as recorded"),check:docs("232 generated files in sync"),check:authorable-surface,check:llms-txt,check:generated,check:cross-package-test-inputs,check:test-source-alias,check:type-check-debt,check:dual-build-cjs-loads,check:lean-entry-closure,check:i18n,check:doc-authoring,check:issue-citations,check:nul-bytes,check-adr-0087-registration --base origin/main,check-empty-changeset --base origin/main,check-changeset-no-major --base origin/mainand the--self-testbatteries.dispatch-gates --ran: "86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN".pnpm --filter @objectstack/spec check:generatedafter the merge and a spec rebuild: all 14 artifacts up to date. Before the merge it foundapi-surface/andexport-origins/stale (the two new names), regenerated withgen:api-surfaceandgen:export-origins.origin/mainmerged once, ase402956d, viascripts/pm/os-regen-merge.sh(main had not moved either regenerated artifact).pnpm lint, Test Core shards, the UI jobs) is CI's and was not re-run locally. No dogfood battery: no!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic. (The new functions are document derivations beside the existing provenance derivation, tolerant of un-parsed input like the rest of the module.)pnpm lintis CI's.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code