|
1318 | 1318 | }, |
1319 | 1319 | { |
1320 | 1320 | "id": "access-security.owd-save-gate", |
1321 | | - "title": "OWD save gate: external ≤ internal on every object write, and a packaged object's OWD can only be tightened at runtime — Studio inline AND server-side", |
| 1321 | + "title": "OWD save gate: external ≤ internal on every object write, and a packaged object's OWD cannot be changed at runtime — the managed-content seal refuses the write before any OWD rule judges it — Studio inline AND server-side", |
1322 | 1322 | "since": "v16", |
1323 | 1323 | "status": "active", |
1324 | | - "revision": 3, |
| 1324 | + "revision": 4, |
1325 | 1325 | "priority": "P1", |
1326 | 1326 | "surface": "mixed", |
1327 | 1327 | "personas": [ |
|
1335 | 1335 | "the Studio object designer Settings tab (objectui ObjectSettingsPanel / PackageOwdOverviewPanel), which computes isExternalWider inline" |
1336 | 1336 | ], |
1337 | 1337 | "knownGaps": [ |
1338 | | - "runtime EVALUATION of externalSharingModel against external principals is liveness `planned` (#2696) — this item tests the AUTHORING/save gate (external ≤ internal at write; packaged tighten-only), NOT external-principal enforcement; do not tick an external-principal read/write result", |
1339 | | - "R1 (owd_widening_forbidden) fires on a STOCK deploy too — the escape hatch is NOT what reaches it. What selects the answering layer is the DIRECTION OF THE SUBMITTED BODY, not the deploy posture (measured on stock showcase, #9477): saveMetaItem runs the plugin-security authoring gate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch is skipped), so a WIDENING body meets R1 first and always answers 403 owd_widening_forbidden — hatch open or closed, ?package= named or not. A body that does NOT widen passes R1 and only then meets the overlay door: 403 NOT_OVERRIDABLE (no ?package=) or 403 ITEM_LOCKED (?package= names the read-only base) on stock, and 2xx under OS_METADATA_WRITABLE=object. ⚠ WRITABLE_PACKAGE_REQUIRED was observed on NONE of those six legs — do not expect it here. The run must still record WHICH layer answered the 4xx, and must read the code from `declaredCode`: the dispatcher maps this refusal to `code: PERMISSION_DENIED` on the wire and carries the ledgered spelling in `declaredCode: owd_widening_forbidden`, so a check keyed on `code` alone MISSES it" |
| 1338 | + "runtime EVALUATION of externalSharingModel against external principals is liveness `planned` (#2696) — this item tests the AUTHORING/save gate (external ≤ internal at write; a packaged object sealed against runtime change), NOT external-principal enforcement; do not tick an external-principal read/write result", |
| 1339 | + "R1 (owd_widening_forbidden) is UNREACHABLE through the metadata door on a stock deploy, and with OS_METADATA_WRITABLE=object too. saveMetaItem asks the package door (refusePackagedBaseOverride, predicate isSealedManagedItem: the item is artifact-backed and `object` opens no overlay channel) on every kernel topology, ahead of the runtime lint door and the plugin-security authoring gate (#22338). Since ADR-0131 D6 (#22401) the escape hatch no longer opens an item a managed package ships. So any OWD write onto showcase_announcement is refused 403 NOT_OVERRIDABLE (no ?package=) or 403 ITEM_LOCKED (?package= names the read-only base), whatever its direction: widening or tightening, hatch open or shut. The revision-3 reading, measured at #9477, no longer holds: a widening body met R1 first and answered 403 PERMISSION_DENIED with declaredCode owd_widening_forbidden, and a tightened control passed R1 and met NOT_OVERRIDABLE, or 2xx under OS_METADATA_WRITABLE=object. R1 keeps its oracle in unit pins: packages/plugins/plugin-security/src/object-posture-gate.test.ts for its verdict, and packages/rest/src/meta-object-owd-gate.test.ts for the door order that now puts the seal in front of it. ⛔ Do not build a fixture package only to reach R1 live (#23121 triage). If a run does see the R1 refusal on some other door, read the code from `declaredCode` (owd_widening_forbidden), not `code`: the wire `code` is PERMISSION_DENIED" |
1340 | 1340 | ] |
1341 | 1341 | }, |
1342 | 1342 | "steps": [ |
1343 | 1343 | "boot showcase with the console; admin session", |
1344 | 1344 | "Studio inline (browser): open the object designer Settings for a writable/draft object; set sharingModel=private and externalSharingModel=public_read; screenshot — the panel must flag the D11 external-wider violation inline (owd-sharing.ts isExternalWider) and not offer a clean save", |
1345 | 1345 | "server reject: PUT /api/v1/meta/object/qa_owd_probe with sharingModel:'private' + externalSharingModel:'public_read' — capture the 4xx + error code. ⚠️ Drive this at the ACTIVE-state door, NOT ?mode=draft: the D11 refusal is raised by the runtime lint block (validateSecurityPosture, rule security-external-wider-than-internal), and draft saves deliberately defer it to the draft→active promotion gate (#4463 D1), so a ?mode=draft PUT is ADMITTED and the clause cannot be observed there", |
1346 | 1346 | "server admit: PUT the same object with externalSharingModel:'private' (≤ private) — capture the 2xx (the gate orders, it does not blanket-deny)", |
1347 | | - "packaged tighten (R1): PUT /api/v1/meta/object/showcase_announcement widening its shipped sharingModel public_read→public_read_write (keep externalSharingModel:'private' so the D11 lint is not tripped instead) — capture the 4xx AND its code. Expect 403 with `declaredCode: owd_widening_forbidden` (wire `code` is PERMISSION_DENIED) on a STOCK deploy, no escape hatch needed. Control leg, to prove the probe reached R1 rather than a blanket deny: re-PUT the same body TIGHTENED (public_read→private) — R1 passes it and the next door answers 403 NOT_OVERRIDABLE (or ITEM_LOCKED with ?package=com.example.showcase), 2xx under OS_METADATA_WRITABLE=object", |
| 1347 | + "packaged object (the seal): PUT /api/v1/meta/object/showcase_announcement widening its shipped sharingModel public_read→public_read_write (keep externalSharingModel:'private' so the D11 lint is not the door under test). Capture the 4xx AND its code. Expect 403 NOT_OVERRIDABLE from the managed-content seal (saveMetaItem's package door, ADR-0131 D6), on a stock deploy and under OS_METADATA_WRITABLE=object alike. With ?package=com.example.showcase the same door answers 403 ITEM_LOCKED. Control leg, to show that the seal answers and not a direction-keyed gate: re-PUT the same body TIGHTENED (public_read→private). It is refused the same way, because a managed item is sealed whatever the body says. R1 (owd_widening_forbidden) is not reached on this door; clause 4 names its unit pins", |
1348 | 1348 | "GET /api/v1/meta/object/showcase_announcement before and after the denied PUT — confirm byte-identical", |
1349 | 1349 | "cross-check the shipped declarations: GET /api/v1/meta/object/showcase_account and showcase_announcement — both satisfy external ≤ internal" |
1350 | 1350 | ], |
|
1368 | 1368 | "evidence": "the PUT trace + read" |
1369 | 1369 | }, |
1370 | 1370 | { |
1371 | | - "clause": "a packaged object's OWD cannot be widened at runtime: the widening PUT on showcase_announcement answers 4xx with a ledgered code, recorded with WHICH layer answered. On a stock deploy that is 403 owd_widening_forbidden from the plugin-security authoring gate — the gate runs ahead of the overlay/package doors here, so the escape hatch is not what reaches it (#9477)", |
| 1371 | + "clause": "a packaged object's OWD cannot be changed at runtime: the widening PUT on showcase_announcement is refused 403 with a ledgered code, recorded with WHICH layer answered. On a stock deploy that is 403 NOT_OVERRIDABLE from the managed-content seal, saveMetaItem's package door (ADR-0131 D6, #22401). It answers ahead of every check that judges the body, on every kernel topology (#22338), and OS_METADATA_WRITABLE does not open it. So the plugin-security R1 gate (owd_widening_forbidden) is unreachable here on stock, and R1's oracle is its unit pins, not this run", |
1372 | 1372 | "oracle": "api", |
1373 | | - "verify": "PUT /api/v1/meta/object/showcase_announcement (widened) status >=400 and the ledgered code is owd_widening_forbidden. ⚠ read it from `declaredCode`, not `code`: the dispatcher answers `{code: 'PERMISSION_DENIED', declaredCode: 'owd_widening_forbidden'}` on the live REST route, so a check keyed on `code` alone misses the gate it is looking for (measured #9477; note the unit pin packages/rest/src/meta-object-owd-gate.test.ts reads the bare spelling off `code`, which is the in-process shape, not the wire shape)", |
1374 | | - "evidence": "the PUT trace + the code + the answering-layer note" |
| 1373 | + "verify": "PUT /api/v1/meta/object/showcase_announcement (widened, no ?package=) answers 403 with code NOT_OVERRIDABLE and no declaredCode; with ?package=com.example.showcase, which names the read-only base, the same door answers 403 ITEM_LOCKED. Either way the answering layer is the package door, not R1. The door order is pinned by packages/rest/src/meta-object-owd-gate.test.ts (the '[#7674] R1 owd_widening_forbidden through PUT /api/v1/meta/object/:name' block: a widening overlay of a packaged object answers 403 NOT_OVERRIDABLE with no declaredCode and stores nothing, hatch open). R1's own verdict is pinned in-process by packages/plugins/plugin-security/src/object-posture-gate.test.ts (the 'R1 — env-tighten-only over a packaged declaration (ADR-0086 D1)' block). Cite both pins per rule 6 for R1; do not build a fixture package to reach it live", |
| 1374 | + "evidence": "the PUT trace + the code + the answering-layer note (+ the two cited pin outputs for R1)" |
1375 | 1375 | }, |
1376 | 1376 | { |
1377 | 1377 | "clause": "the denied widening is inert: GET /api/v1/meta/object/showcase_announcement is byte-identical before and after the refused PUT", |
|
1393 | 1393 | ], |
1394 | 1394 | "variants": [ |
1395 | 1395 | "R2 external ≤ internal (authoring gate, all object writes)", |
1396 | | - "R1 env-tighten-only (packaged/artifact-backed objects)" |
| 1396 | + "the managed-content seal (packaged/artifact-backed objects: 403 NOT_OVERRIDABLE, or ITEM_LOCKED with ?package=, ahead of R1); R1 env-tighten-only is unreachable on this door and unit-pinned" |
1397 | 1397 | ], |
1398 | 1398 | "traps": [ |
1399 | 1399 | "stale-console-bundle", |
|
1404 | 1404 | "packages/lint/src/validate-security-posture.ts#OWD_WIDTH (OWD_WIDTH + SECURITY_EXTERNAL_WIDER lint parity)", |
1405 | 1405 | "objectui packages/app-shell/src/views/studio-design/owd-sharing.ts (isExternalWider) + PackageOwdOverviewPanel.tsx + ObjectSettingsPanel.tsx", |
1406 | 1406 | "examples/app-showcase/src/data/objects/announcement.object.ts#externalSharingModel + account.object.ts (externalSharingModel declarations)", |
1407 | | - "ADR-0090 D11, ADR-0086 D1, ADR-0124 D1, #3050, objectui#2508" |
| 1407 | + "packages/metadata-protocol/src/protocol.ts#refusePackagedBaseOverride (saveMetaItem's package door — the managed-content seal: isSealedManagedItem, 403 NOT_OVERRIDABLE, or ITEM_LOCKED for a named read-only base; asked on every kernel topology ahead of the body checks since #22338, and not opened by OS_METADATA_WRITABLE since ADR-0131 D6, #22401)", |
| 1408 | + "packages/rest/src/meta-object-owd-gate.test.ts (the door-order pin: a packaged object's overlay is refused by the package door before the lint door and R1, hatch open) + packages/plugins/plugin-security/src/object-posture-gate.test.ts (R1's in-process unit pins)", |
| 1409 | + "ADR-0090 D11, ADR-0086 D1, ADR-0124 D1, ADR-0131 D6, #3050, objectui#2508" |
1408 | 1410 | ], |
1409 | 1411 | "history": [ |
1410 | 1412 | { |
|
1414 | 1416 | "ref": "claude/platform-test-checklist-ocwugl" |
1415 | 1417 | }, |
1416 | 1418 | { "revision": 2, "date": "2026-08-18", "change": "corrected clauses 1-2 and steps 2-3 for the #8310 retirement (maintainer ruling 2026-08-13). The item tested R2 owd_external_wider / 403 at the ?mode=draft door; R2 is RETIRED from object-posture-gate.ts as a duplicate of the runtime lint door, whose refusal is 422 INVALID_METADATA rule security-external-wider-than-internal raised at the ACTIVE-state door — and draft saves deliberately defer to the draft-to-active promotion gate (#4463 D1), so the clause was unobservable where it was pointed (#9453 CF-2). ⚠️ #9453 CF-3 (that the R1 knownGap mispredicts which layer answers) was NOT applied: object-posture-gate.ts states verbatim that R1 applies only to overlay writes over an artifact-backed object and that 'the default deploy already 403s these before this gate runs' (if (!ctx.isArtifactBacked) return), which is what the knownGap and clause 4 already say", "ref": "#9386" }, |
1417 | | - { "revision": 3, "date": "2026-08-19", "change": "the R1 knownGap, step 5 and clause 4 named the WRONG answering layer, and #9453 CF-3 was RIGHT — re-measured on a genuinely stock showcase boot and OVERTURNING the revision-2 note above (#9477). Proven stock from the server process's own /proc/<pid>/environ: zero METADATA_WRITABLE occurrences among 147 vars. Target artifact-backed, proven by GET ?layers=true returning a populated `code` layer (_packageId com.example.showcase) with `overlay: null`. A widening PUT then answered 403 declaredCode owd_widening_forbidden — reproduced twice, object byte-identical after. The mechanism the old text missed: saveMetaItem runs runAuthoringGate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch never arms — the #7674 note in that file says the showcase is exactly that shape). So the deploy posture does not select the layer; the body's DIRECTION does. Control leg (same boot, same object, tightened body) passed R1 and answered 403 NOT_OVERRIDABLE, i.e. the door the knownGap called 'earlier' is in fact LATER. Six legs measured (widen/tighten x stock/hatch x ?package= present/absent) and WRITABLE_PACKAGE_REQUIRED appeared on none. ⛔ Not a product defect: every widening leg refused and the object never changed — object-posture-gate.ts behaved exactly as its executable guard (if (!ctx.isArtifactBacked) return) specifies; only the prose parenthetical in that file's header, and this item, mispredicted the door order", "ref": "#9477" } |
| 1419 | + { "revision": 3, "date": "2026-08-19", "change": "the R1 knownGap, step 5 and clause 4 named the WRONG answering layer, and #9453 CF-3 was RIGHT — re-measured on a genuinely stock showcase boot and OVERTURNING the revision-2 note above (#9477). Proven stock from the server process's own /proc/<pid>/environ: zero METADATA_WRITABLE occurrences among 147 vars. Target artifact-backed, proven by GET ?layers=true returning a populated `code` layer (_packageId com.example.showcase) with `overlay: null`. A widening PUT then answered 403 declaredCode owd_widening_forbidden — reproduced twice, object byte-identical after. The mechanism the old text missed: saveMetaItem runs runAuthoringGate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch never arms — the #7674 note in that file says the showcase is exactly that shape). So the deploy posture does not select the layer; the body's DIRECTION does. Control leg (same boot, same object, tightened body) passed R1 and answered 403 NOT_OVERRIDABLE, i.e. the door the knownGap called 'earlier' is in fact LATER. Six legs measured (widen/tighten x stock/hatch x ?package= present/absent) and WRITABLE_PACKAGE_REQUIRED appeared on none. ⛔ Not a product defect: every widening leg refused and the object never changed — object-posture-gate.ts behaved exactly as its executable guard (if (!ctx.isArtifactBacked) return) specifies; only the prose parenthetical in that file's header, and this item, mispredicted the door order", "ref": "#9477" }, |
| 1420 | + { "revision": 4, "date": "2026-10-13", "change": "clause 4, acceptance[3], re-pointed to the managed-content seal, with step 5, the R1 knownGap, the first knownGap's packaged tighten-only parenthetical, the R1 variant, the title and two new source notes (stale spec, #23104 A3). Revision 3 measured, at #9477, that a widening PUT on showcase_announcement met R1 first and answered 403 PERMISSION_DENIED with declaredCode owd_widening_forbidden. Two later changes reorder that. #22338 made saveMetaItem ask the package door on every kernel topology, ahead of the checks that judge the body. ADR-0131 D6 (#22401, e02833c24) made the OS_METADATA_WRITABLE hatch stop opening an item a managed package ships. A packaged object, whose type opens no overlay channel, is now refused 403 NOT_OVERRIDABLE (ITEM_LOCKED with ?package=) whatever the body says, so R1 is unreachable on this door on stock. The protection holds: nothing widens. Per the #23121 triage, no fixture package is built to reach R1. Its oracle is its unit pins, named in the clause: plugin-security object-posture-gate.test.ts for the verdict, and rest meta-object-owd-gate.test.ts, whose R1 block now pins the package door answering first, hatch open", "ref": "#23121" } |
1418 | 1421 | ] |
1419 | 1422 | }, |
1420 | 1423 | { |
|
0 commit comments