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
fix(metadata-protocol): key the #3050 authoring gate on authoringChannel so ADR-0090 D11 OWD rules run on host-config deployments (#7710)
* fix(metadata-protocol): key the #3050 authoring gate on authoringChannel (#7674)
The pre-persistence authoring gate call site in `saveMetaItem` was wrapped in
`if (this.environmentId !== undefined)`. The CLI's lightweight host-config
assembler constructs `new ObjectQLPlugin()` with no options, so `environmentId`
stays undefined on a self-hosted app server whose `PUT /api/v1/meta/*` is an
END-USER surface — and plugin-security's ADR-0090 D11 object posture gate (R1
`owd_widening_forbidden`, R2 `owd_external_wider`) therefore ran on no
host-config deployment at all.
This is the proxy-signal hazard #6710 diagnosed and retired for the sibling
#4463 gate; the #3050 call site was simply never moved onto the declared
`authoringChannel`. Both doors now read one key, and the default stays the
gated one.
Also adds the integration coverage whose absence let this survive: the gate was
18/18 green in its unit suite while a repo-wide grep for `owd_external_wider`
found only the gate source and that suite. The new
`packages/rest/src/meta-object-owd-gate.test.ts` drives R1 and R2 through a real
sqlite engine, a real protocol on the host-config topology and the real
`PUT /api/v1/meta/object/:name` route — draft path, active path and `?package=`
— and pins the negative direction too (legal pairs still save; the
`package-author` channel still bypasses).
Fixes#7674
* test(rest): clear two CI ratchets the new OWD gate suite tripped (#7674)
Both are ratchets on the new integration test's own surface, not on the fix.
Neither is remedied by raising a number, and neither was raised.
1. `query-options-erasure` — the test surface grew 242 -> 243. The new site was
`engine.find('sys_metadata', { … } as any)` in the suite's persistence probe.
The input is not off-contract, so the remedy is the first one the rule's
message prescribes: drop the assertion and let it infer against
`EngineQueryOptions`, keeping `tsc` as the enforcing channel for those keys
(#4674). No `as unknown as EngineQueryOptions` escape is warranted here, and
the baseline is unchanged — measured back at 242, the ceiling.
2. `TypeScript Type Check` TEST_DEBT — `@objectstack/rest` measured 159 against
a recorded 155, which since #6939 carries no margin. All four were in the new
file: one TS2835 (a relative import without its `.js` extension — the trap
AGENTS.md names, and the class that is already 124 of this entry's debt) and
three TS2554 from hand-rolled `registerObject` calls missing the required
`packageId`. The import gains its extension; the three platform-object
registrations move onto `registerApp` under
`com.objectstack.metadata-objects`, which is the seam
`assembleMetadataProtocol` itself uses — so the harness got more faithful
rather than merely quieter. Re-measured at exactly 155; the ledger is
untouched and no other entry was lowered.
Neither ratchet is evaluated by a package's own `test`/`typecheck` scripts —
rest's tsconfig excludes its own tests while the TEST_DEBT ratchet measures raw
`tsc --noEmit` including them — which is why a green local run said nothing
about either.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Fix: the #3050 pre-persistence authoring gate now keys on the declared `authoringChannel` instead of `environmentId`, so ADR-0090 D11 object posture enforcement reaches host-config deployments.
6
+
7
+
The gate call site in `saveMetaItem` was wrapped in `if (this.environmentId !== undefined)`. The CLI's lightweight host-config assembler constructs `new ObjectQLPlugin()` with no options, leaving `environmentId` undefined while serving an end-user `PUT /api/v1/meta/*` — so plugin-security's object posture gate (`owd_widening_forbidden` / `owd_external_wider`) ran on no self-hosted deployment at all. This is the same proxy-signal hazard #6710 retired for the sibling #4463 gate; the two doors now read one declared key.
8
+
9
+
Behaviour change for self-hosted deployments: an object write whose `externalSharingModel` is wider than its `sharingModel` — or an environment overlay that widens a packaged object's OWD — is now refused with `403` (`owd_external_wider` / `owd_widening_forbidden`) on the draft path, the active path and package authoring, instead of being accepted. Fix the posture in the object definition; widening a packaged object legitimately is authored in the package source and published (ADR-0090 D7). A kernel that declares `authoringChannel: 'package-author'` is unaffected — package authoring stays gated at build time by `validateSecurityPosture`.
// ran on NO host-config deployment: `new ObjectQLPlugin()` leaves
507
+
// `environmentId` undefined and serves an end-user `PUT /api/v1/meta/*`.
508
+
// The old case could not see that, because it drove the control-plane row
509
+
// (undefined) only through the `package-author` channel — the one column
510
+
// where both keys agree.
511
+
//
512
+
// The four-cell matrix below is what makes the two keys distinguishable.
513
+
// Note the one cell whose verdict FLIPS: `('env_test', 'package-author')`
514
+
// was gated and is not any more. That is #6710's direction applied
515
+
// honestly rather than half-applied — a kernel that claims to BE the
516
+
// package author is treated as one by both doors, and package authoring is
517
+
// gated at build time instead (`validateSecurityPosture` is `CLI_ONLY` in
518
+
// `AUTHORING_RULES`, and R1's own message prescribes exactly that route:
519
+
// "widen it in the package source and publish through the package
520
+
// pipeline"). No assembly in this repo declares that channel today; only
521
+
// the genuine control plane may.
522
+
it.each([
523
+
{envId: undefined,channel: undefined,gated: true,why: 'THE DEFECT: the host-config assembler — `new ObjectQLPlugin()`, no environment id, undeclared channel ⇒ the fail-safe default'},
524
+
{envId: 'env_test',channel: undefined,gated: true,why: 'the ordinary tenant kernel, unchanged'},
525
+
{envId: undefined,channel: 'package-author'asconst,gated: false,why: 'the genuine control-plane bootstrap kernel'},
526
+
{envId: 'env_test',channel: 'package-author'asconst,gated: false,why: 'a declared package author that also carries a row scope — the cell that flips'},
0 commit comments