Skip to content

feat(spec): userActions.edit/delete accept per-record CEL predicates (objectui#2614) - #3076

Merged
os-zhuang merged 6 commits into
mainfrom
claude/row-crud-cel-predicates-uzb7pz
Jul 16, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/row-crud-cel-predicates-uzb7pz

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Summary

Spec slice of objectstack-ai/objectui#2614 — the built-in row CRUD actions (Edit/Delete) could only be gated by object-level booleans, so an author could not hide/disable the Edit button per record (e.g. frozen snapshot rows).

userActions.edit / userActions.delete now accept, in addition to the plain boolean, an object form (RowCrudActionOverrideSchema):

userActions: {
  edit:   { disabledWhen: 'record.frozen == true' },          // grey out per row
  delete: { visibleWhen: 'record.frozen != true' },           // hide per row
  // enabled?: boolean — same meaning as the bare boolean form
}
  • Predicates are ExpressionInputSchema (CEL; string shorthand normalizes to the { dialect: 'cel', source } envelope). Unknown keys are rejected (.strict()), so e.g. a hideWhen typo fails at parse time.
  • Semantics documented on the schema: visibleWhen false → button not rendered (fail-closed); disabledWhen true → rendered disabled (fail-soft). Advisory UI gating only — server enforcement stays with permissions/hooks.
  • resolveCrudAffordances() keeps returning resolved booleans (enabled falls back to the managedBy bucket default) and now surfaces the predicates as editPredicates / deletePredicates, passed through as authored. Boolean-only inputs produce byte-identical output — zero behavior change for existing schemas.
  • clampManagedObjectWrites (ADR-0092 D2 /me/permissions hint clamp) treats the object form by its explicit enabled flag only: per-record predicates are not a write grant, so managed objects stay fail-closed unless enabled === true.

The UI consumption (grid row menu + related-list data-table) lands in the companion objectui PR on the same branch name.

Tests

  • packages/spec/src/data/object.test.ts — boolean back-compat, string-shorthand normalization, predicate pass-through with bucket-default enabled, boolean-equivalent object form, unknown-key rejection.
  • packages/plugins/plugin-hono-server/src/fold-wildcard-superuser.test.ts — object form counts as opt-in only via explicit enabled: true.
  • Full spec suite: 254 files / 6888 tests green; @objectstack/spec + @objectstack/plugin-hono-server build clean (dts included).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE


Generated by Claude Code

…(objectui#2614)

Extend the object-level userActions.edit / userActions.delete flags from
plain booleans to a union with an object form
{ enabled?, visibleWhen?, disabledWhen? } (RowCrudActionOverrideSchema),
so the built-in row Edit/Delete affordances can be hidden or disabled per
record via CEL predicates — the same evaluation contract custom actions
already use. Semantics: visibleWhen false → not rendered (fail-closed);
disabledWhen true → rendered disabled (fail-soft). The predicates are
advisory UI gating; server enforcement stays with permissions/hooks.

resolveCrudAffordances() keeps returning the resolved booleans (enabled
falls back to the managedBy bucket default) and now surfaces the
predicates as editPredicates / deletePredicates, pass-through as
authored. Boolean-only inputs produce byte-identical output — zero
behavior change for existing schemas.

clampManagedObjectWrites (ADR-0092 D2 hint clamp) treats the object form
by its explicit enabled flag only: per-record predicates are not a write
grant, so a managed object stays fail-closed unless enabled === true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@vercel

vercel Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 16, 2026 4:46pm

Request Review

@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-hono-server, packages/qa, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server)
  • content/docs/permissions/authorization.mdx (via packages/qa, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Jul 16, 2026
…e exports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
…58 expression ledger

The new data/object.zod.ts:visibleWhen / :disabledWhen surfaces
(objectui#2614) tripped the conformance ratchet. Two rows, matching
their actual fail policies: visibleWhen is fail-closed (a faulting
predicate hides the row button), disabledWhen is fail-soft-log (a
faulting predicate leaves it enabled; server hooks are the boundary).
Both interpret on the canonical celEngine via objectui useRowPredicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
… (objectui#2614)

A PAID invoice's fields already freeze via readonlyWhen; the built-in
row actions now follow the same truth: Edit renders DISABLED on paid
rows (disabledWhen), Delete is HIDDEN outright (visibleWhen). Draft and
sent invoices keep the untouched menu. Browser-verified against the
objectui HMR console: paid row menu = greyed Edit only; sent row menu =
enabled Edit + Delete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@os-zhuang
os-zhuang marked this pull request as ready for review July 16, 2026 16:44
@os-zhuang
os-zhuang merged commit 627f225 into main Jul 16, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/row-crud-cel-predicates-uzb7pz branch July 16, 2026 16:50
os-zhuang added a commit that referenced this pull request Jul 17, 2026
content/docs/references/** is generated from packages/spec and committed, but no CI
job ever regenerated and diffed it, so the public reference docs drifted silently
while main stayed green. #3076 added RowCrudActionOverride to the spec and the docs
never learned the type existed.

Regenerate: 7 files, every change traced to a spec change that shipped without
re-running the generator — RowCrudActionOverride and ServiceSelfInfo missing outright,
dashboard filterBindings/name missing, readonly (#2948/#3003) and allowTransfer (#3004)
stale, and connector ADR-0096 → ADR-0097 (both ADRs exist and are distinct, so the
published docs were pointing readers at the wrong one). Verified deterministic: two
consecutive runs produce byte-identical output.

Gate: build-docs.ts --check, following the sibling convention. Every write goes through
emit() and every wiped folder through manageDir(), so check and write run identical
generation logic and differ only in the final disposition — it cannot pass on output a
real run would not produce. Verified output-identical to the previous generator across
all 258 files, and proven to fail on stale content, a missing page, a stale leftover
page, and a vacuous no-schema run. Not `git diff --exit-code`: that misses untracked
files.

Placement: lint.yml's "TypeScript Type Check" — no paths filter and a required status
check, so the gate can neither go dormant nor be merged past. ci.yml's "Build Docs" is
gated on a `docs` filter excluding packages/spec/**, so it skips exactly the spec-only
PRs that cause this drift.

Also un-dormants the sibling gates: check:spec-changes / check:upgrade-guide read the
ADR-0087 registries but ran under a filter listing only skills/**, and that filter
watched content/docs/guides/skills.mdx, a path #2584 moved. Job renamed
check-skill-docs → check-generated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…icate union (objectstack-ai#7692) (objectstack-ai#7758)

objectstack-ai#3076 (objectui#2614) gave `userActions.edit`/`delete` a boolean-or-predicates
union so the built-in row affordances could be gated on record state.
`create`/`import` were left as bare booleans with no other lever, so a child
object's related-list [+ New] button could not be gated on the parent record's
state while the row Edit/Delete beside it could. On a frozen parent the row
actions grey out and [+ New] still renders; the server guard 409s the insert, so
it is an affordance leak an app has no way to close.

Both keys now take the SAME union — the same RowCrudActionOverrideSchema, not a
new dialect. resolveCrudAffordances carries the predicates through as
createPredicates/importPredicates alongside the existing edit/delete pair, via
the same normalizeRowCrudOverride collapse.

What `record.*` binds to differs between the two positions, and the schema says
so rather than implying a symmetry it does not have: edit/delete evaluate per
row against that row's own record; create/import gate a record that does not
exist yet, so they evaluate once per toolbar against the record in scope — the
host (parent) record on a related list, and nothing on a standalone object list,
where a `record.*` predicate therefore hides the button under the fail-closed
rule.

plugin-hono-server tracks the widened producer: the /me/permissions managed-write
clamp tested `create` with a bare `!== true`, which would have clamped away a
legitimate `create: { enabled: true, visibleWhen: … }` opt-in. It now reads
`create` through the same opt-in helper as edit/delete.

The renderer half (related-list toolbar honouring create.visibleWhen) is
objectui's downstream card and is not part of this change.


Claude-Session: https://claude.ai/code/session_017aiuit7rFUsQWropgqtfnR

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ned and URL-spelled citations, pinned in one spelling table (objectstack-ai#20989)

Fixes objectstack-ai#20636
Clause-②: no

The family closeout for the citation extractor in
`scripts/check-issue-citations.mjs`: every spelling a seat measured as
invisible to the diff gate and the census is now read, every exclusion
keeps only the shapes it was measured protecting, and the self-test
carries the one enumeration table the triage asked for (48 spellings,
each "extracted as" or "not a citation, because"). Landing site:
`scripts/check-issue-citations.mjs` only.
`scripts/check-doc-authoring.mjs` is untouched; H2 below says why.

## What changed

- **Hyphen after the number.** The lookahead no longer refuses a `-`, so
`objectstack-ai#13398-class`, `objectstack-ai#5347-A` and `ui#6206-B` read as citations of their
number. It still refuses a word character, so a hex colour stays out.
- **Slash before the `#`.** A `/` is now valid context before a bare
`#`. It stays refused only before a qualifier candidate, so a URL path
fragment such as `https://example.com/docs/page#12` never reads `page`
as prose. A `/` right after a digit is the exception, so
`objectstack-ai#3076/objectui#2614` still reads its own qualifier.
- **Slash-joined continuation.** In `#A/#B`, the second number takes the
chain head's reading: bare after a bare or prose head, the head's
repository after a qualified head, and an ordinal after an ordinal. Only
a joined `/` continues a chain. `objectui#1 / objectstack-ai#2` and `objectui#1 + objectstack-ai#2`
stay two separate readings.
- **URL spelling.** `https://github.com/OWNER/REPO/issues/N` and
`.../pull/N` are now citations, qualified by their own `OWNER/REPO`.
That puts `objectstack-ai/framework` in this repository and makes every
other repository's URL cross-repo, never a finding. Inside a markdown
link `[#N](URL)`, the citation counts once.
- **Head rows.** I retired `re-charter`, `clause` and `option` (named in
the thread), plus `acceptance` and the section mark (found by the same
measurement). Each protects 0 sites repo-wide at two or more digits and
hides board citations. The grammar's two-digit floor already keeps
one-digit ordinals out. I added a `](` row, the narrower guard the
hyphen exclusion leaves behind for markdown in-page heading anchors.
- **Self-test.**
  - A new `spellings` battery (56 cases) reads the table row by row.
- Every head row must excuse at least one table row, and every required
spelling (the card's list plus the thread's) must be present.
- The `live-corpus` battery gains three floors, one per new arm, each
counting only a number spelled once on its line.
  - The roster floor rises from 6 to 9 batteries.
  - Total: 114 cases in 8 batteries became 173 in 9.
- I edited the header in place and kept its line count, because
`scripts/pm/dispatch-gates.mjs`'s self-test pins
`scripts/check-issue-citations.mjs:204 local-env`. The marker is still
on line 204, and that pin passes (see Gates).

## H1: what each exclusion protected and hid

Measured on `3693a1b50` over the declared surfaces. Every arm was judged
against one enumerated board: 188 pages, frontier 20959,
2026-09-30T22:55Z. The instrument mirrors the gate's extractor and
matched it file for file on all 2,640 files (0 mismatches).

| exclusion | hid (sites, dead) | protected in the declared surfaces |
disposition |
|---|---|---|---|
| hyphen after the number | 58, 1 dead (8 cross-repo) | 0: no numeric
range, slug or hex-like token. Repo-wide: in-page heading anchors, 16
lines in `docs/design/**` and `skills/**`, plus one range,
`docs/audits/...md`, whose first number is a citation | dropped; the
`](` row keeps the anchor out |
| `/` before the `#` | 528, 10 dead: 523 `#A/#B` second numbers and 5
`TOKEN/#N` such as `ADR-0049/objectstack-ai#1888` | 0 paths and 0 URL fragments |
narrowed to the candidate arm; continuations read as their chain |
| head `re-charter` | 0 left on this tree (the 26 dead `re-charter
objectstack-ai#13135` were rewritten by PR objectstack-ai#20750) | 0; only the gate's own fixtures
used it | retired |
| head `clause` | 0 in the surfaces; 4 in deferred test files, all board
citations | 0 | retired |
| head `option` | 1 (`option objectstack-ai#14088`, live); 1 more under `scripts/**` |
0 | retired |
| head `acceptance` | 1 (`the silent acceptance objectstack-ai#6132 closed`, live) | 0
at two or more digits | retired (in-place, below) |
| head section mark | 0 in the surfaces; 4 in test files (`§6 objectstack-ai#11176's
decisions`) | 0 at two or more digits | retired (in-place, below) |
| heads kept | none measured hiding a citation | directive 203 (max 13),
`PD` 85 (max 13), batch 276 (69 distinct, 11 to 227), `OQ` 10, `PKCS` 1
| kept |

The 8 slash chains headed by another repository are not a case where the
two populations cannot be told apart. The 5 on objectui's public board
each name objectui's record, the issue and then the pull request that
fixed it, read one by one against both boards:

- `objectui#2715/objectstack-ai#2717`
- `objectstack-ai#2711/objectstack-ai#2722`
- `objectstack-ai#2725/objectstack-ai#2732`
- `objectstack-ai#2967/objectstack-ai#2904`
- `objectstack-ai#4648/objectstack-ai#4901`

This repository's records with the same numbers are unrelated. The other
3 (`cloud`, `hotcrm-heimao`) are boards one credential cannot read, so
they stay unjudged, as they were before.

## H2: where the URL spelling belongs

The extractor. At `3693a1b50`, 57 URL sites sit in the gate's
projection: 56 in package comments and 1 link on a release page. 4 of
them are dead. Only 2 URL sites in package sources are inside string
literals, both internal `note:` strings in
`packages/runtime/src/route-ledger.ts`.

`check:doc-authoring` asks a different question: may a runtime string
carry a tracker reference at all? It reads string literals, skills and
spec refusal messages. The two projections are disjoint, so adding the
URL to the extractor double-counts nothing there. Inside the extractor,
the one double-spelled site (`[objectstack-ai#15325](...objectstack-ai/issues/15325)` on
`v17/17-3.mdx`) counts once. `check-doc-authoring.mjs` is not touched.

## H3: open PRs' added lines

All 13 open PRs at 2026-09-30T23:2xZ: their heads were fetched into a
private ref namespace (deleted afterwards). For each, the BASE extractor
and this one were run over the lines it adds, against its merge base.
Result: 85 added-line citations under both extractors, 0 newly
extracted, 0 lost. No PR's verdict changes. Lines a PR does not add are
never judged, which is unchanged and pinned in the `diff-scope` battery.

## Census, before and after

The gate's own `--census --json`, once with the `3693a1b50` script and
once with this one, over the same tree:

| | judged | resolves | resolves as PR | cross-repo |
allocated-but-absent |
|---|---|---|---|---|---|
| before (frontier 20965) | 37,152 | 33,403 | 1,985 | 1,024 | 740 |
| after (frontier 20966) | 37,796 | 33,917 | 2,083 | 1,041 | 755 |

That is 644 more judged sites and 15 more dead ones, with 0 findings
lost. By arm: hyphen 1 dead, slash 10 dead, URL 4 dead.

Newly visible dead sites per lane. I rewrote none of them; they belong
to the lane cards:

- **objectstack-ai#20594 (`domain:cli`): 1.** `packages/rest/src/rest-server.ts:7456`,
objectstack-ai#11006.
- **objectstack-ai#20595 (`domain:engine`): 6.**
- `driver-sql`: `sql-driver.ts:3933` (URL, objectstack-ai#17590), `:12110` (objectstack-ai#10629),
`:16106` (objectstack-ai#17343).
  - `metadata`: `loaders/ambiguous-metadata-stem.ts:40` (objectstack-ai#14423).
- `metadata-protocol`: `migrations/partial-index-probe.ts:395` (objectstack-ai#16657).
  - `objectql`: `plugin.ts:1496` (objectstack-ai#10629).
- **objectstack-ai#20596 (`domain:services`): 0.**
- **objectstack-ai#20597 (`domain:spec`, `packages/lint`): 0.**
- **objectstack-ai#20234 (`packages/spec/src`): 7.**
  - `data/datasource.zod.ts:701` (objectstack-ai#9040).
  - `data/filter.zod.ts:1040` (URL, objectstack-ai#17590) and `:1042` (URL, objectstack-ai#17286).
  - `data/value-roundtrip-conformance.ts:100` (URL, objectstack-ai#12380).
  - `ui/component.zod.ts:593` and `:669` (objectstack-ai#6276), and `:3771` (objectstack-ai#9972).
- **Release pages (`domain:devx`, no lane card): 1.**
`content/docs/releases/v17/index.mdx:168` (objectstack-ai#6075).

This census was run on the PR head's tree, not after landing. A re-run
after landing reads the same corpus plus whatever `main` has gained by
then.

## In-place fixes beyond the three named rows

I retired the `acceptance` and section-mark rows here rather than filing
them. All four conditions hold:

1. They are the same defect class as `option` and `clause`: a head row
hiding a board citation.
2. The fix is mechanical, and the shape is pinned by the table.
3. The file is this claim's own surface (`NON_CITATION_HEADS`).
4. The same gate's self-test covers them, so no new verification surface
is added.

Evidence: `acceptance objectstack-ai#6132` (live) in
`packages/formula/src/cel-pushdown-limits.ts:82`, and the section-mark
sites in deferred test files. Neither row has any two-or-more-digit
ordinal anywhere in the repository.

## Gates (final head `25d96fcc0`)

I re-derived the list with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`, which gave 32 commands.
The same derivation on a throwaway tree at `origin/main` (`05be35259`)
with this diff applied gave an identical list. I ran all 32, plus `pnpm
check:doc-authoring` and the self-test, and every one exited 0.
Reconciliation verdict line:

`✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0
NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of
them is 3).`

Verdict lines worth quoting:

- `node scripts/check-issue-citations.mjs --self-test`: `✅ ... every
spelling enumerated ... (173 cases, 9 batteries)`. It also passes on
`origin/main` `05be35259` with this diff applied.
- `node scripts/check-issue-citations.mjs` (diff mode): `✅
check-issue-citations: no issue citations added against 3693a1b (0
file(s) read).` The script lives in the deferred `scripts/**` surface.
- `pnpm check:pm-dispatch-gates`: `✓ dispatch-gates self-test: 1976
cases pass.` (1237.9s). Its pin `scripts/check-issue-citations.mjs:204
local-env` holds.
- `pnpm check:doc-authoring`: `✓ doc authoring guard: ... hold the
baseline — 549 pinned site(s)`.
- `pnpm check:nul-bytes`: `check-nul-bytes: OK (scanned 9582 text
file(s) ...)`.
- `node scripts/check-scripts-symbol-anchors.mjs`: `✅ ... 3706 anchors
across 282 scripts resolve`.

## Ablations (one-shot, from committed `25d96fcc0`, via
`scripts/ablation-replace.mjs`)

Every leg was expected to go red, and every leg did. Each one restored
to blob `c732ce2e21c7` (equal to HEAD) with an empty `git diff HEAD`. No
permanent ablation file is left.

| mutation | first red |
|---|---|
| hyphen refused again after the number | `the live corpus must yield a
#N-word citation` |
| `/` refused again before the `#` | `the live corpus must yield a
slash-joined #A/#B second number` |
| URL arm disabled | `the live corpus must yield a URL-spelled citation`
|
| `option` head row restored | `spelling option #N in "the option objectstack-ai#14088
gave" must read objectstack-ai#14088; got nothing` |
| continuation disabled | `spelling repo#A/#B ... must read
objectstack-ai/objectui#2711, objectstack-ai/objectui#2722` |
| `](` row disabled | `a markdown link's in-page heading anchor is not a
citation` |
| link de-duplication disabled | `spelling [#N](URL) ... must read
objectstack-ai#15325` |

The first slash ablation, run before the floors were tightened, went red
in the table but left the live-corpus continuation floor green. A line
citing the same number twice let a plain citation stand in for the
slash-joined one. Commit `25d96fcc0` makes each new floor count only a
number spelled once on its line. The re-run above is red at that floor.

## Acceptance notes

- **URL spelling in runtime strings.** `check:doc-authoring` does not
read it. At `3693a1b50` the population is 2 internal route-ledger
`note:` strings (`packages/runtime/src/route-ledger.ts:459`, `:465`),
and no author-facing door shows them. Noted, not filed; carrier: none.
- **Range second numbers.** In a range such as `objectstack-ai#712-714`, the second
number carries no `#` and is not read. There is 1 site repo-wide, in
`docs/audits/**`, outside the declared surfaces. Pinned in the table
with its reason.
- **Dormant test-file citations.** The 8 test-file citations the retired
`clause` and section-mark rows hid are in the deferred test surface.
They become visible only when that surface is swept.
- **Line-number pin.** `dispatch-gates.mjs` pins this file's `local-env`
marker by line number (`:204`), so any future header growth above it has
to move that pin in the same PR.

No changeset: a root `scripts/` file publishes nothing (the root
`package.json` is private, and no package's `files` ships `scripts/`),
so this PR takes `skip-changeset`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 3b90fd68 Deployed Jul 16, 2026 by vercel[bot]
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants