Skip to content

feat(lint): permission/book cross the runtime publish gate; object measured dirty stays behind; security-role-word splits to stay behind whole (#8310) - #8546

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-8310-runtime-types-flip
Aug 13, 2026
Merged

feat(lint): permission/book cross the runtime publish gate; object measured dirty stays behind; security-role-word splits to stay behind whole (#8310)#8546
qq9340100 merged 2 commits into
mainfrom
claude/issue-8310-runtime-types-flip

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

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 + book cross, object measured dirty and stays behind (escalated — see "What did NOT cross"), security-role-word stays behind WHOLE via an entry split. #8310 remains open for the object decision.

What crossed

packages/lint/src/authoring-rules.ts — the validateSecurityPosture entry now declares runtimeTypes: ['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 422 invalid_metadata envelope, warning/info findings ride the response as non-blocking advisories (#4463 P1/D3).

Zero breakage, measured per the #4001 discipline (not inherited):

  • full @objectstack/metadata-protocol suite: 83 files / 1235 tests green
  • full @objectstack/objectql suite: 198 files / 3543 tests green
  • full @objectstack/rest suite: 113 files / 1870 tests green
  • corpus validate (showcase, CRM, todo): all pass
  • direct replay of every shipped-corpus item through the real runRuntimeAuthoringRules (built dist/objectstack.json of 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-readscope on 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 object declared, the full @objectstack/metadata-protocol suite is green (the historical "26 writes → 422s across 8 files" predates #8308's METADATA_CREATE_SEEDS.object repair) 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 failure security-owd-unset (85 refusals) — suites publish objects with no authored sharingModel through saveMetaItem
  • @objectstack/rest: 12 tests / 3 files fail — the same owd-unset class plus two genuine contract collisions no fixture edit can honestly settle:

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 object is: 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/apps are neither carried by the per-write snapshot nor mapped in TYPE_TO_STACK_KEY (PR #8491 deliberately excluded them), and both types are allowRuntimeCreate: true — so declaring permission/book on 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 named role_manager while a position named sales_role walks 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 whose surfaceReason records the crossing recipe (carry + map positions/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), and packages/lint/src/index.ts (new export). All inside @objectstack/lint; no runtime-gate.ts, no packages/spec/**, no content/docs/releases/** edits.

Tests

pnpm --filter @objectstack/lint build → exit 0
pnpm --filter @objectstack/lint test -- --maxWorkers=2
Test Files 72 passed (72)
Tests 2012 passed (2012) (2009 on main before this card)
pnpm --filter @objectstack/lint typecheck → clean
pnpm --filter @objectstack/metadata-protocol test -- --maxWorkers=2
Test Files 83 passed (83) · Tests 1235 passed (1235)
pnpm --filter @objectstack/objectql test -- --maxWorkers=2
Test Files 198 passed (198) · Tests 3543 passed (3543)
pnpm --filter @objectstack/rest test -- --maxWorkers=2
Test Files 113 passed (113) · Tests 1870 passed (1870)
(both of the above were red only while `object` was declared — 83 and 12
failures respectively; that measurement is what kept `object` behind)
corpus: validate ×3 green; real-gate replay of 66 corpus writes → 0 refusals

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-changeset self-test, check-adr-0087-registration (self-test + run: no declared-breaking changeset), check-changeset-no-major, check-empty-changeset, plus the convention-triggered check:query-options-erasure (240 sites, at the ceiling, no new files), check:type-check-coverage (OK, ledger unchanged), and check: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; object awaits the escalated decision on #8310; security-role-word awaits positions/apps wiring (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

…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
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 6:40pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx(via @objectstack/lint)
  • content/docs/deployment/validating-metadata.mdx(via packages/lint)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint)

1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/v17.mdx(via @objectstack/lint)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@qq9340100@claude