Skip to content

fix(spec)!: an object permission that edits or deletes a record also reads it - #30180

Merged
objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/sec-hardening-u
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/sec-hardening-u

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Clause-②: no (narrowing) — the authoring object-permission schema refuses an entry granting allowEdit: true or allowDelete: true without read (allowRead, viewAllRecords or modifyAllRecords), a permission set it accepted before; the permission evaluator grants edit and delete only together with read; and a by-id update or delete from a caller the read door refuses answers that refusal. Nothing is widened.
Metadata-protocol: narrowed
Skills-docs: docs moved — content/docs/permissions/permission-sets.mdx states the rule under the object permission bits; skills/** teaches no entry granting edit or delete without read (its examples grant read beside edit), so no skill moves.

维护者速读

  • 改了什么:对象权限里「能编辑 / 能删除」必须同时「能读」。规范层拒收只给编辑或删除、不给读的权限条目;运行时把编辑和删除按「读 ∧ 授权」判定;设置界面勾编辑或删除时自动勾上读,取消读时一并取消编辑和删除。
  • 为什么改:按维护者 2026-11-04 裁决(第 4 批第 5 项选 B,前端设置界面配套改;第 6 项由席位按授权决定并入验收)。
  • 风险与回滚:收窄。已存库的旧权限集照常加载,但其中无读的编辑 / 删除授权不再生效;旧构建产物若含此形状,解析时被拒,需改源码后重建。回滚即回退本 PR。
  • 先看:packages/spec/src/security/permission.zod.ts(规则与运行时折叠),packages/plugins/plugin-security/src/permission-evaluator.ts(按调用者合并后的条目判定),ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx(勾选联动)。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/security/permission.test.ts -t "also reads it",预期 11 条全绿。
  • 需要你做的:无,席位落地。

What changes

The rule: an object permission that edits or deletes a record also reads it.

  • Spec. ObjectPermissionSchema (and so PermissionSetSchema, definePermissionSet, defineStack, os validate / os build and the metadata save door) refuses an entry granting allowEdit or allowDelete with none of allowRead, viewAllRecords, modifyAllRecords: one custom issue per bit at the bit's own path, naming both fixes. objectPermissionGrants answers edit and delete as read ∧ grant, the shape allowExport already had. The response-side EffectiveObjectPermissionSchema stays tolerant. The allowEdit / allowDelete descriptions state the rule (reference docs regenerated). ADR-0087 D3 semantic entry permission-write-without-read-refused with FROM → TO; no D2 conversion, because either rewrite would change an access decision.
  • Runtime (plugin-security). PermissionEvaluator.checkObjectPermission asks the fold of the caller's MERGED entry (the most-permissive per-object merge /me/permissions hands the client), so the read half may come from another set, exactly as export's does, and the door and the client map give one answer. Object admission (canReadObject / canWriteObject), security/explain and the middleware gate all read through it. A permission set stored before this release still loads; its unread edit or delete grant grants nothing.
  • By-id writes. A by-id update or delete the caller addressed, from a caller whose read of the object is refused (with these grants: a capability the object requires for a read and not for a write), answers that read refusal for every id: a caller who cannot read an object writes none of its records by id. Where a write-class row filter applies, the caller-context re-read already refuses every id with one record-level 403, and that answer (a store fault included) is unchanged.
  • ui/ (@object-ui/app-shell). The permission set editor ticks Read when Edit or Delete is ticked, and unticking any bit that reads (Read, View All or Modify All) unticks Edit and Delete once no bit that reads remains on the entry (clearing Delete beside Modify All would author the pair the spec refuses).
  • Docs. content/docs/permissions/permission-sets.mdx states the rule.
  • Fixtures. Every test that authored edit or delete without read now builds that set raw, as a stored pre-upgrade row, and pins what it grants now (nothing). Census: no shipped permission set (packages, examples, skills, docs, platform seeds) carries the shape.

Not changed: allowCreate and allowTransfer are outside this rule.

Tests

  • Spec: permission.test.ts › "an object permission that edits or deletes a record also reads it" (refusal per bit and path, wildcard, stack schema, every read bit accepted, wire tolerance, the fold).
  • Runtime: permission-evaluator.test.ts (exhaustive fold matrix against an independent reference, stored rows, cross-set read); by-id-write-unreadable-not-found.test.ts › "a caller the read door refuses gets that refusal from every by-id write it addresses, a row or none" (read gated on a capability; with and without row-level policies; capability-holder control).
  • Fixture triage pins the stored-row answer in controlled-by-parent-*, explain-asker-gate, predicate-write-unreadable-not-matched, org-admin-assignment-boundary.posture, get-effective-object-permissions, data-conflict-caller-visibility, share-links-enforcement-context; lint's access-matrix reference fold.
  • ui/: PermissionMatrixEditor.writeImpliesRead.test.tsx (both directions, saved payload parses).
  • Dogfood (HTTP door): object-permission-write-implies-read.dogfood.test.ts (save door 422 at the bit's path; a stored set without read: explain denies, PATCH / DELETE one refusal for a row and an id naming none, row untouched; control with read). upsert-by-id-caller-read.dogfood.test.ts seeds its edit-without-read personas as stored rows.

Acceptance notes

  • Census. No shipped permission set grants edit or delete without read: a brace-level scan of packages/**, examples/**, skills/**, content/docs/**, ui/** found 48 such entries, all in tests or in code guarded by modifyAllRecords; the showcase, CRM and multi-package example apps build under the new rule. Every test fixture that authored the shape was triaged: built raw as a stored pre-upgrade row (it now pins what such a row grants), never re-spelled to hide it.
  • Cross-set read. The evaluator merges the caller's resolved entries before asking the fold, so a caller holding edit from one set and read from another keeps edit, as with export. For every pre-existing cell the merged reading equals the old per-set one (each is a disjunction of bits); the exhaustive 3^8-entry matrix pins it.
  • What a caller the read door refuses now meets. Outside the permission model, the one remaining source of a declared read refusal for a caller the write door admits is an object requiredPermissions map that gates read and not update/delete (ADR-0066 ⑤). The rule above covers it at the by-id write door; pinned with and without row-level policies, plus a capability-holder control.
  • Out of this rule, unchanged: allowCreate and allowTransfer without read.
  • access-upgrade-report.ts reads a customer set's edit grant through grantsEditBit (the declared bit and the read the fold requires), so a stored entry without read, which edits nothing, is not reported; it does not fold in View All / Modify All holders, whose meeting with a package's field denies is a separate question.
  • Local ui/ type-check NOT MEASURED: @object-ui/app-shell type-check reports 1096 TS2307 (the @object-ui/* workspace packages have no built declarations in this container) and the TS7006 cascade they cause, none on an edited line; CI's Type Check UI measures it.

Verification

Readings on head 403f77c0 unless named. d7bf39c3 changed one code comment and the changeset wording; f4a273bb applied the review's should-fixes (editor cascade for every reading bit, grantsEditBit) with 113 derived families run, 0 NOT MEASURED, plugin-security 242 files / 5283 tests, the ui targeted set 75 files / 607 tests; 58698caa is prose only (changeset and docs wording), 54 derived families run, 0 NOT MEASURED.

Ablation of the runtime conjunction (one shot, restored)

objectPermissionGrants' allowEdit cell mutated to permission.allowEdit === true || modifyAll through scripts/ablation-replace.mjs, spec rebuilt, ablation-dist-preflight found the marker in 2 built files. Red: spec permission.test.ts 1 failed / 121 ("the fold grants neither verb to such an entry: read ∧ grant"); plugin-security permission-evaluator.test.ts 3 failed / 38 (the exhaustive matrix, "update: … a grant without read grants nothing", "update: the read half may arrive from a different set"); dogfood object-permission-write-implies-read 1 failed / 8 ("explain denies both"). The by-id PATCH pin stayed green under the mutation, because the read-refusal step refuses the write on its own. Restore: blob equals HEAD (27bdf93c5901), git diff HEAD empty, spec rebuilt, marker absent from all 592 built files, tree clean.

Suites
  • @objectstack/spec pnpm test: 812 / 812 files, 24413 tests.
  • @objectstack/plugin-security full: 242 / 242 files, 5282 tests.
  • @objectstack/runtime full: 543 / 543 files, 9826 tests. @objectstack/verify full: 48 / 48 files. @objectstack/core 139 / 139, @objectstack/formula 64 / 64, @objectstack/lint 192 / 193 before the reference-fold fix, the one red file (build-access-matrix.test.ts) 8 / 8 after it, @objectstack/plugin-hono-server 38 / 38.
  • ui/: the permission editor, permission slice and advanced facet suites, packages/permissions/ and every ui/ census file: 82 / 82 files, 703 tests; PermissionMatrixEditor.writeImpliesRead 9 / 9 at f4a273bb.
  • Typecheck: spec, plugin-security, runtime, lint, dogfood exit 0.
Dogfood battery (full, once)

pnpm --filter @objectstack/dogfood test under the verify lock: 417 passed / 1 failed / 3 skipped files, 3469 tests passed / 1 failed. The one failure is a 5000 ms timeout in package-commit-revert-path-package ("control: a writable package's edit commit restores its own row in place"), taken while the gate sweep shared the box; the file alone passes 12 / 12.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 113 families, each run with its exit code captured before any pipe. Three first refused with exit 3 (check-plugin-teardown-shape --self-test on the shallow clone, check:skill-examples and check:dual-build-cjs-loads on unbuilt packages); after git fetch --unshallow and building those packages, all three exit 0. --ran: "113 derived famil(ies) accounted for — 113 run, 0 NOT-MEASURED". spec check:generated: all 14 artifacts current after merging main.

claude added 10 commits November 4, 2026 18:26
… entry; a refused read answers every addressed by-id write

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
…ecord also reads it — stored rows, cross-set read, and the read-refused caller's by-id writes

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
…elete descriptions

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
…lete and clears them with Read; dogfood pins at the HTTP door

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
…as read ∧ grant

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
…so reads it, at the HTTP doors; stored edit-without-read personas

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-security, @objectstack/spec, touching 16 documentable anchor(s).

18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json a35fc6656636772766917e79c4d4692d0b7fe216.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 173 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a35fc6656636772766917e79c4d4692d0b7fe216 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f7ed8e3b38f0a9e85ca355d145fd132402e0e977 — the merge of head 58698caa351d546295248ade5a30c1040e140262 into base a35fc6656636772766917e79c4d4692d0b7fe216, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f7ed8e3b38f0a9e85ca355d145fd132402e0e977 && git checkout f7ed8e3b38f0a9e85ca355d145fd132402e0e977
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a35fc6656636772766917e79c4d4692d0b7fe216 58698caa351d546295248ade5a30c1040e140262 && git checkout -B drift-repro a35fc6656636772766917e79c4d4692d0b7fe216 && git merge --no-ff 58698caa351d546295248ade5a30c1040e140262

node scripts/docs-audit/affected-docs.mjs --json a35fc6656636772766917e79c4d4692d0b7fe216

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a35fc6656636772766917e79c4d4692d0b7fe216 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

Contract-review-claim: session_01QSeKYn5CmmdShCJifm6WYj · head d7bf39c · 2026-11-04T22:17Z

…te once nothing reads; the upgrade report's edit cell reads through the fold

Claude-Session: https://claude.ai/code/session_01QSeKYkouVh79J2UzzrhWsr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl and removed size/l labels Nov 4, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f4a273bb49a3e5cdf79ffb1884c318a60da1c8bd
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.permission-write-without-read-refused.ts, packages/spec/src/security/permission.zod.ts + packages/plugins/plugin-security/src/access-upgrade-report.ts, packages/plugins/plugin-security/src/access-upgrade-check.test.ts, ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx, ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.writeImpliesRead.test.tsx, .changeset/sec-hardening-u-write-implies-read.md, content/docs/permissions/permission-sets.mdx

Delta review by the same reviewer, on top of the FAIL record at d7bf39c3301c73fb62829b966a9c9f7350cae1d4. Inputs: git diff d7bf39c3..f4a273bb (one commit, 4 files, 77 / 9), the rewritten PR body read in full, and the check-runs on f4a273bb — all ten required contexts success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Lint UI, Type Check UI, Test UI, Build & E2E UI, Live E2E UI). Read-only: nothing built, run or re-run. Every judgment of the earlier record that the delta does not touch stands as written there.

① Derived judgments

  • The must-fix is cleared. The body's "Out of this rule, unchanged" bullet now carries only "allowCreate and allowTransfer without read". Re-read in full against §6: title, body, changeset, D3 entry, docs pages, the new commit message ("unticking View All or Modify All clears Edit and Delete once nothing reads; the upgrade report's edit cell reads through the fold") and every test name stay at rule level. No disclosure remains.
  • PermissionMatrixEditor.tsx. The read-off cascade now fires on unticking any bit that reads (unticksARead: Read, View All or Modify All, value false) and clears Edit and Delete only when no reading bit is left on the entry (!allowRead && !viewAllRecords && !modifyAllRecords). That is exactly the schema's predicate, in both directions: cleared iff the entry would otherwise carry allowEdit/allowDelete with no read, kept while any reading bit remains (so the Permission sets: no way to grant "edit any record" without also granting delete (modifyAllRecords implies allowDelete) #25575 pair is never authored beside Modify All). Four new pins cover the stored entries the first record named ({ viewAllRecords, allowEdit, allowDelete } and { allowEdit, allowDelete, modifyAllRecords } → both cleared, payload parses) and the reverse (View All off keeps Edit/Delete while Read, or Modify All, still reads). The first record's should-fix is closed.
  • access-upgrade-report.ts. Both edit reads go through grantsEditBit = raw allowEdit ∧ objectPermissionGrants(entry, 'allowEdit'), i.e. the declared bit ∧ read. Keeping the raw bit as a conjunct is right for this report: its question is whether a customer set's own edit grant meets a package field deny, and widening the cell to modifyAllRecords alone would report super-user entries whose field semantics are a different question (the dev's FLS reason). A stored set granting edit without read is no longer reported as lacking an edit deny (pinned in access-upgrade-check.test.ts, beside a reading control that still is). The last enforcement-adjacent raw read is gone; the should-fix is closed.

② Semver level

Unchanged from the first record: @objectstack/spec minor with !, Clause-②: no (narrowing), Metadata-protocol: narrowed, ADR-0087 marker and FROM → TO, D3 entry, no D2; @objectstack/plugin-security minor and @object-ui/app-shell minor cover the two files the delta touches. No generated artifact is affected by the delta (no schema, no public export); the regenerated reference pages from the earlier head stand.

The dev's open question — should the changeset's ui/ bullet name all three reading bits? Yes. The changeset ships as the CHANGELOG.md sentence an upgrading author greps, and at this head it (and the ui/ bullet of the PR body, and the In Setup sentence of permission-sets.mdx) still describe the narrower cascade of the previous head: "unticks Edit and Delete when Read is unticked (unless View All or Modify All still grants the read)". That sentence is still true, so it is not a contract breach, but it under-states what shipped. State the rule as the code and its test header now do: ticking Edit or Delete ticks Read; unticking any bit that reads — Read, View All or Modify All — unticks Edit and Delete once no bit that reads is left. Should-fix, see ③.

③ Boundary flags

  • §6: clear on this head (see ①).
  • Should-fix (prose, three places): (a) the changeset's @object-ui/app-shell bullet, the body's ui/ bullet and the docs' In Setup sentence — name all three reading bits as above; (b) the body's acceptance note "access-upgrade-report.ts still reads the raw allowEdit bit … Noted, not changed" is now false at this head and contradicts the delta — replace with the grantsEditBit statement; (c) the Verification section names 403f77c0 / d7bf39c3 only and reports PermissionMatrixEditor.writeImpliesRead as 5 / 5 where the head carries 9 — name f4a273bb and its readings, or say the head's check-runs are the measurement. None of these changes a contract; (a) edits the changeset and so takes one more head and a fresh record at it, which the seat may fold together with (b) and (c) or accept as noted.
  • Residuals (2) and (3) of the first record: unchanged by the delta, still follow-ups through §3 with the two maintainer questions named there (ruling C; ADR-0066 ⑤).
  • Governed surfaces: none in the file list.

④ Design judgment

  • The two spec hits are untouched by the delta; the first record's judgment on them stands.
  • PermissionMatrixEditor.tsx against ObjectPermissionSchema's refusal: holds — the editor can no longer hand either save door an entry the schema refuses through any single tick or untick, and every pin reads the saved payload and parses it.
  • access-upgrade-report.ts against the fold and the report's own question: holds — declared bit ∧ read, deliberately not the super-user widening, with a pin for the stored unread shape.
  • No must-fix. Should-fixes: the three prose alignments in ③.

Implemented-by: claude/sec-hardening-u
Reviewed-by: session_01QSeKYn5CmmdShCJifm6WYj

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj

席位核对

ACCEPT · head 58698caa351d546295248ade5a30c1040e140262 · 2026-11-05T00:23Z

Regen-provenance: 58698caa35 is prose only (.changeset/sec-hardening-u-write-implies-read.md and content/docs/permissions/permission-sets.mdx wording), on top of f4a273bb49. The delta record at f4a273bb (comment 6645404692, PASS) continues to govern, and this commit closes that record's wording should-fix.

  • Shape: 26+ files. Not governed. Minor for @objectstack/spec, with !, Clause-②: no (narrowing) and Metadata-protocol: narrowed. D3 entry permission-write-without-read-refused.
  • Rule held (maintainer rulings of 2026-11-04, batch 4 item 5 = B; item 6 delegated to the seat): an object permission that edits or deletes a record also reads it.
    • Authoring refuses edit or delete without a bit that reads.
    • The runtime treats stored rows as read ∧ grant, the same way it treats export.
    • The Setup editor keeps the two in step.
    • Item 6 is met: where a read refusal outside the permission model remains, a by-id write gets that read refusal for every id.
  • Contract review: the first record at d7bf39c3 was a FAIL with one must-fix: the PR body named private residuals in public text. The seat rewrote the body, and the delta record at f4a273bb (comment 6645404692) is a PASS, with the must-fix and the should-fixes closed.
  • Open questions:
    • Old build artifacts carrying edit or delete without read: answered A. There is no D2 conversion; the author fixes the source.
    • Transfer without read: goes to the maintainer as a separate decision. Not changed here.
  • Check-runs at this head (latest per name): 9 skipped, 56 success. mergeable_state: clean.
  • Gates (dev):
    • --ran: 113 run and 0 NOT MEASURED at f4a273bb; 54 run and 0 NOT MEASURED at 58698caa.
    • Full suites: spec 812 files, plugin-security 242, runtime 543.
    • Dogfood battery: 417/421 files, 1 load timeout that passes when run alone.
    • Ablation: red, then restored.
  • Seat edits: the body is rewritten to rule level, and its ui, access-upgrade and Verification lines are brought to this head.
  • Landing: ready, then auto-merge.

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant