Uh oh!
There was an error while loading. Please reload this page.
feat(lint): permission/book cross the runtime publish gate; object measured dirty stays behind; security-role-word splits to stay behind whole (#8310) - #8546
Conversation
…ime publish door; security-role-word splits out to stay behind whole (#8310) The #7891 flip, measured per #4001: permission and book cross (zero refusals on the full metadata-protocol suite and an 88-write corpus replay); object stays behind — re-measured dirty one package over (objectql 83 tests all security-owd-unset; rest 12 tests incl. #7674's 403-door contract pins) and escalated on the card. security-role-word becomes its own CLI-only registry entry (validateSecurityRoleWord) so one rule id sits on one side of the wall (#7220) instead of enforcing for a subset of its six collections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
…al gate, object's residue via the snapshot builder (#8310) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #8310
Programme slice [3 of #7891] (the strictness-rollout direction is ruled there, not re-decidable here). Predecessors on this exact surface: PR #8390 (#8307, the seed crossing) and PR #8491 (#8309, the snapshot enrichment + stack-key wiring that priced this card as a registry data edit). This PR ships the measured partial flip the card's own fork clause defines:
permission+bookcross,objectmeasured dirty and stays behind (escalated — see "What did NOT cross"),security-role-wordstays behind WHOLE via an entry split. #8310 remains open for theobjectdecision.What crossed
packages/lint/src/authoring-rules.ts— thevalidateSecurityPostureentry now declaresruntimeTypes: ['seed', 'permission', 'book']. Permission-set and book writes through the runtime publish door (Studio, REST/meta, MCP/AI authors) are judged by the D7 security block: error-tier findings (e.g.security-wildcard-vama,security-anchor-high-privilege) refuse the write with the same 422invalid_metadataenvelope, warning/info findings ride the response as non-blocking advisories (#4463 P1/D3).Zero breakage, measured per the #4001 discipline (not inherited):
@objectstack/metadata-protocolsuite: 83 files / 1235 tests green@objectstack/objectqlsuite: 198 files / 3543 tests green@objectstack/restsuite: 113 files / 1870 tests greenvalidate(showcase, CRM, todo): all passrunRuntimeAuthoringRules(builtdist/objectstack.jsonof showcase/CRM/todo plus the blank template's one object): 31 objects, 10 permission sets, 1 book, 24 seeds — 0 refusals, 2 non-blocking advisories (security-private-no-readscopeon showcase's own member-default set — the same verdicts the whole-stack CLI run already reports)What did NOT cross —
object, re-measured dirty, escalated (fork clause)The old blocker really is dead: with
objectdeclared, the full@objectstack/metadata-protocolsuite is green (the historical "26 writes → 422s across 8 files" predates #8308'sMETADATA_CREATE_SEEDS.objectrepair) and the corpus replay above stays at 0 refusals. But one package over, the same declaration breaks the platform's own write paths — measured on this exact tree with the flip in place:@objectstack/objectql: 83 tests / 13 files fail, every failuresecurity-owd-unset(85 refusals) — suites publish objects with no authoredsharingModelthroughsaveMetaItem@objectstack/rest: 12 tests / 3 files fail — the same owd-unset class plus two genuine contract collisions no fixture edit can honestly settle:meta-object-owd-gate.test.tspins owd-save-gate: the ADR-0090 D11 authoring gate never runs on any host-config deployment (keyed on environmentId, not authoringChannel) #7674's ADR-0094 403owd_external_widerdoor; the 422 lint gate runs earlier insaveMetaItemand now preempts it for the same defect (same refusal, different status/code/vocabulary)privatedefault) — whichsecurity-owd-unsetexists to refuse (absence must be an authored decision)Forcing this green would mean either editing ~16 test files across two packages outside this card's file surface or weakening a rule — the fork clause forbids both. The decision that actually gates
objectis: which door answers for OWD defects (403 ADR-0094 vocabulary vs 422 lint vocabulary), and is an unauthored OWD a runtime refusal at all? That is escalated in the report on #8310; the runtime-surface test keeps the would-be behavior executable (OWD-less write would be refused, the #8308 create seed would be clean, a clean write is not blamed for context defects) so the eventual flip is one array element plus flipping pins to the real gate.security-role-word— stays behind WHOLE (stated explicitly, as the card demands)The rule judges six collections (object names/fields/actions, permission sets, positions, apps, plus books).
positions/appsare neither carried by the per-write snapshot nor mapped inTYPE_TO_STACK_KEY(PR #8491 deliberately excluded them), and both types areallowRuntimeCreate: true— so declaringpermission/bookon a function that still contained the rule would have enforced ONE rule id for a strict subset of its collections: a door that refuses a permission set namedrole_managerwhile a position namedsales_rolewalks through. That is the #7220 split in mirror image, which the parent card forbids in either direction.Since the registration is whole-function, "stays behind" required a split: the D3 section moved into its own exported rule
validateSecurityRoleWord(same file, same rule id, byte-identical findings) with its own CLI-only registry entry whosesurfaceReasonrecords the crossing recipe (carry + mappositions/apps, then declare all its types in one edit — whole, per #7220). CLI behavior is unchanged: both entries run on all three commands, and the runtime-surface test pins that their finding union equals the pre-split set while NO runtime-gated type reaches the role-word entry.File surface — one declared expansion beyond the dispatch
The dispatch named
authoring-rules.ts+ the runtime-surface test (+ metadata-protocol fixture repairs, which turned out to be needed nowhere — zero fixture edits there). The role-word whole-or-behind constraint could not be satisfied inside that surface, so the split touches in addition:packages/lint/src/validate-security-posture.ts(the extraction),packages/lint/src/validate-security-posture.test.ts(role-word cases now call the split function; the reachability meta-test drives both), andpackages/lint/src/index.ts(new export). All inside@objectstack/lint; noruntime-gate.ts, nopackages/spec/**, nocontent/docs/releases/**edits.Tests
Local gates
Re-derived against the final diff with
node scripts/pm/dispatch-gates.mjs(6 changed paths). All derived families run and green:check:changeset-gate-self-tests,check:cross-package-test-inputs(self-test + real run),check:objectui-changesetself-test,check-adr-0087-registration(self-test + run: no declared-breaking changeset),check-changeset-no-major,check-empty-changeset, plus the convention-triggeredcheck:query-options-erasure(240 sites, at the ceiling, no new files),check:type-check-coverage(OK, ledger unchanged), andcheck:nul-bytes(7686 files clean — it caught and removed 3 raw NULs a tool materialized into a test file mid-edit, exactly the documented trap).Changeset
.changeset/security-posture-object-permission-book-runtime-publish.md—@objectstack/lint: minor (a class of runtime writes that used to succeed can now be refused; PR #8390/#8491 precedent for this exact surface).Programme state on merge
The #7891 flip set is not complete:
seed(#8307),permission/book(this PR) are on the door;objectawaits the escalated decision on #8310;security-role-wordawaitspositions/appswiring (its entry records the recipe). Whether #8379 unblocks for the spec seat on a partial flip is the PM's call — flagged in the report's open questions rather than silently decided here.Generated by Claude Code