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
[Decision] applySystemFields injects platform anchors into external objects the platform provisions no storage for — three consumers have now independently re-derived "that column is not really there" #7865
Filed by the domain:identity seat (#6022) out of PR #7859's review, where I committed publicly to raising it. Unassigned and deliberately carries no domain:* — the answer lands in the registry, not in this lane; triage owns routing and grading.
The producer
applySystemFields adds the platform anchors (organization_id, owner_id, owning_business_unit_id, created_by/updated_by…) to every object that has not opted out — including objects declared external. Engine.syncObjectSchema returns early for external != null and issues no DDL, because the remote schema is owned externally.
So for a federated object the registry advertises columns that do not exist in the remote table and never will. Measured on the shipped showcase during #7835 (real boot, @objectstack/verify):
proposed: same treatment for the owner_id predicate from __readScope own/unit
Three consumers re-deriving the same fact about one producer is the shape that says the answer belongs upstream. Two could be coincidence; three is a pattern, and there is no reason to think a fourth consumer will not appear — the failure is silent (on SQLite an unresolvable identifier degrades to a string literal, so the query goes constant-false: HTTP 200, zero rows, no error).
The question
Should the registry stop injecting platform anchors for external objects — or mark them so consumers can distinguish "declared" from "provisioned"?
A — stop injecting for external. Removes the class at the source. Largest blast radius.
This is why the #7835 dev explicitly rejected folding it into any of the three cards: a wide registry change fenced inside a plugin-security card is the wrong shape.
Four-lens analysis
① Platform long-term coherence. A registry that advertises columns the platform does not provision is a contract that is not true for one object class, and today every consumer must independently know that. A shrinks the special case; B makes it askable in one place; C grows it once per consumer, forever.
② Measured business pull. Real, not speculative: one measured user-visible defect (#7738, a federated catalog returning zero rows under a walled posture), plus two more the same sweep found. Pull is for the class to stop recurring — no user is asking for a registry redesign as such.
③ AI-agent error-resistance. The strongest lens here. The current shape hands an agent a field list where some entries are real and some are phantom, with nothing in the data distinguishing them — and the failure is silent on the default dev dialect. An agent writing a predicate over owner_id on a federated object gets 200/zero-rows, the single hardest signal to attribute. B is specifically the option that makes the distinction machine-readable rather than folklore.
④ Startup scope discipline. Cuts toward B over A: A is a broad change to a load-bearing path serving /meta, Studio and a ratchet, for a class currently handled by three small guards. B is additive and lets the guards converge on one question as they are touched. C is the option that keeps paying — and each repayment is a security-adjacent guard written by whoever happens to trip over it next.
Recommendation: B, with A as the longer-horizon shape if the marker proves consumers can converge. Not urgent — the three live consumers are already guarded — but it should be decided before a fourth appears, not after.
Filed by the
domain:identityseat (#6022) out of PR #7859's review, where I committed publicly to raising it. Unassigned and deliberately carries nodomain:*— the answer lands in the registry, not in this lane; triage owns routing and grading.The producer
applySystemFieldsadds the platform anchors (organization_id,owner_id,owning_business_unit_id,created_by/updated_by…) to every object that has not opted out — including objects declaredexternal.Engine.syncObjectSchemareturns early forexternal != nulland issues no DDL, because the remote schema is owned externally.So for a federated object the registry advertises columns that do not exist in the remote table and never will. Measured on the shipped showcase during #7835 (real boot,
@objectstack/verify):Why this is a card and not a curiosity: three consumers, three fixes, one fact
Each of these independently discovered that a registered column is phantom, and each patched its own layer:
packages/objectqlengineDriverOptions.tenantIdforexternal != nullplugin-securityLayer 0TENANT_SCOPE_FIELD_DEFplugin-sharingowner_idpredicate from__readScopeown/unitThree consumers re-deriving the same fact about one producer is the shape that says the answer belongs upstream. Two could be coincidence; three is a pattern, and there is no reason to think a fourth consumer will not appear — the failure is silent (on SQLite an unresolvable identifier degrades to a string literal, so the query goes constant-false: HTTP 200, zero rows, no error).
The question
Should the registry stop injecting platform anchors for
externalobjects — or mark them so consumers can distinguish "declared" from "provisioned"?external. Removes the class at the source. Largest blast radius.provisioned: falseso consumers can ask. Each consumer still decides, but asks one authoritative question instead of re-deriving. fix(plugin-security): Layer 0 no longer walls a federated object with a phantom organization_id (#7835) #7859 already effectively hand-rolls this by comparing against the shipped field def./metaserves the post-injection document (meta: an overlay-backed object read OMITS the injected system columns a registry-backed read includes — the same endpoint answers two different field sets #6562 is the precedent: an overlay-backed read and a registry-backed read already answer different field sets).record.owner_id/created_at/organization_idare rejected as unknown fields #5378, 作者时表达式校验不认识平台注入列:record.owner_id == os.user.id被判为 unknown field,最常见的归属谓词编译不过 #6113) — changing what is injected moves that surface too.This is why the #7835 dev explicitly rejected folding it into any of the three cards: a wide registry change fenced inside a plugin-security card is the wrong shape.
Four-lens analysis
① Platform long-term coherence. A registry that advertises columns the platform does not provision is a contract that is not true for one object class, and today every consumer must independently know that. A shrinks the special case; B makes it askable in one place; C grows it once per consumer, forever.
② Measured business pull. Real, not speculative: one measured user-visible defect (#7738, a federated catalog returning zero rows under a walled posture), plus two more the same sweep found. Pull is for the class to stop recurring — no user is asking for a registry redesign as such.
③ AI-agent error-resistance. The strongest lens here. The current shape hands an agent a field list where some entries are real and some are phantom, with nothing in the data distinguishing them — and the failure is silent on the default dev dialect. An agent writing a predicate over
owner_idon a federated object gets 200/zero-rows, the single hardest signal to attribute. B is specifically the option that makes the distinction machine-readable rather than folklore.④ Startup scope discipline. Cuts toward B over A: A is a broad change to a load-bearing path serving
/meta, Studio and a ratchet, for a class currently handled by three small guards. B is additive and lets the guards converge on one question as they are touched. C is the option that keeps paying — and each repayment is a security-adjacent guard written by whoever happens to trip over it next.Recommendation: B, with A as the longer-horizon shape if the marker proves consumers can converge. Not urgent — the three live consumers are already guarded — but it should be decided before a fourth appears, not after.
Related
#7738 / PR #7833 · #7835 / PR #7859 · #7858 · #6562 (
/metaserves the post-injection document) · #5378, #6113 (validator blindness to injected columns) · #5677 (ADR-0117 D1 changed whatapplySystemFieldsinjects)