Skip to content

feat(lint): ADR-0091 seed pair crosses the runtime publish gate (#8307) - #8390

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-8307-seed-pair-runtime-types
Aug 13, 2026
Merged

feat(lint): ADR-0091 seed pair crosses the runtime publish gate (#8307)#8390
hotlong merged 1 commit into
mainfrom
claude/issue-8307-seed-pair-runtime-types

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#8307

Programme slice of #7891 (staging per #4001 pattern, #7220/PR #7479 precedent — the strictness-rollout direction is ruled, not re-decidable in this slice).

What changed

packages/lint/src/authoring-rules.ts — the validateSecurityPosture registry entry now declares surfaces: ['cli', 'runtime-publish'] with runtimeTypes: ['seed'] (previously CLI_ONLY with a surfaceReason). This puts the ADR-0091 seed pair — security-grant-expired-at-authoring, security-delegation-missing-reason — on the runtime metadata publish door (Studio, REST /meta, MCP/AI authors) for a seed write, per #7576's own staging note that this pair was the one slice ready to cross.

Why declaring it on the WHOLE registry entry is safe

This entry is the WHOLE validateSecurityPosture function (all 13 rule ids) — there is no per-rule-id split in authoring-rules.ts. runtime-gate.ts's baseline/candidate differential is what makes runtimeTypes: ['seed'] safe to declare here without leaking the other 11 rule ids' verdicts: a seed write's candidate stack carries objects IDENTICAL to the baseline (only data differs), so every finding this function derives from stack.objects / stack.permissions / stack.positions / stack.apps / stack.books is produced byte-identically in both passes and cancels in the diff. Only the ADR-0091 pair's stack.data[] reads can differ between the two passes. This is proven — not just asserted in a comment — by a new positive-control test: a seed write against a context object that WOULD trip security-owd-unset and security-role-word if the isolation failed, asserting zero leaked findings.

object / permission / book remain undeclared — that is #8310, still blocked (a strictness rollout on object, RUNTIME_NEEDS_FULL_SNAPSHOT on permission/book). security-role-word is deliberately not in this slice either (#7220 constraint — it judges six collections and wiring only the snapshot-safe two would split one rule id across the wall).

Re-measured, not inherited (hard constraint from the dispatch)

The dispatch required re-running the "trip-free on the shipped corpus" claim rather than trusting #7576/#8308's numbers. I grepped every defineSeed(...) call site across all four shipped stacks (showcase, CRM, todo, the blank create-objectstack template):

  • examples/app-showcase/src/data/seed/index.ts seeds Account, Contact, Project, Task, Category, BusinessUnit, Team, Product, ProjectMembership, FieldZoo, Invoice, InvoiceLine, ExpenseReport, ExpenseLine, Inquiry, Preference, Announcement.
  • examples/app-crm/src/data/index.ts seeds Account, Contact, Opportunity, Lead, Activity.
  • examples/app-todo/src/data/index.ts seeds Task.
  • packages/create-objectstack/src/templates/blank has no seed data at all.

None of the four apps authors a sys_user_position or sys_user_permission_set seed row — grep for valid_until, delegated_from, and either object name across all three source trees returns zero hits. Showcase's own approval demo (src/security/seed-approval-demo.ts) explicitly does this at runtime instead, precisely because "users can't be seeded (they sign up) and position assignments are runtime admin actions." So the ADR-0091 loop body (GRANT_SEED_OBJECTS.has(seedObject)) never executes for any of the four shipped apps — this is a structural zero for the current corpus, not a measured-and-hoped-stays-zero one. No STOP condition was hit.

Tests

packages/lint/src/validate-security-posture.runtime-surface.test.ts extended with the pin recording the crossing:

  • the whole registry entry now reports surfaces: ['cli', 'runtime-publish'] / runtimeTypes: ['seed'];
  • the pair is refused through the realrunRuntimeAuthoringRules({ type: 'seed', ... }) (previously only the pre-crossing mirror could be asked);
  • a clean grant/delegation write earns zero errors/advisories at the real gate (positive control);
  • a seed write leaks no finding from the other 11 rule ids this entry carries, even against a context that would trip two of them over the whole stack (isolation proof);
  • object / permission / book / position / app still reach no rule (unaffected — [3 of #7891] Flip the registration: runtimeTypes gains object + permission/book — whole rule families cross the publish door #8310's residue).
pnpm --filter @objectstack/lint build → exit 0
pnpm --filter @objectstack/lint test -- --maxWorkers=2
Test Files 72 passed (72)
Tests 1978 passed (1978)
pnpm --filter @objectstack/lint typecheck → clean (tsc --noEmit)

Local gates (node scripts/pm/dispatch-gates.mjs <changed paths>)

Re-derived against the actual committed diff (git diff --name-only 2d8dba312 HEAD) — same file set as before commit, no drift:

  • check:spec-parsed-alias — OK
  • check:cross-package-test-inputs — OK (self-test + real run)
  • check:changeset-gate-self-tests (empty-changeset / adr-0087-registration / changeset-no-major self-tests) — OK
  • check:objectui-changeset, check:objectui-pin-fresh self-tests — OK
  • node scripts/check-changeset-no-major.mjs — "This diff introduces no major bump"
  • node scripts/check-adr-0087-registration.mjs — "this PR adds no declared-breaking changeset" (correct: this is additive enforcement, not a spec key removal/rename)
  • Convention-triggered (new test file): check:query-options-erasure — ratchet holds, no new unswept sites; check:type-check-coverage — unaffected, @objectstack/lint typechecks clean
  • check:nul-bytes — OK, no raw control bytes

check:api-surface is untouched, as expected — no packages/spec source in this diff.

Changeset

.changeset/security-posture-seed-pair-runtime-publish.md, "@objectstack/lint": minor — following the direct precedent of .changeset/visibility-predicate-family-runtime-publish.md (#7220's own family crossing the runtime-publish wall, also minor) rather than the dispatch prompt's default "patch unless conventions say otherwise": this is a real behavior change (a class of runtime writes that used to succeed will now be refused), and the established convention for "a rule crosses onto runtime-publish" in this repo is minor, not patch.

Scope note

File surface matches the dispatch exactly: the validateSecurityPosture registration in packages/lint, the runtime-surface pin extension, and the changeset. No object/permission/book wiring, no security-role-word. Did not touch #8273's (error-code-ledger + service-settings) or #8057's (objectql engine + kernel schema) files.


Generated by Claude Code

Register runtimeTypes: ['seed'] on the validateSecurityPosture registry
entry (packages/lint) so security-grant-expired-at-authoring and
security-delegation-missing-reason enforce at the runtime metadata write
door for a seed publish, per the #7891 programme's #4001-pattern staging.
Re-measured trip-free rather than inherited from #7576/#8308: none of the
four shipped stacks (showcase, CRM, todo, blank) author any seed row on
sys_user_position / sys_user_permission_set at all (grep across every
defineSeed() call site), so the ADR-0091 loop body never executes for the
current corpus -- structurally zero, not merely counted zero.
The other eleven rule ids this one registry entry also carries (object /
permission / book posture, security-role-word, ...) stay undeclared -- that
is #8310, still blocked on a strictness rollout and
RUNTIME_NEEDS_FULL_SNAPSHOT. Declaring runtimeTypes: ['seed'] on the WHOLE
entry rather than splitting it is safe because runtime-gate.ts's
baseline/candidate differential holds stack.objects identical across both
passes for a seed write, so every other-rule finding fires identically in
both passes and cancels in the diff -- proven in
validate-security-posture.runtime-surface.test.ts, including a positive
control (a trippy object context that WOULD leak if the isolation failed).
Extends validate-security-posture.runtime-surface.test.ts with the pin
recording the crossing (real runRuntimeAuthoringRules calls, not the
pre-crossing mirror) and a changeset.
Fixes#8307
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2
@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 10:52am

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.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 11:19
@hotlong
hotlong added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit e41c1f2Aug 13, 2026
26 checks passed
@hotlong
hotlong deleted the claude/issue-8307-seed-pair-runtime-types branch August 13, 2026 11:48
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[0 of #7891] ADR-0091 seed pair crosses the publish door: runtimeTypes: ['seed'] — measured trip-free first slice

2 participants

@hotlong@claude