Skip to content

Migrate documentation site to Fumadocs with monorepo structure and shared content - #11

Merged
hotlong merged 11 commits into
mainfrom
copilot/setup-fumadocs-site
Jan 19, 2026
Merged

hotlong merged 11 commits into
mainfrom
copilot/setup-fumadocs-site

Conversation

Copilot AI commented Jan 18, 2026 •

Copy link
Copy Markdown
Contributor

Replace the custom @objectdocs/cli documentation tooling with Fumadocs, a modern Next.js-based documentation framework, and restructure it into a proper monorepo architecture under apps/docs/ while keeping documentation content at the root level for sharing.

Changes

Monorepo Structure

  • Multi-package Architecture: Moved all Next.js files to apps/docs/ to avoid polluting the root directory
  • Workspace Isolation: Documentation site now has its own package.json with isolated dependencies
  • Clean Separation: Clear boundary between protocol packages (packages/*) and applications (apps/*)
  • Shared Content: Documentation content remains at /content/docs/ (root level) for easy access and sharing across the repository

Core Infrastructure

  • Next.js 16 + App Router: Full static site generation for 136 documentation pages
  • Fumadocs MDX: Auto-compilation from /content/docs/ (root level) with type-safe collection handling
  • Tailwind CSS v4: New CSS architecture with @tailwindcss/postcss

Directory Structure

Repository Root:
├── content/docs/     # Documentation content (MDX files) - SHARED LOCATION
│   ├── concepts/
│   ├── references/
│   └── specifications/
└── apps/docs/
    ├── app/              # Next.js App Router
    ├── source.config.ts  # Fumadocs collection config (references ../../content/docs)
    ├── next.config.mjs   # Next.js configuration
    ├── tailwind.config.js
    ├── postcss.config.mjs
    ├── tsconfig.json
    └── package.json      # Isolated dependencies

Configuration

// apps/docs/source.config.ts - Collection definition
export const docs = defineDocs({
  dir: '../../content/docs',  // References root-level content
}) as any; // Workaround for pnpm zod@4 type portability

// apps/docs/app/source.ts - Loader setup
import { docs } from 'fumadocs-mdx:collections/server';
export const source = loader({
  baseUrl: '/docs',
  source: (docs as any).toFumadocsSource(),
});

UI Components

  • Collapsible sidebar with auto-generated navigation from meta.json files
  • Search (⌘K), theme toggle, GitHub integration
  • MDX components (Callout, Card, Cards) rendered via fumadocs-ui/mdx

Build Output

Route (app)
├ ○ /
└ ● /docs/[[...slug]] (136 pages)
  ├ /docs
  ├ /docs/concepts/*
  ├ /docs/references/*
  └ /docs/specifications/*

Workspace Configuration

  • Updated pnpm-workspace.yaml to include apps/*
  • Root package.json scripts use workspace filters: pnpm --filter @objectstack/docs
  • Root package.json includes shared dependencies (lucide-react) used by MDX files
  • Root tsconfig.json excludes apps directory
  • .gitignore updated for apps structure

Screenshot

Scripts

Scripts remain the same from repository root:

pnpm docs:dev    # Start development server
pnpm docs:build  # Build for production
pnpm docs:start  # Start production server

Benefits

  1. Clean Root Directory: No Next.js files in repository root
  2. Shared Content: Documentation content at /content/docs/ accessible to other tools and workflows
  3. Workspace Isolation: Each app/package manages its own dependencies
  4. Scalability: Easy to add more apps (e.g., apps/playground, apps/studio)
  5. Standard Pattern: Follows Turborepo/Nx monorepo conventions
  6. Maintainability: Clear separation of concerns

All existing MDX content preserved at the root level. The .source/ directory (auto-generated by Fumadocs) is gitignored.

Original prompt

帮我使用fumadocs搭建此文档站点


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel

vercel Bot commented Jan 18, 2026 •

Copy link
Copy Markdown

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

Project Deployment Review Updated (UTC)
spec Error Error Jan 19, 2026 5:28am

Request Review

Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
…rver

Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Copilot AI changed the title [WIP] Add setup for Fumadocs documentation site Migrate documentation site from custom @objectdocs/cli to Fumadocs Jan 18, 2026
Copilot AI requested a review from huangyiirene January 18, 2026 17:46
@hotlong

hotlong commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

@copilot nextjs项目使用多包架构放到apps/docs,不要污染根目录

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

Copilot AI commented Jan 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot nextjs项目使用多包架构放到apps/docs,不要污染根目录

Done! The Next.js documentation site has been moved to apps/docs/ following monorepo best practices.

Changes:

  • ✅ All Next.js files moved to apps/docs/
  • ✅ Documentation content now in apps/docs/content/
  • ✅ Root directory cleaned up
  • ✅ Workspace configuration updated
  • ✅ Scripts still work from root: pnpm docs:dev, pnpm docs:build

See commit 3124fe9 for the complete restructuring.

Documentation Site

Copilot AI changed the title Migrate documentation site from custom @objectdocs/cli to Fumadocs Migrate documentation site to Fumadocs with monorepo structure (apps/docs) Jan 19, 2026
Copilot AI requested a review from hotlong January 19, 2026 01:02
@hotlong

hotlong commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

@copilot 我的文档还是放在很目录,不要换位置 /content/docs

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

Copilot AI commented Jan 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot 我的文档还是放在很目录,不要换位置 /content/docs

Done! Documentation content is now kept at the root directory /content/docs/ as requested.

Changes:

  • ✅ Content moved back to /content/docs/ (root level)
  • ✅ apps/docs/source.config.ts updated to reference ../../content/docs
  • ✅ lucide-react added to root dependencies (required by MDX files)
  • ✅ Build verified: All 136 pages generated successfully

The Next.js app remains in apps/docs/ but references the shared content at the root. This provides the best of both worlds: clean app structure while keeping content accessible at the root level.

See commit c60ebe7 for the changes.

Documentation Site

Copilot AI changed the title Migrate documentation site to Fumadocs with monorepo structure (apps/docs) Migrate documentation site to Fumadocs with monorepo structure and shared content Jan 19, 2026
…mprove documentation formatting, and refine build scripts
This was referenced Sep 3, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…orktree path, and make the self-test exit non-zero when it fails (objectstack-ai#18165)

Fixes objectstack-ai#17602

## PREMISE P was measured first, and it HOLDS — so the driver is not the
defect

The card asks whether a zero exit from `merge=os-regen` can be trusted.
The `domain:devx`
seat's mechanism reading (comment `5631161285`) narrowed that to one
falsifiable premise:
is `content/docs/references/**` really "generated whole", as
`.gitattributes:84` declares?
If yes, the unconditional deferral is the design and `markPending` +
regeneration closes
the loop; if no, the row is MIXED without a `mixed` field and the
deferral is a silent
deletion — and the defect would be in the declaration.

Measured on `origin/main` `394ccf549`, with the post-driver state
reproduced exactly (the
artifact missing main's side while the source still carries it), the
generator re-run, and
both controls lit:

```
                                          before mutation   after mutation   after gen:docs
main's #groupBy whitespace note                  2                0                2     <- P
FIRING CONTROL  (marker injected into the mdx)   0                1                0
EMPTY CONTROL   (a string never written)         0                0                0
blob sha of content/docs/references/ui/view.mdx
  HEAD          2fa435c
  mutated       2e40a12d888d9babaa254974a69b3feece62a93a   (the mutation landed on disk)
  regenerated   2fa435c   (byte-identical to HEAD)
```

The note is not hand-written: it is a `.describe()` at
`packages/spec/src/ui/view.zod.ts:917`,
rendered into the page by `gen:docs`. The firing control proves the
generator rewrites this
file wholesale rather than skipping or appending — the injected marker
is gone. Regeneration
reproduces the page **byte for byte**.

⇒ `.gitattributes:84` is true for this family.
`scripts/git-merge-regen.mjs` behaved exactly
as its own comment says it should, and ⛔ nothing in this PR changes the
driver's merge
behaviour, weakens a gate, adds an exemption, or makes the driver prefer
a side in a new case.

## So the work is ask 2 — and the safety net DOES hold, but nothing
proved it

The remaining question is the one the ruling names: on the path this
incident took — a
human or agent hand-repairing a merge **in a linked worktree** — does
the refusal actually
fire? Measured live, in this worktree, with a real merge of a real
routed path:

```
git merge exit                                      0   (the card's zero exit, reproduced)
marker written to .git/worktrees/.../os-regen-pending YES
the ordinary commit right after                     REFUSED by .githooks/pre-commit
the push                                            REFUSED by .githooks/pre-push
```

The net holds. What is unguarded is that **no test says so.** The driver
ends every deferral
by *printing* "The pre-commit hook will not let this commit through
until you do", and the two
cases that come closest each stop one step short in the same direction:

- `endToEnd()` performs a real merge, asserts the marker exists, and
never commits — so git
  never runs a hook.
- `check-regen-pending.mjs`'s deferred-merge fixture sets
`core.hooksPath=/dev/null` ("the
fixture drives the script itself") and writes the marker by hand — it
grades the checker's
verdict, never git's willingness to invoke it, and never the path the
driver wrote to.

The joint property had no coverage at all, and the one arrangement in
which it can break is
the one AGENTS.md Prime Directive objectstack-ai#11 makes mandatory. `markPending()`
in the driver and
`gitDirPath()` in the checker are two **independent**
`--absolute-git-dir` call sites; in a
primary checkout that directory and `--git-common-dir` are the **same
directory**, so a
regression to the common dir keeps every single-tree fixture green while
every real merge in
every agent's worktree silently loses its refusal.

## What landed

**1. `endToEndWorktreeRefusal()` — a sixteenth self-test battery.** It
builds a throwaway
repo with a linked worktree, reaches the REAL `.githooks` and the REAL
checker through
untracked symlinks (so the *relative* `core.hooksPath` resolves per
worktree, as
`setup-git-hooks.mjs` registers it — a hook this file wrote out would
test an imitation),
merges a routed path inside the linked worktree, and asserts nine legs:

| leg | assertion |
|---|---|
| 1 | the marker lands in the LINKED worktree's own git dir |
| 2 | …and NOT in the shared common git dir — the discrimination only a
linked worktree can make |
| 3 | the ordinary commit after the merge is REFUSED, by git, through
the real `pre-commit` |
| 4 | …naming the artifact, so the refusal is this one and not some
other failure |
| 5,6 | the push is REFUSED too, by the real `pre-push`, via its
os-regen leg |
| 7,8,9 | **FIRING CONTROL** — with the gate clean the same commit is
ACCEPTED, the marker is gone, and the push goes through |

Legs 7-9 are what legs 1-6 rest on: a hook that can only fail — a
missing interpreter, a
typo'd path, node erroring — satisfies every refusal assertion while
proving nothing, and
from the outside that reads exactly like "the net holds".

**2. The self-test's exit code now agrees with its own verdict.** Found
by the ablation below,
not by reading. The `--self-test` dispatch computed `failures` from the
booleans its callees
return and printed `✗ merge driver wiring is inconsistent — N
failure(s)` from that count —
then exited **0**. The only thing that ever set a non-zero code was
`fail()`, which every
callee happened to call. The two agreed by convention, not by
construction, and
`pnpm check:merge-driver` chains this file with `&&`: the first callee
to report a failure by
returning `false` alone would have made the whole battery advisory. The
verdict line and the
exit code now come from the same count.

This is an in-place fix under the bounded exemption — same defect class
as the card (a failed
check reporting as a pass), mechanical, on a file this PR already
declares, in the same gate
family, adding no new verification surface.

## Ablation — the battery can fail, and the existing cases cannot catch
what it catches

`markPending()` regressed to the shared common git dir (the exact
regression class the case
exists for), run from the committed state, mutation proven on disk
before the run and the
restore proven byte-identical after:

```
occurrences of the replaced line   1 -> 0       injected marker  0 -> 1
blob sha  HEAD 4506a6d -> ebd8f1077e725251161a067d50905c3d8b116c10

self-test exit under the ablation                          1     (before fix 2: 0 — see above)
  ✗ endToEndWorktreeRefusal — all six refusal legs red, firing control red
  ✓ end-to-end: conflicting packages/spec/spec-changes.json … recorded as pending   <- STILL GREEN
  ✓ end-to-end (mixed): anchors-only deferred to OURS …                             <- STILL GREEN
  ✓ probe gate: `git merge-tree` reaches the driver and records nothing             <- STILL GREEN

restore: blob sha back to 4506a6d…, residual injected text 0, `git diff HEAD` empty
```

Three pre-existing end-to-end cases stay green through a regression that
disarms the refusal
for every worktree in the container. That asymmetry is the case's whole
justification.

## Which of the three unreliable signals this makes trustworthy

Triage `5628590957` records three cards describing one apparatus whose
every signal is
unreliable, and asks which one a change repairs. This PR repairs **the
driver's exit code
being the only evidence the merge was safe** (objectstack-ai#17602): after it, the
claim "the hook refuses
until regeneration has run" is a test rather than a sentence, on the
path real merges take.

It also, incidentally, moves **objectstack-ai#13799**'s signal for this one member:
the `os-regen-merge`
self-test (`.github/workflows/lint.yml`) is named in objectstack-ai#13799's
population, and fix 2 means that
battery can no longer report a failure at exit 0. ⛔ That is one member,
⛔ not objectstack-ai#13799.

It does **not** touch **objectstack-ai#16717** — `check:merge-driver` remains red on
macOS for the
runner-missing classifier's reasons, so a green local run of this
battery is not evidence
there. The new case is platform-neutral (git, symlinks, `pnpm -s` on a
stub), but it inherits
objectstack-ai#16717's blind spot like every other case in the file.

## Effect on objectstack-ai#16612 (shares this file, serialized behind this card)

**Neither easier nor harder — but it is now instrumented.** objectstack-ai#16612
proposes retiring the only
`mixed` row, which would drop `system-context.mdx` onto the
unconditional-deferral branch this
card measured. Nothing here changes that branch. What changes is that
whoever takes objectstack-ai#16612 now
has a case that proves the deferral's refusal actually fires end to end,
so the question
"is the safety net behind the deferral real?" is answerable by running
`pnpm check:merge-driver`
instead of by argument. ⛔ Not fixed in passing; ⛔ not re-priced here.

## Gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived
**32** commands from the real diff (1 path,
`scripts/git-merge-regen.mjs`, vs merge base
`394ccf549`). All 32 run and exit 0, reconciled with `--ran`. Exit codes
landed to disk per
command. `pnpm check:merge-driver` — the gate that owns this battery —
is among them.

`pnpm lint`'s scan was not narrowed and needs no narrowing argument: the
whole set ran at
`3d8e4c63e` — `eslint . --no-inline-config --format json` over **6752**
files, **0** errors,
**0** warnings. (The config declares it never enables type-aware linting
for any file, so a
one-file diff could not have moved another file's verdict in any case.)

changeset: **`skip-changeset`**, measured rather than assumed. The diff
is one root
`scripts/**` file; the root package is `private: true`; all 70 published
packages declare
`files[]` and none can reach root `scripts/`; the new symbol occurs in
zero published package
sources, against a lit positive control (`defineStack` hits in
`packages/spec/src`).

## Acceptance notes

- `SELF_TEST_BATTERY_FLOOR` moved 15 to 16 and the roster/dispatch
gained the new callee, as
that mechanism requires. The callee-count prose in the same block moved
with it; one number
there ("the other thirteen") was already off by one against a roster of
fifteen and is now
correct at fifteen against sixteen. Mechanical, in the block being
edited.
- Noted, not filed: `check-regen-pending.mjs`'s deferred-merge fixture
still sets
`core.hooksPath=/dev/null` and writes its marker by hand. That is
deliberate and correct for
what it grades (the checker's verdict machine), and the new case covers
the half it cannot;
a second end-to-end fixture there would duplicate it. Successor: whoever
next widens that
  fixture's scenario set.
- Noted, not filed: the driver's `--self-test` prints its verdict and,
before this PR, derived
its exit code elsewhere. The sibling self-tests chained beside it in
`check:merge-driver`
(`git-env.mjs`, `check-regen-pending.mjs`) were not audited for the same
shape — out of scope
here, and neither is known to have it. Successor: none currently queued.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…mes a known repository, so pre-#N / post-#N are judged and framework#N is this repository (objectstack-ai#20554)

Fixes objectstack-ai#20330
Clause-②: no

## What changed

`scripts/check-issue-citations.mjs` read ANY `word#N` as a repository
reference, so `pre-objectstack-ai#12248`, `post-objectstack-ai#6640`, `Pre-#N`, `POST-#N` and
`Framework#N` were classed cross-repo and never judged. Its qualifier is
now a **closed set**. A token joined to `#N` names a repository only
when it is an `owner/repo` form or a name in the new
`KNOWN_REPOSITORIES` table, matched case-insensitively. Any other prefix
is prose, and the number after it is this repository's and is judged.

One recogniser, `repositoryOf`, is asked in all three places: by
extraction, by the board's probe set (`boardWanted`) and by the
classifier (`namesThisRepository`). The three can no longer disagree
about a token.

- `KNOWN_REPOSITORIES` rows: `objectstack` and `framework` (both THIS
repository), `objectui`, `ui` (an objectui alias, joined form only),
`cloud`, `hotcrm`, `hotcrm-heimao`, `os-tianshun-mtc` and
`os-project-titanwind-ehr`. Each row carries the measurement that put it
in.
- **Prose form.** `objectui PR objectstack-ai#10264`, `objectui objectstack-ai#2670`, `cloud objectstack-ai#2937`
and `framework objectstack-ai#2679` read as their repository. The form is a known
repository name, whitespace, an optional `PR` or `issue`, then `#N`. The
alias `ui` is excluded from it, because `UI #N` is ordinary English.
- **No carry across a pair.** In `objectui#6110 + objectstack-ai#6111` the second
number stays this repository's. The convention is to qualify each
number, and the one live site in the claim's surface is respelled:
`packages/spec/src/data/field.zod.ts:370` now reads `objectui#6110 +
objectui#6111`. This is comment-only, with a `patch` changeset.
- **Ordinal heads.** Closing the set exposes ordinals that were hidden
behind a fake qualifier. `NON_CITATION_HEADS` gains `OQ` (`ADR-0076
OQ#10`, 10 sites) and `PKCS` (`PKCS#11`, 1 site). A hyphen joining a
head to its `#` is now read as the same head (`Prime-Directive-objectstack-ai#12`, 2
sites). `PD#12` (8 sites) was already covered by the existing `pd` head
once its candidate is refused.
- **Refusal text.** The `REMEDY` text now states the grammar that judged
the author.

### A false red in the same seam, fixed because this change would have
widened it

`buildBoard` probed only UNQUALIFIED numbers, so a diff adding
`objectstack#N` was classified against a board that never asked about N.
Reproduced on unmodified `288611e3e5`: I appended `objectstack#20330`
(this live card) to a swept file and ran `node
scripts/check-issue-citations.mjs`. It exited **2**, reading `board:
probed (0 citations)` and `[allocated-but-absent] ...
objectstack#20330`. Reading `framework#N` as this repository would have
inherited that false red on every site. The probe set is now
`boardWanted`, meaning every citation judged here. The self-test pins it
through `probeBoard` over a stub. The blocking rule is unchanged:
findings still exit 2.

## Measurements the design rests on

- **`framework` names this repository.** `git ls-remote
https://github.com/objectstack-ai/framework` answered HEAD `288611e3e5`,
identical to `objectstack-ai/objectstack`. The controls diverged: a
nonexistent name under the same owner exited 128, and
`objectstack-ai/objectui` answered its own HEAD `0eb9f36aca`. The REST
and web routes to `framework` answered 403 from this session's proxy
(bound to configured repositories), so git's rename redirect was the
readable instrument. `framework#N` / `Framework#N` therefore read as
THIS repository.
- **Qualifier census on `288611e3e5`, over the declared surfaces.**
There were 27 distinct candidates behind 1,655 sites:
- This repository: `framework` 255, `objectstack` 111,
`objectstack-ai/objectstack` 9, `Framework` 2.
- Siblings: `objectui` 672, `cloud` 213, `hotcrm` 24,
`objectstack-ai/objectui` 12, `ui` 11, `objectstack-ai/cloud` 9,
`better-auth/better-auth` 3, `os-tianshun-mtc` 2, and 1 each of
`hotcrm-heimao`, `os-project-titanwind-ehr`, `objectstack-ai/objectos`,
`objectstack-ai/ats` and `objectstack-ai/hotcrm`.
- Prose: `pre-` 284, `post-` 12, `Pre-` 4, `Post-` 4, `PRE-` 1 and
`POST-` 1.
  - Ordinals: `OQ` 10, `PD` 8, `Prime-Directive-` 2, `PKCS` 1.
- **`ui` is objectui.** `objectstack-ai/ui` does not exist, and
`ui#6837`, `ui#6206` and `ui#6207` are objectui's records on its board
(`objectstack#6206` answers 404, so it would have been a false death).
- **Prose form, 27 sites.** For every objectui number I read objectui's
board and this repository's. The objectui record is the one each
sentence describes: `objectui objectstack-ai#2670` is "Flow designer: render loop /
parallel / try_catch as nested", cited from `loop-node.ts`, and
`objectui PR objectstack-ai#4264` diagnoses a path on the right side of `==`, cited
beside `PATH_SHAPED_LITERAL`. This repository's same number is unrelated
on every site. The `cloud` sites could not be read (private) and follow
their context. There were zero false positives.
- **Pair carry, 48 sites. The measurement refuses a carry rule.**
- `,` and `and`: every cross-repo pair I could judge names THIS
repository's second number. `cloud#1013 and objectstack-ai#10645` is this repository's
cli `serve` issue (4 sites), `cloud#1020, objectstack-ai#5233` its org gate issue (6
sites), `objectui#2561, objectstack-ai#3021` its lazySchema PR, and `objectui#3136 and
objectstack-ai#14492` answers 404 on objectui.
- `/`: mostly carries, but not always. `objectui#3226 / objectstack-ai#4827` is this
repository's objectstack-ai#4827, a conversion entry handed over from objectui and
cited from `conversions/registry.ts`.
- `+`: exactly one distinct pair exists in the corpus, which is too thin
to establish a convention.

## Census of the newly judged spellings

Taken with the gate's own `--census --json` at `a3c14755f8` against an
enumerated board (184 pages, frontier objectstack-ai#20551). The per-site transition
comes from the gate's `--list` before and after the change. Dead means
`allocated-but-absent`. Four of the numbers (14657, 12998, 10194 and
8692) were re-probed directly and answered 404.

| spelling | sites now judged | dead |
|---|---:|---:|
| `pre-#N` | 284 | 26 |
| `framework#N` | 255 | 0 |
| `post-#N` | 12 | 0 |
| `Pre-#N` | 4 | 1 |
| `Post-#N` | 4 | 0 |
| `Framework#N` | 2 | 0 |
| `PRE-#N` | 1 | 0 |
| `POST-#N` | 1 | 0 |
| **total** | **563** | **27** |

Other readings, same run:

- **Newly deferred (25 sites):** the 24 prose-form sites plus the
respelled `field.zod.ts:370`. Four of them were base census deaths that
were never deaths: `objectui PR objectstack-ai#8758` three times, and the respelled
`objectstack-ai#6111`.
- **No longer extracted (21 ordinals):** `OQ#10` ×10, `PD#12`/`PD#10`
×8, `Prime-Directive-objectstack-ai#10`/`objectstack-ai#12` ×2 and `PKCS#11` ×1.
- **Whole-census tally:**
- Base `288611e3e5`: 38,109 judged. resolves 32,202 · resolves-as-pull
1,863 · cross-repo-unjudged 1,535 · allocated-but-absent 2,509.
- Branch `a3c14755f8`: 38,088 judged. resolves 32,689 · resolves-as-pull
1,891 · cross-repo-unjudged 976 · allocated-but-absent 2,532.
- The board moved between the two runs, so the per-site transition above
is the reading, not the tally difference. The cross-repo count
reconciles exactly: 1,535 − 563 − 21 + 25 = 976.

## Verification

All gates below ran at `a3c14755f8`, the branch head.

- **Self-test.** `node scripts/check-issue-citations.mjs --self-test`
exits 0 with 114 cases across 8 batteries (base: 73 cases across 7). The
new battery `qualifier` (floor 40) pins every spelling both ways: lit on
a live number and a FINDING on a dead one for `pre-` `post-` `Pre-`
`Post-` `PRE-` `POST-`, and for `framework` `Framework`
`objectstack-ai/framework` `objectstack`. It also pins cross-repo even
when dead for `objectui` `OBJECTUI` `ui` `cloud` `hotcrm`
`objectstack-ai/objectui` `better-auth/better-auth`, and covers:
  - an unknown word prefix read as prose;
  - the four ordinal heads;
- the prose form, lit and dead, including `PR #N` and `UI #N` NOT being
the prose form;
- the pair, no carry (dead second number red) and qualified number by
number (both deferred);
  - `boardWanted` and a probed-board resolution of `objectstack#20330`;
  - registry hygiene.
`live-corpus` gains a pin that every qualifier the live corpus keeps
names a repository.
- **Ablations.** Six mutations went through
`scripts/ablation-replace.mjs`, each landing on disk with the anchor
count 1 → 0 and the blob changed, each red on its own case, and each
restored with blob equal to HEAD `8b6cf12653dd` and `git diff HEAD`
empty:
- M1: the recogniser returns any bare candidate. The self-test reds on
"`pre-` is prose".
- M2: the probe set reverts to unqualified-only. It reds on "the board's
probe set".
- M3: the `framework` row is renamed. It reds on "`Framework` is THIS
repository".
  - M4: the hyphen head is off. It reds on "`Prime-Directive-objectstack-ai#12`".
- M5: the prose form is off. It reds on "`objectui PR #N` names
objectui".
  - M6: the `OQ` head is removed. It reds on "`ADR-0076 OQ#10`".
- **Diff-scoped verdict** (`node scripts/check-issue-citations.mjs`, as
CI runs it): exit 0 on this branch. It judged the respelled line's 2
citations, both `cross-repo-unjudged`.
- **One-time end-to-end proof** (no permanent test; injected uncommitted
and restored with blob equal to HEAD and `git diff HEAD` empty). I
appended `pre-objectstack-ai#12248` (dead) and `framework#20330` (live) to
`packages/cli/src/commands/generate.ts` and ran the diff verdict twice:
- The branch gate exits **2**. `pre-objectstack-ai#12248` is `allocated-but-absent`,
`framework#20330` resolves on a board `probed (2 citations)`, and the
respelled pair stays cross-repo.
- The `288611e3e5` gate, from a temporary copy, exits **0** with all 4
`cross-repo-unjudged`. That is the hole this closes.
- **Census** (`--census --json`): exit 0. It is report-only and never
fails.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 97 commands, and all 97
ran. 96 exit 0, including `pnpm check:pm-dispatch-gates`, whose
`dispatch-gates.mjs --self-test` passes 1,976 cases. That self-test pins
this file's `:204 local-env` declaration line, and every edit here stays
below it or is line-neutral. `--ran` reconciliation reads "97 derived
famil(ies) accounted for — 96 run, 1 NOT-MEASURED (1 DERIVED from a
recorded exit 3)".
- **NOT MEASURED: `check:dual-build-cjs-loads`.** Reason: it loads every
package's built CJS entry, and this box holds no dist for 80+ packages.
The diff changes no emitted code. `@objectstack/spec` was rebuilt, and
its entry gates (`check:browser-reachable-entries`,
`check:entry-nameability`) exit 0. CI's Lint and Repo Gates owns this
one.
- **Spec package.** `pnpm --filter @objectstack/spec typecheck` returned
VERDICT command-exit 0. See the report comment for the test run.

## Acceptance notes

- **Dead sites the census hands over, not rewritten here.** One is under
`packages/spec/src/**` and is input for the staged sweep on objectstack-ai#20234:
`packages/spec/src/meta-spelling/manifest-collection-spelling.ts:71`
`pre-objectstack-ai#10194`. The other 26 are outside `packages/spec/src/**`, and no
card names them:
  - `packages/cli/src/commands/generate.ts:1842` `pre-objectstack-ai#14657`
  - `packages/cli/src/utils/storage-driver.ts:206` `pre-objectstack-ai#6345`
- `packages/drivers/driver-sql/src/schema-drift.ts:2458`, `:2474`
`pre-objectstack-ai#12998`
- `packages/drivers/driver-sql/src/sql-driver.ts:3872`, `:16545`
`pre-objectstack-ai#17590`
- `packages/drivers/driver-sql/src/sql-driver.ts:18285`, `:18324`
`pre-objectstack-ai#12998`
  - `packages/drivers/driver-sql/src/sql-driver.ts:20095` `Pre-objectstack-ai#12380`
- `packages/drivers/driver-turso/src/remote-transport.ts:2624`
`pre-objectstack-ai#12380`
  - `packages/lint/src/validate-searchable-fields.ts:312` `pre-objectstack-ai#8404`
  - `packages/metadata-protocol/src/protocol.ts:2793` `pre-objectstack-ai#10888`
  - `packages/metadata-protocol/src/seed-loader.ts:1947` `pre-objectstack-ai#11674`
  - `packages/objectql/src/action-governance.ts:339` `pre-objectstack-ai#14423`
  - `packages/plugins/plugin-auth/src/auth-manager.ts:5629` `pre-objectstack-ai#14762`
-
`packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:266`,
`:630` `pre-objectstack-ai#8692`
- `packages/plugins/plugin-security/src/per-organization-catalog.ts:314`
`pre-objectstack-ai#8692`
-
`packages/plugins/plugin-security/src/permission-set-projection.ts:482`
`pre-objectstack-ai#6483`
-
`packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts:5`
`pre-objectstack-ai#14484`
  - `packages/runtime/src/domains/mcp.ts:364` `pre-objectstack-ai#8726`
- `packages/runtime/src/sandbox/body-runner.ts:548`, `:735` `pre-objectstack-ai#14758`
  - `packages/runtime/src/sandbox/script-runner.ts:440` `pre-objectstack-ai#14758`
  - `packages/types/src/driver-error-classification.ts:608` `pre-objectstack-ai#13324`
  - `packages/types/src/node.ts:1428` `pre-objectstack-ai#10943`
- **The same `objectui#6110 + objectstack-ai#6111` pair outside the claim's surface.**
It still reads `objectstack-ai#6111` as this repository's (404 here), so these are
existing census deaths: `packages/spec/src/ui/view.zod.ts:3634` (for the
objectstack-ai#20234 sweep),
`packages/metadata-core/src/form-predicate-root-policy.ts:14`, `:120`
and `:205`, and `packages/metadata/src/plugin.ts:910`. Each respells to
`objectui#6110 + objectui#6111`.
- **Pairs that read silently wrong, not dead.** Several `REPO#N / #M`
pairs name the qualifier's own second number, which resolves here as an
unrelated record. Examples: `hotcrm-heimao#35/objectstack-ai#40/objectstack-ai#59`,
`objectui#2715/objectstack-ai#2717`, `objectui#2711/objectstack-ai#2722`, `objectui#4648/objectstack-ai#4901`,
`objectui#5018 / objectstack-ai#6469`, `cloud#957 / objectstack-ai#962` and `cloud#930/objectstack-ai#944`. No
gate can see these, because they resolve. The convention in the refusal
text (qualify each number) is the remedy when someone next touches the
line.
- **Seat 4's `objectui PR objectstack-ai#10264` specimen.**
`packages/spec/src/api/export-job-family-retirement.test.ts:25` sits on
a DEFERRED surface (`packages/**/*.test.ts`), and `surfaceFor` answers
`null` for it. The census never judged that site. The prose form it
names is now read correctly wherever the census does look.
- **Observed once: a truncated board enumeration accepted as a
reading.** My first branch `--census` read `enumerated (126 pages)` with
frontier objectstack-ai#13977, against 184 pages and objectstack-ai#20551 on the re-run minutes
later, and reported 9,160 `never-issued` phantoms. `enumerateBoard`
stops at the first page without `rel="next"` and trusts the maximum it
saw as the frontier. This diff does not touch that code. The diff-scoped
verdict enumerates only past 400 distinct numbers. Recorded, not filed;
the seat decides.
- **Scope declaration.** `NON_CITATION_HEADS` (two rows) and
`nonCitationHead` (the hyphen) are grammar next to the qualifier, not
the qualifier itself. They are here because closing the qualifier made
those 13 ordinal sites judged citations of this repository's objectstack-ai#10, objectstack-ai#11
and objectstack-ai#12, which is false. No gate was added, and the diff-scoped blocking
rule is unchanged.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…mits that decided them (objectstack-ai#20626)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the second stage of the `domain:services` lane of the
dead-citation sweep. It covers `packages/plugins/plugin-sharing/src/**`
and nothing else. By census, it is the largest package in the lane that
no open PR or in-flight claim holds (the claim, `5886159115`, gives the
order). Later stages cover the other packages, so this PR says `Part of`
and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by stage 1's method (PR objectstack-ai#20609, landed as
`422db788a`). That is **87 sites on 86 lines in 23 files, covering 13
numbers**: the 60 census sites outside the generated headers, and 27
sites in test comments, which the census defers. Each rewritten line now
cites the commit in `origin/main` history that decided what the line
describes, and it says in its own words what that commit decided.

No ADR or ruling-record file records the decision behind any of the 13
numbers (ADR-0131 names objectstack-ai#14484 only as evidence, not as the record of
its ruling), so every anchor is a commit: **13 distinct shas**. No
number was dropped.

Only comments changed. Every touched source file keeps its line count
(87 lines out, 87 in, over 23 files), so no line citation into these
files moves. One of those 87 lines held no dead citation:
`backfill-sys-record-share-organizations.ts:14`, where 「the cliff the
card names」 lost its referent once line 10 named a commit instead of a
card. It now reads 「the cliff that commit pins」, and `3f64fe6c6`'s
backfill test is the one titled 「the cliff」. No code token moves (see
the guard below).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. Over the whole diff, added minus
removed is 0 or negative for every number, and no number is new to the
diff. No PR number stands on an added line. The one PR spelling in scope
(`PR objectstack-ai#5973`, dead) became its squash commit.

Nineteen dead sites are left on purpose: 1 string literal, 14 test
titles, 1 verbatim ruling quotation and 3 generated file headers (see
the list below).

One more file: a `patch` changeset for `@objectstack/plugin-sharing`,
because the rewritten docblocks ship (see Changeset below).

## Census: `plugin-sharing`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/plugins/plugin-sharing/`. Each run counts as a reading only
because its board frontier equals the newest issue number, read by a
separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
plugin-sharing sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `422db788a`, run 2026-09-29T08:04:49Z to 08:08:19Z |
enumerated, 185 pages, frontier objectstack-ai#20614 (newest objectstack-ai#20614), 18,441 numbers |
2,300 | **63** | 62 | 14 | 13 |
| after | head `a6d231713`, run 08:27:44Z to 08:31:07Z | enumerated, 185
pages, frontier objectstack-ai#20616 (newest objectstack-ai#20616), 18,443 numbers | 2,240 | **3** |
3 | 3 | 1 |

The before count matches the 63 that census `5884031174` read at
`f11b5f20`. The whole-repo drop is 60, exactly this diff's census sites.
The `resolves` tally is 32,803 in both runs, and
`resolves-as-pull-request` (1,891) and `cross-repo-unjudged` (983) did
not move either. The 3 left are the generated headers below. No run was
truncated or discarded: all three enumerations in this stage (two census
runs and the supplementary board below) read 185 pages at the newest
frontier.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes both. So a second
reading runs the gate's own exported `extractCitations` (whole-file and
comment-prose projections) and `classifyCitation` over every `.ts` file
under `plugin-sharing/src` (74 files). It uses one board, enumerated by
the gate's own `enumerateBoard` at 08:12:18Z (185 pages, frontier
objectstack-ai#20614, equal to the newest).

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `422db788a` | 1,187 | **106** | 63 | 28 | 1 | 14 |
| after, `a6d231713` | 1,100 | **19** | 3 | 1 | 1 | 14 |

Its src-comment column equals the census's 63, which is the control on
the second instrument. The 989 resolving, 87 pull-request and 5
cross-repo citations are the same in both readings.

## Per-number table

Sites and files count all dead sites in scope at the base (comments and
strings, tests included). `rewritten / left` counts the sites rewritten
and the sites left. Each anchor was read in its message and diff, not
only its subject. It is the commit that decided what the line describes:
its own message or diff names the number it replaces, or, for a
squash-merged PR, it is the merge of that PR.

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#5973` | 4/2 | 3/1 | `abeb3751f`:
`HierarchyScopeContext.organizationId` is the tenancy authority, and it
is required. `objectstack-ai#5973` was the PR itself; this is its squash commit |
| `objectstack-ai#6206` | 12/7 | 11/1 | `8e13ca876`: the share-link routes hand
enforcement the whole authz envelope, per maintainer ruling A of
2026-08-07. It is the plugin-sharing half; the spec stages anchor the
contract half at `d7e0b4212`. Three sites name the ruling in words, 「the
full-envelope ruling」, beside `aa4b90d9a`, which applied it |
| `objectstack-ai#6523` | 3/3 | 3/0 | `aa4b90d9a`: 36 contract signatures converge on
the full `ExecutionContext`. The same anchor the spec stages gave this
number |
| `objectstack-ai#8710` | 10/3 | 9/1 | `04d03c3a0`: a deactivated `sys_position`
confers no sharing-rule shares. Its message quotes the 2026-08-15
ruling: access-conferring paths filter, addressing paths do not |
| `objectstack-ai#8792` | 2/1 | 2/0 | `83c661d97`: the bulk-write merge's missing
provenance mark is recorded as ruled (2026-08-15), not oversight |
| `objectstack-ai#8836` | 1/1 | 1/0 | `1850ebbb0`: it pins 「no filter object that can
be vouched 'author' may outlive the request that vouched it」, the
invariant the line names. The same anchor the spec stages gave this
number |
| `objectstack-ai#11671` | 5/5 | 2/3 | `09b4f4e4e`: `os i18n extract --source-hashes`
writes the provenance companion (maintainer ruling objectstack-ai#12069 Option A,
which stays cited). The same anchor stage 1 gave it |
| `objectstack-ai#11674` | 4/2 | 4/0 | `1cba33f16`: the seed loader warns at load time
when a required column is deferred, and the ordering constraint is
written at the four pointer-pair sites, these two among them |
| `objectstack-ai#12493` | 2/2 | 2/0 | `aa5994e17`: the Operation Message Catalog
gains `record_write_denied` ahead of its emitters. The same anchor the
spec stages gave this number |
| `objectstack-ai#13279` | 3/2 | 3/0 | `6a180e42d`: a permission-store read that
throws raises `AuthzStoreUnavailableError` (503), and each transport
re-raises it rather than laundering it into a 401 |
| `objectstack-ai#13398` | 1/1 | 1/0 | `953a81f4a`: the class ruling on published
logger sinks, applied at this site. `error` is reachable only because
the sink already declares it; growing `error?` onto a published sink is
forbidden. No record of the ruling exists in the repo, and this commit,
which wrote this heading, is its earliest application in history |
| `objectstack-ai#13608` | 23/3 | 21/2 | `fc9ba76a5`: `publicSharing.eligibility` is
held at redemption, not only at mint. The same anchor the spec stages
gave this number |
| `objectstack-ai#14484` | 36/8 | 25/11 | `3f64fe6c6`: `organization_id` is stamped on
every `sys_record_share` write, the stranded rows are backfilled, and
the object is admitted to the tenancy ledger (the 2026-09-02 ruling,
decision batch objectstack-ai#11 item 3) |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each), and every one is an ancestor of the
base (`merge-base --is-ancestor`, exit 0 for all 13). The history is
complete (`--is-shallow-repository` false, 15,073 commits). A
line-origin pickaxe (`git log -S` on each dead line's exact text) found
each line entering either in its anchor commit or in a later commit that
cites that commit's decision. For example, `65759baca` is the consumer
half that cites `aa5994e17`'s key, and `b70a55d62` cites `3f64fe6c6`'s
ledger admission.

Wordings to check, each true of its commit:
- `backfill-sys-record-share-organizations.ts:5`: 「rows that
`SharingService.grant`, before commit 3f64fe6, stranded」. `3f64fe6c6`
is the writer fix, and this module is its backfill.
- `backfill-sys-record-share-organizations.ts:36` and `:124`: 「the
2026-09-02 ruling commit 3f64fe6 applies (decision batch objectstack-ai#11 item 3,
…)」. The verbatim maintainer quotation on line 37 is untouched.
- `share-link-service.ts:841`: 「(published-sink level ruling, commit
953a81f)」. The heading's body already states the ruling (option C
allowed, option B forbidden).
- `exec-context-annotation.pin.ts:7-8`, `sharing-rule-service.ts:12-13`
and `sharing-service.ts:20`: 「since commit aa4b90d (the full-envelope
ruling: no per-site subset contracts)」. `aa4b90d9a`'s message: 「Apply
the … ruling default (converge on the full envelope, keep no per-site
subset contracts)」.
- `share-link-routes.ts:81`: 「[commit 8e13ca8, full-envelope ruling]」,
so that 「the whole point of the ruling」 five lines down still has a
referent.

## The 19 sites left

- **Non-test string (1 site).** `sharing-service.ts:1674` sits inside
the operator-facing `warn` text for a hierarchy scope that was not
widened (「… resolveOwnerIds, objectstack-ai#5973); …」). It is a runtime string, so it
is form D, not form C, and the shrink-only `doc-authoring-prose-id`
baseline already holds it (`sharing-service.ts` → `objectstack-ai#5973: 1`). Left and
listed, as stage 1 left its refusal strings.
- **Test titles (14 sites).** `describe` titles in
`backfill-sys-record-share-organizations.test.ts:185`, `:274`, `:324`,
`:367`, `record-share-organization-stamp.test.ts:194`, `:239`, `:278`,
`:315`, `:353`, `:436`, `sharing-service.test.ts:1798` (the `objectstack-ai#14484`
titles), `share-link-eligibility.test.ts:607` (`objectstack-ai#13608`),
`share-link-enforcement-context.test.ts:226` (`objectstack-ai#6206`) and
`sharing-rule.test.ts:1898` (`objectstack-ai#8710`). Tokens, left as they were.
- **A verbatim ruling quotation (1 site).**
`share-link-service.test.ts:478` is point 2 of the maintainer's
2026-09-01 ruling, quoted verbatim and untranslated. It carries 「沿
objectstack-ai#13608 先例」. AGENTS.md keeps a quoted Chinese ruling in its original
words, and rewriting the quote would rewrite the ruling. Left.
- **Generated headers (3 sites).** Line 8 of the `es-ES`, `ja-JP` and
`zh-CN` `.source-hashes.generated.ts` files carries 「(objectstack-ai#11671, maintainer
ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B)」. `os i18n extract`
writes that line from `packages/cli/src/utils/i18n-extract.ts`, so the
fix belongs at the producer, the carrier stage 1 named. The hand-written
`translations/index.ts:26` is rewritten here, with the same wording
stage 1 used.

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as
trivia and JSDoc nodes excluded, base `422db788a` against head. Template
literals are therefore read in context. It ran over all 23 touched `.ts`
files.

- Real run: 96,665 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control in `share-link-service.ts`: 0 files changed,
as expected (exit 0). The first attempt was a no-op: its replacement
still contained the anchor, so `scripts/ablation-replace.mjs` refused it
before the guard ran. It was redone with an anchor the replacement does
not contain.
- Positive control, a code token changed in `share-link-service.ts`
(`Boolean(eligibility),` to `Boolean(eligibility) && true,`): DIFFER
(exit 1).
- Positive control, one digit changed inside the kept
`sharing-service.ts:1674` warn string: DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs`. Each restore
was proven byte-identical to the HEAD blob (`ba7fba2e8199`,
`2833b9a1616d`), with `git diff HEAD` empty and a clean tree afterwards.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/plugin-sharing` is included. It says only that the
provenance comments were re-anchored.

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After the build, the rewritten comments reach both
halves of `dist`: `3f64fe6c6` appears 6 times in `dist/index.d.ts` and 8
in `dist/index.js`, `fc9ba76a5` 3 and 3, `04d03c3a0` 2 and 2,
`8e13ca876` twice in `index.d.ts`, and `1cba33f16` 4 times in
`index.js`. esbuild keeps only some comments, so the positive controls
are unchanged lines beside rewritten ones that shipped.
`share-link-service.ts:891` is found once in each half, and
`sharing-service.ts:1269` once in `index.js`. A never-written negative
phrase appears nowhere. The only dead number left in `dist` is the kept
`objectstack-ai#5973` warn string.

## Gates (head `a6d231713`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
(self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0:
the diff-scoped run judged 9 citations across 11 files, and all 9
resolve.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the
sibling-package prose ids at their baseline and no growth.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `a6d231713` (re-derived after a
fresh `git fetch` at 09:58Z: the same 65, and none of the 8 new `main`
commits touch anything it derives from) derived 65 commands. They
include all 50 derived at dispatch, plus 15 more. All 65 exit 0. `--ran`
reports 65 run, 0 NOT MEASURED, 0 unrun, and exits 0.
- Three gates first exited 3 (PREREQUISITE NOT MET) because the
workspace was only partly built: `check:dual-build-cjs-loads`,
`check:i18n` and `check:type-check-debt`. A full `turbo run build` of
`./packages/*` and `./packages/*/*` then ran under the shared verify
lock (71 tasks, exit 0), and all three exited 0 on their rerun.
`check:dts-closure`, `check:sourcemap-no-sources-content` and
`check:lean-entry-closure` were rerun too, over 71, 68 and 15 built
packages, and exited 0.
- **Tests and typecheck, under the verify lock:**
- `pnpm --filter @objectstack/plugin-sharing test`: 37 files and 913
tests pass. That is every test file in the package, the 12 touched ones
included.
- `pnpm --filter @objectstack/plugin-sharing typecheck` exits 0. Its
main `tsc` program reads the 37 non-test files, and its
`check:test-typecheck` program (`tsconfig.test.json`) reads all 74 files
under `src/`, the 37 test files included (`--listFiles`).
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 23 touched `.ts` files gives 23 files, 0 errors and 0
warnings. All 23 are in eslint's own population (`isPathIgnored` is
false for each). `eslint.config.mjs` never enables type-aware linting
(no `parserOptions.project`, as its own line 328 states), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the 24 changed files for control bytes finds none.

## Acceptance notes

- **The census instrument did not truncate in this stage.** Three
enumerations read 185 pages each at the newest frontier. The truncation
stage 1 saw (1 run in 5) is carried on objectstack-ai#20556, and this stage changes no
instrument.
- **What stays for later stages.**
- The 3 generated `objectstack-ai#11671` headers, whose producer is
`packages/cli/src/utils/i18n-extract.ts`.
- The `objectstack-ai#5973` warn string (form D, held by the `doc-authoring-prose-id`
baseline), the 14 test titles and the verbatim ruling quotation.
- **Anchors the next stages can reuse.** The same numbers stand
elsewhere in `packages/**/src`: `objectstack-ai#6206` at 53 sites and `objectstack-ai#11674` at 46
(the ordering-constraint note has two sibling copies outside this
package, in `sys-approval-request.object.ts` and
`sys-audit-log.object.ts`). `8e13ca876` / `d7e0b4212` / `aa4b90d9a` and
`1cba33f16` are the anchors used here.
- **Base.** The branch is 8 commits behind `origin/main` (`1322cc72c`,
read at 09:58Z). None of them touches `plugin-sharing` or this
changeset, and none re-anchors any of these 13 numbers, so there was no
merge.

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

---------

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

This branch had an error being deployed

1 failed deployment
Preview — d523741e Deployed Jan 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants