Skip to content

chore: version packages - #21988

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

The changelog information of each package has been omitted from this message, as the content exceeds the size limit.

@objectstack/hono@17.8.0

@objectstack/cli@17.8.0

@objectstack/console@17.8.0

@objectstack/core@17.8.0

@objectstack/lint@17.8.0

@objectstack/metadata-protocol@17.8.0

@objectstack/platform-objects@17.8.0

@objectstack/plugin-auth@17.8.0

@objectstack/plugin-email@17.8.0

@objectstack/plugin-hono-server@17.8.0

@objectstack/plugin-security@17.8.0

@objectstack/plugin-sharing@17.8.0

@objectstack/rest@17.8.0

@objectstack/runtime@17.8.0

@objectstack/service-analytics@17.8.0

@objectstack/service-automation@17.8.0

@objectstack/service-messaging@17.8.0

@objectstack/service-realtime@17.8.0

@objectstack/service-storage@17.8.0

@objectstack/spec@17.8.0

@objectstack/types@17.8.0

@objectstack/verify@17.8.0

@objectstack/account@17.8.0

@objectstack/setup@17.8.0

@objectstack/studio@17.8.0

@objectstack/client@17.8.0

@objectstack/client-react@17.8.0

@objectstack/cloud-connection@17.8.0

@objectstack/connector-mcp@17.8.0

@objectstack/connector-openapi@17.8.0

@objectstack/connector-rest@17.8.0

@objectstack/connector-slack@17.8.0

@objectstack/driver-memory@17.8.0

@objectstack/driver-mongodb@17.8.0

@objectstack/driver-sql@17.8.0

@objectstack/driver-sqlite-wasm@17.8.0

@objectstack/driver-turso@17.8.0

@objectstack/formula@17.8.0

@objectstack/mcp@17.8.0

@objectstack/metadata@17.8.0

@objectstack/metadata-core@17.8.0

@objectstack/metadata-fs@17.8.0

@objectstack/objectql@17.8.0

@objectstack/observability@17.8.0

@objectstack/embedder-openai@17.8.0

@objectstack/knowledge-memory@17.8.0

@objectstack/knowledge-ragflow@17.8.0

@objectstack/organizations@17.8.0

@objectstack/plugin-approvals@17.8.0

@objectstack/plugin-audit@17.8.0

@objectstack/plugin-dev@17.8.0

@objectstack/plugin-pinyin-search@17.8.0

@objectstack/plugin-webhooks@17.8.0

@objectstack/service-cache@17.8.0

@objectstack/service-cluster@17.8.0

@objectstack/service-cluster-redis@17.8.0

@objectstack/service-datasource@17.8.0

@objectstack/service-i18n@17.8.0

@objectstack/service-job@17.8.0

@objectstack/service-knowledge@17.8.0

@objectstack/service-package@17.8.0

@objectstack/service-queue@17.8.0

@objectstack/service-settings@17.8.0

@objectstack/service-sms@17.8.0

@objectstack/trigger-api@17.8.0

@objectstack/trigger-record-change@17.8.0

@objectstack/trigger-schedule@17.8.0

create-objectstack@17.8.0

@objectstack/sdui-parser@17.8.0

@objectstack/example-crm@4.0.100

@objectstack/example-multi-package@0.0.7

@objectstack/example-showcase@0.3.22

@objectstack/example-todo@4.0.100

@objectstack/example-embed-objectql@0.0.40

@objectstack/dogfood@0.0.48

@objectstack/downstream-contract@0.0.46

@objectstack/http-conformance@0.1.8

This was referenced Oct 6, 2026
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 3deab49 to db6c3cd Compare October 6, 2026 18:22
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from db6c3cd to c2bbee9 Compare October 7, 2026 00:29
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from c2bbee9 to 3732751 Compare October 7, 2026 06:17
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ui#11670 and objectstack-ai#11669) (objectstack-ai#22015)

Fixes objectstack-ai#21996
Clause-②: no

