Skip to content

docs(qa): name the condition that selects the OWD gate's answering layer (#9477) - #9953

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-9477-owd-gate-remeasure
Aug 19, 2026
Merged

docs(qa): name the condition that selects the OWD gate's answering layer (#9477)#9953
os-warren merged 1 commit into
mainfrom
claude/issue-9477-owd-gate-remeasure

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#9477

Measurement-first card: the deliverable is the differential diagnosis, and the only file touched is docs/qa/platform-checklist/areas/access-security.json. No plugin-security code was edited.

Outcome 3. Both refusals are real on the same stock deploy; the checklist's knownGap named the wrong one as "earlier". #9453 CF-3 was right, and the revision-2 note that rejected it is overturned.

The three answers

1. Was OS_METADATA_WRITABLE set? — No. Proven from the server process's own environ.

The stock leg was booted with pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 3477 -d file:<tmp>/data.db, then its /proc/<pid>/environ was read directly rather than trusting the shell:

server pid: 16208
cmdline: node .../@objectstack/cli/bin/run.js serve --dev --port 3477 --ui
every OS_* / OBJECTSTACK_* / *METADATA* var in /proc/16208/environ:
OS_INTERNAL_ARTIFACT_PATH=.../examples/app-showcase/dist/objectstack.json
OS_ENVIRONMENT_ID=env_local
OS_SEED_ADMIN=1
OS_SEED_ADMIN_EMAIL=admin@objectos.ai
OS_SEED_ADMIN_PASSWORD=admin123
OS_DATABASE_URL=file:<tmp>/run9477/data.db
METADATA_WRITABLE occurrences in server environ: 0
env vars readable: 147 # sanity: environ was read, not empty

No .env / .env.local exists at the repo root or in examples/app-showcase. Framework 90417a8, objectui pin 82a9417.

2. Was the target artifact-backed? — Yes. Read from the protocol's own layered answer.

isArtifactBacked resolves through lookupArtifactItemSchemaRegistry.getArtifactItem, which is the same source the layered read reports as its code layer. GET /api/v1/meta/object/showcase_announcement?layers=true → 200:

code: PRESENT (13 keys) sharingModel='public_read' _packageId='com.example.showcase'
overlay: None
overlayScope: None

A populated code layer with a genuine package id is exactly the condition isArtifactBacked returns true for; overlay: null confirms nothing had been overlaid first. The shipped source agrees (announcement.object.ts: sharingModel: 'public_read', externalSharingModel: 'private').

3. Which layer produced the 403? — object-posture-gate.ts's R1, on the stock deploy.

PUT /api/v1/meta/object/showcase_announcement with the packaged body and sharingModel widened public_readpublic_read_write (externalSharingModel left 'private', so the D11 lint cannot answer instead):

HTTP_STATUS=403
{"error":"[owd_widening_forbidden] object/showcase_announcement: an environment overlay may only
TIGHTEN a packaged object's OWD, never widen it (sharingModel 'public_read_write' > declared
'public_read'). Widen it in the package source and publish through the package pipeline
(ADR-0090 D7) instead of an environment overlay (ADR-0086 D1).",
"code":"PERMISSION_DENIED","declaredCode":"owd_widening_forbidden"}

Reproduced twice on fresh requests. GET before and after is byte-identical, so the refused write is inert.

The control leg — what makes this a diagnosis rather than one observation

A gate that fires proves the probe arrived, but not that the layer the checklist named is absent. So the same body was re-sent tightened (public_readprivate), which R1 passes by construction — whatever answers next is the next door:

posture?package=bodystatuscodeanswering layer
stockabsentwidening403owd_widening_forbiddenobject-posture-gate.ts R1
stockabsenttightening403NOT_OVERRIDABLESysMetadataRepository.assertAllowed, registry limb
stocknamedwidening403owd_widening_forbiddenobject-posture-gate.ts R1
stocknamedtightening403ITEM_LOCKEDSysMetadataRepository.readOnlyBaseOverrideError, package door
OS_METADATA_WRITABLE=objectabsentwidening403owd_widening_forbiddenobject-posture-gate.ts R1
OS_METADATA_WRITABLE=objectabsenttightening200admitted

The hatch leg was a second, independently booted server on port 3478 with its own DB; its /proc/<pid>/environ shows OS_METADATA_WRITABLE=object.

Three things fall out, none of which the old text allowed for:

  • The direction of the body selects the answering layer — not the deploy posture. A widening body meets R1 first on every leg, hatch or no hatch, package named or not.
  • The door the knownGap called "earlier" is in fact later. It answers only for bodies R1 lets through.
  • WRITABLE_PACKAGE_REQUIRED — the code the knownGap names — appeared on none of the six legs. The real codes are NOT_OVERRIDABLE and ITEM_LOCKED.

The mechanism is already recorded in the framework, one file over: saveMetaItem calls runAuthoringGateahead of the overlay and package doors, and its #7674 note states that the CLI's host-config assembler leaves environmentId undefined and that this "is the flagship showcase's own boot shape". With environmentId undefined the env-partitioned NOT_OVERRIDABLE branch never arms, so nothing stands between a stock PUT and R1.

Why this is not a product defect

Every widening leg refused, and the object was byte-identical after each. object-posture-gate.ts behaved exactly as its executable guard specifies — if (!ctx.isArtifactBacked) return; did not return because the target is artifact-backed. Nothing was admitted that should have been refused, and the gate's verdict is the correct one. What was wrong is a prose prediction about door ordering, in two places: this checklist item (fixed here) and the gate header's parenthetical "the default deploy already 403s these before this gate runs", which belongs to the plugin-security lane and is left untouched per the dispatch.

The change

access-security.owd-save-gate revision 2 → 3:

  • fixtures.knownGaps[1] — the deliverable. Now names the distinguishing condition (body direction), the mechanism, all six measured legs, and the fact that WRITABLE_PACKAGE_REQUIRED is not among them.
  • steps[4] and acceptance[3] (clause + verify) — carried the identical wrong posture→code mapping; corrected so the item is not self-contradictory. steps[4] also gains the tightening control leg, so the next runner can tell "gate didn't fire" from "probe didn't arrive".
  • New: read the code 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.
  • history — a revision-3 entry recording the overturn of the revision-2 note.

pnpm check:platform-checklistOK — 15 areas, 190 items (190 active); coverage: 30 kinds mapped, 0 waived.


Generated by Claude Code

…yer (#9477)
`access-security.owd-save-gate` predicted that on a stock deploy a runtime
meta PUT to a packaged object is refused by the writable-package gate
(WRITABLE_PACKAGE_REQUIRED) BEFORE plugin-security's R1 can answer, and that
R1 `owd_widening_forbidden` is reachable only through the
OS_METADATA_WRITABLE=object escape hatch. Re-measured against a genuinely
stock showcase boot: both halves are wrong, and #9453 CF-3 — rejected by the
revision-2 note as contradicting the gate's source — was right.
What the deploy posture does NOT decide, and what does:
posture body → status code
stock widening → 403 owd_widening_forbidden (R1)
stock tightening → 403 NOT_OVERRIDABLE (overlay door)
stock+package widening → 403 owd_widening_forbidden (R1)
stock+package tightening → 403 ITEM_LOCKED (package door)
hatch=object widening → 403 owd_widening_forbidden (R1)
hatch=object tightening → 200 admitted
The DIRECTION of the submitted body selects the answering layer, not the
posture. `saveMetaItem` runs `runAuthoringGate` ahead of the overlay and
package doors on the host-config kernel the showcase boots (environmentId
undefined, so the env-partitioned NOT_OVERRIDABLE branch never arms — the
#7674 note in protocol.ts records that the showcase is exactly that shape),
so a widening body meets R1 first on every leg. The door the knownGap called
"earlier" is in fact later, and WRITABLE_PACKAGE_REQUIRED appeared on none of
the six legs.
Also recorded: the wire shape. The dispatcher answers `code:
PERMISSION_DENIED` with the ledgered spelling in `declaredCode`, so a check
keyed on `code` alone misses the gate it is looking for.
Not a product defect — no code change. Every widening leg refused and the
object stayed byte-identical; object-posture-gate.ts behaved exactly as its
executable guard (`if (!ctx.isArtifactBacked) return`) specifies. Only the
prose mispredicted the ordering — in this item, and in that file's header
parenthetical, which is left for the owning lane.
Fixes#9477
Co-Authored-By: Claude <noreply@anthropic.com>
@os-warrenos-warren added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 19, 2026 — with Claude
@os-warren
os-warren marked this pull request as ready for review August 19, 2026 10:57
@os-warren
os-warren added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit b3042e3Aug 19, 2026
23 of 24 checks passed
@os-warren
os-warren deleted the claude/issue-9477-owd-gate-remeasure branch August 19, 2026 11:10
os-warren pushed a commit that referenced this pull request Aug 19, 2026
…ate.ts
The R1 header parenthetical claimed "the default deploy already 403s
these before this gate runs" for the OS_METADATA_WRITABLE escape-hatch
path. Measured false on a stock showcase boot (#9477, landed PR #9953
revision 3): what selects the answering layer is the direction of the
submitted body, not the deploy posture. A widening body meets R1 first
on every leg, hatch open or closed, `?package=` named or not, because
saveMetaItem runs runAuthoringGate ahead of the overlay/package doors
on the host-config kernel the showcase boots. The "earlier" refusal
the old prose named answers only later, and only for non-widening
bodies.
Comment-only — the executable guard (`if (!ctx.isArtifactBacked)
return;`) is untouched.
Refs: #9957, #9477, PR #9953, #9958
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unresolved: a run observed owd_widening_forbidden on a stock deploy, but the gate's source says it cannot fire there

1 participant

@os-warren