Found while wiring client-side validation for sharing_rule in objectui#3561 (objectui PR pending). Filed unassigned, observation-class — the round-trip consequence below is reasoned, not executed, and that limit is stated explicitly at the bottom.
Measurement
Against the published @objectstack/spec@17.0.0-rc.5:
SharingRuleSchema (= CriteriaSharingRuleSchema) is .strict() over nine keys and declares none of the seven ADR-0010 envelope keys (_lock, _lockReason, _lockSource, _provenance, _packageId, _packageVersion, _lockDocsUrl).- Every registered metadata KIND schema declares all seven. Sampled
object, page, flow, job, agent, permission, email_template, webhook, translation — 9 of 9 strict, 9 of 9 with the full envelope. SharingRuleSchema is the outlier at 0. - Consequently
SharingRuleSchema.safeParse({ ...validRule, _packageId: 'crm_pkg' }) fails with unrecognized_keys.
Meanwhile the read path stamps that key without looking at the type — packages/metadata-protocol/src/protocol.ts (main), in both the list and single-item branches:
constrecPkg=record.package_id??undefined;if(recPkg&&data&&typeofdata==='object'&&data._packageId===undefined){data._packageId=recPkg;}Why it may matter
#6245 bound sharing_rule at PUT /api/v1/meta/sharing_rule/:name. So the same key the read path stamps onto a package-owned rule is a key that write door's schema rejects. A read-modify-write of a package-owned sharing rule that echoes the body back would 422 on a field the platform itself wrote.
The existing comment in kernel/metadata-type-schemas.ts anticipates the question and draws the boundary deliberately:
This shape is .strict() with no stored/stamped envelope, so it is an AUTHOR-shape check and is applied only where author shapes are submitted: the write door. It is not a filter on rows already in sys_sharing_rule.
That resolves the read side. What it does not obviously resolve is the case above, where an author-shaped submission is a body that came out of the read path. #6931's own note that these three doors "sat outside the one gate that exists to catch 'declares no envelope'" — and that sharing_rule was judged "by a 422" — suggests the envelope question was decided per-door by hand rather than by the invariant.
Not verified
I did not run a server and did not execute the round trip; there is no reproduction here. The stamping site and the schema are both read directly, but whether any caller actually echoes a stamped body back through that door is exactly the unverified step. If callers always strip or reconstruct the body, this is inert and the right outcome is to close it — worth someone confirming rather than assuming in either direction.
Downstream
objectui#3561 does not depend on a fix. It wires sharing_rule on the create door only and leaves edit server-only, precisely so the client cannot become stricter than the server (objectstack#5316). If the envelope is later declared on this shape, that restriction can be lifted and the type wired on both doors.
Filed per AGENTS.md Prime Directive #10. Not assigned; no PR.
Generated by Claude Code
Found while wiring client-side validation for
sharing_rulein objectui#3561 (objectui PR pending). Filed unassigned, observation-class — the round-trip consequence below is reasoned, not executed, and that limit is stated explicitly at the bottom.Measurement
Against the published
@objectstack/spec@17.0.0-rc.5:SharingRuleSchema(=CriteriaSharingRuleSchema) is.strict()over nine keys and declares none of the seven ADR-0010 envelope keys (_lock,_lockReason,_lockSource,_provenance,_packageId,_packageVersion,_lockDocsUrl).object,page,flow,job,agent,permission,email_template,webhook,translation— 9 of 9 strict, 9 of 9 with the full envelope.SharingRuleSchemais the outlier at 0.SharingRuleSchema.safeParse({ ...validRule, _packageId: 'crm_pkg' })fails withunrecognized_keys.Meanwhile the read path stamps that key without looking at the type —
packages/metadata-protocol/src/protocol.ts(main), in both the list and single-item branches:Why it may matter
#6245 bound
sharing_ruleatPUT /api/v1/meta/sharing_rule/:name. So the same key the read path stamps onto a package-owned rule is a key that write door's schema rejects. A read-modify-write of a package-owned sharing rule that echoes the body back would 422 on a field the platform itself wrote.The existing comment in
kernel/metadata-type-schemas.tsanticipates the question and draws the boundary deliberately:That resolves the read side. What it does not obviously resolve is the case above, where an author-shaped submission is a body that came out of the read path. #6931's own note that these three doors "sat outside the one gate that exists to catch 'declares no envelope'" — and that
sharing_rulewas judged "by a 422" — suggests the envelope question was decided per-door by hand rather than by the invariant.Not verified
I did not run a server and did not execute the round trip; there is no reproduction here. The stamping site and the schema are both read directly, but whether any caller actually echoes a stamped body back through that door is exactly the unverified step. If callers always strip or reconstruct the body, this is inert and the right outcome is to close it — worth someone confirming rather than assuming in either direction.
Downstream
objectui#3561 does not depend on a fix. It wires
sharing_ruleon the create door only and leaveseditserver-only, precisely so the client cannot become stricter than the server (objectstack#5316). If the envelope is later declared on this shape, that restriction can be lifted and the type wired on both doors.Filed per AGENTS.md Prime Directive #10. Not assigned; no PR.
Generated by Claude Code