This moves the bundled Console's objectui pin from `0abd4f9f8769`
(objectstack-ai#21807, PR objectstack-ai#21827) to `a58626c88dc85954bd0af24f16ebb69454c03eee`, which
was objectui `main` when this run started. The new pin carries
`5ba255538a` (objectui#11670): the Studio flow designer saves a screen
field's `Min` / `Max` as numbers. That is the commit the timing clause
of objectstack-ai#21898 (PR objectstack-ai#21974) waits on. The range also carries `c3623eb1`
(objectui PR 11669, Studio's shared picklists).

The bump follows the repo's own procedure (`scripts/bump-objectui.sh`,
then `pnpm objectui:build` and `node
scripts/gen-sdui-manifest-node.mjs`) and the gates it derives. It adds
no step to that procedure. Every file below is one the procedure or a
gate wrote, and nothing else rides here.

⛔ This is not a release act. Version Packages PR objectstack-ai#21988 is untouched. It
edits `packages/console/CHANGELOG.md` and
`packages/console/package.json`, and this diff edits neither. The
console's release input is the new `.changeset/console-a58626c88dc8.md`.

## Range

- objectui `git ls-remote origin refs/heads/main` read
`a58626c88dc85954bd0af24f16ebb69454c03eee` at 2026-10-06T15:32:48Z, the
same sha the dispatch read at 15:08Z.
- **REST `compare` on objectstack-ai/objectui proves containment**
(base...head, head = the new pin):
- `5ba255538a...a58626c88d`: `status: ahead`, `ahead_by: 18`,
`behind_by: 0`, merge base `5ba255538a1115e2dc2d52fa094ecf491621a5fe`.
The pin contains objectui#11670.
  - `c3623eb1...a58626c88d`: `ahead`, 15 ahead, 0 behind.
  - `0abd4f9f87...a58626c88d`: `ahead`, 36 ahead, 0 behind.
- `0abd4f9f8769..a58626c88dc8` has 36 commits, 0 merges and 252 changed
paths.
- objectui declared 36 changesets over the range, and all 36 release.
There are 0 release-nothing changesets and 0 commits without a
changeset. None declares `major`. Three carry the author's breaking
annotation. **The highest declared level is `minor`, so the console
changeset is `minor`.** The script takes the highest level objectui
itself declared over OLD..NEW. These counts are the bump's own digest
output.
- No commit subject in the range carries `!`. `git log --format='%h %s'
0abd4f9f8..a58626c88 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'`
matches nothing (exit 1).
- Re-read at 16:25:59Z, objectui `main` reads `834c5594b`, four commits
past the pin (objectui#11685, objectui PR 11724, objectui#11682 and
objectui#11687). This PR does not carry them.

## Declared-breaking entries and the ADR-0087 disposition

The script wrote its `adr-0087: TODO` placeholder into the console
changeset. It is answered `not-required (no-migration-prescription)`, in
the wording the previous bumps used. `check-adr-0087-registration --base
origin/main` reports "1 declared-breaking changeset(s), each carrying an
ADR-0087 disposition". `check-changeset-no-major --base origin/main`
exits 0.

Each entry is objectui's own package surface:

1. **objectui#4425 (`eb4552e71`)**: objectui's `@object-ui/types` faces
refuse `label` on a `metric-card` in an objectui `dashboard` node's
`widgets[]` slot.
2. **objectui#11709 (`89cc738da`)**: inside a widget's legacy
`component` envelope, a `metric-card` is judged by the slot's component
arm alone.
- `metric-card` is not a member of `@objectstack/spec`'s
`PageComponentType` and has no `ComponentPropsMap` row.
- The ObjectStack dashboard widget's `type` vocabulary is the chart
types (`metric`, `kpi` and the rest).
- No example or package source here authors a `metric-card` node, and
the regenerated `sdui.manifest.json` has no `metric-card` entry.
3. **objectui#11678 (`48c82c9d5`)**: `@object-ui/app-shell`'s
`RecentItem` becomes a union, and `addRecentItem` stops taking a label
for an object, dashboard, page or report entry. No code here imports
`@object-ui/app-shell`. The only code here that names the `ui.recent`
preference is the SQL driver's tests, which store the row as an opaque
value.

## What changed here (22 files, +491 / -161)

| file | written by | gate that required it |
|---|---|---|
| `.objectui-sha` | `scripts/bump-objectui.sh
a58626c88dc85954bd0af24f16ebb69454c03eee --no-commit` | Console Pin Gate
builds what it names |
| `.changeset/console-a58626c88dc8.md` | the same run (the digest); the
only later edit is the ADR-0087 answer above |
`check-adr-0087-registration`, `check:objectui-changeset` |
| `sdui.manifest.json`, `scripts/sdui-manifest.record.json` | `node
scripts/gen-sdui-manifest-node.mjs` over `.cache/objectui-a58626c88dc8`
| `check-sdui-manifest.mjs` |
| `packages/sdui-parser/objectui-lockstep.json` | `pnpm
gen:sdui-lockstep` against the same tree | `check:sdui-lockstep` |
| 54 asserting citations in 12 files under `packages/spec/src`;
`migrations/registry.ts` via `gen:migration-registry` | re-measured by
hand (below) | `check:objectui-pin-citations` |
| `.changeset/objectui-pin-citations-a58626c88dc8.md` | hand-written,
`@objectstack/spec` patch | `check:published-files` / Check Changeset
(the shipped describes name the pin) |
| `content/docs/references/ui/view.mdx` | `check:generated --fix` |
`check:docs` |

- **The bump script.** It ran against the sibling objectui checkout
after `git fetch origin main` there, with the target sha passed
explicitly. The range walked completely without a deepen. The checkout
was read and fetched only.
- **The manifest.** It still has 107 components. The sha256 moves from
`f95d406a584e…` to `e0654735a318…`.
- One input moved: `record:details` `sections` gained a sentence in its
description. A section that names a field group now takes the group's
`visibleWhen` as well (objectui#11630).
  - No key, type or required flag moves.
- The record moves its pin and `modulesRoot`. objectui's workspace
version stays 17.7.0.
- **The lockstep.** It records 214 grammar lines (blob `0131f27cf86d`),
25 diagnostic codes and containment predicate `76c18fb95d1f`. All are
unchanged, so no port is owed.
- **The 54 asserting pin citations were re-measured, not restamped.**
- **Method.** Each record's cited objectui files were resolved against
the tree at `0abd4f9f8` and intersected with the range's 252 changed
paths. Every anchor in a changed file was mapped through `git diff -U0
0abd4f9f8 a58626c88` and its text compared at both pins.
- **Fifteen cited files changed:** `ObjectKanban.tsx`, `KanbanImpl.tsx`,
`ObjectTree.tsx`, `ObjectTimeline.tsx`, `record-details.tsx`,
`action-group.tsx`, `action-menu.tsx`, `static-params.ts`,
`MetricWidget.tsx`, `MetricCard.tsx`, `form.tsx`, `plugin-kanban.mdx`
and the `en` / `zh` / `de` packs.
- **Result.** Every cited line in them is byte-identical at the new pin.
97 anchor citations in the records' current text MOVED and were
re-pointed, and each record's new hop sentence says by how much. No read
point an asserting record cites changed content or died. Every other
cited file is byte-identical across the hop (`git diff --quiet`).
- **Records.** Each of the 41 comment records gains a dated 2026-10-06
hop sentence and keeps its earlier history. The `FormField.span`
describe changes its sha only: `WIDE_FIELD_TYPES`, the field-type alias
table and `spanLadderFor` are byte-identical.
- **Counts.** Each was re-taken by the record's own method and reads as
before: the `keyboardNavigation` hit lines (15, against 3 for
`schema.editable`), `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd` (2
each, against 11 for `onCardClick`), and the `ElementDataSourceGate`
occurrences in the five `src/index.tsx` shells (0, 3, 3, 3 and 4).
- **Corpus counts.** The six migration entries' counts were re-taken
with `git grep -o -F`. That method first reproduced every `0abd4f9f8`
number: 7650 files, `objectstack` 17390, `@objectstack/spec` 7209,
`timeout` 1360, `RuntimeConfig` 293, `resourceLimits` 2, `useState`
2478, `window` 4194, `period` 238, `interval` 195, `metrics` 401, `Span`
508, `SpanSchema` 57, `TTL` 182 and `tenant` 1318.
- The new readings are 7754 files, `objectstack` 17468,
`@objectstack/spec` 7246, `timeout` 1431, `RuntimeConfig` 299,
`resourceLimits` 2 (the same two `packages/app-shell` hits), `useState`
2491, `window` 4255, `period` 247, `interval` 200, `metrics` 401, `TTL`
184 and `tenant` 1319.
- All 99 checked tokens still read zero at both pins: the export lists
of `plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and
`metrics.zod.ts`, plus every named key. The exceptions are `Span` /
`SpanSchema`, which read 509 / 57. The one new `Span` hit is a
`colSpan`.

No example, test or gate needed adapting, and no code changed outside
generated records, citations and changesets.

## Console build (the local Console Pin Gate equivalent)

Both steps ran under the verify lock.

- **Dependency build.** `turbo run build --filter=@objectstack/client
--filter=@objectstack/spec --filter=@objectstack/sdui-parser` ran 33
tasks and exited 0 in 2m05s.
- **Console build.** `pnpm objectui:build` exited 0 in 7m03s on the
shared box.
- `OBJECTUI_ROOT` pointed at a scratch repository holding only the pin
commit, so the build's `git worktree add` registered nothing in the
sibling objectui checkout.
  - The log reports "Bundle canary 'import/jobs' present".
- It reports "Single-zod canary: exactly one zod version literal
{major:4,minor:6,patch:5}".
- It reports **"Console bundle carries THIS tree's @objectstack/spec,
and only it"**.
- It reports "@objectstack/console dist ready (64400 KB) from
objectui@a58626c88dc8".
- `check:console-sha` and `check:console-injection` exit 0 against that
dist.

## Gates and tests (head `b86e6f264`)

- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 127 commands from the 22-path
diff. A throwaway merge onto current `main` (`1bc6ca1d3`) derives the
same 127.
- All 127 ran at `b86e6f264` after a full workspace build (`turbo run
build --filter=!@objectstack/docs`), each exit code written to disk. All
127 exit 0. `--ran` reports "127 derived, 127 run, 0 NOT-MEASURED, 0
UNRUN", with every exit code recorded.
- An earlier pass also ran all 127. There, `check:skill-examples` and
`check:dual-build-cjs-loads` answered PREREQUISITE NOT MET (exit 3).
`check:dts-closure` and `check:docs-transcript-drift` read a
`packages/spec/dist` that a concurrent build of mine was rewriting. All
four are green on the rerun above.
- **Pin citations.** `check:objectui-pin-citations --verify-anchors`
reads the sibling checkout at the pin. It reports "54 asserting objectui
pin citation(s) match .objectui-sha (a58626c88)" and "7 anchor content
assertion(s) verified against objectui at a58626c88".
- **Other pin gates.** `check:sdui-lockstep` reports "this copy is
byte-identical to objectui@a58626c88dc8". `check-sdui-manifest` reports
"recorded at the live objectui pin a58626c88dc8… @object-ui 17.7.0 is
the version objectui a58626c88dc8… declares".
- **`@objectstack/spec`.** `vitest run --project local` passed 619
files: 18485 tests, 1 todo. `typecheck` exits 0.
- **`@objectstack/sdui-parser`.** 14 files and 225 tests pass, and
`typecheck` exits 0.
- **Suites that read the regenerated manifest:**
- `@objectstack/lint`
`src/validate-jsx-pages.production-witness.test.ts`: 5 passed.
- `@objectstack/metadata-protocol`
`src/protocol.runtime-authoring-gate.test.ts`: 70 passed.
- `@objectstack/cli` unit tier `src/utils/sdui-manifest.test.ts`,
`src/utils/console.sha-drift.test.ts` and
`test/validate-build-gate-parity.test.ts`: 3 files, 87 passed.
- The CLI integration tier (`test/jsx-gate-manifest-notice.e2e.test.ts`
spawns the CLI) is declared to CI.
- **Lint.** `eslint --no-inline-config --format json` over the 15
changed TS files reports 15 files, 0 errors and 0 warnings.
- The lint population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
(`eslint.config.mjs:971`).
- The config enables no type-aware linting (`:328`), so this diff cannot
move a verdict on an untouched file.
  - Repo-wide `pnpm lint` is left to CI.
- **Console Pin Gate.** It is CI's to run on this head, and its verdict
is read on the PR.
- **Browser smoke.** NOT MEASURED. The dispatch asked for none, and the
range's own landings carry their objectui-side tests.

## Acceptance notes

- **objectui#11638 retired a read that a dated record still lists.** The
two action containers no longer read a member's `properties.params`.
- `ui/component.zod.ts`'s action-container member docblock still lists
that read under "Read, and refused anyway". The docblock is in the
historical spelling, measured at `.objectui-sha` pin `2e818d0b51ec`.
- The spec refuses the key either way, so the accept set and the save
gate do not move, and the renderer and the save gate still agree.
- The record is a dated measurement that the pin-citation gate does not
hold to the current pin, so nothing here re-measures it. Noted, not
filed. Carrier: none.
- **The shipped console now reads `KanbanConfig.summarizeField`**
(objectui#11629): each kanban column header totals it. The key is
declared in `ui/view.zod.ts`, and the liveness ledger carries no row for
it, so nothing here is owed.
- **The console's sign-up gating now reads `features.audiencePosture`**
from `/api/v1/auth/config` (objectui#11691, objectui#11705). This repo's
auth plugin serves that key (`auth-manager.ts`, `getPublicConfig`) and
serves `GET /auth/bootstrap-status`, so that seam holds.
- `main` moved four commits past this branch's base
(`f0022c46c..1bc6ca1`). None of them touches a file this diff touches,
and `git merge-tree --write-tree HEAD origin/main` is clean. No merge
was made; the merge queue rebuilds on the current `main`.
- **Writes.** One draft PR through the relay, one label write on the PR
(assignee `os-justin`), and the report comment on objectstack-ai#21996.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 3732751 to eb92180 Compare October 7, 2026 12:17
This was referenced Oct 8, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…-pr lane, and stop prerelease cuts re-dating the last GA (objectstack-ai#22095)

Part of objectstack-ai#22085. This PR carries the half of the card that holds on
measurement. The half it does not carry needs a decision first (see
"What still stops the refresh").

Clause-②: no

## What this changes

1. **`sync-release-index-currency` no longer re-dates the newest GA on a
prerelease cut.** `rewriteStatusField` re-stamped today's date into a
`current series:` field that already named the newest GA. The gate it
serves (`indexCurrencyFindings`) judges the version only. The date it
holds is the release date of the version the run moves the field TO, so
a run that moves nothing (every `next` or `rc` cut) is not that
version's version commit. A same-version field is now left alone.
Battery B's case that pinned the re-date is inverted, and a new battery,
`Control H: a prerelease cut never re-dates the newest GA` (7 cases),
pins the `next` cut, the `rc` cut, a positive control (a stale entry on
the same later day still gets the version and that day), and `syncIndex`
end to end on a temp checkout (no write, bytes identical). The roster
floor goes from 7 to 8.
2. **`release.yml` `version-pr` › `Validate the post-version tree`
validates the blank template's three major-boundary paths instead of
refusing them.** It reuses gates the repo already runs, with no build:
- `pnpm --filter create-objectstack test`: the template rendered by the
scaffolder's own copy and identity rewrite, from a template packed the
way npm ships it, plus the ratchets in `template-consistency.test.ts`
that judge all three stamps against create-objectstack's NEW major;
   - `pnpm --filter @objectstack/spec check:template-manifests`.

   `protocol-version.ts` gets its gates too:
- `protocol-version.test.ts` as ONE file (5 s). It does not need the
whole spec suite, which is what the step's comment used to claim;
- `check:spec-changes` and `check:upgrade-guide`, the two artifacts
derived from `PROTOCOL_MAJOR`.

It **stays refused**, for the measured reason below. The refusal is now
collected instead of exiting first, so a boundary run reports every
gate's verdict together. Every pnpm filter carries `--fail-if-no-match`,
because a filter that matches nothing exits 0 having run nothing
(measured: 0 without it, 1 with it).

## Pin: the boundary train, replayed (throwaway tree, never committed)

Tree: this branch at `3079e4aef0` plus PR objectstack-ai#22084's `.changeset/pre.json`
and `.changeset/22080-v18-line-opens.md`, carried by one local commit
that was never pushed. The steps were extracted from this branch's
`release.yml` with a YAML parser and run verbatim with `RUNNER_TEMP` /
`GITHUB_OUTPUT` / `GITHUB_STEP_SUMMARY` set.

| step | exit | reading |
|---|---|---|
| `Render the post-version tree` (the full root `version` script) | 0 |
69 of 69 `fixed` members at `18.0.0-next.0`, `pre.json` unchanged
(`mode: pre`, `tag: next`). `PROTOCOL_VERSION 17.0.0 → 18.0.0`. Template
stamps at `^18.0.0` / `'^18'`. 9 docs pins moved to `18.0.0-next.0`.
Release index: "already names the newest GA … nothing rewritten" |
| release index v17 entry | unchanged | `(current series: 17.7.0,
released 2026-10-06)`; `git diff HEAD --
content/docs/releases/index.mdx` empty. Before this PR, the same replay
wrote `released 2026-10-07` (reproduced on PR objectstack-ai#22084's head
`52f7a509c6`) |
| shape assertion | pass | 235 paths moved, 0 outside the reviewed
surface. It was 236 before this PR; the difference is the release index
|
| `Validate the post-version tree` | **1** | 11 gates green: the 9
existing content gates except `check:release-notes`, plus the
create-objectstack suite, `check:template-manifests` and the lockstep
test. 3 gates red: `check:release-notes`, `check:spec-changes`,
`check:upgrade-guide`. 1 unvalidated: `protocol-version.ts` |
| `Restore the pre-version tree` | 0 | 37 pending changesets, tree clean
|

**Control on an ordinary train.** This branch without the two opening
files is the 17.8.0 refresh objectstack-ai#21988 gets today. `Render` exits 0 (194
paths, and the release index stamps `17.8.0, released 2026-10-07`,
version and date together). `Validate` exits **0** with all 14 gates
green, holding 44 s. `Restore` exits 0.

**Negative control for the template gate.** On the boundary tree,
`specVersion` in the blank manifest was set back to `^17.0.0`.
`template-consistency.test.ts` then exits 1 with 2 failed, and the file
was restored by hash (`0956082cfd8e` both sides).

**Baseline.** `main`'s own step, replayed on PR objectstack-ai#22084's head, exits 1
at the old blanket refusal and names all 4 paths. This reproduces the
measurement the card rests on.

## Why `protocol-version.ts` is still refused (measured, not guessed)

Moving the protocol major at version time does more than move a
constant:

- **Two derived artifacts go stale.** On the boundary tree,
`check:spec-changes` exits 1 ("spec-changes.json is stale") and
`check:upgrade-guide` exits 1. Both pass on the pre-version tree
(control). Regenerating them in the throwaway changes
`packages/spec/spec-changes.json` by 6342 lines, and adds a 1024-line
`Protocol 17 → 18` section to `docs/protocol-upgrade-guide.md`. The
version pass regenerates neither. Both gates run in the required
`TypeScript Type Check` job, so a version PR let through as things stand
would turn `main` red on its next ordinary PR.
- **The handshake refuses this repository's own example apps.**
`assertProtocolCompat` runs on the app load seam
(`packages/runtime/src/app-plugin.ts:421`). Probed with the post-version
constant, `checkProtocolCompat` gives `^17` → `incompatible`
(`OS_PROTOCOL_INCOMPATIBLE`) and `^18` → `ok`. On the pre-version tree
it is the reverse. `examples/app-crm`, `app-showcase` and `app-todo`,
plus the two packages in `app-multi-package`, declare `engines: {
protocol: '^17' }`. No gate this lane can afford boots them.

So the refusal stays, as Done-when 1's last clause provides ("The
refusal stays for any major-only path still unvalidated"), with its
message rewritten to say exactly this.

## What still stops objectstack-ai#21988 refreshing into 18.0.0-next.0

These are three decisions, set out with options and a recommendation in
the card's `os-dev-report`. None of them is made here:

1. **How the protocol major moves at the opening.** Either the version
pass regenerates the two artifacts and restamps in-repo
`engines.protocol` (the version chain grows), or an ordinary PR with CI
moves the protocol major ahead of the version PR (the lockstep test's
definition changes).
2. **`check:release-notes` is red at the boundary** (it is already in
this step). It counts `## 18.0.0-next.0` as "shipped a 18.x release" and
asks for `content/docs/releases/v18.mdx` plus its `meta.json` entry.
That content is release-owned, and this PR does not write it.
3. **Docs image pins during the `next` line.** These were measured and
not edited (Done-when 4). On the first prerelease,
`sync-docs-image-tags` moves 9 pins from `17.7.0` to `18.0.0-next.0`:
   - `docker/README.md`: 3 image tags and 1 build-arg;
- `content/docs/deployment/self-hosting.mdx`: 3 image tags and 1 npm
pin;
   - `content/docs/upgrading.mdx`: 1 image tag.

Meanwhile `npm view` gives `latest: 17.7.0` (and `rc: 17.0.0-rc.6`, no
`next` yet) for `@objectstack/cli`, `@objectstack/spec` and
`create-objectstack`. The ghcr `latest` tag does not move for a
prerelease (`docker-publish.yml:83`).

## Verification

- Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `a4cbcfdb16`): 50 commands, all exit 0,
exit codes written to disk before any pipe. `--ran`: `50 derived, 50
run, 0 NOT-MEASURED, 0 UNRUN`. The battery `pnpm
check:pm-dispatch-gates` passed 1976 cases in 997.8 s.
- `node scripts/sync-release-index-currency.mjs --self-test`: exit 0, 42
cases.
- **Ablation, committed first.** With `ablation-replace.mjs`, the
pre-fix logic (`const rewritten = …; return rewritten === field ? null :
rewritten;`) was put back in place of the fix. The self-test exits 1
with exactly 5 failures: battery B's inverted case and 4 of Control H's
7. H's three controls stay green, as they should. The file was restored
with blob equal to HEAD and `git diff HEAD` empty. A first, cruder
ablation that only deleted the guard line also reddened B and C. That
mutation was too strong, so the faithful one above is the reading.
- Not run locally: the whole spec suite (the step no longer needs it),
and the repo-wide lint farm, which belongs to CI.

## Acceptance notes (noted, not fixed here)

- The comment above `Create or update the "chore: version packages" PR`
in `release.yml` still says `pnpm run version` is FOUR rewriters. It is
five (`sync-release-index-currency.mjs` joined).
- `cut-rc.yml` says "On an RC cut this rewriter writes NOTHING". That
was false for the date before this PR (an rc cut on a later day re-dated
the entry) and is true after it. `cut-rc.yml` is not edited, by the
card.
- `release.yml`'s step comment said the lockstep test was "reachable
only through the whole @objectstack/spec suite". That is corrected in
place, because it is the step this card owns.

Changeset: none. The diff touches `.github/workflows/release.yml` and
`scripts/sync-release-index-currency.mjs`, and neither is in any
package's `files[]`.

Commits carry this repository's model-free trailer pair (AGENTS.md).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…ollow the newest GA, not the prerelease (objectstack-ai#22134)

Fixes objectstack-ai#22131

Clause-②: no

Refs objectstack-ai#22085 (the parent; this PR carries its Q3 half). Ruling record:
comment 6049734955 on objectstack-ai#22085, quoted as the dispatch carried it:

> **Q3 → B. During the `next` line, the documented image and install
versions follow the newest GA, not the prerelease.** In pre mode
`sync-docs-image-tags` and `check-docs-image-tag` pin the newest GA
(read from the CHANGELOG, the same way the GA is already judged);
outside pre mode nothing changes. ⛔ Not taken: A (self-hosting and
upgrade documents reading `18.0.0-next.N` while npm `latest` and the
published image stay 17.7.0, so a production user copying the documents
is led into the breaking prerelease line).

## What changed

Two files, both under `scripts/`, nothing published:

- `scripts/check-docs-image-tag.mjs` gains `expectedVersion(root)`, the
ONE function that answers which version the doc surfaces must pin.
- No `.changeset/pre.json`: `packages/cli/package.json`'s version
(`VERSION_SOURCE`, kept with its name), as before.
- `.changeset/pre.json` in mode `pre` or `exit`: the newest GA in
`packages/spec/CHANGELOG.md`, overall (not of the CLI's major).
- It reads through the existing readers only: `SPEC_CHANGELOG`,
`gaVersions` and `newestGaOfMajor` from
`check-release-section-coverage.mjs` (unchanged), and `readPre` from
`check-changeset-no-major.mjs` (unchanged). No second GA reader and no
third `pre.json` reader.
- Every pre-mode state it cannot read throws instead of falling back: a
`pre.json` that is present but does not parse, a mode Changesets never
writes, no spec CHANGELOG, or a CHANGELOG with no GA heading.
- `main()` judges against it. The STALE detail, the scope line and the
red footer name the source used. A pre-mode red also prints why the
expectation is not `packages/cli`'s prerelease. Outside pre mode the
gate's output is byte-identical to `main`'s.
- `scripts/sync-docs-image-tags.mjs` gains `syncRepo({ root })`, which
`main()` runs: the target comes from the gate's `expectedVersion()`,
followed by the existing `syncSurfaces()`. The rewriter and the gate
therefore read one value. A pre-mode run logs why the docs did not
follow `packages/cli`.

Not touched: `content/docs/**`, `release.yml`, `cut-rc.yml`, `lint.yml`,
the root `version` script, `.changeset/**`, objectstack-ai#21988.

## Premises measured on `origin/main` `8fc50b7647`

1. The root `version` script (`package.json:20`) is `changeset version
&& sync-protocol-version && sync-template-versions &&
sync-docs-image-tags && sync-release-index-currency`. Holds.
2. `VERSION_SOURCE`, `SURFACES` and `PROSE_CLAIMS` are as the card
states, and the rewriter imports them from the gate. Holds.
3. `check-release-section-coverage.mjs` exports `SPEC_CHANGELOG`,
`gaVersions` (ascending, GA-only, end-anchored) and `newestGaOfMajor`.
Holds.
- On a pre-mode tree the answer has to be the newest GA overall. After
the first `next` cut the CHANGELOG's top heading is `## 18.0.0-next.0`
and there is no GA of 18, so `newestGaOfMajor(versions, 18)` is `null`.
- `@objectstack/spec` and `@objectstack/cli` are in one `fixed` group in
`.changeset/config.json`, so the spec CHANGELOG's newest GA is also the
CLI's.
4. Pre mode is `.changeset/pre.json`. **Refined:** Changesets writes it
in two modes, and both are the prerelease line.
- `changeset pre exit` only rewrites the mode to `"exit"`, so the tree
stays on `18.0.0-next.N`.
- The next `changeset version` then computes the GA and deletes
`pre.json`. Measured in `@changesets/apply-release-plan` 8.1.1, which
removes the file when the mode is `exit`; replayed below.
- That `exit` commit reaches `main` before the Version Packages PR does.
Keying on `"pre"` alone would expect `18.0.0-next.N` in that window
while the docs read the GA, which reds this required gate on every PR in
between, the exiting PR included.
   - So `exit` counts as pre mode here. The self-tests pin this case.
5. Other consumers: no change in meaning except the rc lane below.
- `release.yml:569` and `cut-rc.yml:605` import `SURFACES` only. Re-run
after the change, the import prints the same 3 paths and exits 0; the
two new imports are side-effect-free, and `check:entry-guard` is green.
- `lint.yml:2649` and `lint.yml:2672` run the two gates. Outside pre
mode the output is identical.
- **Correction to the dispatch's reading of `cut-rc.yml`:** it does not
refuse pre mode. It refuses unless `pre.json` is mode `pre` **with tag
`rc`** (`cut-rc.yml:232-236`). So it never runs on the `next` line, but
it does run in pre mode on an rc line.
- Under this change, an rc cut's version pass leaves the doc surfaces at
the newest GA instead of stamping `X.Y.Z-rc.N`. That follows the
ruling's text ("in pre mode") and its reason, a production reader led
into a prerelease, which applies to an rc as well.
- Its allowlist permits the doc surfaces to change and does not require
it (`git add -A -- … "${DOCS_SURFACES[@]}"`), so a cut that leaves them
unchanged passes. The self-test pins the rc case: CLI `17.8.0-rc.0`,
expected `17.7.0`.

## The pin, replayed

Each run used a throwaway `git worktree add --detach` under the session
scratch directory, then `pnpm install --frozen-lockfile --offline
--ignore-scripts` and `pnpm run version`. All trees were removed
afterwards. PR objectstack-ai#22084's two files were copied from its head
`a630de657d`: `.changeset/pre.json` (`{"mode": "pre", "tag": "next"}`)
and `.changeset/22080-v18-line-opens.md`.

**Tree 1: this branch (`a4b1b5106d`) plus PR objectstack-ai#22084's two `.changeset`
files.** `pnpm run version` exit 0. `packages/cli` went from `17.7.0` to
`18.0.0-next.0`, and the spec CHANGELOG's top heading is now `##
18.0.0-next.0`, above `## 17.7.0`.

```text
✓ sync-docs-image-tags: all 9 concrete pin(s) across 3 surface(s) already at the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0 — nothing rewritten.
  Pre mode: .changeset/pre.json is in mode "pre" (tag "next"), so the documented versions follow the newest GA, 17.7.0, and not packages/cli/package.json's 18.0.0-next.0. ...
$ git diff -- docker/README.md content/docs/deployment/self-hosting.mdx content/docs/upgrading.mdx | wc -l
0
pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:17.7.0, 1 x OS_CLI_VERSION=17.7.0, 1 x @objectstack/cli@17.7.0
$ pnpm check:docs-image-tag      # exit 0
check-docs-image-tag: OK (3/3 enumerated surface(s) read, 9 concrete pin(s) compared against the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0, ...)
```

**Tree 1, continued: the exit window and the GA pass.** These are the
states premise 4 is about.

```text
$ pnpm exec changeset pre exit   # .changeset/pre.json → {"mode": "exit", "tag": "next"}; packages/cli still 18.0.0-next.0
$ pnpm check:docs-image-tag      # exit 0, compared against the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0
$ pnpm run version               # exit 0; .changeset/pre.json deleted; packages/cli 18.0.0
✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 18.0.0 (lockstep with packages/cli/package.json).
$ pnpm check:docs-image-tag      # exit 0, compared against packages/cli/package.json 18.0.0
```

**Tree 2: this branch on plain `main`, without `pre.json`.** `pnpm run
version` exit 0. `packages/cli` went from `17.7.0` to `17.8.0` (the 58
pending changesets), and the surfaces move to the CLI's version, as they
do today.

```text
✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 17.8.0 (lockstep with packages/cli/package.json).
pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:17.8.0, 1 x OS_CLI_VERSION=17.8.0, 1 x @objectstack/cli@17.8.0
$ pnpm check:docs-image-tag      # exit 0, compared against packages/cli/package.json 17.8.0
```

**Control tree: `main` at `8fc50b7647`, without this change, plus PR
objectstack-ai#22084's two files.** This is the state the ruling rejects (⛔ A):

```text
✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 18.0.0-next.0 (lockstep with packages/cli/package.json).
pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:18.0.0-next.0, 1 x OS_CLI_VERSION=18.0.0-next.0, 1 x @objectstack/cli@18.0.0-next.0
```

## Self-tests and the ablation

New batteries, each case with its positive control:

- In the gate, `Pre mode: the expectation is the newest GA (objectstack-ai#22131)` has
18 cases. The roster floor goes from 11 to 12, and the run has 112
assertions.
- In the rewriter, `Control I: in pre mode the target is the newest GA,
from the gate's one function` has 10 cases and runs `syncRepo()`. The
roster floor goes from 8 to 9, and the run has 45 assertions.

The three Done-when cases, and what each is controlled against:

| Case | Expected result | Positive control |
|---|---|---|
| Pre mode, CLI `18.0.0-next.0`, newest GA `17.7.0` | expects `17.7.0` |
The same tree's CLI reads `18.0.0-next.0`, and the CHANGELOG has no GA
of 18 |
| The same tree without `pre.json` | expects the CLI's `18.0.0-next.0` |
Case 1 answers differently, and `pre.json` is the only difference
between the two trees |
| Pre mode, docs already at the GA | gate green, rewriter writes nothing
(byte-identical, mtime unchanged) | The same docs against the CLI's
version are 3 STALE in the gate and 3 rewrites in the rewriter |

Further cases cover mode `exit` and an rc pre mode, which both expect
the GA. Pins moved onto the prerelease in pre mode are STALE, and the
finding names the pre-mode source. Each of the four unreadable states is
refused, and the rewriter refuses before any write.

**Ablation, at `a4b1b5106d`.** The change was committed first. `node
scripts/ablation-replace.mjs` rewrote the pre-mode test in
`expectedVersion()` (`if (!existsSync(join(root, PRE_STATE))) {` became
`if (true) {`), which is the old logic: `pre.json` ignored.

- The mutation landed: the anchor count went 1 → 0, the marker count
during the mutation was 1, and the blob went `e27045098c80` →
`97307ba4e54a`.
- `node scripts/check-docs-image-tag.mjs --self-test` exited 1 with **13
failures**, every one a new pre-mode case.
- `node scripts/sync-docs-image-tags.mjs --self-test` exited 1 with **6
failures**, every one a new pre-mode case.
- The controls that do not depend on the branch stayed green.
- The restore was proven by blob hash: after the restore the blob is
`e27045098c80`, equal to `HEAD:scripts/check-docs-image-tag.mjs`, and
`git diff HEAD` is empty.

## Gates, at `a4b1b5106d`

`node scripts/pm/dispatch-gates.mjs --commands` was derived from this
worktree over the actual diff (2 paths, +548/-39) and gave 31 commands,
the same 31 the dispatch named. All 31 were run and every one exited 0.

- `pnpm check:pm-dispatch-gates` passed 1976 cases (856s).
- `pnpm check:docs-image-tag` (112 assertions, then OK on the live tree)
and `pnpm check:docs-image-tag-sync` (45 assertions) passed.
- `check:entry-guard`, `check:nul-bytes`, `check:scripts-symbol-anchors`
and `check:declaration-mirrors` passed.
- `dispatch-gates --ran`: 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN.

Lint was a proven narrowing to the changed files, not the repo-wide
`pnpm lint`, which CI runs:

- Population, read from eslint's own config: `isPathIgnored` is `false`
for both files.
- Count, from `--format json`: 2 results, 0 errors and 0 warnings, with
`--no-inline-config`.
- Invariance: this repo's eslint config never enables type-aware linting
(no `parserOptions.project`), so this diff cannot change the verdict on
any untouched file.

No changeset is needed. The root package is private and no package's
`files[]` ships `scripts/`. Labelled `skip-changeset`.

## Acceptance notes

None of these blocks this PR, and none is a defect. Each is wording that
this change makes slightly inaccurate, in a file outside this PR's
declared surface. Carrier for each: none.

- `scripts/check-changeset-no-major.mjs`'s entry-guard comment says
"Nothing imports this file today". `check-docs-image-tag.mjs` now
imports `readPre`. The guard already makes the import inert, as
`check:entry-guard` confirms.
- `release.yml`'s post-version comment ("check:docs-image-tag — the 3
doc surfaces against packages/cli's NEW version") and `lint.yml`'s step
name "Docs image tags track packages/cli's version" are true outside pre
mode only. In pre mode the expectation is the newest GA.
- `cut-rc.yml`'s staging comment (around line 497) lists "the 3 doc
surfaces" among a cut's paths. An rc cut now leaves them unchanged
(premise 5).
- dispatch-gates now routes `.changeset/pre.json` to
`check:docs-image-tag`, because the gate spells it. It does not route
`packages/spec/CHANGELOG.md`, which is read only through an import from
a gate module. That file is release-owned and only the version pass
writes it.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…arker, so v18 opens at 18.0.0-next.0 (objectstack-ai#22084)

Fixes objectstack-ai#22080

Clause-②: no

Opens the v18 line on `main`, as ruled B on objectstack-ai#22050 (record
`6037890422`). Changesets enters pre mode with the tag `next`, and one
`major` changeset takes the `fixed` group to 18, so the first version
cut is `18.0.0-next.0`. File surface: `.changeset/` plus one line in
`scripts/check-adr-0087-registration.mjs` (the seat's surface amendment
`6040954045` on objectstack-ai#22080; see the patch round below). ⛔ No edit to
`.changeset/config.json`, to either changeset guard's logic, or to
`.github/workflows/release.yml`. ⛔ No `changeset version` output is
committed, and nothing was published.

## The change

1. **`.changeset/pre.json`**, written by `pnpm changeset pre enter next`
(`@changesets/cli` 3.0.3) and committed as the tool wrote it (commit
`d3d35a1029`):

   ```json
   {
     "mode": "pre",
     "tag": "next"
   }
   ```

Under v3 the file holds `mode` and `tag` only. There is no
`initialVersions` and no `changesets` list.

2. **`.changeset/22080-v18-line-opens.md`**, front matter
`"@objectstack/spec": major` (commit `52f7a509c6`). Its body is the v18
line's opening note (ADR-0131; objectstack-ai#22050). It carries `Clause-②: no` and
its ADR-0087 disposition (see Gates).
- **Step 2 was not skipped.** The card's count command, re-run on the
cut `3d9188502e`, prints `17 : minor` and `32 : patch`, and no `major`.
No `major` was pending, so without this marker pre mode would version
`17.8.0-next.0`.
- **Why `@objectstack/spec`.** Any `fixed`-group member moves all 69 to
the same version, so the choice decides only where the note lands and
what the major means. `@objectstack/spec` is the protocol package.
`scripts/sync-protocol-version.mjs`, which the root `version` script
runs, derives `PROTOCOL_VERSION` from this package's major, and ADR-0131
C8 calls the line "protocol 18". So the major sits on the package whose
major is the protocol version, and the note lands where an upgrading
reader looks first: `packages/spec/CHANGELOG.md`, under `### Major
Changes`.

## The no-major guard reads `pre.json` from the checkout it runs in (the
head), so steps 1 and 2 ship together

- `scripts/check-changeset-no-major.mjs:396` sets `REPO_ROOT` to the
checkout the script runs in. `readPre(root)` (`:2018`) reads `join(root,
'.changeset', 'pre.json')` from that working tree, `:2098` hands the
result to `judge`, and `judge` returns `exempt` when `pre?.mode ===
'pre'` (`:662`). `--base` sets only where the diff starts. It is never
where `pre.json` is read.
- In CI, the `Check Changeset` job in
`.github/workflows/pr-automation.yml` checks out the PR merge ref (the
default for a `pull_request` event) and runs `node
scripts/check-changeset-no-major.mjs --base "$MERGE_BASE"` (`:1130`).
The merge ref carries this PR's `pre.json`.
- Both legs were measured at `52f7a509c6`:
- **As committed:** exit 0, `✓ Changesets is in pre-release mode (tag:
next) — ... skipping the no-major guard.`, plus a notice naming
`@objectstack/spec`.
- **Control:** with `.changeset/pre.json` deleted from the working tree
only, exit 1, `⛔ This PR introduces changeset(s) that declare a major
bump.` The base `3d9188502e` has no `pre.json` at all, so a guard that
read the base could never have answered `exempt`. Restored with `git
checkout HEAD -- .changeset/pre.json`: `git diff HEAD` is empty, and the
guard exits 0 again.
- So no split is needed, and this PR carries steps 1 and 2.

## Pins

| Pin | Reading |
|---|---|
| In a throwaway worktree, never committed, `changeset version` puts the
`fixed` group at `18.0.0-next.0` | Detached throwaway worktree at
`52f7a509c6`: `pnpm changeset version` exits 0. All 69 `fixed`-group
members read `18.0.0-next.0`, and `pre.json` is unchanged. The 37
consumed changesets (36 pending plus this one) moved to
`.changeset/pre/`. `packages/spec/CHANGELOG.md` opens `## 18.0.0-next.0`
/ `### Major Changes` with this note. The worktree was then removed. |
| `check-changeset-no-major` and `check-changeset-fixed` pass on the PR
| `node scripts/check-changeset-no-major.mjs --base origin/main` exits 0
on the pre-release exemption above. Driven with a `pull_request` payload
carrying this body (`--event`), it also exits 0 with the level axis
read. `node scripts/check-changeset-fixed.mjs` exits 0: `✓
.changeset/config.json "fixed" group is in sync with 69 public workspace
packages.` |
| `.changeset/pre.json` is present at the merge, with `"mode": "pre"`
and `"tag": "next"` | Added in `d3d35a1029` with the content above. No
later commit touches it. It is in the merge ref CI checks out, which is
the file the guard's exemption reads. |

## `release.yml`: in pre mode, `changeset publish` publishes under
`next`, and `latest` stays 17.7.0

This is a reading of the file. Nothing was edited, and no publish was
run.

- `.github/workflows/release.yml:1762` is job `publish`, step `Publish
to npm + push version tags`. At `:1772` it runs `pnpm run release`.
- The root `package.json` `release` script is `pnpm run build && bash
scripts/build-console.sh && bash scripts/release-publish.sh`.
- `scripts/release-publish.sh:64` runs `changeset publish` with no
`--tag`.
- In `@changesets/cli` 3.0.3, `dist/getPublishPlan.mjs:599-603`
`getReleaseTag` works like this: with no `--tag` and a pre state
present, the tag is `preState.tag`, which is `next`. The one exception
is a package whose every published version is already a `next`
prerelease. Separately, `dist/publish.mjs:61-63` refuses a custom
`--tag` in pre mode.
- `npm view` read today: all 69 `fixed`-group packages have `latest` =
`17.7.0` and no `next` tag. So each publishes to `next`, and `latest`
stays `17.7.0`.

## Gates (run on `52f7a509c6`, exit codes captured before any pipe)

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 20 commands for this diff. All 20
exit 0, and `--ran` reconciles: `20 derived, 20 run, 0 NOT-MEASURED, 0
UNRUN`.

**Patch round, head `a630de657d` (seat-appended from addendum
`6041313497`).** At `52f7a509c6` the required `Lint & Repo Gates` was
red at `PM dispatch-gates self-test`, case `the extension narrowing
costs no lead … (refused: .changeset/pre)`: with `pre.json` tracked, the
battery's loose and strict hint rules disagreed on the literal
`.changeset/pre/` at `scripts/check-adr-0087-registration.mjs:536`. That
line now names the existing exclusion as `CONSUMED_PRERELEASE_EXCLUDED`
(the predicate's match is unchanged), which the extractor reads as an
excluded surface. At `a630de657d`: 41 derived commands, 41 run, all exit
0; `pnpm check:pm-dispatch-gates` → `1976 cases pass` (965.7 s), the
case reads `(refused: none)`; `check-adr-0087-registration --self-test`
441 assertions and `--base origin/main` exit 0.

- **Changeset gates:**
- `check-adr-0087-registration --base origin/main` exits 0. The gate
requires a disposition for a `major`: a probe commit without one was
refused, exit 1, with `declares a breaking change (major) but no
adr-0087: disposition marker`. The probe was never pushed. The changeset
now carries the `not-required (no-migration-prescription)` marker in the
ADR-0087 HTML-comment form, because the marker moves no authorable key,
export, type or stored shape.
- `check-changeset-no-major --base origin/main` and
`check-empty-changeset --base origin/main` exit 0.
- The self-tests of all three, plus `check:changeset-gate-self-tests`,
exit 0.
- **Remaining derived commands, all exit 0:**
`check-closing-keyword-parity` and its self-test,
`check-comment-mask-corpus`, `release-rehearsal-clone --self-test`,
`release-pending-publish --self-test`, `check:driver-memory-census`,
`check:gitlink-declared`, `check:nul-bytes`, `check:objectui-changeset`,
`check:pm-changeset-deadline-census`, `check:published-files`,
`check:refd-timer-probe` and `check:watch-hint-literal`.
- **Run beyond the derivation, all exit 0:** `check-changeset-fixed` (an
artifact roster under `.changeset/`), `check:future-spec-major`,
`check:lockstep-package-count` and `check:docs-image-tag`.
- **NOT MEASURED:** the four type-check lanes. The diff touches no
TypeScript.

## Read before merging: after this lands, the version-PR refresh goes
red until its major-boundary gates are wired

The card expects changesets/action to refresh objectstack-ai#21988 into the
`18.0.0-next.0` version PR once this lands. On the files as they stand,
it will not.

- The `version-pr` job in `release.yml` (cron `0 */6 * * *`) runs `pnpm
run version` in the step `Render the post-version tree` (`:450`). The
step `Validate the post-version tree` (`:518`) then exits 1 at
`:610-613` when the pass wrote any major-boundary path: "this version
pass crossed a MAJOR boundary ... Wire them in before letting this
refresh through." The step `Create or update the "chore: version
packages" PR` has no `if:`, so it does not run after that failure.
- I measured this in the same throwaway. After `changeset version`, the
other three rewriters of the root `version` script exit 0. Replaying the
step's shape assertion gives 0 unexpected paths and 4 major-only paths:
`packages/spec/src/kernel/protocol-version.ts` (`PROTOCOL_VERSION
17.0.0` to `18.0.0`) and the blank template's `objectstack.config.ts`,
`objectstack.manifest.json` and `package.json`.
- The other lane, `cut-rc.yml`, refuses a pre tag other than `rc`
(`:232-237`) and a version not shaped `X.Y.Z-rc.N` (`:156-159`). The
ruled tag is `next`.
- What this means: once this lands, no lane in this repository cuts
`18.0.0-next.0` without a workflow change. That change is outside this
card, which forbids any `release.yml` edit. The red is the lane's own
designed refusal. It publishes nothing and queues nothing. objectstack-ai#21988 stays
at its current 17.8.0 content and, as the card says, ⛔ is not merged.

## Acceptance notes

- The same throwaway `pnpm run version` shows two more things that the
lane wiring has to account for.
- `scripts/sync-release-index-currency.mjs` re-dates the v17 entry in
`content/docs/releases/index.mdx` from `17.7.0, released 2026-10-06` to
`released 2026-10-07`. That is the prerelease cut's date. 17.7.0's
version commit `4e4e881427` is dated 2026-10-06. Reported to the seat as
a finding.
- `scripts/sync-docs-image-tags.mjs` moves the 9 pins in
`docker/README.md`, `content/docs/deployment/self-hosting.mdx` and
`content/docs/upgrading.mdx` from `17.7.0` to `18.0.0-next.0`. The
self-hosting docs on `main` would then name a prerelease while `latest`
is 17.7.0. Noted only.
- The opening note says the dist-tag `latest` stays on 17.x until GA.
The pre-mode publish path above is what makes that true.

## 维护者速读(草稿)

**改了什么**:在 `main` 上进入 Changesets 预发布模式(标签 `next`),并加一条把
`@objectstack/spec` 标为 `major` 的 changeset。合并后,下一次切版本得到的是
`18.0.0-next.0`,而不是 `17.8.0`。改动是 `.changeset/`
下的两个文件,加上门禁脚本里的一行重命名:`scripts/check-adr-0087-registration.mjs` 把已有的排除路径
`.changeset/pre/` 写成 `_EXCLUDED` 命名常量(匹配逻辑不变),这样 `pre.json` 存在后 PM
门禁自检能正确识别它。没有代码或工作流改动。

**为什么改**:按 objectstack-ai#22050 的裁决 B,v18 直接在 `main` 上开发。v18 的破坏性改动要以 `18.0.0-next.N`
预发布版发到 npm 的 `next` 标签上;`latest` 保持 17.7.0,普通安装的用户不受影响,直到 18.0 正式版。

**风险与代价(含回滚)**:合并后,版本 PR 的自动刷新(`release.yml` 的 `version-pr`,每 6 小时一次)会在
"Validate the post-version tree" 一步变红。原因是这一版跨了大版本,会改写 `PROTOCOL_VERSION`
和模板文件,而这条通道对这些文件还没有门禁,按设计会拒绝。它不发布,也不排队任何东西,但在另开卡把这些门禁接上之前,objectstack-ai#21988 不会变成
`18.0.0-next.0`。`cut-rc.yml` 只接受 `rc` 标签,也走不通。objectstack-ai#21988 无论如何都不要合并。回滚办法:删除
`.changeset/pre.json` 和 `.changeset/22080-v18-line-opens.md`
两个文件(在切出任何预发布版之前,这一步没有副作用)。

**席位意见**:

**你要做的**:确认后合并本 PR。合并后需要另开一张卡,让版本通道能切出 `18.0.0-next.0`;这张卡由席位提出。

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…project's own lockfile (objectstack-ai#22217)

Fixes objectstack-ai#22150
Clause-②: no

## What was wrong

Where pnpm is on PATH, `create-objectstack` installs with pnpm, so the
project gets `pnpm-lock.yaml` and no `package-lock.json`. The
`Dockerfile` it writes copied `package*.json` and ran `npm ci`, so
`docker build` stopped at `RUN npm ci` with `EUSAGE` before `os build`
ran. `content/docs/deployment/self-hosting.mdx` showed the same stage.

## What changed

- `packages/create-objectstack/src/templates/blank/Dockerfile`, build
stage only:
- `COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml*
package-lock.json* ./` copies whichever of these exist.
- `RUN` installs from the lockfile that is present: `pnpm-lock.yaml`
gets `corepack pnpm@10 install --frozen-lockfile`, `package-lock.json`
gets `npm ci`. With neither, the build stops with "No lockfile: run pnpm
install or npm install, then commit it."
  - A four-line comment says the same in the file.
- `FROM node:22-slim AS build`, `COPY . .`, `RUN npx os build` and the
whole runtime stage are byte-identical to `main`.
- `content/docs/deployment/self-hosting.mdx`: the Dockerfile block's
build stage now shows the same lines. This is the cross-lane
`domain:devx` edit declared on the claim. The block's image-tag line and
the page's other tag lines (the ones the Version Packages PR objectstack-ai#21988
edits) are untouched.
- New pin:
`packages/create-objectstack/src/dockerfile-build-stage.test.ts`
(below).
- `.changeset/22150-scaffold-dockerfile-lockfile.md`:
`create-objectstack` patch.
- Not touched: `index.ts`, `runtime-image.ts`,
`detect-package-manager.ts`, `packages/cli`, `packages/spec`.

## Route: the stage follows the lockfile; the scaffolder does not
rewrite it (differs from the PM's H2 lean)

The PM leaned toward emitting the build stage after install, from the
detected package manager. Before writing that, I measured two reasons
against it:

1. **The CI docker leg scaffolds one way and installs another.**
`scaffold-e2e.yml` `scaffold-local` scaffolds with `--skip-install` on a
runner whose `pnpm` is a Corepack shim (`setup-pnpm` runs `corepack
enable`). It then runs `npm install` and `docker build`s the scaffolded
Dockerfile. A stage emitted from the detected manager would be
pnpm-shaped there and `COPY` a `pnpm-lock.yaml` that does not exist. The
alternative is to emit only after a successful install. Then every
`--skip-install` pnpm user keeps the `npm ci` stage, and the scaffolder
already tells that user to run `pnpm install`.
2. **The detected manager is not stable there.** Outside the repo, `pnpm
--version` resolves through Corepack's LastKnownGood or the registry's
latest. Today that is pnpm 12, which the probe below shows Corepack 0.34
cannot run, so the same runner can detect either manager.

A stage that reads the lockfile at build time is right on every path: a
pnpm install, an npm install, `--skip-install` followed by either, and a
later switch of package manager. It needs no scaffolder code, and the
docs page can show the one file. The ruling's outcome is unchanged: pnpm
gets Corepack plus `pnpm install --frozen-lockfile`, npm gets `npm ci`,
and the docs show the same file.

## Readings

| | Reading |
|---|---|
| H1 reproduce | Confirmed. I scaffolded with this branch's base
scaffolder, pnpm on PATH, `--skip-skills`: the project has
`pnpm-lock.yaml` and no `package-lock.json`. The old stage, run from a
clean copy, copies only `package.json`. `npm ci` exits 1 with `EUSAGE`.
|
| H2 emission point | Not taken. The reasons are in the section above. |
| H3 what pnpm must copy | `package.json` and `pnpm-lock.yaml` are
enough for `--frozen-lockfile` to pass on pnpm 10.34.6. Without
`pnpm-workspace.yaml` the install still exits 0 but prints `Ignored
build scripts: better-sqlite3@13.0.3, esbuild@0.28.2`, because the
template's build approvals live in that file. So the stage copies it.
The template has no `packageManager` field, so Corepack has nothing to
read; see the version pin below. |
| H3 per manager | `detect-package-manager.ts` answers `pnpm` or `npm`.
A project with `pnpm-lock.yaml` gets the pnpm branch, one with
`package-lock.json` gets `npm ci`. No manager was added. If both
lockfiles exist, `pnpm-lock.yaml` wins, which is the one the template's
`ci.yml` installs from. With no lockfile the build fails loudly; it does
not run an unpinned install. |
| H4 unchanged | The runtime stage and its pin code are byte-identical
to `main`. `runtime-image.test.ts` and `template-consistency.test.ts`
pass unchanged inside the full package run. `RUN npx os build` is
byte-identical, and it built the artifact in both legs below. |
| H5 docs | The block now shows the template's build stage. A new pin
holds the two equal (below). |

**Why pnpm is pinned to a major.** These readings use Node 22.22.0 and
its bundled Corepack 0.34.0, each run with a fresh `COREPACK_HOME`:
- Unpinned (`corepack enable pnpm` then `pnpm install
--frozen-lockfile`): Corepack resolves the registry's latest, pnpm
12.10.1, and dies with `MODULE_NOT_FOUND` on
`pnpm/12.10.1/bin/pnpm.cjs`. pnpm 12 ships `bin/pnpm.mjs` plus a native
wrapper.
- `COREPACK_DEFAULT_TO_LATEST=0`: Corepack falls back to its bundled
pnpm 10.13.1, which is below the template's `engines.pnpm >=10.15`.
- `corepack pnpm@10`: resolves 10.34.6 and works. `corepack
pnpm@11.28.2` also runs.

The stage pins major 10, the same major the template's
`.github/workflows/ci.yml` passes to `pnpm/action-setup`, and a pin
keeps the two equal.

## Measured: the stage run from clean copies (no Docker daemon here)

Each run takes the COPY and RUN text from the committed Dockerfile
itself. It COPYs with BuildKit glob semantics into an empty directory
(no `node_modules`), then runs the RUN line under `/bin/sh`, which is
dash here and in `node:22-slim`. Each run gets a fresh HOME, a fresh
`COREPACK_HOME` and a fresh package store, against the real registry.
After the install it runs `COPY . .` (honouring the template's
`.dockerignore`) and `RUN npx os build`.

| Project (scaffolded for real) | Old stage | New stage |
|---|---|---|
| pnpm-installed (pnpm 10.28.0 wrote the lockfile) | copies
`package.json` only; `npm ci` exit 1 `EUSAGE` | copies `package.json
pnpm-lock.yaml pnpm-workspace.yaml`; pnpm 10.34.6 "Lockfile is up to
date"; install exit 0; `os build` exit 0, `dist/objectstack.json`
written |
| npm-installed (scaffolded with pnpm absent from PATH) | install exit 0
| copies `package-lock.json package.json pnpm-workspace.yaml`; "added
524 packages"; install exit 0; `os build` exit 0 |
| no lockfile | n/a | exit 1, "No lockfile: run pnpm install or npm
install, then commit it." |

## The pin, and its ablation

`dockerfile-build-stage.test.ts` reads the stage from scaffolded output
(`copyDir`) and emulates the COPY into an empty directory. It then runs
the RUN line under `/bin/sh` for three legs:
- a pnpm-installed project,
- an npm-installed project,
- a project with no lockfile.

To stay hermetic:
- The fixture swaps the registry dependencies for one local-directory
dependency. With no dependencies at all, `pnpm install
--frozen-lockfile` answers "Already up to date" even with no lockfile.
With one dependency, a missing lockfile gives `ERR_PNPM_NO_LOCKFILE` /
`EUSAGE` and a drifted one gives `ERR_PNPM_OUTDATED_LOCKFILE` /
`EUSAGE`, offline.
- `corepack` is a stub that runs the repository's own pinned pnpm
(resolved from inside the package), and it refuses when that pnpm is
outside the major the stage asks for. pnpm itself is not on the stage's
PATH.
- Every install runs with `npm_config_offline=true`.

Two more cases in the file: the docs block's build stage equals the
template's, and the Dockerfile's pnpm major equals `ci.yml`'s.

Ablation, run once inside one lock hold on head `cc944d6b`, via
`scripts/ablation-replace.mjs`. It put the old `COPY package*.json ./`
and `RUN npm ci` lines back: anchor 1 to 0, blob `ce699d28904a` to
`04316706900a`, on-disk counts of the new line 0 and the old line 1.
- Green before: `Tests 5 passed (5)`.
- Mutated: `Tests 3 failed | 2 passed (5)`.
- Red: the pnpm leg (`npm error code EUSAGE`, the card's failure), the
docs-equality pin and the pnpm-major pin.
  - Green: the npm leg and the no-lockfile leg.
- Restored: blob back to `ce699d28904a`, which equals HEAD, and `git
diff HEAD` is empty.

An earlier ablation also reddened the npm leg. The only cause was that
the leg pinned `pnpm-workspace.yaml` in the npm copy set. Commit
`cc944d6b` narrowed that assertion to what `npm ci` needs, and the run
above is on that commit.

## Verification

Everything below ran on head `cc944d6b`, the final commit, with a clean
worktree.

- **Package.** The dependency closure was built first: `pnpm --filter
'create-objectstack^...' build` (`@objectstack/spec`), exit 0.
- `pnpm --filter create-objectstack typecheck`: exit 0. `tsc
--listFiles` includes all 17 `src/*.test.ts`, the new one among them.
- `pnpm --filter create-objectstack test`: exit 0, `Test Files 17 passed
(17)`, `Tests 254 passed (254)`. The package has no integration tier.
- **Builds for gates that read built packages.** `turbo run build
--filter='!@objectstack/docs' --concurrency=2`: 72/72 tasks successful.
- **Gate union.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 93 commands for this
diff, the same 93 the dispatch order lists; the order adds `pnpm lint`.
  - All 93, plus `pnpm lint`, exited 0.
- `--ran` over that record: "93 derived, 93 run, 0 NOT-MEASURED, 0
UNRUN".
- Five gates had first answered exit 3 (`PREREQUISITE NOT MET`) before
the builds above. Their exit-0 runs here are real measurements.
- **Verdict lines:**
- `check:nul-bytes`: "OK (scanned 10161 text file(s) … no raw ASCII
control bytes)".
- `check:cross-package-test-inputs`: "OK: 30 package(s) read outside
themselves, all declared". The new test reads `content/docs` through the
existing `$TURBO_ROOT$/content/**` input of `create-objectstack#test`.
- `check:docs-image-tag`: "OK (3/3 enumerated surface(s) read, 9
concrete pin(s) compared …)".
  - `check-empty-changeset`: "1 declaring changeset(s) added".
- **`pnpm lint`** (`eslint . --no-inline-config`): exit 0, no findings,
about 108 s on a shared box.
- **Narrowed lint, also run.** eslint lints 1 of the 4 changed paths,
the new `.ts` test. The `.mdx`, the changeset and the `Dockerfile` each
answer "File ignored because no matching configuration was supplied".
Result: 0 errors, 0 warnings.
- `eslint.config.mjs` never enables type-aware linting, so this change
cannot move a verdict on an untouched file.
- **Left to CI:** the path-scheduled `scaffold-e2e.yml` docker build,
which exercises the `npm ci` branch.

## Acceptance notes

- CI's only docker build of a scaffold (`scaffold-e2e.yml`
`scaffold-local`) installs with `npm install`, so it exercises the `npm
ci` branch alone. The pnpm branch is proven by the measurement above and
the hermetic pin, not by a CI `docker build`. This is a coverage note,
not filed; `scaffold-e2e.yml` is outside this card's file surface.
- The Corepack readings are from Node 22.22.0 / Corepack 0.34.0 in this
container. `node:22-slim` tracks the newest 22.x, so a later Corepack
may run pnpm 12; the major pin keeps the image on the same pnpm line as
CI either way.
- A project scaffolded before this release keeps its own Dockerfile. The
changeset says how to update one.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…l, organization, ownership, and every guest key's fate (objectstack-ai#22239)

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

## What this PR is

Round 3 of objectstack-ai#22146: one new decision record,
`docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md`,
**Status: Proposed**. It transcribes the maintainer's ruling
[`6054113537`](objectstack-ai#22146 (comment))
(batch objectstack-ai#290 item 1, 「22146 同意」: A on all eight questions and G2 on the
gap, with the Q4 and Q2 clarifications) into nine decisions. Each one
states its contract and its enforcement point. The revision round
applies the ruling supplement
[`6056614963`](objectstack-ai#22146 (comment))
(maintainer 「D1 A′ D2b R」): D1 is revised to A′, and D2b is closed as R.

- **No code changes.** Nothing changes in `packages/**`,
`content/docs/**` or `skills/**`. The ruling places every code change
after acceptance, as execution cards E1 to E4 (the record's *Execution
plan*).
- **Tier H.** The diff touches `docs/adr/**`, so this PR stays draft and
lands only by the maintainer's hand.
- **The card stays open** for the execution cards, so line 1 is `Part
of`.

## What the record says, one line per decision

- **D1, identity and ownership (A′).** The guest is a principal and
never owns a record; a forged owner is refused; the audit names the
guest as the actor. Who owns a guest-written row is the business
scenario's own metadata: the door's declaration, the object's hooks,
record-change flows and assignment rules. The platform stamps nothing
and declares no default owner. Empty state: the owner stays unset, as
the form doors do today, with the existing authoring advisory and no
publish refusal. The enforcer is the guest branch of the owner-anchor
stamp in `SecurityPlugin` (card E3).
- **D2, the closed list of doors.** Exactly five door classes serve an
unauthenticated request:
  - the public form doors;
  - share links;
  - the public book and doc reads;
  - `authRequired: false` endpoints of type `object_operation`;
  - `authRequired: false` endpoints of type `flow`.

Everything else answers 401, decided once per domain by
`shouldDenyAnonymous`. The control-plane allowlist, the signed
inbound-hook channel and MCP are credentialed or infrastructure, so they
are outside the guest model.
- **D2b, anonymous door × elevated flow (R).** Publish refuses an
`authRequired: false` flow endpoint whose target declares `runAs:
'system'`, in both directions, with a prescription: an authenticated
endpoint, the signed inbound-hook channel, a public form, and `runAs:
'automation'` once ADR-0073 M2 lands. No door triggers an elevated flow
directly. Card E2 implements it with a registered ADR-0087 semantic
entry. Record-change flows fired by a guest-written row stay recorded
and undecided.
- **D3, the grants channel (ADR-0090 D9, enforced).** The `guest`
anchor's bindings resolve for the guest, read through the one binding
reader every position uses. An empty set denies all. There is no second
channel: not the baseline, not `everyone`, and not the position-name
fold. The binding tier is unchanged. The row scope runs on one pipeline,
and E1 pins it per sharing model.
- **D4, organization.** The guest's organization is resolved only when
the deployment has a unique organization, using the same predicate as
ADR-0131 D9. On a multi-organization deployment the request is refused
until D5 exists. The question is asked only where the organization is
needed.
- **Clarification (1), carried into the record:** the form doors'
declared default-organization binding stays as it is. Nothing that
serves today starts refusing.
- **D5, site binding.** The record gives the shape only: match,
organization, guest grants and allowed doors. ⛔ It declares no metadata
type and reserves no key. The binding is built when a named deployment
needs it.
- **D6 and D7.** ADR-0106 D7, explain's `EXTERNAL` floor and ADR-0121 D6
are unchanged. The webhook signature vocabulary goes to a follow-up
card, F1. That card is named in the record and not filed.
- **D8, guest keys.** Every declared guest key gets a fate and an
ADR-0087 disposition.
- `sys_record_share`'s `guest` recipient is to be **removed** on its own
card, E4. The basis is ADR-0090 D11 and the already-registered
`sharing-rule-recipient-reconcile` entry.
- The form doors' `guest_portal` set name was not on the card's list. It
is recorded too, with the fate keep.
- **D9.** The record now holds the maintainer's ruling of 2026-08-08
(Option A, `f586f1a89`), which until now lived only in the module doc of
`assemble-execution-context.ts`.

## What the revision changed (supplement
[`6056614963`](objectstack-ai#22146 (comment)))

- **D1 → A′.** Points 3, 5 and 6 of the draft (the organization-level
default owner, its cardinality, the empty-state refusal) are replaced:
ownership is the scenario's own metadata, and the empty state is the
owner left unset. Points 1, 2 and 4 are kept.
- **D5.** The default-owner element is removed from the shape.
- **D2b → R**, with its enforcement text. The four-axis table stays as
the reasoning. M and the first-drafted default owner move to
*Alternatives considered*.
- **Execution plan.** E2 carries R and its registered ADR-0087 semantic
entry. E3 shrinks to the stamp's guest branch (never the guest, never
the system principal, a forged owner refused, the owner unset unless the
scenario sets it): no new key and no disposition.
- **Elsewhere.** The Consequences paragraph on owner-assigning hooks is
withdrawn. *What the ruling did not settle* loses D2b and D1's empty
state. Acceptance criterion 3 (D2b chosen) is met. The supplement is
added to *Decided by*.
- **Consistency edits the ruled changes forced:** the Status line, the
Consumers line (the spec change is now D2b's refusal, not a D1 key), D4
point 3 (no owner stamp left to need the organization), D2's class 5
row, follow-up F3 (M2 now only extends R's prescription), and the
References.

## What stays open

- **The indirect path:** record-change flows fired by a guest-written
row are recorded and not decided.
- **The spelling of D5's binding** belongs to the card that builds it.
- **The guest's row scope** is stated as a contract; E1 pins its
measured outcome.

## How the number was chosen: 0138

- `origin/main` at `73a0a6bf1d`, after this round's merge, tops out at
0137, and 0136 is absent. Still no open PR adds 0136 or 0138; objectstack-ai#22198,
the one ADR PR at the first count, has landed on ADR-0048.
- **I checked every open PR's file list.** That is all 20 open PRs,
paged to the end for objectstack-ai#22142 (104 files) and objectstack-ai#21988 (229 files). Only
objectstack-ai#22198 touches `docs/adr/`, and it touches
`0048-cross-package-metadata-collision.md`. No open PR adds 0136 or
0138.
- **0136 is not reused.** It was handed to a decision once: unmerged PR
objectstack-ai#18480 added `0136-declared-journeys-as-priority-anchor.md`, and objectstack-ai#18985
renumbered its own record from 0136 to 0137 because of it.
`scripts/check-adr-anchors.mjs` computes the next free number as the
highest number plus one, which gives 0138.

## Back-pointers: none in this PR, by house practice

- **Where the lines go.** The house form for an amended record is a
status-line continuation. ADR-0042, ADR-0046 and ADR-0131 carry lines of
the form "· **Amended** (date, ADR-NNNN Dk) — …". These lines are
written when the amendment is in force. No record in the registry
carries such a line pointing at a Proposed record. So the exact lines
for **ADR-0090** (D9) and **ADR-0056** (D2) are written into the
record's *Acceptance criteria*, to land with the accepting change.
- **Which ADRs get no line.** ADR-0106 D7, ADR-0121 D6 and ADR-0096
D5/E1 are left unchanged by this record, so they get none. **ADR-0135**
gets none either: it mirrors cloud ADR-0024 and adds no clause of its
own, and this record amends none of its decisions.

## Verification at `2d69b2b714` (the revision, on a merge of main
`73a0a6bf1d`)

I ran every command in the claim-time gate list at the revision head.
Each exit code was captured before any pipe.

| Command | Exit |
|:--|:--|
| `node scripts/check-adr-links.mjs` (and `--self-test`) | 0, 0 |
| `node scripts/check-adr-symbol-anchors.mjs` (and `--self-test`) | 0, 0
|
| `node scripts/check-ci-filter-parity.mjs` | 0 |
| `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`) |
0, 0 |
| `node scripts/check-comment-mask-corpus.mjs` | 0 |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0 (see note) |
| `pnpm check:adr-anchors` | 0 |
| `pnpm check:cross-package-test-inputs` | 0 |
| `pnpm check:doc-authoring` | 0 |
| `pnpm check:driver-memory-census` | 0 |
| `pnpm check:gitlink-declared` | 0 |
| `pnpm check:nul-bytes` | 0 |
| `pnpm check:pm-governed-merges` | 0 |
| `pnpm check:pm-prior-rulings` | 0 |
| `pnpm check:refd-timer-probe` | 0 |
| `pnpm check:watch-hint-literal` | 0 |

- **`check-adr-symbol-anchors`.** It reports "2225 anchors across 141
records resolve". Positive control: the record count is 141, which is
the 140 at base plus this record. No anchor carries a line number.
- **`check:pm-prior-rulings`.** The self-test passes 155 cases. The
tool's `--card 22146` read returns 16 ADR decision hits and 1 ruling on
the thread (`6054113537`).
- **`check:doc-formula-expressions`.** In the first round its first run
exited 3 (PREREQUISITE NOT MET: the closure was not built), which
measured nothing. I rebuilt the `@objectstack/formula` and
`@objectstack/lint` closure under the verify lock after this round's
merge (VERDICT command-exit 0), and the gate exited 0.
- **Re-derivation.** `dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `2d69b2b714` gives the same 19 families.
The `--ran` reconciliation shows 19 run and 0 NOT-MEASURED, a zero
derived from the recorded exit codes. The change set is one file,
+640/−0, against merge base `73a0a6bf1`.

## Not measured, and named

- **The G2 sweep (the booted per-door-class anonymous sweep).** I did
not run it, by the ruling: it is an acceptance precondition, to be run
in an environment that permits the probe. This round wrote no driver and
no probe.
- **The platform documentation links.** I could not re-fetch them,
because the container's proxy answers 403 to CONNECT for those hosts.
The record carries the URLs that the measurement round recorded.
- **The cloud repository's anonymous surfaces.** This round did not read
them.

## Changeset

I read the Check Changeset job in `pr-automation.yml`. It has two
exemptions: the `skip-changeset` label (read live, twice) and the
Changesets release PR. Its failure text prescribes: "if it releases
nothing …, apply the 'skip-changeset' label". This PR adds no
`.changeset/*.md` and changes none, and `docs/adr/**` ships in no
package's `files[]`. So the label is `skip-changeset`.

## Acceptance notes

- **A doc comment that describes the old form-door semantics.** The
comment above `RestServer.registerFormEndpoints` in
`packages/rest/src/rest-server.ts` still says that security is delegated
to a `guest_portal` set carried on the context, and that the middleware
falls open when none is registered. What admits a form submission today
is the `publicFormGrant` branch in `SecurityPlugin`.
- This is comment drift only, and no reach was measured. It is noted,
not filed.
  - Carrier: none. Card E2, once cut, edits that domain.

## 维护者速读(草稿)

- **改了什么:** 按你「D1 A′ D2b R」的补充裁决修订 ADR-0138 草稿,其余内容不动。
- D1:访客永不拥有记录、伪造的 owner 一律拒绝、操作留痕记在访客名下,这三条保留。"本组织声明一个默认
owner"整条删掉:访客写进来的记录归谁,由各业务场景在元数据里自己定(入口声明、对象钩子、记录触发流程、分配规则),平台不打任何默认值。没人设置时
owner 就空着,跟今天公开表单一样;表单的作者视图照常提示,发布不拒绝。
- D2b:匿名入口不能触发以系统身份运行的流程。发布时直接拒绝,端点和流程两头都检查,并给出替代办法(带凭据的端点、签名 webhook
通道、公开表单,以及 M2 落地后的 automation)。
  - 站点绑定的形状里去掉"默认 owner";执行卡 E3 缩成只校验访客分支,不新增任何键。
- **为什么改:** 你指出归属是业务场景的事,平台级默认值在多数部署里是错的,还会抢在对象自己的分配逻辑前面。D2b 选 R 之后,AI
照着 `runAs` 的说明写出"匿名入口 + 系统身份流程"时,发布就会被拦下。
- **风险与代价(含回滚):**
  - 本 PR 仍只改一个文档文件,合并后运行时行为不变;回滚就是删掉这个文件。
  - D2b 的拒绝会让"匿名端点指向系统身份流程"这种写法从此发布不了。仓内没有这样的声明;仓外的部署本轮没有测。
  - 接受前提还剩一项:在允许探测的环境里做一次启动后的逐类匿名入口实测(G2)。
- **席位意见:**
- **你要做的:** 修订版就绪后,批准这份 ADR(Tier H,点 Approve 或亲手合并)。

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…D_TOO_LARGE (objectstack-ai#22359)

Fixes objectstack-ai#22314
Clause-②: yes (widening: a new owner provenance row in the published
error-code ledger)

The upload size refusal that PR objectstack-ai#22311 added (card objectstack-ai#22283) now answers
`413` with the registered code `PAYLOAD_TOO_LARGE` at all four
`service-storage` upload doors, in place of the status-derived
`VALIDATION_ERROR`. The error-code ledger lists `PAYLOAD_TOO_LARGE`
under `@objectstack/service-storage`, beside `@objectstack/rest`. This
is ruling B from the `domain:services` seat in PR objectstack-ai#22311's ACCEPT,
carried on the card.

It is not a new code. The status (`413`), the message, the four doors,
and what is stored or refused are all unchanged.

## What changed

- **`packages/spec/src/api/error-code-ledger.zod.ts`:** one provenance
row, `'PAYLOAD_TOO_LARGE'`, under the `@objectstack/service-storage`
owner key.
- It follows the shape the ledger's other multi-emitter rows use
(`PACKAGE_DELETE_PARTIAL` and `WRITABLE_PACKAGE_REQUIRED` under
`@objectstack/runtime`): a comment naming the wire path and the other
emitter, then "Provenance, not identity".
- `ErrorCode`, `RegisteredErrorCode` and `REGISTERED_ERROR_CODES` are
unchanged, because the union dedupes.
- Every sentence of the docblock at `standardSynonymOf` stays true,
including "a phrase for a status the catalog does not name
(`PAYLOAD_TOO_LARGE`, 413) is NOT a synonym".
- **`packages/services/service-storage/src/storage-routes.ts`:** the one
constant changes from `UPLOAD_TOO_LARGE_CODE: StandardErrorCode =
'VALIDATION_ERROR'` to `UPLOAD_TOO_LARGE_CODE: RegisteredErrorCode =
'PAYLOAD_TOO_LARGE'`. Its docblock is rewritten to state the new choice
and the reason for it.
- **Tests:** they now expect `PAYLOAD_TOO_LARGE` and still assert both
`code` and `status` at every door.
- `storage-limits.test.ts`: the shared `expectTooLarge` helper asserts
`413` + `PAYLOAD_TOO_LARGE`, and every door case calls it:
    - presigned;
    - chunked start;
    - local raw PUT, by bytes read and by `content-length`;
    - chunk PUT, by bytes read (three cases) and by `content-length`.
- `error-envelope.conformance.test.ts`: the case for the over-limit
upload.
- `packages/qa/dogfood/test/storage-upload-limits.dogfood.test.ts`:
three assertions over a real boot.
- **Text that describes this refusal:**
  - the `service-storage` README limits row;
- `content/docs/api/error-catalog.mdx`: the `VALIDATION_ERROR` 413
callout moves to a new `PAYLOAD_TOO_LARGE` entry, the quick-reference
413 row changes, and the wire-code count goes from 51 to 52;
- `content/docs/protocol/kernel/error-handling.mdx`: the 413 row and the
`VALIDATION_ERROR` entry.
- **Changesets:**
- `.changeset/22314-storage-payload-too-large-provenance.md` is new:
`@objectstack/spec` `minor`, carrying the Clause-② line above.
- The pending `.changeset/22283-storage-limits-honoured.md` gets a
one-sentence correction (see the next section).

## Pending release note corrected: confirmation requested (`Check
Changeset` is red by design)

`.changeset/22283-storage-limits-honoured.md` is objectstack-ai#22283's pending,
unreleased note (`@objectstack/service-storage` `minor`). This PR
changes one sentence in it:

- **Before:** "is refused with `413` and the standard code
`VALIDATION_ERROR`, in the usual error envelope,"
- **After:** "is refused with `413` and the registered code
`PAYLOAD_TOO_LARGE`, in the usual error envelope,"

Nothing else in that note changes. With this edit, the release that
first ships the refusal names one code.

`check-empty-changeset` refuses a changed foreign changeset by design.
This PR is the DELIBERATE CORRECTION class: the gate's own remedy is to
say so on the PR and have it confirmed. Do not restore the old sentence,
which would put the false code back. Do not apply `skip-changeset`.
Please confirm the correction here.

The `service-storage` code change rides on that note. This PR's own
changeset declares `@objectstack/spec` only. No `check-changeset*` gate
asked for `@objectstack/service-storage` in an added changeset:

- `check-changeset-no-major` exits 0.
- `check-adr-0087-registration` exits 0.
- `Require a changeset` counts the added `22314` changeset.

## Release coupling, checked before building

- npm `@objectstack/service-storage`:
  - `latest` is `17.7.0`, published 2026-10-06T12:09Z.
- Its tarball has 0 dist files matching `limitsSnapshot` or
`UPLOAD_TOO_LARGE_CODE`. The control, `upload/presigned`, matches in 4
dist files.
- PR objectstack-ai#22311 merged on 2026-10-08T16:35Z, and its changeset is still
pending on `main`.
- The Version Packages PR objectstack-ai#21988 is open.

So no published version ships the `VALIDATION_ERROR` refusal.

## Measurements (the dispatch's hypotheses)

- **H1 holds.**
- `PAYLOAD_TOO_LARGE` was listed under `@objectstack/rest` only, which
answers it for both import routes' row ceilings (`import-prepare.ts`,
5,000 and 50,000 rows).
  - `HTTP_REASON_PHRASE_STATUS` maps it to 413.
  - The new row is one entry in the existing multi-emitter shape.
- **H2 holds, with the typed path that already exists.**
- `sendError` (`@objectstack/types`) takes `ErrorCode` =
`StandardErrorCode | RegisteredErrorCode`.
- `RegisteredErrorCode` is the type `service-analytics` already uses for
ledger-code constants (`read-scope-refusal.ts`, `dataset-refusal.ts`).
  - No shared type was widened, and no cast was added.
  - Reverse check: a misspelled code fails to compile (see Tests).
- **H3: every test that pins this refusal now expects
`PAYLOAD_TOO_LARGE`.** Sweep hits outside the tests, with line numbers
at the base `41d0d4038`:
- **Edited** (each describes this refusal): the service-storage README
`:113`, `error-catalog.mdx` `:67` and `:920`, `error-handling.mdx`
`:217`, `:405` and `:421`, the dogfood test `:68`, `:91` and `:102`, and
the pending changeset `:15`.
  - **Not edited** (none describes this refusal):
- `storage-adapter-list.conformance.test.ts` `:436` and `:447`
(`VALIDATION_ERROR` for the list `limit`/`cursor`);
- `import-mappings.mdx` `:224` and `import-job-integration.test.ts`
`:202` (rest's import 413, already `PAYLOAD_TOO_LARGE`);
- `references/api/contract.mdx` and
`references/api/error-code-ledger.mdx` (generated union listings, which
this change does not move);
    - `error-code-ledger.test.ts` `:87` (the synonym pin, still true).
- The sibling `objectui` checkout has no branch on this refusal's code.
- **H4 holds.** `check:error-code-provenance` is the gate that makes the
row required:
- **Before**, at the base `41d0d4038`: exit 0, with 334 stamp sites (315
listed, 19 waived).
- **Ablation**, at commit `b5f9d332f`: the storage constant switched and
the ledger row removed. The removal went through
`scripts/ablation-replace.mjs`, which showed the anchor count go from 1
to 0 and the blob change, then restored the file to equal `HEAD`.
Result: exit 1, `@objectstack/service-storage stamps 'PAYLOAD_TOO_LARGE'
(constdef) at packages/services/service-storage/src/storage-routes.ts:93
— not listed under its own owner key`.
- **After**, at `5b5b812ce`: exit 0, with 335 stamp sites (316 listed,
19 waived).
- **Generated artifacts:** none moves. After `pnpm --filter
@objectstack/spec build`, `check:generated` reports all 15 generated
artifacts up to date, and `git status` is clean.
- **Doc ⇄ runtime status reconciliation:**
`check:error-status-conformance` now reconciles `PAYLOAD_TOO_LARGE`. Its
scope line reads "4 ledger code(s) a doc page publishes a status for
(INVALID_REQUEST, PAYLOAD_TOO_LARGE, UNIQUE_VIOLATION,
VALIDATION_FAILED)", and the derived producer is `413
storage-routes.ts:315`. `VALIDATION_ERROR` keeps no 413 producer and no
413 doc claim. Exit 0.

## Tests

At `5b5b812ce`, each run under the shared verify lock:

- `pnpm --filter @objectstack/service-storage exec vitest run
--maxWorkers=2`: 45 files and 749 tests passed.
- The targeted spec run (`src/api/` plus every spec test that reads the
ledger) with `vitest run --project local --maxWorkers=2`: 59 files and
1724 tests passed.
- `pnpm --filter @objectstack/service-storage run typecheck`: exit 0.
- `pnpm --filter @objectstack/spec run typecheck`: exit 0.

At `b5f9d332f`. The only change from there to `5b5b812ce` is comment and
changeset wording in three files (5 lines changed):

- `pnpm --filter @objectstack/spec test`: 626 files passed, with 18740
tests passed and 1 todo.
- The dogfood file over a real boot,
`storage-upload-limits.dogfood.test.ts`: 1 file and 2 tests passed. The
`service-storage` dist it loaded had 1 `PAYLOAD_TOO_LARGE` match.
- Reverse type check: the constant was set to
`'PAYLOAD_TOO_LARGE_ABLATED'` through `ablation-replace.mjs`, and `tsc
--noEmit` failed with `TS2820 ... Did you mean '"PAYLOAD_TOO_LARGE"'?`.
The file was then restored to equal `HEAD`, with an empty `git diff
HEAD`.

## Gates

At `5b5b812ce`:

- `dispatch-gates --commands` derived 115 commands. All 115 ran:
  - 114 exited 0.
- `node scripts/check-empty-changeset.mjs --base origin/main` exited 1,
by design (the DELIBERATE CORRECTION above).
- The `--ran` verdict: `✓ dispatch-gates --ran: 115 derived famil(ies)
accounted for — 115 run, 0 NOT-MEASURED`.
- Also run: `check:generated`, plus the roster gates
`check:authz-resolver` and `check:filter-alias-parity`. All exited 0.

ESLint was narrowed to the touched files, with this proof:

1. **What eslint checks:** `eslint.config.mjs` lints
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`. The touched TypeScript files are
the five listed in the next point. The `.md` and `.mdx` files match no
`files` glob.
2. **What ran:** `pnpm exec eslint --no-inline-config --format json`
over those 5 files at `5b5b812ce` reported 5 files, 0 errors and 0
warnings.
3. **Why other files are unaffected:** type-aware linting is not enabled
(no `parserOptions.project` and no typed rules; the config says so at
`:327`). The config's only file reads are two baselines this diff does
not touch. So this diff cannot change the lint result of any untouched
file.

## Acceptance notes

- **Landing beyond the claim's file surface:** two hand-written docs
pages, the `service-storage` README, and the dogfood test. The H3 sweep
named each one as describing this refusal, and each is edited only where
it does.
- The catalog's new `PAYLOAD_TOO_LARGE` entry also names
`@objectstack/rest`'s import row ceilings. The entry catalogs the code,
not one door.
- Documenting a status for the code makes it a reconciled wire code.
That is what requires the heading and the count of 52, by
`error-catalog-docs.test.ts`.
- `content/docs/references/**` is unchanged and regenerates
byte-identically.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 9, 2026
github-merge-queue Bot pushed a commit that referenced this pull request Oct 10, 2026
…t scaffold with the workspace protocol (nightly tiers) (#22699)

Fixes #22622
Clause-②: no
Two nightly-tier CLI test files change and nothing else: no accepted
input, key, export or error code moves, and nothing publishes.

Two independent reds on the nightly tiers, both in `domain:cli`. **Both
are fixed inside the nightly tests**, following the seat's review
6099406666 on #22622. Nothing else changes:

- the family-roster check;
- the noise budget and every assertion in that file;
- the `create-objectstack` template, the version-time stamp
(`scripts/sync-template-versions.mjs`) and their tests, which are
byte-for-byte as `main` has them.

## (a) Both reds, reproduced before any fix

Base `9646991283`, `OS_TEST_TIERS=nightly`, `--project integration`,
through the verify lock: `Tests 2 failed | 51 passed (53)`.

- `json-stdout-purity.e2e.test.ts`, `is exactly the set listed here`:
`expected [ 'meta resync', …(17) ] to deeply equal [ 'meta resync',
…(15) ]`, with `+ "migrate organization-ownership"` and `+ "migrate
security-catalog-overlays"`. `migrate organization-ownership` came from
PR #22643, after the nightly that filed the card. The members were taken
from `discoverFamily()`'s output.
- `serve-boot-diagnostics-noise-budget.e2e.test.ts`, `highlights the
dead button once…`: `expected 0 to be greater than 0` at `:176`. The
boot printed `✗ package 'com.example.support-desk' targets protocol ^17
(engines.protocol) but this runtime is protocol 18.0.0. This is a
major-version break. Run: objectstack migrate meta --from 17`. That is a
refusal, so no *Boot diagnostics* block is printed at all.

After, on `3d63ed7db5`: `Test Files 2 passed (2)`, `Tests 59 passed
(59)`. That is 53, plus 3 cases for each of the two new members.

## Failure 1: the two new `--json` members are driven

`FAMILY` gains both members, each in its bare form. The CLI was first
run against the purity fixture to see each one's face:

- `migrate organization-ownership` shows its refusal face. Over a
database with none of the platform tables it prints
`{"error":"plan_refused","reason":"not-an-objectstack-database",…}` and
exits 1. The JSON is emitted after `stack.shutdown()`, and no plan file
is written.
- `migrate security-catalog-overlays` shows its read-only preview:
`listed: 0`, exit 0. There is no `sys_metadata` hydration and nothing is
deleted.

Both print all three boot diagnostics on stderr. The discovery and the
`toEqual` roster check are untouched.

## Failure 2: the noise-budget e2e aligns the pair it boots

### (b) What the version-time stamp writes

Measured read-only at `9646991283`:

- `changeset status --output` (`pre.json` is
`{"mode":"pre","tag":"next"}`) gives `@objectstack/spec major 17.7.0 ->
18.0.0-next.0` and `create-objectstack major 17.7.0 -> 18.0.0-next.0`
(the `fixed` group).
- `loadScaffolderVersion()` on `18.0.0-next.0` gives
`{"version":"18.0.0-next.0","major":"18","range":"^18.0.0"}`. So the
version pass stamps `engines: { protocol: '^18' }` into the released
template.

**Triage's "every project that v18's `create-objectstack` scaffolds
would boot with the protocol-gap warning" is false.** No released v18
scaffold carries the gap. PR #22215's changeset says the same ("the
template keeps `'^17'` until the version pass stamps it").

The red is `main`'s pre-mode window: packages at `17.7.0`,
`PROTOCOL_VERSION` at `18.0.0`. Only an in-repo pairing sees it: a
published-line scaffold booted on the workspace runtime with nothing
installed.

### Round 1 changed the product side, and CI falsified that

Round 1 (`bff640d98`) stamped the template's `engines.protocol` from
`PROTOCOL_VERSION`'s major. On that head, `Scaffold with repo dist`
(`scaffold-e2e.yml`, job 1) went red: "package 'com.example.e2e-app'
targets protocol ^18 (engines.protocol) but this runtime is protocol
17.0.0".

That gate guards the path a user takes. It scaffolds with the repo-built
scaffolder and installs `@objectstack/*` from the registry (`^17.0.0`,
so protocol 17). On that path a template's range must match the protocol
of the packages it installs, which is the package major. The sync script
and the `template-consistency` ratchet already enforce exactly that on
`main`.

Commit `60fa01c7a7` removes round 1's product-side change:

- `templates/blank/objectstack.config.ts`,
`template-consistency.test.ts`, `template-version-stamps.test.ts` and
`scripts/sync-template-versions.mjs` are back at their `main` blobs
(`6308f29e`, `d32c819c`, `48ba146e`, `f7704db8`, all equal at base and
`origin/main`);
- the `create-objectstack` changeset is deleted.

### This round: the setup aligns the pair it boots

This follows the seat's verification-strategy ruling in 6099406666.

After scaffolding, and before the fixture files are written,
`alignProtocolRange()` rewrites the scaffold's `engines.protocol` to
`'^' + PROTOCOL_MAJOR`. `PROTOCOL_MAJOR` is read from
`@objectstack/spec/kernel`, the workspace spec this file boots, the same
import other CLI e2e files use.

- **Only the major's digits are rewritten.** When the majors agree, the
scaffold is left byte-for-byte as written, and the helper does not write
the file at all.
- Measured on the template: at `'^17'` (the window) the hash moves
`684e38ed0d1a` → `5d6e6468255e`, and only the `engines` line differs.
- At `'^18'` (majors agree) the hash stays `5d6e6468255e` →
`5d6e6468255e`, byte-identical.
- **A missing stamp throws in `beforeAll`.** A template that stops
declaring the key fails this file loudly instead of skipping the
alignment.
- **Why a direct rewrite of the one key, and not `objectstack migrate
meta --from 17 --write`.** The `--write` command is the step the refusal
prescribes, and it was measured first. On a scaffold under
`packages/cli/node_modules/` it exited 0 with `not written
[outside-project]` and left the config unchanged. The codemod refuses
any file whose real path has a `node_modules` segment
(`authored-source-codemod.ts`, the `outside-project` refusal). This
project has to live under this package's `node_modules` so that its
imports resolve to workspace copies.
- **What stays the same:** the noise budget, all three assertions, and
the ticket object and actions. Once the version pass lands, the window
closes and the alignment becomes a no-op.

## Ablations

Both ran through `scripts/ablation-replace.mjs` in wrap mode on
committed trees, under the verify lock. Each restore was proven by blob
equal to HEAD and an empty `git diff HEAD`. Neither subject resolves
through `dist/`: the FAMILY and the setup live in the test files.

| Mutation | Anchor | Result |
|---|---|---|
| `alignProtocolRange(join(dir, 'objectstack.config.ts'));` deleted from
the setup | x1 → x0, blob `058fbecec7dc` → `bfedb0522aa5` | noise-budget
red with the original refusal: `✗ package 'com.example.support-desk'
targets protocol ^17 … this runtime is protocol 18.0.0 …`, `expected 0
to be greater than 0`, 1 failed and 2 passed. After the restore to blob
`058fbecec7dc`: 3 passed. |
| `'migrate security-catalog-overlays': [],` deleted from `FAMILY`
(round 1, same blob as now) | x1 → x0, blob `698034ba9bcb` →
`5dfe8780897a` | roster pin red: `expected [ 'meta resync', …(17) ] to
deeply equal [ 'meta resync', …(16) ]`, `+ "migrate
security-catalog-overlays"` |

## Tests (on `3d63ed7db5`)

- **Nightly tier** (`OS_TEST_TIERS=nightly`, `--project integration`,
verify lock): the two card files, 59 passed.
- **`create-objectstack`:** suite 17 files and 254 tests passed. `pnpm
check:template-version-sync` is green with 40 assertions, the same count
as on `main`. Both are unchanged, because this diff does not touch the
package or the script.
- **`@objectstack/cli`:**
  - unit layer: 279 files and 4126 tests passed;
- `typecheck` (`tsc --noEmit` plus `check:test-typecheck`) is OK, and
`tsconfig.test.json`'s `--listFiles` includes both edited files.
- **Changeset:** `node scripts/check-empty-changeset.mjs --base
origin/main` exits 0. It reports no empty-frontmatter changeset added
and no merge-base changeset modified or deleted.
- **Gates:** `dispatch-gates --commands` (no paths) derived 50 commands
from the 2-file change set. `--ran` reports `50 derived, 50 run, 0
NOT-MEASURED, 0 UNRUN`, a derived zero: every command recorded `exit 0`.
`check:type-check-debt` and `check:dual-build-cjs-loads` ran last, after
every locked suite.
- `dispatch-gates` noted that the tree is behind `origin/main`: 6
derived-from files changed there (`ci.yml`,
`check-shard-attestation.mjs` and the fleet-write relay scripts).
- No conflict exists, so per the round's direction there was no merge.
CI judges the merge ref.
- **Lint, proven narrowing:**
1. Population is read from ESLint's own config: both changed files
return results with no "File ignored".
2. File count is from `--format json`: 2 results, 0 errors, 0 warnings.
3. Invariance: `eslint.config.mjs` enables no type-aware linting, and
this diff touches neither the config nor a baseline it reads.

## Changeset

Nothing publishes: `@objectstack/cli` ships `dist`, `README.md` and
`CHANGELOG.md`, and this diff is two `test/` files. By the repo's rule
this PR takes the `skip-changeset` label. An empty-frontmatter changeset
would be the risky path that `check:empty-changeset` refuses. The seat
applies the label.

## Acceptance notes

- `objectstack migrate meta --write` on a project whose path has a
`node_modules` segment says `the literal is in …/objectstack.config.ts,
outside the project at …`, although the file is inside the project. The
real cause is the `node_modules` segment rule. Only a test layout
reaches it, so this is a wording polish, not a finding. No carrier.
- Version Packages PR #21988 was last refreshed on 2026-10-08T06:19Z,
before pre mode was entered (`a87d8be29`). This is release-lane state,
recorded as a reading, ⛔ not acted on.

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

---------

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

This branch has not been deployed

No deployments
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.

0 participants