Skip to content

docs(spec): the protocol upgrade guide's public address is one docs-site page per protocol major; docs/protocol-upgrade-guide.md becomes a pointer stub (#22449 B′, condition 2) - #22556

Merged
objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-22483-upgrade-guide-address
Oct 10, 2026
Merged

objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-22483-upgrade-guide-address

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22483
Clause-②: no

The protocol upgrade guide now has a public address: the docs site, one page per protocol major, at https://objectstack.ai/docs/protocol-upgrade/MAJOR, with an index at https://objectstack.ai/docs/protocol-upgrade. The pages are generated at docs build from the ADR-0087 registries and never committed. docs/protocol-upgrade-guide.md stays at its path as a short, hand-written pointer stub naming that address. That is condition (2) of ruling 6078203801 on #22449 (B′), and the two notes in triage 6081410390.

The address: the seat's three hypotheses, measured first

Stable across releases No GitHub login Versioned per major Verdict
H1: Release attachment no: one URL per VERSION yes (public repo) per version, not per major fails
H2: docs-site page, generated at docs build yes yes (public site) yes: one page per major taken
H3: npm CDN (unpkg/jsDelivr) n/a n/a keyed by the npm range, not a protocol major not chosen (outside the ruling's two options)

H1, measured. The newest spec Release, @objectstack/spec@17.7.0, carries one asset, spec-changes.json, at https://github.com/objectstack-ai/objectstack/releases/download/%40objectstack/spec%4017.7.0/spec-changes.json (302 to the asset store, no auth). One URL per VERSION. releases/latest answers 302 to releases/tag/@objectstack/verify@17.7.0, another package's Release. releases/latest/download/spec-changes.json redirects to .../download/@objectstack/verify@17.7.0/spec-changes.json, which is 404. So no GitHub URL resolves to "the newest spec release of a major". Also, no Release carries the guide yet: the 17.7.0 tarball holds spec-changes.json and no guide, because the publish lane from #22533 landed after it.

H2, measured.

  • The build can run a spec script. apps/docs's build already runs @objectstack/spec's gen:schema and gen:docs before next build. Vercel runs pnpm turbo run build --filter=@objectstack/docs (apps/docs/vercel.json), and CI's Build Docs evaluates that same string. This PR adds gen:upgrade-guide to that chain.

  • The URLs. SITE_ORIGIN is https://objectstack.ai (apps/docs/lib/site.ts) and the docs baseUrl is /docs (apps/docs/lib/source.ts). So the pages are /docs/protocol-upgrade, /docs/protocol-upgrade/17 and /docs/protocol-upgrade/18.

  • The build sees unreleased majors. main carries PROTOCOL_VERSION 18.0.0 while packages/spec/package.json says 17.7.0. npm's latest is 17.7.0, whose tarball carries protocol 17.0.0 (read from its dist). So the 17 → 18 hop is unreleased, and the docs site builds from main.

  • Choice: mark, never hide. Every page carries one state line, judged from the tree's own @objectstack/spec version under the lockstep contract in src/kernel/protocol-version.ts:

    • spec major below the page's major: Not released yet (the pre-mode pending major);
    • equal major on a prerelease version: Prerelease;
    • otherwise: Released.

    Every page also says it is generated from main, ahead of the release that ships a newly landed entry. Why mark rather than hide:

    1. objectstack migrate meta already replays the chain up to the highest registered major, so the page shows what the tool prints.
    2. The 18.0.0-next.N line (.changeset/22080-v18-line-opens.md) has readers, and hiding the page would leave them no address.
    3. Even a released major's page gains entries between releases, because launch-window minors register under the current major. "Released majors only" would still not equal "what shipped". The shipped copy stays the one in the tarball and on the Release, and every page says so.
  • The local build, with and without the pages. Same box, one run each, rm -rf .next before each. Each run was under the verify lock, but unlocked sibling work was running beside it:

    next build exit compiled static pages peak next-build RSS
    with the pages 0 105 s 1255 in 35.5 s 6,166,396 kB
    without 0 103 s 1246 in 33.3 s 6,653,292 kB

    So the pages add no measurable peak memory: noise between the two runs is larger than the effect. Rendered sizes: protocol-upgrade/18.html 3.48 MB, 17.html 1.20 MB, the index 235 KB. The sidebar shows "Protocol Upgrade Guide" after "Upgrading".

  • Login-free. The site is public. A live probe of objectstack.ai is NOT MEASURED: this container's proxy refuses CONNECT to that host (403).

H3, measured. Not chosen: the ruling names two options and this one is outside them. It also does not key by protocol major: npm view @objectstack/spec@18 version answers E404 No match found for version 18, because protocol 18 ships as 18.0.0-next.N and a bare @18 range never matches a prerelease. @17 resolves to 17.7.0, which carries no guide, and pre mode cuts no further 17.x. Live CDN reachability is NOT MEASURED: the proxy refuses CONNECT to unpkg.com and cdn.jsdelivr.net (403).

What changed

  • packages/spec/scripts/build-upgrade-guide.ts: build() is split into section renderers:
    • howToUpgrade, hopSection and footer;
    • the single-file guide --out writes is byte-identical to before, except its second header comment. That comment now names the command that writes the file (exec tsx scripts/build-upgrade-guide.ts --out FILE), because gen:upgrade-guide no longer does. Measured: diff of the base's and this branch's --out output is that one line.
    • No flag (gen:upgrade-guide) now writes the docs pages into content/docs/protocol-upgrade/, never docs/protocol-upgrade-guide.md, so the stub cannot be overwritten. --docs DIR writes them elsewhere.
    • --check and --out are unchanged.
  • packages/spec/scripts/lib/upgrade-guide-docs.ts (new) assembles the tree: index.md, one MAJOR.md per hop the chain carries, and a meta.json listing them newest first.
    • A page holds the shared how-to, its own hop section verbatim from the same renderer, and the footer. It has no body h1, and its frontmatter is quoted so YAML never reads a colon or an arrow as structure.
    • The writer removes only files it wrote. A page whose hop fell below the support floor does not linger, and a directory holding anything else is refused untouched.
  • apps/docs/package.json: build runs gen:upgrade-guide before next build.
  • content/docs/meta.json: protocol-upgrade is listed after upgrading. When the folder is absent (a checkout that has not run the docs build), fumadocs skips the entry. Measured in fumadocs-core's resolveFolderItem: an item that resolves to no node returns without one.
  • .gitignore: content/docs/protocol-upgrade/.
  • docs/protocol-upgrade-guide.md: the stub. It names the index, one line per major today (17 and 18), and the pattern for the next one. It also says where a release's shipped copy lives, and how to generate either form.
  • packages/spec/scripts/check-generated.ts: the check:upgrade-guide row names the docs pages as its artifact.
  • packages/spec/scripts/lib/projection-cli.ts: committedPath is optional. The guide no longer has a committed copy, so its runner passes none. A bare run without one is a usage error (exit 2) that builds nothing.
  • .gitattributes / scripts/regen-artifacts.mjs (seat answer 6092165230, option B): the guide's merge=os-regen route and its REGEN_ARTIFACTS row are removed. gen:upgrade-guide is recorded as NOT_DRIVER_MANAGED for content/docs/protocol-upgrade/** (untracked). The header prose no longer calls the guide a generated single file. The spec-changes.json route stays for spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485.
  • scripts/check-role-word.mjs (seat answer 6093145489, Q2 → A): content/docs/protocol-upgrade is in SKIP_SUBTREES, for the reason content/docs/references is: generated prose whose fix site, the registry .ts, this markdown gate cannot reach.
    • The self-test now accepts an untracked tree that NOT_DRIVER_MANAGED declares where it required one on disk, and it requires every such tree under a ROOT to be skipped.
    • A new program-level battery runs the gate over a fixture page inside each skipped tree (exit 0, nothing read), one level up (NEW use, exit 1), and in a sibling directory sharing the prefix (NEW use, exit 1).
    • Both directions hold: with the pages on disk the gate exits 0, and a violation planted in content/docs/build-without-code.mdx still exits 1, the only problem reported.
    • Ablation, dropping the entry: exit 1, with 3 failures with the pages on disk and 2 without, restored to blob == HEAD.
  • .changeset/22483-upgrade-guide-docs-address.md: @objectstack/spec patch. The shipped protocol-upgrade-guide.md changes by its header comment, and the address is user-facing news. apps/docs is private.

Every published pointer, with its resolves check

Every pointer spells the repo path docs/protocol-upgrade-guide.md as text, never as a URL (git grep -o over the tree: 71 bare docs/protocol-upgrade-guide.md spellings, no github.com, raw.githubusercontent.com or objectstack.ai form). The check is the same for each:

  • the path exists on this branch and is the stub;
  • the stub names https://objectstack.ai/docs/protocol-upgrade;
  • for the one pointer that names an entry id, that id is on the page the stub sends the reader to.

On GitHub, blob/claude/issue-22483-upgrade-guide-address/docs/protocol-upgrade-guide.md and its raw.githubusercontent.com form both answer 200, and the fetched bytes name the address 4 times.

Pointer (file · mentions · first line) Shipped in its package Resolves
packages/client/README.md · 1 · :141 (cites batch-options-validate-only-retired) yes (files[] has README.md) ✓ stub → /docs/protocol-upgrade/17; that id is on the 17 page (3 hits in the built 17.html)
packages/cli/CHANGELOG.md · 3 · :14221 yes ✓ stub
packages/client/CHANGELOG.md · 4 · :5655 yes ✓ stub
packages/core/CHANGELOG.md · 1 · :2013 yes ✓ stub
packages/create-objectstack/CHANGELOG.md · 1 · :1544 yes ✓ stub
packages/metadata-protocol/CHANGELOG.md · 2 · :14511 yes ✓ stub
packages/runtime/CHANGELOG.md · 3 · :10073 yes ✓ stub
packages/services/service-automation/CHANGELOG.md · 2 · :9260 yes ✓ stub
packages/services/service-package/CHANGELOG.md · 1 · :3432 yes ✓ stub
packages/spec/CHANGELOG.md · 41 · :1825 yes ✓ stub. Some of its mentions are the bare protocol-upgrade-guide.md, the in-package file, which still ships
packages/spec/src/migrations/entries/README.md · 1 · :99 no (files[] ships src/**/*.zod.ts only) ✓ stub

These are the eleven of the ruling: ten shipped files plus the entries README. No CHANGELOG is edited. The in-repo, unpublished mentions keep resolving through the same stub: ADR-0087 :348, docs/audits/2026-08-partial-retirement-annotation-signal.md:94, the #22482 changeset, and scripts.

File surface

Inside the claim: the stub, build-upgrade-guide.ts and its tests, the docs wiring that generates the address (apps/docs/package.json, content/docs/meta.json, .gitignore), check-generated.ts's row (named by the dispatch), and one changeset.

Beyond it, each named here:

  • scripts/check-future-spec-major.mjs: deleted one QUOTATION_EXEMPTIONS row. This was forced: with the stub in place the gate went red, QUOTATION_EXEMPTIONS entry 6 (docs/protocol-upgrade-guide.md, major 4997) matched NOTHING … Re-point it or delete it. The row excused the generated copy of one entry's evidence sentence, and the stub no longer carries it. After the deletion the gate reads 7 witnessed ledger entr(y|ies), every witness still matching, and --self-test reports 29 cases.
  • packages/spec/scripts/lib/projection-cli.ts: the optional committedPath above. This is the no-flag contract of build-upgrade-guide.ts's default write target, the item the claim names.
  • packages/spec/scripts/lib/upgrade-guide-docs.ts and upgrade-guide-docs.test.ts: new files in the same scripts tree.
  • .gitattributes (the guide's route line, and the header prose from :20) and scripts/regen-artifacts.mjs (the row moves to NOT_DRIVER_MANAGED), per seat answer 6092165230.
  • scripts/check-role-word.mjs (seat answer 6093145489): a local docs build made the gate report 99 NEW uses (17.md 56, 18.md 43) that CI never saw, because the tree is gitignored.

Removed here (seat answer 6092165230, option B). .gitattributes drops the guide's route, and its header no longer calls the guide a generated single file. regen-artifacts.mjs drops the row and records gen:upgrade-guide as NOT_DRIVER_MANAGED content/docs/protocol-upgrade/** (untracked). check:merge-driver reads 17 routed paths, 42 generators with a disposition, and 4 untracked dispositions. The spec-changes.json route stays for #22485.

Why it went here: the route was actively wrong for a hand-written file. Before the removal, scripts/pm/os-regen-merge.sh twice put main's generated guide back over the stub (bd5a3284d0), and the stub was restored by blob (9473b0f21f). The scratch merge after the removal measured three cases:

  • From a checkout carrying this PR, merging a branch with a regenerated guide CONFLICTS on the stub. With the route (8f4ae58944), the same merge was silent.
  • A branch forked before this PR still defers on its own route at its first plain git merge of main. Its next commit and push are then refused, where with the route they passed and the stub was lost. So no direction stays silent.
  • This PR's own merge of main at bf6942b723 conflicted on the stub and kept it.

Verification

All at 7bc190c714 unless a line says otherwise. The last merge of origin/main is bf6942b723, which brought in 96e4be4829, whose 20e7d52097 regenerated the guide; it conflicted on the stub, which was kept (blob 9473b0f21f). origin/main has stayed at 99801d831f since, and GitHub reports the PR mergeable.

  • Spec suites, under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0:
    • at 87a9a3435e: vitest run --project local --maxWorkers=2: 636 files passed, 18970 tests passed, 1 todo;
    • at 87a9a3435e: --project repo: 54 files, 915 tests passed;
    • at bf6942b723: build-schemas-check-mode.test.ts (repo project) 88/88;
    • at 7bc190c714: the five generator and ledger test files, 50/50.
  • pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0. tsc -p tsconfig.scripts.json --listFiles lists all five touched or new script files, tests included.
  • The new and changed tests: upgrade-guide-docs.test.ts (15) and projection-cli.test.ts (12, one new), 27 of 27 passing. The real-generator block proves three things:
    • every page's hop section is the single file's section verbatim, and pages exist for exactly the file's hops;
    • --docs is deterministic;
    • --docs with --check, with --out, or with no directory exits 2 and writes nothing;
    • a directory holding a hand-written file exits 1 with nothing removed.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands derived 128 at 7bc190c714. All ran with the gitignored docs pages on disk (gen:upgrade-guide first), and --ran reports 128 derived, 128 run, 0 NOT-MEASURED, 0 UNRUN. All 128 exit 0, check:role-word included. Without the pages (CI's tree) check:role-word also exits 0, and it reads 254 files either way. Among them: check:generated ("All 15 generated artifacts are up to date"), check:upgrade-guide, check:spec-changes, check:future-spec-major, check:merge-driver, check:cross-package-test-inputs, check:doc-frontmatter, check:nul-bytes and check:pm-dispatch-gates.
  • ESLint, narrowed and measured.
    • Population: the 9 changed JS/TS files (adding scripts/regen-artifacts.mjs and scripts/check-role-word.mjs), measured at 7bc190c714, all matched by eslint.config.mjs's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} object.
    • --no-inline-config --format json reports 9 files, 0 errors and 0 warnings.
    • The config sets no parserOptions.project, so no type-aware rule can move a verdict on a file this diff does not touch.
    • The repo-wide pnpm lint is CI's.
  • Docs build: apps/docs pnpm build (gen chain plus next build), exit 0, at 278751de7a (this PR's content, before the merge). See H2. pnpm turbo run build --concurrency=2 gave 73/73 tasks with @objectstack/docs#build at bf6942b723 (6m23s), and 73/73, all cached, at 7bc190c714.

Acceptance notes


Generated by Claude Code

claude added 5 commits October 9, 2026 23:33
…e per major; docs/protocol-upgrade-guide.md becomes a pointer stub

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…regen merge step took from main

The merge took origin/main's side of docs/protocol-upgrade-guide.md because
both sides changed it; on this branch the file is a hand-written stub, so its
bytes come back from 278751d.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 3e72f9391b44721c3c3ba12bc3beef5c34d98cca → packageMentionDocs.

…s generator's docs pages are an untracked NOT_DRIVER_MANAGED entry

docs/protocol-upgrade-guide.md is a hand-written pointer stub that nothing
generates, so the driver's "defer and regenerate" would keep one side whole
with no generator to restore the other. Drop its .gitattributes route and
correct the header prose that still called it a generated single file; move
gen:upgrade-guide's disposition to NOT_DRIVER_MANAGED as the gitignored
content/docs/protocol-upgrade/** pages it writes. spec-changes.json keeps its
route.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…ide's removed route

A merge made from a checkout carrying the new .gitattributes conflicts on the
stub. A branch forked before this change still defers the guide on its first
merge of main, because git reads routes from the merging checkout; the merged
tree has no row to discharge the deferral against, so the next commit and the
push are refused where, with the route, they passed and the stub was lost.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…regenerated guide and is kept

With the guide's merge=os-regen route gone, main's regeneration (20e7d52,
+3 lines) conflicted on docs/protocol-upgrade-guide.md instead of being
deferred; resolved to the stub (blob 9473b0f, unchanged).

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…he gate already skips references

content/docs/protocol-upgrade/ is the upgrade guide's docs pages, written by
gen:upgrade-guide from the ADR-0087 registries and gitignored. Its fix site is
the registry .ts, which this markdown gate cannot reach, the same reason
content/docs/references/ is skipped. CI never generates the pages, so reading
them made a local run judge a tree CI never sees: after a docs build the gate
reported 99 NEW uses (17.md 56, 18.md 43) while CI was green on the same commit.

The self-test's existence pin now accepts an untracked tree that the register
of untracked generator output (NOT_DRIVER_MANAGED in regen-artifacts.mjs)
declares, and a new case requires every such tree under a ROOT to be skipped.
A new program-level battery runs the gate over a fixture page inside each
skipped tree (exit 0, nothing read), the same page one level up (NEW use, exit
1), and in a sibling directory sharing the prefix (NEW use, exit 1).

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…kipped, not the set itself

The ablation (drop content/docs/protocol-upgrade from SKIP_SUBTREES, pages on
disk) left the walk case green: it read the mutated set to decide which pages
the walk must not reach, so it could not see the dropped entry. It now judges
against SKIP_SUBTREES plus the register's untracked trees under a ROOT, the
same union the program-level legs build from.

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

One conflict, docs/protocol-upgrade-guide.md: main's 3e72f93 regenerated
the committed guide the old way, and this branch turns that file into a
hand-written pointer stub (its merge=os-regen route is removed here). Resolved
to this branch's stub, blob 9473b0f, byte-identical to 7bc190c's.
Every other path merged without conflict.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 05:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 10, 2026 05:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit 514bf3c Oct 10, 2026
38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22483-upgrade-guide-address branch October 10, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants