Skip to content

Release version 0.1.x for ObjectStack Protocol packages - #43

Merged
hotlong merged 6 commits into
mainfrom
copilot/release-version-update
Jan 20, 2026
Merged

hotlong merged 6 commits into
mainfrom
copilot/release-version-update

Conversation

Copilot AI commented Jan 20, 2026 •

Copy link
Copy Markdown
Contributor
  • Fix failing driver.test.ts by adding missing required fields (connect, disconnect, checkHealth, execute, count, beginTransaction, commit, rollback)
  • Update all CHANGELOG files to accurately describe patch release changes instead of "Initial release"
  • All 434 tests passing
Original prompt

发一个版本


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Jan 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 20, 2026 9:50pm

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Release new version of the application Release version 0.1.x for ObjectStack Protocol packages Jan 20, 2026
Copilot AI requested a review from hotlong January 20, 2026 20:19
@hotlong
hotlong marked this pull request as ready for review January 20, 2026 20:21
Copilot AI review requested due to automatic review settings January 20, 2026 20:21
@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file size/m labels Jan 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prepares a coordinated patch release across all ObjectStack packages, bumping versions from 0.1.0 to 0.1.1 for most packages, and from 0.1.1 to 0.1.2 for @objectstack/spec. The release includes changelog updates and appropriate dependency version updates.

Changes:

  • Version bumps across 7 core packages and 4 example packages
  • CHANGELOG.md files added/updated for all packages documenting the release
  • Peer dependency update in @objectstack/plugin-hono-server to match new runtime version

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/spec/package.json Bumps version from 0.1.1 to 0.1.2
packages/spec/CHANGELOG.md Adds changelog entry for 0.1.2 release
packages/types/package.json Bumps version from 0.1.0 to 0.1.1
packages/types/CHANGELOG.md Adds initial changelog with 0.1.1 release notes
packages/runtime/package.json Bumps version from 0.1.0 to 0.1.1
packages/runtime/CHANGELOG.md Adds initial changelog with 0.1.1 release notes and dependency updates
packages/plugin-hono-server/package.json Bumps version and updates runtime peer dependency to ^0.1.1
packages/plugin-hono-server/CHANGELOG.md Adds initial changelog with 0.1.1 release notes and dependency updates
packages/objectql/package.json Bumps version from 0.1.0 to 0.1.1
packages/objectql/CHANGELOG.md Adds initial changelog with 0.1.1 release notes and dependency updates
packages/driver-memory/package.json Bumps version from 0.1.0 to 0.1.1
packages/driver-memory/CHANGELOG.md Adds initial changelog with 0.1.1 release notes and dependency updates
packages/client/package.json Bumps version from 0.1.0 to 0.1.1
packages/client/CHANGELOG.md Adds initial changelog with 0.1.1 release notes and dependency updates
examples/todo/package.json Bumps version from 1.0.0 to 1.0.1
examples/todo/CHANGELOG.md Adds changelog noting dependency updates
examples/plugin-bi/package.json Bumps version from 1.0.0 to 1.0.1
examples/plugin-bi/CHANGELOG.md Adds changelog noting dependency updates
examples/host/package.json Bumps version from 0.1.0 to 0.1.1
examples/host/CHANGELOG.md Adds changelog noting dependency updates
examples/crm/package.json Bumps version from 1.0.0 to 1.0.1
examples/crm/CHANGELOG.md Adds changelog noting dependency updates

Comment thread packages/runtime/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.


### Patch Changes

- Initial release of ObjectStack Protocol & Specification packages

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Comment thread packages/objectql/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Comment thread packages/driver-memory/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Comment thread packages/client/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Comment thread packages/spec/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but this is version 0.1.2 (a patch bump from 0.1.1). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Comment thread packages/types/CHANGELOG.md Outdated
Comment on lines +7 to +16
- Initial release of ObjectStack Protocol & Specification packages

- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry describes this as "Initial release of ObjectStack Protocol & Specification packages", but according to the PR description, this is version 0.1.1 (a patch bump from 0.1.0). The changelog message should reflect that this is a patch release with specific changes, not an initial release. Consider describing the actual changes in this release (e.g., "Remove debug logs from registry and protocol modules" as mentioned in the PR description).

Suggested change
- Initial release of ObjectStack Protocol & Specification packages
- Complete protocol specifications with Zod schemas
- Core runtime and query engine
- Client SDK
- Memory driver implementation
- Hono server plugin
- Comprehensive documentation
- Remove debug logs from registry and protocol modules
- Remove debug logs from registry and protocol modules.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 1e0362b to accurately reflect patch release changes.

Copilot AI and others added 2 commits January 20, 2026 20:29
@hotlong

hotlong commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

@copilot fix ci

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

Copilot AI commented Jan 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix ci

Fixed in commit 1e0362b. All driver test mocks now include the required fields (connect, disconnect, checkHealth, execute, count, beginTransaction, commit, rollback). All 434 tests passing.

This was referenced Sep 5, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…stem-* migration entries states each lesson in words, not tracker numbers (stage 3) (objectstack-ai#20384)

Part of objectstack-ai#20233

Clause-②: no

**Stage 3 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `surface`, `from`
/ `to`, conversion or matching logic moves, and the chain rewrites
exactly what it rewrote before.

## What this does

`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on the parent card sets the shape: the lesson
in words, and no number, dead or alive.

This stage covers the next three families by site count, `driver-`,
`kernel-` and `system-`: **132 sites → 0** in the three prose fields.
None of the 26 entries carries a tracker id in `surface` (ruling A of
the stage-1 ACCEPT, `5858839916`, is checked and has nothing to do
here). Each site now says what the cited ruling, measurement or fix
decided. ADR ids stay. `registry.ts`, `spec-changes.json` and
`docs/protocol-upgrade-guide.md` are regenerated from the entries
(`gen:migration-registry`, `gen:spec-changes`, `gen:upgrade-guide`),
never hand-edited. The stage-1 pin now holds `engine-`, `ui-`,
`plugin-`, `driver-`, `kernel-` and `system-`.

## Census — tracker ids in the author-shown fields

**Instrument.** The stage-2 AST instrument, unchanged: a TypeScript-AST
walk over every `packages/spec/src/migrations/entries/**/*.ts`. For each
`entry` object literal it evaluates the string value of `replacement`,
`reason`, `acceptanceCriteria` and (counted separately) `surface`,
joining string literals with `+`, then counts `#` followed by 4 or 5
digits at a word boundary. **Validated first** by reproducing the
stage-1 readings on the stage-1 tree (`443b2f4fdc`, extracted with `git
archive`): `driver-` 7 entries / 44 sites (0 / 44 / 0, 25 distinct),
`kernel-` 9 / 44 (1 / 41 / 2, 11 distinct), `system-` 10 / 44 (0 / 42 /
2, 6 distinct), `engine-` 5 / 67, whole tree 266 entries / 1,016 sites /
9 `surface` sites — every figure equal to the stage-1 census. **Tree
measured:** `objectstack-ai/objectstack` at `569d4d2dbf` (this branch's
base). Unevaluable fields: 0.

**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), the reading stages 1 and 2 took.
- **Dark (comment lines):** 794 `//` lines in entry files carry a
tracker id, and none is counted. Comment lines belong to the sibling
card, and ⛔ this PR touches none (794 before and after).
- **Dark (field boundary):** the 7 `surface` sites left in the tree
(other families) count 0 in the three-field total and 7 in the `surface`
column.

**Re-measured on the base, matching the stage-1 census:** `driver-` 7
entries, **44** sites (replacement 0 / reason 44 / acceptanceCriteria
0), 25 distinct ids; `kernel-` 9 entries, **44** (1 / 41 / 2), 11
distinct; `system-` 10 entries, **44** (0 / 42 / 2), 6 distinct. 37
distinct ids across the three (the families share `objectstack-ai#14478`, `objectstack-ai#15939`,
`objectstack-ai#17635` and `objectstack-ai#3733`). `surface`: 0 in all three. Whole tree: 300
entries, **843** sites, 7 `surface` sites.

**After this PR:** `driver-` 0, `kernel-` 0, `system-` 0; `engine-`,
`ui-`, `plugin-` still 0; whole tree **843 → 711** sites; `surface` 7
(unchanged, other families).

| entry | sites (replacement / reason / acceptanceCriteria) |
|---|---|
| `17.driver-aggregate-undeclared-key-aliases-removed` | 6 (0 / 6 / 0) |
| `17.driver-capabilities-inert-bits-removed` | 4 (0 / 4 / 0) |
| `18.driver-options-timeout-to-timeout-ms` | 1 (0 / 1 / 0) |
| `17.driver-sql-distinct-bare-filter-typed` | 9 (0 / 9 / 0) |
| `18.driver-sql-unresolvable-where-column-refused` | 14 (0 / 14 / 0) |
| `18.driver-sql-upsert-cross-row-identity-merge-refused` | 9 (0 / 9 /
0) |
| `18.driver-turso-config-local-path-wasm-retired` | 1 (0 / 1 / 0) |
| `18.kernel-compatibility-matrix-estimated-migration-time-unit-in-key`
| 5 (0 / 5 / 0) |
| `18.kernel-context-preview-mode-retired` | 5 (1 / 4 / 0) |
| `18.kernel-event-bus-retention-unit-in-key` | 3 (0 / 3 / 0) |
| `18.kernel-health-check-and-hot-reload-durations-unit-in-key` | 7 (0 /
5 / 2) |
| `18.kernel-package-lifecycle-durations-unit-in-key` | 3 (0 / 3 / 0) |
| `18.kernel-plugin-health-report-durations-unit-in-key` | 3 (0 / 3 / 0)
|
| `18.kernel-plugin-security-durations-unit-in-key` | 4 (0 / 4 / 0) |
| `18.kernel-runtime-config-timeout-unit-in-key` | 11 (0 / 11 / 0) |
| `18.kernel-startup-orchestrator-durations-unit-in-key` | 3 (0 / 3 / 0)
|
| `18.system-cache-durations-unit-in-key` | 3 (0 / 3 / 0) |
| `18.system-collaboration-durations-unit-in-key` | 4 (0 / 4 / 0) |
| `18.system-failover-health-check-interval-unit-in-key` | 3 (0 / 3 / 0)
|
| `18.system-metrics-jsdoc-durations-unit-in-key` | 12 (0 / 12 / 0) |
| `18.system-metrics-window-durations-unit-in-key` | 5 (0 / 3 / 2) |
| `18.system-object-storage-durations-unit-in-key` | 3 (0 / 3 / 0) |
| `18.system-registry-config-durations-unit-in-key` | 3 (0 / 3 / 0) |
| `18.system-tracing-otel-exporter-durations-unit-in-key` | 5 (0 / 5 /
0) |
| `18.system-tracing-span-duration-unit-in-key` | 3 (0 / 3 / 0) |
| `18.system-worker-queue-rate-limit-duration-unit-in-key` | 3 (0 / 3 /
0) |
| **total, 26 entries** | **132 (1 / 127 / 4)** |

## Every citation read, and what the text now says

I read each cited issue or PR myself with single-card REST reads: the
body, and the comments where a ruling or a measurement lives. Ids are in
code spans so this body posts no cross-references. All 36 bare ids were
resolved against this repository, because every sentence that cites one
is about this repository's code; the one cross-repo id is `cloud#1651`.

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `objectstack-ai#3733` | The pruned `cached` field key: measured, the parse succeeded
and the removed key was dropped without a word; the orphan schema was
deleted. | "an earlier field-key prune measured exactly that — the parse
succeeded and the removed key was dropped without a word" (health-check,
OTel exporter) |
| `objectstack-ai#3821` | The sharing-rule page: an unsortable query fell through to
an empty page, and the driver fix made an unsortable query lose its
ORDER BY, not its rows. | "the unknown-column recovery ladder (an
unsortable query loses its ORDER BY, not its rows)"; "the ladder's own
premise — rows matter more than their order"; "the ladder's recoveries"
|
| `objectstack-ai#4484` | `IDataDriver.findStream` removed: no production caller, two
of three implementations buffered the whole set, and no tombstone
because nothing parses a driver object. | "Retiring
`IDataDriver.findStream` (it had no production caller, and two of its
three implementations read the whole result set into memory …)";
"(`IDataDriver.findStream`, removed with no tombstone because nothing
parses a driver object)"; by entry id in the `distinct` entry |
| `objectstack-ai#4583` | The datasource ledger's dead keys removed; `capabilities.*`
went as a whole block (11 of 11 unread). | "was retired separately, as a
whole block nothing read" |
| `objectstack-ai#4634` | Audit of all 34 `DriverCapabilities` bits: 3 live, 31 dead
and tombstoned. | the entry already states the audit ("the follow-up
audit checked every bit"); the trailing id is dropped |
| `objectstack-ai#4914` | Maintainer, 2026-08-04: remove `manifest.loading` and
`PluginHotReloadSchema`; keep `HotReloadConfigSchema`, the side with an
implementation (`HotReloadManager`), as the start point. | "kept twice:
as the hot-reload vocabulary that had an implementation when the
manifest-side copy was removed, …" |
| `objectstack-ai#4984` | An org-axis red-line gate read only aliases the schema
rejects while its own fixtures spelt them: tests green, rule dead. |
"the family of the org-axis red-line gate that read only rejected
aliases while its own fixtures spelt them, so its tests stayed green and
the rule stayed dead" |
| `objectstack-ai#5181` | Narrow the query parameter of `IDataDriver`'s methods
(`DriverQuery`, no redundant `object`). | "neither the narrowing of
`IDataDriver`'s query parameters to `DriverQuery` nor the follow-through
…" |
| `objectstack-ai#5499` | Maintainer, 2026-08-05: freeze investment in `driver-memory`
/ `driver-mongodb`; fully lifted 2026-08-11 (comments `5249019855`,
`5252526378`). | "the maintainer's 2026-08-05 investment freeze on
driver-memory, which was lifted on 2026-08-11" |
| `objectstack-ai#5540` | Remove `IStorageService.list(prefix)`: zero consumers, and
the two adapters answered differently and both incompletely. | "(the
zero-consumer `IStorageService.list`, whose two adapters answered
differently and both incompletely)" |
| `objectstack-ai#6011` | Maintainer: close the `ctx.user` `roles` alias now. | "(the
`ctx.user` `roles` alias, closed at once on the maintainer's word rather
than given a window)" |
| `objectstack-ai#6075` | **404** — see Acceptance notes. | "the follow-through that
brought five drivers' implementations in line" |
| `objectstack-ai#6320` | `distinct`'s third argument meant different things on memory
and sql; the sql half was dispatched, the memory half held under the
freeze. | "(the measurement that found the two drivers reading this
argument differently split the fix: the sql half is this entry, and the
memory half was held back by that freeze)" |
| `objectstack-ai#6321` | `query.aggregate` / `agg.func` are undeclared aliases whose
only writers are driver fixtures; order: re-spell the fixtures, delete
the aliases, then narrow the signature. | "The removal ran in a fixed
order — the fixtures re-spelt first, the two alias branches deleted
second, the parameter narrowed to `DriverQuery` last — because the
reverse order yields red nobody can explain." |
| `objectstack-ai#6404` (PR) | Executed that order and narrowed `aggregate`'s query
parameter to `DriverQuery`. | the same sentence |
| `objectstack-ai#7929` | Maintainer, 2026-08-12, ruling B: `driver-sql`'s filter
refusal stops echoing `$field` operands, for every caller; the full
diagnostic goes to the server log. | "the same predicate-text disclosure
shape the driver's field-reference filter refusals had already been made
to stop echoing (the full diagnostic goes to the server log, never the
response)"; "that disclosure shape closed on the last dialect" |
| `objectstack-ai#8371` | Ruled option 2: a dotted filter key whose head is a
relation, a formula or a scalar is refused at both doors; a structured
head stays unjudged. | "the axis owned by the dotted-filter verdict,
which refuses a dotted key whose head is a relation, a formula or a
plain column at the protocol and engine doors" |
| `objectstack-ai#8592` | Measured on live MySQL: knex compiles the named conflict
target away. | stated by the entry ("knex drops the named keys before
the statement leaves the process"); the trailing id is dropped |
| `objectstack-ai#8621` | Option A: a pre-flight refusal when no unique index backs
the caller-named conflict target. | "Two earlier pre-flight refusals
closed the half where no unique index backed a caller-named target …" |
| `objectstack-ai#8622` | `id` becomes insert-only on the merge path: a merge on a
non-primary conflict key was measured rewriting the existing row's
primary key. | "`id` is insert-only on the merge path (made so once a
merge on a non-primary conflict key was measured rewriting the existing
row's primary key)" |
| `objectstack-ai#8755` | Ruling option A: a pre-flight refusal when a second unique
key could absorb a backed, caller-named target. | "… and the half where
a rival unique key could absorb a caller-named one" |
| `objectstack-ai#8790` | Maintainer, 2026-08-15: refuse both halves with
`INVALID_FILTER` / 400, naming the column. | "Ruled by the maintainer on
2026-08-15: refuse BOTH halves …"; "Recover-both was excluded by the
ruling's own argument" |
| `objectstack-ai#8807` | Maintainer, 2026-08-15: an upsert must never modify a row
whose identity the caller did not supply and whose conflict key it did
not name; enforcement delegated, blanket refusal excluded. | "Ruled by
the maintainer on 2026-08-15, as a contract principle …" (the principle
itself was already quoted verbatim) |
| `objectstack-ai#8926` | Maintainer, 2026-08-16, option A: MySQL's spelling joins the
one shared predicate (envelope and recoveries together). | "Addendum
2026-08-16." — the paragraph already states option A |
| `objectstack-ai#9061` (PR) | Implemented that option A. | the same |
| `objectstack-ai#11825` | Maintainer, 2026-08-25: retire the declarative
`AdvancedPluginLifecycleConfig` container; the classes stay a
host-driven library. | "… and as a host-driven library when the
declarative lifecycle config container was retired" |
| `objectstack-ai#11846` | **404** — see Acceptance notes. | "maintainer ruling
2026-08-27 (Option A: remove)"; "(as the removal ruling recorded)" |
| `objectstack-ai#14478` | Ruling B, 2026-09-02: a no-baseline gate plus an ADR-0087
rename of every offender (`DriverOptions.timeout` among the seven
named); ruling B again, 2026-09-05: the population is every authored and
every runtime-emitted duration, minus exemptions declared on the schema.
| "Maintainer ruling B on duration units (2026-09-02, its population
widened on 2026-09-05 to every authored and every runtime-emitted
duration, bar the exemptions a schema declares on the key itself)"; "the
duration-unit rule (…)" |
| `objectstack-ai#14519` | The two tenant timeouts published a describe naming no unit
(the unit sat in the JSDoc only); folded into the rename. | "the
unit-nowhere shape (no unit in the name or in the published describe,
first measured on two tenant timeouts)" |
| `objectstack-ai#15626` (PR) | Landed the gate and the seven founding renames, the
tenant `idleTimeout` → `idleTimeoutSeconds` among them. | "The tenant
half was already renamed, in the same change that landed the duration
gate itself" |
| `objectstack-ai#15678` | `kernel/`: the 14 remaining duration keys carry their unit
in the key name. | "the kernel-directory duration renames" / "the
kernel-directory round"; trailing ids dropped |
| `objectstack-ai#15679` | `system/`: the 15 remaining duration keys carry their unit
in the key name; `size` got an honest name. | "the system-directory
duration round"; trailing ids dropped |
| `objectstack-ai#15939` | The gate did not read JSDoc. Ruled 2026-09-07: refuse the
JSDoc / describe divergence. Ruled A 2026-09-11: remediate the
population per file first, land the widened gate last. | "Director-seat
ruling A of 2026-09-11 on the JSDoc-channel finding … a duration key
whose JSDoc names a unit its describe does not is refused, and the keys
in that shape are remediated per file before that refusal lands" |
| `objectstack-ai#16024` | Maintainer, 2026-09-06, per key: forward `timeout`, remove
`localPath` and `wasm`. | "ruled per key by the maintainer on
2026-09-06, once all three of this package's unread config keys had been
measured" |
| `objectstack-ai#17635` (PR) | The widened gate: refuse a duration key whose JSDoc
names a unit its describe does not; landed last. | "lands that widened
gate last, into a tree already clean" |
| `objectstack-ai#18669` | Ruling A, 2026-09-17: rename `FileValue.duration` and
`estimatedMigrationTime`, each with an ADR-0087 entry; no new closed
type, no narrowing of stored data. | "Maintainer ruling A of 2026-09-17
on the last two duration keys no closed duration type could express …" |
| `cloud#1651` | **Not readable from this session** — see Acceptance
notes. | "cloud — a census closed 2026-08-26: OS_PREVIEW_MODE there is a
routing-only switch …" |

No call-shaped token moves: a `name(` census over `registry.ts` is
identical before and after (297 distinct tokens), so textual
call-spelling ratchets read the same.

## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`,
widened

`COVERED_PREFIXES` is now `engine-`, `ui-`, `plugin-`, `driver-`,
`kernel-`, `system-`. The pin still spawns the real CLI (`os migrate
meta --from 16 --to 18`) once, locates each covered block **verbatim**
in stdout, and asserts the printed block — `surface` included — carries
no `#` plus 4 or 5 digits. Anti-vacuity:
- the `REWRITTEN` floor rises from 29 to **55** ids: the 26 entries of
this stage (7 `driver-`, 9 `kernel-`, 10 `system-`) are added, and every
covered prefix must still select at least one entry;
- presence in stdout is asserted before cleanliness (the
`driver-sql-unresolvable-where-column-refused` reason carries two
blank-line paragraph breaks, and its block is found verbatim);
- the detector is exercised on both sides first (lit on 4 and 5 digits,
dark on 3, 6 and `ADR-0112`).

The file keeps its stage-1 name; the header lists the six covered
families.

## Ablation — the widened pin can fail on a `driver-` block

From committed state, HEAD `1c0dc7ad54`, with
`scripts/ablation-replace.mjs` in wrap mode (it owns the restore trap)
and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is
built from the generated `registry.ts`, so that is the file mutated.
- **Mutation.** In `registry.ts`, the reason of
`driver-sql-upsert-cross-row-identity-merge-refused`: anchor `pre-flight
refusals closed the half` → `pre-flight refusals (objectstack-ai#8621) closed the
half`. The tool read anchor 1 → 0 and replacement 0 → 1, blob `b41e1d44`
→ `8f8d226e`.
- **Mutate leg** (one lock turn: build, preflight, pin). Spec build exit
0. Preflight: marker present in 4 built files. Pin: **red**, `1 failed |
2 passed` — `driver-sql-upsert-cross-row-identity-merge-refused: the
printed guidance cites a tracker id: expected 'objectstack-ai#8621' to be undefined`.
- **Restore.** Tool-proven: blob `b41e1d44` == HEAD, `git diff HEAD`
empty.
- **Restore leg.** One lock turn, taken on the second try (the first
waited out its 540 s budget, exit 99, NOT MEASURED, the tree already
restored). Spec build exit 0. The `--absent` preflight found the marker
in none of 222 built files, with the working tree clean against HEAD.
Pin: **green**, `3 passed`.

## Verification

Final head **`1c0dc7ad54`** for every line below; each heavy run went
through `scripts/pm/os-verify-lock.sh` (one turn, `VERDICT command-exit
0`, per-step exits recorded separately).

- **Build:** `pnpm exec turbo run build --concurrency=2
--filter='@objectstack/cli^...'` gives `Tasks: 55 successful, 55 total`.
- **Pin and its neighbour:** `pnpm --filter @objectstack/cli exec vitest
run --project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`,
`Tests 10 passed | 1 skipped` (the skip is the default-range file's own
pre-existing `skipIf`).
- **Spec tests that read these entries or the registry:** `pnpm --filter
@objectstack/spec exec vitest run --maxWorkers=2 src/migrations
src/kernel/preview-mode-retirement.test.ts
scripts/build-schemas-check-mode.test.ts` plus the 19 other spec test
files that read `MIGRATIONS_BY_MAJOR`, the registry or an entry file:
`Test Files 24 passed`, `Tests 696 passed`.
- **CLI unit:** `test/vitest-tiers-partition.test.ts` and
`src/utils/spec-release-changes.test.ts`: `Test Files 2 passed`, `Tests
28 passed`.
- **The call-spelling census that reads `registry.ts`:** `pnpm --filter
@objectstack/driver-sql exec vitest run --maxWorkers=2
src/sql-driver-query-signature.test.ts` gives 15 passed.
- **Typecheck:** `pnpm --filter @objectstack/spec typecheck` exits 0
(test layer: 53 files / 255 errors held in its ledger); `pnpm --filter
@objectstack/cli typecheck` exits 0 (test layer: 3 files / 28 errors
held, unchanged).
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives **89** families at
`1c0dc7ad54` (after `git fetch origin main`). `--ran` over the recorded
exit codes reads **89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN**, all
exit 0. They include `check:doc-authoring` ("16466 customer-facing
string(s) across 1135 spec sources clean"), `check:issue-citations`,
`check:migration-registry` ("registry.ts is current (300 semantic, 219
retired-key, 199 retired-def)"), `check:spec-changes`,
`check:upgrade-guide`, `check:generated` ("All 15 generated artifacts
are up to date"), `check:duration-unit-keys`, `check:nul-bytes`,
`check:adr-0087-registration` and `check:changeset-no-major`.
- `check:dual-build-cjs-loads` refused first with `PREREQUISITE NOT MET`
(exit 3: twelve packages outside the CLI closure had no `dist/`). Those
`dist/` directories were written later in the same pass (04:48–04:49Z,
inside the `check:type-check-debt` run, whose re-measure builds them);
re-run at the same head it exits 0 (104 entries / 66 packages / 659 CJS
files). The reconciled list takes that latest run.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 28 changed `.ts`
files reports 28 files, 0 errors, 0 warnings.
- The population is read from `eslint.config.mjs`:
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 28
are in it (no file-ignored warning).
- Invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so a text edit cannot move the
verdict on a file it does not touch.
- **Mergeability:** see Acceptance notes (driver-free `merge-tree`
against `862b6ce869` exits 0).

## Acceptance notes

- **Two dead ids, rewritten from the code on `main`.** `objectstack-ai#6075` and
`objectstack-ai#11846` answer 404 on both the issues and the pulls endpoint, re-probed
with a 200 control (`objectstack-ai#14478`).
- `objectstack-ai#6075` (`distinct`'s "never reached it" sentence):
`packages/drivers/driver-sql/CHANGELOG.md` (commit `d367f03`) and
`sql-driver-query-signature.test.ts` record what it did — the five
drivers' implementations followed `IDataDriver`'s `DriverQuery`
narrowing. The sentence now says exactly that.
- `objectstack-ai#11846` (preview mode): `packages/spec/CHANGELOG.md` (commit
`0c2334f`), `packages/spec/src/kernel/context.zod.ts` and
`preview-mode-retirement.test.ts` record the 2026-08-27 ruling (Option
A: remove), the three-repo zero-consumer measurement and the
re-declare-fresh condition. Dropped because `main` does not state them:
"decision-inbox batch 2" and "all four decision facets pointed the same
way". "The objectstack-ai#11846 card records the measurement" (objectui leg) now reads
"zero consumers, measured when the removal was ruled", which is what the
changelog and the test header say.
- **One cross-repo id this session cannot read.** `cloud#1651` answers
403 here: `objectstack-ai/cloud` is not attached to this session
(`add_repo` refused: no access). It is neither confirmed nor refuted, so
its sentence was rewritten from what `main` records about it —
`packages/spec/CHANGELOG.md` (`0c2334f`: closed 2026-08-26 with positive
controls, `RuntimeMode` zero hits, `ArtifactKernelFactory` 20+ hits and
never touching `previewMode`) and `context.zod.ts` (`OS_PREVIEW_MODE`
there is routing-only). The cloud-side detail `main` does not state —
`previewMode` "only as a local variable" whose effect is adding
wildcards to "CSRF" trusted origins — is dropped; the parenthesis that
replaces it describes this repository's own `serve.ts` (the one reader
of `OS_PREVIEW_MODE` here only widens better-auth's trusted origins to
preview-domain wildcards), which is measured on `main`.
- **Decision-batch numbers went too (invisible to the regex).** Nineteen
sites cited a decision batch as `#` plus two or three digits (`objectstack-ai#43` ×13,
`objectstack-ai#115` ×4, `objectstack-ai#151` ×1, `objectstack-ai#158` ×1). They are numbers an author is shown
and cannot follow, so each is dropped. One consequence worth naming: 13
entries said `Maintainer ruling B on objectstack-ai#14478 (2026-09-02, decision batch
objectstack-ai#43)`, which fused two rulings on the same card — B of 2026-09-02 (the
gate and the no-baseline rename) and B of 2026-09-05, decided in that
batch (the population: every authored and every runtime-emitted
duration, minus schema-declared exemptions). The sentence now names both
dates. The `objectstack-ai#158` sentence (the agreement shape ruled an offence on
2026-09-18) is corroborated by
`.changeset/18075-agreement-shape-is-an-offence.md` on `main`.
- **"issue NNNN" / "PR NNNN" spellings, checked by hand.** No
bare-number spelling exists in these 26 entries' author-shown text; the
two `PR` citations (`PR objectstack-ai#6404`, `PR objectstack-ai#9061`) were `#`-spelled, so the
instrument saw them and they are gone. The only `#` left in these 26
files is on `//` comment lines (sibling card's surface), including a
`Prime Directive objectstack-ai#13` reference.
- **A citation whose page says something narrower than the text.**
`kernel-health-check-and-hot-reload-durations-unit-in-key` called
`shutdownTimeout`'s shape "the objectstack-ai#14519 unit-nowhere shape". `objectstack-ai#14519`'s
keys carried their unit in the JSDoc; "unit nowhere" is the gate's name
for it (`check-duration-unit-keys.ts` header: "no unit ANYWHERE (the
objectstack-ai#14519 shape)"), because the gate did not read JSDoc. The sentence now
says what the shape is — no unit in the name or in the published
describe — and that it was first measured on two tenant timeouts.
- **A comment that my text edit makes slightly stale.**
`18.system-metrics-window-durations-unit-in-key.ts` carries a `//`
comment saying its acceptanceCriteria sentence "is objectstack-ai#15679's, left word
for word". That sentence now says "that JSDoc-channel gap is filed as a
finding of its own" where it said "is objectstack-ai#15939": same content, no number.
The comment is the sibling card's surface (comment lines), so it is
untouched here.
- **Three "card" references re-anchored.** Removing an id left "the same
card" in the Turso entry pointing at nothing; it now says "the same
measurement". The kernel entries' "renamed by this same card" carry no
number and were not otherwise rewritten, so they are left.
- **Cross-PR check: no open PR adds or edits a `driver-`, `kernel-` or
`system-` semantic entry.** Read at 2026-09-28T04:0xZ: the 18 open PRs'
file lists (`GET /pulls/{n}/files`) carry 0 files matching
`migrations/entries/semantic/NN.(driver|kernel|system)-*`. The Version
Packages PR (`objectstack-ai#17076`) lists more than 1,000 files; the 1,100 rows read
carry no entry file, and it is the bot-generated release PR. Nothing in
flight will be held by the widened pin on arrival.
- **`main` moved 4 commits past the base** (`862b6ce869`: `objectstack-ai#20364`,
`objectstack-ai#20341`, `objectstack-ai#20366`, `objectstack-ai#20352`); none touches
`packages/spec/src/migrations/` or the pin. A driver-free bare-clone
`merge-tree --write-tree` of this head against `862b6ce869` exits 0 with
no conflicted path, so `registry.ts` needs no merge, and `main` was not
merged in.
- **Generated projections** (`spec-changes.json`,
`docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1 and 2;
their `--check` legs are green. Only the three `driver-` entries
registered at protocol 17 appear in them, which is why those diffs are
small.
- **No other test pins these entries' text.** A `git grep` of test files
for the 26 entry ids finds one (`preview-mode-retirement.test.ts`),
which names the entry in a comment and reads no prose; a grep of tests
for the 37 cited numbers finds only comment lines. So no test needed
re-pinning this stage (stage 2's `migrations.test.ts` case has no
counterpart here).

## Line budget

Entry files: **352 changed lines** (+228 / −124) across 26 files,
against the stage-1 ≈400 budget. The whole diff is **776 lines** (+516 /
−260) in 31 files. Of the rest, `registry.ts` is 352, the two
projections are 18 (`spec-changes.json` 12, the upgrade guide 6), the
widened pin is 33 and the changeset is 21.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…ction-*, data-* and element-* migration entries states each lesson in words, not tracker numbers (stage 4) (objectstack-ai#20509)

Part of objectstack-ai#20233
Stage 4: the datasource-, filter-, action-, data- and element- families.

Clause-②: no

**Stage 4 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `surface`, `from`
/ `to`, conversion or matching logic moves, and the chain rewrites
exactly what it rewrote before.

## What this does

`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on objectstack-ai#19123 (`5749154545`) sets the shape: the
lesson in words, and no number, dead or alive; a cross-repo number
(`cloud#N`, `objectui#N`) is still a tracker number.

This stage covers the next five families, `datasource-`, `filter-`,
`action-`, `data-` and `element-`: **150 sites → 0** in the three prose
fields, across 33 entry files. None of the 37 entries in these families
carries a tracker id in `surface` (ruling A of the stage-1 ACCEPT,
`5858839916`, is checked and has nothing to do here). Each site now says
what the cited ruling, measurement or fix decided. ADR ids stay.
`registry.ts`, `spec-changes.json` and `docs/protocol-upgrade-guide.md`
are regenerated from the entries (`gen:migration-registry`,
`gen:spec-changes`, `gen:upgrade-guide`), never hand-edited. The pin now
holds eleven families.

## Census — tracker ids in the author-shown fields

**Instrument.** The stage-2 AST instrument, re-written to its published
description (the stage-2 and stage-3 copies lived in removed
scratchpads): a TypeScript-AST walk over every
`packages/spec/src/migrations/entries/**/*.ts`. For each entry object
literal it evaluates the string value of `replacement`, `reason`,
`acceptanceCriteria` and (counted separately) `surface`, joining string
literals with `+`, then counts `#` followed by 4 or 5 digits at a word
boundary. **Validated first** by reproducing earlier readings on their
own trees (extracted with `git archive`): on `443b2f4fdc` (stage 1)
`engine-` 67, `datasource-` 43, `filter-` 30, `action-` 26, `element-`
25, `data-` 24, whole tree 266 entries / 1,016 sites / 9 `surface`; on
`0d7ed5a378` (stage 3 landed) whole tree **711** sites / 7 `surface` —
every figure equal to the stage-1 census and to stage 3's after-count.
Unevaluable fields: 0.

**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), before and after.
- **Dark (comment lines):** 823 `//` / docblock lines in entry files
carry a tracker id, and none is counted; 823 before and after. Comment
lines are the sibling card objectstack-ai#20234's surface, and this PR touches none
(proved below).
- **Dark (field boundary):** the 7 `surface` sites left in the tree
(other families) count 0 in the three-field total.

**Base `fc0db22b`:** `datasource-` 9 entries, **43** sites (3 / 38 / 2);
`filter-` 11, **32** (1 / 30 / 1); `action-` 6, **26** (0 / 23 / 3);
`element-` 5, **25** (2 / 19 / 4); `data-` 6, **24** (0 / 24 / 0).
**150** sites (6 / 134 / 10) in 33 of the 37 entries; 88 distinct ids
(80 bare, 8 `objectui#`, 2 `cloud#`). Whole tree: 310 entries, **721**
sites (711 after stage 3, plus entries `main` added since in other
families), 7 `surface`.

**After this PR:** all five families **0**; the six earlier families
still 0; whole tree **721 → 571**; `surface` 7 (other families). The
PM's rough line count (156 sites, 33 files) is a wider instrument; the
AST reading is 150 in the same 33 files.

| entry | sites (replacement / reason / acceptanceCriteria) | short
`#NN` |
|---|---|---|
| `17.action-descriptor-is-async-retired` | 2 (0 / 2 / 0) |  |
| `17.action-descriptor-resume-authority-default-flip` | 10 (0 / 7 / 3)
| |
| `17.action-session-roles-to-positions` | 12 (0 / 12 / 0) |  |
| `17.data-driver-find-stream-retired` | 1 (0 / 1 / 0) |  |
| `17.data-driver-query-omit-object` | 9 (0 / 9 / 0) |  |
| `17.data-engine-batch-retired` | 1 (0 / 1 / 0) |  |
| `17.data-field-changed-event-retired` | 4 (0 / 4 / 0) |  |
| `17.datasource-config-inline-credential-refused` | 1 (0 / 1 / 0) |  |
| `17.datasource-config-placeholder-refused` | 5 (0 / 5 / 0) |  |
| `17.datasource-config-url-userinfo-refused` | 4 (0 / 4 / 0) |  |
| `17.filter-regex-options-retired` | 8 (0 / 8 / 0) |  |
| `18.action-bulk-dispatch-contract-undeclared` | 1 (0 / 1 / 0) |  |
| `18.action-engine-facade-find-query-envelope` | 1 (0 / 1 / 0) | 1 |
| `18.data-file-value-duration-unit-in-key` | 6 (0 / 6 / 0) | 1 |
| `18.data-nosql-query-options-timeout-unit-in-key` | 3 (0 / 3 / 0) | 1
|
| `18.datasource-config-mongo-options-credential-refused` | 5 (0 / 5 /
0) | |
| `18.datasource-config-postgres-url-unparseable-refused` | 4 (0 / 4 /
0) | |
| `18.datasource-config-url-query-credential-refused` | 4 (0 / 4 / 0) |
|
| `18.datasource-credentialsref-mongo-composed-no-username-refused` | 10
(2 / 7 / 1) | |
| `18.datasource-credentialsref-mongo-url-no-user-refused` | 10 (1 / 8 /
1) | |
| `18.element-data-source-and-object-block-filter-rule-array` | 11 (1 /
9 / 1) | 1 |
| `18.element-number-filter-rule-array` | 7 (0 / 6 / 1) |  |
| `18.element-record-picker-filter-rule-array` | 7 (1 / 4 / 2) |  |
| `18.filter-between-blank-endpoint-refused` | 3 (0 / 3 / 0) | 1 |
| `18.filter-between-field-reference-endpoint-refused` | 5 (1 / 4 / 0) |
|
| `18.filter-comparand-types-and-widget-nested-slots-refused-at-save` |
2 (0 / 2 / 0) | |
| `18.filter-equality-array-comparand-refused` | 1 (0 / 1 / 0) |  |
| `18.filter-equality-array-comparand-refused-at-save` | 1 (0 / 1 / 0) |
|
| `18.filter-icontains-comparand-refused-at-parse` | 3 (0 / 3 / 0) |  |
| `18.filter-ne-array-comparand-refused` | 1 (0 / 1 / 0) |  |
| `18.filter-preset-ordering-comparand-refused` | 4 (0 / 4 / 0) |  |
| `18.filter-query-face-comparands-refused-at-save` | 1 (0 / 1 / 0) |  |
| `18.filter-text-operator-declared-type-refused` | 3 (0 / 2 / 1) | 1 |
| **total, 33 entries** | **150 (6 / 134 / 10)** | **6** |

## Text only — proved by a base-vs-head AST comparison

For every entry file this PR changes, both versions (`fc0db22b` and the
head) are parsed and three things are compared token for token: every
import declaration, every property other than the three prose fields (so
`id`, `surface`, `from` / `to` and any matcher, by evaluated value), and
every comment token in the file. **33 files compared, 0 with a non-prose
change.** The instrument is shown able to fail first: on an in-memory
copy it reports DETECTED for a mutated `id`, a mutated comment and a
mutated `surface`. So none of objectstack-ai#20234's comment lines moved, and no
entry's identity or matching moved.

## Every citation read, and what the text now says

Each cited id was read with a single-card REST read (body plus the
ruling, measurement or landing comments), resolved against the
repository its sentence names. Ids are in code spans so this body posts
no cross-references. **10 bare ids answer 404** on both the issues and
the pulls endpoint (re-probed with a 200 control, `14478`), and **2
`cloud#` ids are unreadable from this session** (403); those sentences
are rewritten from what `main` records, listed in Acceptance notes.

**`datasource-` (14 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7990` | Maintainer, 2026-08-12, Option A: close inline credentials
per artefact (each schema refuses at publish and diverts to its existing
mechanism); the heuristic `sys_metadata` write guard parked. | "The
maintainer ruled on 2026-08-12 to close that per artefact: each schema
that admitted an inline credential refuses it at publish …"; "the
inline-credential closure" |
| `8078` (PR) | The spec half of that ruling; measured that `${…}`
placeholders reach the client verbatim and that `config.url` still took
the secret. | "measured by the credential census while the
inline-credential refusal was being built"; "measured when the
inline-credential refusal was built" |
| `8082` | Maintainer, 2026-08-12, option A: refuse URL userinfo at
publish through one shared value-level parse; runtime DSNs unaffected. |
"The maintainer ruled on 2026-08-12 (Option A) to refuse the URL
userinfo password at publish, through one value-level parse the driver
schemas share" |
| `8336` | Maintainer, 2026-08-13, direction 2 of two: refuse `${…}` at
publish; implementing resolution rejected. | "The maintainer ruled on
2026-08-13 for the second of two directions: refuse the syntax loudly at
publish" |
| `8337` | The credential-bearing URL query parameter refusal, one
syntax over from userinfo. | "the credential-bearing URL query
parameters"; "the query string was the third spelling of the identical
secret, one syntax over" |
| `8876` | **404** — see Acceptance notes. | "the asymmetry the URL
grammar keeps between its two userinfo halves — a username is not
credential material" |
| `8696` | **404** — see Acceptance notes. | "measured when the bound
secret was made to reach the mongo client on its URL branch"; "`new
URL()` rejects the multi-host form outright, and the mongo arm hands the
authored URL to its client untouched"; "the defect class closed when
each DSN branch was made to inject the bound secret its composed branch
already used" |
| `9041` | **404** — see Acceptance notes. | "the sibling URL-branch
entry, `datasource-credentialsref-mongo-url-no-user-refused`"; "this
URL-branch refusal" |
| `9147` | The composed-branch twin: bound secret + no `url` + no
`username` refused, inheriting the URL-branch ruling. | "this
composed-branch refusal" |
| `7314`, `7385`, `8152`, `8875` | The driver-factory arms that dropped
something declared: the turso loader's install remedy and half its
config, the other optional arms' missing-package remedy, turso's
never-read bound secret, and (`8875`, **404**) the mysql DSN branch. |
"the family of driver-factory arms, closed one driver at a time, that
each dropped something declared without a word: the optional-driver arms
that answered a missing package with no remedy, the turso arm that never
read its bound secret, and the mysql and mongo DSN branches that
discarded one" |
| `8873` | **404** — see Acceptance notes. | "the postgres equivalent is
judged on its own client's measurement, never inherited" |

**`filter-` (24 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4706` | Maintainer, 2026-08-06, option B: retire `$regex` loudly and
add `$icontains`; five-backend true regex (A) excluded (turso remote
cannot, no business pull). | "the maintainer's 2026-08-06 ruling (option
B: retire `$regex` loudly and add `$icontains`, rather than make five
backends agree on one regex dialect)" |
| `5701`, `5702` | The contract half and the driver half of that ruling.
| "the retirement's own contract-half change"; "the contract half (the
`$icontains` declaration, …)"; "the driver half" |
| `6148` | **404** — see Acceptance notes. | "the gate that makes a
breaking changeset state its ADR-0087 disposition" |
| `18012` | **404** — see Acceptance notes. | "Maintainer ruling A of
2026-09-17: a blank `$between` endpoint is refused at the authoring
door, and the refusal names the blank side." |
| `13495` | driver-memory's reference matcher answered a null-bounded
range with every valued row. | "The reference matcher had already been
taught to survive the null-bound form of exactly this" |
| `objectui#9695` | The console filter builder stops padding a
half-typed pair with an empty string. | "is a change to the console's
own filter builder and lands on its own schedule" |
| `7596` | Maintainer, 2026-08-11, ADR-0049 REMOVE: no `{ $field }`
endpoint in either `$between` union. | "Maintainer ruling of 2026-08-11
on column-reference range endpoints, ADR-0049 enforce-or-remove:
REMOVE"; "reviewed and accepted with that ruling" |
| `7713` (PR) | Shipped that removal with a not-required disposition. |
"the 2026-08-11 changeset carried the disposition not-required" |
| `5222` | `$field` compiled to a column-to-column comparison on the SQL
faces. | "the position the column-to-column comparison compiles on every
face" |
| `19377` | **404** — see Acceptance notes. | "filed as an entry of its
own rather than as an already-registered rider" |
| `20116` | The collector for the family "the save door accepts
comparand shapes the query faces refuse", closed in stages. | "the
second stage of closing the family of comparand shapes the save door
accepted and the query faces refused"; "the family of comparand shapes
…" |
| `7872` | Maintainer, 2026-08-12: the shared face accepts `string` /
`number` / `bigint` / `boolean` / `null` / `Date` and refuses everything
else. | "the accepted set the maintainer ruled on 2026-08-12: string,
number, bigint, boolean, null and Date" |
| `19889` | Director seat, 2026-09-24, option A: the schema door refuses
what the compile face refuses (the standing array-equality refusal
applied to it). | "Ruled on 2026-09-24 (option A), applying the standing
refusal of an array in the equality slot to the schema door" |
| `19757` | Maintainer, 2026-09-23, option 乙: refuse an equality-slot
array at the shared face; 甲 (declare array equality) and 丙 (document the
divergence) rejected. | "Maintainer ruling of 2026-09-23 (option 乙 —
rather than declaring an array equality the SQL-family backends would
have to invent, or documenting a divergence that stays silent on one
backend)" |
| `19514`, `objectui#9050` | The protocol half of the maintainer's
2026-09-20 ruling C′ on the console's filter converter, rule 1 「the
differences are the protocol's to close」. | "The protocol half of the
maintainer's 2026-09-20 ruling (option C-prime) on the console's filter
converter, whose first rule reads, verbatim and untranslated: 「the
differences are the protocol's to close」" |
| `18113` | Published the text-comparand refusal predicate and reason
beside `FILTER_TEXT_CASES`. | "the pair published in this package beside
FILTER_TEXT_CASES for exactly this reason" |
| `19886` | Director seat, 2026-09-24, option A on standing text: `$ne`
with an array refused at the shared face and the schema door. | "Ruled
on 2026-09-24 by the director seat, on the standing contract text
(option A)" |
| `8690` | Ruled 2026-08-15: option B (engine door refuses an
uninterpretable temporal comparand) with option C (the preset refusal at
authoring) alongside. | "The authoring half (option C) of the
maintainer's 2026-08-15 ruling on uninterpretable temporal comparands,
ruled alongside the engine door (option B)"; "measured on the defect
report" |
| `8808` (PR) | The engine door. | "before the engine door and a 400
after" |
| `15661`, `15773` | Maintainer, 2026-09-05, C-deny: refuse a text
operator over a never-string declared type, over the existing type sets;
landed at the engine seam. | "Maintainer ruling of 2026-09-05 (option
C-deny: refuse now, over the type sets the contract already declares,
minting no new vocabulary), landed at the engine seam" |
| `14079` | Ruling A: a stored non-string value never satisfies a
positive text operator and satisfies `$notContains`. | "(a stored value
that is not a string never satisfies a positive text operator and
satisfies `$notContains`)" |

**`action-` (19 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `6748` | Retire `isAsync`: zero readers on a fresh three-repo
measurement. | "a fresh three-repo measurement (taken when the key was
filed for retirement, and re-run at pickup)" |
| `6667` | Enforce `supportsPause` at runtime. | "The sibling took the
ENFORCE leg of the same ruling" |
| `3801` | The generic resume route needs an authorization gate keyed on
the suspended node. | "The generic resume route's authorization gate
keys on the SUSPENDED NODE" |
| `3823` | The revise-window `wait` pause is service-owned but inherited
`'any'`. | "The revise-window incident decided the direction" |
| `4484`, `5540`, `6011` | The three retirements whose disposition this
one shares. | named by their entry ids, which the sentence already
carried |
| `5561` | `resumeAuthority` defaulted to `'any'` — fail-open by
omission. | the sentence already states it; ADR-0019's resume-seam
addendum named by date and title |
| `5703` | `supportsPause` / `isAsync` were zero-reader declarations. |
"no longer the declaration nothing enforced" |
| `5613` | Maintainer, 2026-08-06, contract-first ("C skeleton + A
semantics"): declare the shape as it stands, then rename on the typed
face. | "The maintainer ruled contract-first on 2026-08-06 (…: declare
the shape as it stands first, then rename on the typed face)"; "the
runtime half of the same ruling" |
| `5697`, `5779` | Phase 1 (the schema) and phase 2's spec half (the
canonical key and the alias). | "phase 1 declared …"; "`positions` is
now the canonical key …" |
| `5050` | The hook-side `roles` removed outright. | "(removed
outright)" |
| `3280`, `3290` | The hook `ctx.session.tenantId` alias: deprecated,
then removed in the next major. | "the hook `ctx.session.tenantId`
alias: deprecated first, removed in the next major" |
| `4579`, `4657` | The `openApi31` and `activationEvents` retirements. |
named by their surfaces, which the sentence already carried |
| `17319` | Ruled 2026-09-12, A: an action declares its dispatch
contract; B (unify the two wirings) refused. | "the 2026-09-12 ruling
that made an action declare its dispatch contract refused exactly that
option" |
| `14175` | The earlier typing fix that gave `find` the filter alone. |
"the parameter shape an earlier typing fix chose (the filter alone),
ruled by the director seat on 2026-09-12, with the maintainer's
agreement" |

**`data-` (15 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4484`, `4618`, `4673` | The three retirements, each already stated by
its own entry. | trailing ids dropped (ADR ids kept) |
| `4639` | Multi-record predicate writes got `data.records.*` events. |
"since multi-record predicate writes were given events of their own";
"the way bulk writes were given theirs" |
| `3196` | Webhook triggers trimmed to producers that exist. | the
sentence already states it |
| `cloud#1053`, `cloud#1030` | **403 here** — see Acceptance notes. |
"20 such sites were measured in the downstream cloud codebase, and a
`$like` the type layer would have caught reached runtime there through
exactly that hole" |
| `6350` | The stock reconciliation of the v17 train's breaking
changesets against the ledger; this change was its control sample and
was itself an omission. | "Registered by the stock reconciliation that
compared the breaking changesets already on the v17 release train
against this ledger. This change was that audit's CONTROL sample …";
"(backfilled by that reconciliation)" |
| `5181` | The `DriverQuery` narrowing itself. | "this change"; "this
narrowing" |
| `6321`, `6083` | Two later call-parameter changes, both registered
(`6083` **404**; `main` records it as ADR-0122 phase 2). | "Two later,
smaller driver call-parameter changes both registered" |
| `18669` | Maintainer, 2026-09-17, ruling A: rename the last two
duration keys no closed duration type could express, no new closed type,
no narrowing. | "Maintainer ruling A of 2026-09-17 on the last two
duration keys no closed duration type could express" |
| `18122` | Published `DurationMs` / `DurationSeconds` beside `EpochMs`,
the unit set derived from six genuine duration rows. | "published beside
`EpochMs` as a closed duration type"; "one of the six genuine durations
the closed types' unit set was derived from" |
| `14478`, `15680` | Ruling B of 2026-09-02 on duration units; the
data-directory rename round. | "Maintainer ruling B on duration units
(2026-09-02)"; trailing ids dropped |

**`element-` (17 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `objectui#6206` | Maintainer, 2026-08-25, option B: one filter
orthography platform-wide — the `ViewFilterRule` array, not a
record-shaped exception; measurement-first binding. | "the maintainer's
2026-08-25 ruling, option B: …" (three entries); "the console-side half
of this convergence" |
| `15442`, `15449` | Ruled 2026-09-06, option A: converge the binding
and the four `object-*` doors family-wide, one entry; exception and
mixed rejected. | "ruled 2026-09-06, option A: converge the binding and
the four block doors family-wide, under one entry, rather than record an
exception"; "a gap measured on its own" |
| `7751` | Maintainer, 2026-08-12: the `object-*` block props enter
`ComponentPropsMap`. | "a read-point record derived from the renderers
on 2026-08-13, when the `object-*` blocks first got props schemas in the
map" |
| `objectui#6948` | The console's filter converter had no branch for
`$and` / `$or` / `$not`. | "(the console's filter converter had no
branch for them)" |
| `15828` | `POST /analytics/query` refused the array `where` the
adapter sent. | the sentence already states it; "the runtime route's
refusal of the array corrects it here" |
| `16626` | **404** — see Acceptance notes. | "parked behind a bump of
that pin" |
| `objectui#7754`, `objectui#7752`, `objectui#6828` | The console
adapter lowers an array analytics filter before the wire; `aggregate()`
runs `translateFilterArray`. | "the console adapter was changed so
`ObjectStackAdapter.aggregate()` runs the same `translateFilterArray`";
"The adapter-side lowering lands in the console's own repository." |
| `5158` | Maintainer, 2026-08-04, ruling C: `FilterArray` is input-only
sugar with one lowering seam (`parseFilterAST`). | "since the
maintainer's 2026-08-04 ruling C declared the array input-only sugar
with one lowering seam" |
| `5334` | The in-process analytics door, added when an array `where`
was silently dropped. | "(added when an array `where` was found silently
dropped on the analytics path)" |
| `17321` | Ruled 2026-09-12, B: a partial D2 conversion for the
losslessly mappable record forms; combinators pass through, named. |
"(ruled 2026-09-12, option B: convert what maps losslessly and name what
does not, rather than leave every stored row to its next save or flatten
combinators)" |
| `14406` | Converged `element:record_picker`; its census pin asks
whether any `filter` door refuses the array. | "the twin of the census
pin that asks whether any `filter` door still refuses the array"; the
2026-08-25 Option-A ordering ruling stated as "measure the consumer's
read path before the contract moves" |
| `12039` | `element:number` converged. | "after `element:number`
converged" |
| `objectui#7663` | The console registry's `inputs.filter` flip for the
picker. | "a console-side change filed in the objectui repository,
blocked on that release" |
| `15829` | The dashboard widget `filter` location, a finding of its
own. | "a different family, judged on its own" |

## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`,
widened

`COVERED_PREFIXES` is now `engine-`, `ui-`, `plugin-`, `driver-`,
`kernel-`, `system-`, `datasource-`, `filter-`, `action-`, `data-`,
`element-` (`data-` does not select `datasource-` or `dataset-`: the
match is `startsWith('data-')`). The `REWRITTEN` floor rises from **55
to 88** ids: the 33 entries of this stage that carried a tracker id. The
three `it` blocks are textually unchanged: the detector is exercised on
both sides, every covered prefix must select an entry, and each covered
block is found verbatim in the real CLI's stdout before it is asserted
clean. The file keeps its stage-1 name; the header lists the eleven
covered families.

## Ablation — the widened pin can fail on a new-family block

From committed state, HEAD `d8bcc46a`, in one lock turn, with
`scripts/ablation-replace.mjs` in wrap mode (it owns the mutation's
restore trap; the leg script adds its own `trap … EXIT INT TERM` that
restores `registry.ts` from `HEAD` by absolute path and checks the blob)
and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is
built from the generated `registry.ts`, so that is the file mutated.
- **Mutation.** In `registry.ts`, the `acceptanceCriteria` of
`datasource-credentialsref-mongo-url-no-user-refused`: anchor `parse
reports this URL-branch refusal.` → `parse reports the objectstack-ai#9041 refusal.`
The tool read anchor 1 → 0 and replacement 0 → 1, blob `7001c102` →
`6f3f81ff`.
- **Mutate leg.** Spec build exit 0. Preflight: marker present in 4
built files. Pin: **red**, `1 failed | 2 passed` —
`datasource-credentialsref-mongo-url-no-user-refused: the printed
guidance cites a tracker id: expected 'objectstack-ai#9041' to be undefined`.
- **Restore.** Tool-proven: blob `7001c102` == HEAD, `git diff HEAD`
empty.
- **Restore leg.** Spec build exit 0. The `--absent` preflight found the
marker in none of 224 built files, with the working tree clean against
HEAD. Pin: **green**, `3 passed`. Whole tree afterwards: 0 dirty paths.

## Verification

Final head **`d8bcc46a`**. Every heavy run went through
`scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0` on each turn;
per-step exit codes recorded separately). Where a line was taken at
`95fb97ea` it says so: the one commit after it (`d8bcc46a`) changes one
entry sentence and the three generated projections, and every reader of
that text was re-run at `d8bcc46a`.

- **Build:** `pnpm exec turbo run build --concurrency=2
--filter='@objectstack/cli^...'` gives `Tasks: 58 successful, 58 total`
(at `95fb97ea`); the spec package was rebuilt at `d8bcc46a` in both
ablation legs (exit 0).
- **Pin:** at `d8bcc46a`, the ablation's restore leg:
`test/migrate-meta-engine-guidance.test.ts` `3 passed`. With its
neighbour at `95fb97ea`: `pnpm --filter @objectstack/cli exec vitest run
--project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`,
`Tests 10 passed | 1 skipped` (the skip is the default-range file's own
`skipIf`). Re-run with its neighbour at `d8bcc46a`: `Test Files 2
passed`, `Tests 10 passed | 1 skipped`. One earlier run at this head is
void and is not counted: it spawned the CLI while a concurrent lock-free
gate was rebuilding the package `dist/` trees (`MODULE_NOT_FOUND`, both
files); the re-run is the reading.
- **Spec, the whole `local` project** (at `95fb97ea`): `pnpm --filter
@objectstack/spec exec vitest run --project local --maxWorkers=2` gives
`Test Files 572 passed (572)`, `Tests 16789 passed | 1 todo`. **The
`repo` project** (at `d8bcc46a`): `Test Files 38 passed`, `Tests 690
passed`. **The readers of the changed sentence** (at `d8bcc46a`):
`src/migrations`, `src/ui/action-params.test.ts`,
`src/ui/filter-rule-array-guidance.test.ts` give `Test Files 5 passed`,
`Tests 229 passed`.
- **CLI unit** (at `95fb97ea`, no CLI file changed after):
`test/vitest-tiers-partition.test.ts` and
`src/utils/spec-release-changes.test.ts` give `Test Files 2 passed`,
`Tests 28 passed`.
- **The call-spelling census that reads `registry.ts`:** `pnpm --filter
@objectstack/driver-sql exec vitest run --maxWorkers=2
src/sql-driver-query-signature.test.ts` gives 15 passed.
- **Typecheck** (at `95fb97ea`; the last commit edits string literals
only): `pnpm --filter @objectstack/spec typecheck` exits 0 (test layer:
53 files / 251 errors held in its ledger); `pnpm --filter
@objectstack/cli typecheck` exits 0 (3 files / 28 errors held).
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives **90** families at `d8bcc46a`
(the same set it derived at `95fb97ea`). `--ran` over the recorded exit
codes reads **90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN**, all exit 0.
They include `check:doc-authoring` ("16735 customer-facing string(s)
across 1167 spec sources clean"), `check:issue-citations`,
`check:migration-registry` ("registry.ts is current (310 semantic, 230
retired-key, 206 retired-def)"), `check:spec-changes`,
`check:upgrade-guide`, `check:generated` ("All 15 generated artifacts
are up to date"), `check:org-identifier`, `check:nul-bytes`,
`check:dual-build-cjs-loads` (104 require entry points across 66
packages load), `check:type-check-debt`, `check:adr-0087-registration`
and `check:changeset-no-major`.
- How the reading was assembled: a container restart cut the pass at
`d8bcc46a` after 30 families; the other 60 were run afterwards on the
same head, and `check:type-check-debt`, whose first resumed run lost a
package build to an OOM kill (exit 3, `PREREQUISITE NOT MET`), was
re-run alone and exited 0 ("4 ledger entr(ies) re-measured … none above
its recorded number").
- The earlier full pass at `95fb97ea` read 88 exit 0,
`check:dual-build-cjs-loads` exit 3 (dists not yet built) and
**`check:org-identifier` exit 1**: my rewrite of the action-session
entry had spelled the removed hook read literally. `d8bcc46a` names that
alias in words instead, and the gate reads OK there.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 36 changed `.ts`
files at `d8bcc46a` reports 36 files, 0 errors, 0 warnings.
- The population is read from `eslint.config.mjs`:
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 36
are in it (no file-ignored warning).
- Invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so a text edit cannot move the
verdict on a file it does not touch.
- **Mergeability:** `main` moved 10 commits past the base, to
`4a1df196`; the only ones under `packages/spec/src/migrations/` are the
landed `20488` (two entries in the `field-` and `ui-` families, plus
`registry.ts`). A driver-free bare-clone `merge-tree --write-tree` of
`d8bcc46a` against `4a1df196` exits 0 with no conflicted path, and the
census over that merged tree reads 0 sites in all eleven covered
families, so `main` was not merged in; CI's merge ref runs the registry
gates on the merged tree.

## Acceptance notes

- **Ten dead ids, rewritten from the code on `main`.** Each answers 404
on both the issues and the pulls endpoint, with a 200 control (`14478`).
- `8696` / `8875` / `8873` (the bound secret reaching the mongo, mysql
and postgres clients): `service-datasource`'s
`default-datasource-driver-factory.ts`, its CHANGELOG (the mongo
URL-branch entry says it "closes the last arm of the family" the other
cards closed one driver at a time; the mysql DSN fix is `{ uri, password
}`) and `bound-secret-dsn-branches.test.ts`, which pins the bound secret
outranking `options.auth`. `common.zod.ts` and
`pg-url-grammar.server.ts` record why the shared helpers must not parse
(mongo's multi-host form, which `new URL()` rejects).
- `8876`: `common.zod.ts` (`urlUserinfoUsername`, the username half of
the same grammar; a username is not credential material).
- `9041`: `datasource.zod.ts`
(`CREDENTIALS_REF_MONGO_URL_NO_USER_REFUSED` and its docblock); the text
now names the sibling entry by id.
- `6148`: `scripts/check-adr-0087-registration.mjs`'s header (a
declared-breaking changeset must state its ADR-0087 disposition in
writing).
- `18012`: `.changeset/18012-between-blank-endpoint-refused.md` and
`filter.zod.ts` (ruling A of 2026-09-17: a blank bound refused at the
authoring door, the blank side named).
- `19377`: `.changeset/19377-between-field-endpoint-runtime-door.md` and
`filter-comparand-shape.ts` (the runtime door brought under the
2026-08-11 removal).
- `16626`: `.changeset/filter-orthography-binding-and-object-blocks.md`
on `main` records it as the objectui pin bump the element convergence
was parked behind; the text now says "a bump of that pin", and the pin
sha it names is unchanged.
- `6083`: `main` records it as ADR-0122 phase 2 (the spec CHANGELOG);
the sentence keeps its claim ("two later, smaller driver call-parameter
changes both registered") without the numbers.
- **Two cross-repo ids this session cannot read.** `cloud#1053` and
`cloud#1030` answer 403 (`objectstack-ai/cloud` is not reachable here,
and attaching it was refused). The sentence was rewritten from what this
repository records: `packages/spec/CHANGELOG.md` (the published
`DriverQuery` entry: 20 measured `as any` sites downstream, and a
`$like` that reached runtime through that hole) and the `5181` card body
("cloud 实测 20 处 cast … cloud#1030 的 `$like` 本可在类型层拦住"). Nothing the
cloud cards add beyond that is claimed.
- **Short numbers and ruling-record ids went too (invisible to the
regex).** Six decision-batch numbers (`objectstack-ai#43` ×2, `objectstack-ai#55`, `objectstack-ai#123`, `objectstack-ai#146`,
`objectstack-ai#151`), three ruling-record comment ids and one `batch 217 item 3`
spelling are numbers an author is shown and cannot follow, so each is
dropped. So are the maintainer's batch acknowledgements 「146 同意」 and
「217 同意」 and the bare 「同意」 beside `objectui#6206` (×3), `15442` and
`14175`: they record only that a batch was approved, and each sentence
now states the ruling's date and content instead. The one verbatim quote
that carries a lesson, 「the differences are the protocol's to close」, is
kept untranslated.
- **"issue NNNN" / "PR NNNN" spellings, checked by hand.** A scan of the
five families' evaluated prose for any run of three or more digits and
for `issue` / `card` / `PR` / `batch` / `record` / `summon` plus a
number now finds only HTTP statuses, ports and example values. The two
`PR #…` citations were `#`-spelled, so the instrument saw them.
- **One test pinned a removed tracker number.**
`packages/spec/src/ui/action-params.test.ts` asserted `reason` matches
the `5613` number; it is re-pinned on the sentence that carries the
ruling (`/ruled contract-first/`). No other test reads these entries'
prose for a tracker id (a grep of test files for the 88 cited numbers
finds only comment lines and assertions on other runtime strings).
- **No open PR touches these five families.** Read twice. At
2026-09-28T19:04Z: 11 open PRs' file lists (227 rows). Again at 20:28Z,
just before opening this one: 8 open PRs (195 rows; the Version Packages
PR `17076` skipped both times), none carrying a
`migrations/entries/semantic/NN.(datasource|filter|action|data|element)-*`
file. PRs `20504`, `20503`, `20460` and `20458` add or edit entries in
other families (`turso-`, `view-`, `stack-`, `cube-`) and regenerate
`registry.ts`: ordinary concurrency, regenerate on merge. Widening the
pin reds no sibling.
- **Generated projections** (`spec-changes.json`,
`docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1–3;
only the protocol-17 entries appear in them.
- **What later stages pick up** (whole tree at this head, same
instrument): **571** prose-field sites in the other families, **61**
short `#NN` sites, **7** `surface` sites, and three `cloud#` sites
(`storage-service-list-retired`, `cloud-subpath-retired`,
`cluster-driver-dangling-values-removed`; the fourth,
`data-driver-query-omit-object`, is done here).
- **Noted, not filed — the same rule outside this card's fields.**
Runtime strings outside the migration entries still carry tracker
numbers. One read in passing: `AutomationEngine`'s boot warning for a
pausing node type that never declares `resumeAuthority`
(`packages/services/service-automation/src/engine.ts`, the line that
reads `so the objectstack-ai#3801 resume gate REFUSES every pause it creates`) is
printed to a plugin author, and `resume-authority-declaration.test.ts`
asserts the `3801` and `3823` numbers are in it. That is the
runtime-string rule this card applies, on a producer outside its
surface; it is reported to the seat, not fixed here.

## Line budget

Entry files: **367 changed lines** (+214 / −153) across 33 files,
against the stage-1 ≈400 budget. The whole diff is **871 lines** (+527 /
−344) in 39 files. Of the rest, `registry.ts` is 367, the two
projections are 68 (`spec-changes.json` 44, the upgrade guide 24), the
widened pin is 42, the re-pinned test 2 and the changeset 25.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… dataset-*, hook-* and metadata-* migration entries states each lesson in words, not tracker numbers (stage 5) (objectstack-ai#20522)

Part of objectstack-ai#20233
Stage 5: the field-, export-, api-, dataset-, hook- and metadata-
families.

Clause-②: no

**Stage 5 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `from` / `to`,
conversion or matching logic moves, and the chain rewrites exactly what
it rewrote before. One `surface` moves, under ruling A of the stage-1
ACCEPT (`5858839916`): it carried two tracker numbers.

## What this does

`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on objectstack-ai#19123 (`5749154545`) sets the shape: the
lesson in words, and no number, dead or alive; a cross-repo number is
still a tracker number.

This stage covers the next six families, `field-`, `export-`, `api-`,
`dataset-`, `hook-` and `metadata-`: **110 sites → 0** in the three
prose fields and **2 → 0** in `surface`, across 25 entry files. Each
site now says what the cited ruling, measurement or fix decided. ADR ids
stay. `registry.ts`, `spec-changes.json` and
`docs/protocol-upgrade-guide.md` are regenerated from the entries
(`gen:migration-registry`, `gen:spec-changes`, `gen:upgrade-guide`),
never hand-edited. The pin now holds seventeen families.

## Census — tracker ids in the author-shown fields

**Instrument.** Stage 4's TypeScript-AST census, the same script: for
each entry object literal under
`packages/spec/src/migrations/entries/**` it evaluates `replacement`,
`reason`, `acceptanceCriteria` and (separately) `surface`, joining
string literals with `+`, and counts `#` followed by 4 or 5 digits at a
word boundary. On base `9e9bb464` it reads the whole tree at **571**
sites / 7 `surface` / 61 short, which is stage 4's recorded after-count.
Unevaluable fields: 0.

**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), before and after.
- **Dark (comment lines):** 823 `//` / docblock lines in entry files
carry a tracker id, and none is counted; 823 before and after. Comment
lines are objectstack-ai#20234's surface, and this PR touches none (proved below).

**Base `9e9bb464`:** `field-` 10 entries, **28** sites (5 / 20 / 3);
`export-` 3, **21** (0 / 21 / 0); `hook-` 4, **17** (0 / 17 / 0); `api-`
5, **16** (0 / 14 / 2); `metadata-` 7, **16** (1 / 15 / 0); `dataset-`
3, **12** (2 / 8 / 2), plus **2** in `surface`. **110** sites (8 / 95 /
7) in 24 of the 32 entries; 75 distinct ids (71 bare, 1 spelled
`framework#`, 3 `objectui#`). Short numbers: 14.

**After this PR:** all six families **0**, `surface` 0; the eleven
earlier families still 0; whole tree **571 → 461**, `surface` **7 → 5**,
short **61 → 50**. The PM's rough line count (126 sites, 25 files) is a
wider instrument; the AST reading is 110 in 24 files, and the 25th file
carries only short decision-batch numbers and ruling-record ids.

| entry | sites (replacement / reason / acceptanceCriteria) | surface |
short numbers |
|---|---|---|---|
| `17.api-runtime-create-withdrawn` | 9 (0 / 7 / 2) |  |  |
| `17.export-axis-opt-in` | 7 (0 / 7 / 0) |  |  |
| `17.export-field-meta-constraints-retired` | 8 (0 / 8 / 0) |  |  |
| `17.field-runtime-create-withdrawn` | 9 (0 / 6 / 3) |  |  |
| `17.hook-context-session-roles-retired` | 6 (0 / 6 / 0) |  |  |
| `17.hook-register-empty-object-target-refused` | 8 (0 / 8 / 0) |  |  |
| `18.api-assembled-entry-split` | 1 (0 / 1 / 0) |  | 1 |
| `18.api-error-retry-after-unit-in-key` | 3 (0 / 3 / 0) |  | 1 |
| `18.api-runtime-config-durations-unit-in-key` | 3 (0 / 3 / 0) |  | 1 |
| `18.dataset-filter-nested-relation-equality-array-refused-at-save` | 2
(0 / 2 / 0) | | |
| `18.dataset-measure-aggregate-field-type-refused` | 5 (1 / 2 / 2) | 2
| 2 (1 kept) |
| `18.dataset-measure-selecting-aggregate-field-type-refused` | 5 (1 / 4
/ 0) | | 3 (1 kept) |
| `18.export-job-family-retired` | 6 (0 / 6 / 0) |  | 2 |
| `18.field-currency-scale-refused` | 0 |  | 2 |
| `18.field-max-length-malformed-or-misplaced-refused` | 8 (3 / 5 / 0) |
| |
| `18.field-min-length-malformed-or-misplaced-refused` | 3 (1 / 2 / 0) |
| |
| `18.field-multiple-non-capable-type-refused` | 4 (0 / 4 / 0) |  | 1 |
| `18.field-predicate-reference-traversal-refused` | 2 (0 / 2 / 0) | | |
| `18.field-scale-precision-integer-refused` | 2 (1 / 1 / 0) | | 1
(kept) |
| `18.hook-register-undispatched-lifecycle-event-refused` | 3 (0 / 3 /
0) | | |
| `18.metadata-customization-protocol-retired` | 4 (0 / 4 / 0) |  |  |
| `18.metadata-endpoints-switch-radius-repartitioned` | 3 (0 / 3 / 0) |
| |
| `18.metadata-manager-config-cache-ttl-unit-in-key` | 3 (1 / 2 / 0) | |
|
| `18.metadata-manager-config-inert-cache-keys-retired` | 3 (0 / 3 / 0)
| | |
| `18.metadata-plugin-additional-types-retired` | 3 (0 / 3 / 0) |  |  |
| **total, 25 entries** | **110 (8 / 95 / 7)** | **2** | **14 (3 kept)**
|

The seven entries of these families that carried no number are
untouched: `api-endpoint-cache-ttl-unit-in-key`,
`field-inline-and-related-list-columns-closed`,
`field-master-detail-set-null-refused`,
`field-reference-to-spelling-retired`, `hook-timeout-unit-in-key`,
`metadata-changed-event-payload-retired`,
`metadata-item-name-grammar-enforced`.

## Text only — proved by a base-vs-head AST comparison

For every entry file this PR changes, both versions (`9e9bb464` and the
head) are parsed and compared: every import declaration; every property
other than the three prose fields, by evaluated value (so `id`, `from` /
`to` and any matcher); every comment token in the file; and the code
skeleton, token by token with each run of joined string literals
collapsed to one. `surface` is allowed to differ only where the base
value carried a tracker id and the head value carries none. **25 files
compared, 0 with a non-prose change**; one note, the ruling-A `surface`
of `18.dataset-measure-aggregate-field-type-refused`. The instrument is
shown able to fail first: on an in-memory copy it reports DETECTED for a
mutated `id`, a mutated comment, a mutated `surface` whose base carried
no tracker id, a mutated code token and a mutated import, and stays dark
on a prose-only mutation. So none of objectstack-ai#20234's comment lines moved, and
no entry's identity or matching moved.

## Every citation read, and what the text now says

Each cited id was read with a single-card REST read (body plus the
ruling, measurement or landing comments), resolved against the
repository its sentence names: 72 in this repository (one spelled
`framework#`, the repository's old directory name) and 3 in
`objectstack-ai/objectui`. Ids are in code spans so this body posts no
cross-references. **4 ids answer 404** on both the issues and the pulls
endpoint (re-probed with a 200 control, `14478`); those sentences are
rewritten from what `main` records, listed in Acceptance notes.

**`api-` (11 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `5488` | Maintainer, 2026-08-07: flip `api` to `allowRuntimeCreate:
false` and refuse at the write inlet (remove, not converge the read
path); re-entry only with a real consumption path. | the measurement and
the ruling were already in the sentence; the id is dropped |
| `5040` | The declarative endpoint executor project; its acceptance
step moved showcase's endpoints to the artifact route, live. | "showcase
uses the artifact route, and its declared endpoints serve live" |
| `4052` | `BatchOptions.validateOnly`, retired the same way (a runtime
verdict, no D2). | named by the key, which the sentence already carried
|
| `5279` (PR), `5189`, `5203` (PR) | The publish gate for `api` drafts;
`publishPackage` and load-time `buildEndpointIndex` running the endpoint
gate. | named by the functions the sentence already carried |
| `2657` | Studio metadata coverage: Part B asks which un-typed concepts
(`apis` among them) become registered types. | "if the Studio
metadata-coverage work promotes `apis` to a registered type WITH A REAL
CONSUMPTION PATH" |
| `5311` | The direct-active `saveMetaItem` write was a third path past
the namespace and duplicate gates; closed as subsumed by the `5488`
ruling. | "The same refusal closes the direct-active write too, which
had been a third path past the endpoint namespace and duplicate-path
gates." |
| `18576` | Maintainer, 2026-09-17, option B: narrow the `./api` entry,
rather than add a bundle-weight rule (A) or accept the weight (C). |
"Maintainer ruling of 2026-09-17, option B (narrow the entry, rather
than add a bundle-weight rule to the browser-reachability ledger or
accept the weight as it stood)" |
| `14478`, `15677` | Maintainer ruling B of 2026-09-02: a duration's
unit lives in the key name, no offender grandfathered; the `api/` stack
of that ruling. | "Maintainer ruling B of 2026-09-02 on duration-shaped
keys"; trailing ids dropped |

**`export-` (15 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `6350` | The stock reconciliation of the v17 train's breaking
changesets against the ledger, which backfilled this entry. |
"Registered (backfilled) by the stock reconciliation that compared the
breaking changesets already on the v17 release train against this
ledger" |
| `3544`, `3710` | The user-level export axis, and its extension to the
CSV attachments scheduled reports mail out. | "the export axis, and its
extension to the CSV attachments scheduled reports mail out" |
| `6148` | **404** — see Acceptance notes. | "the gate that makes a
breaking changeset state its ADR-0087 disposition" |
| `3956` (spelled `framework#`) | The import dry run skipped the
field-level validation the real write ran; the hand-copied pre-check
mirror was added to close it. | "added when the dry run was found
skipping the field-level validation the real write ran" |
| `4633`, `6532` (PR) | Maintainer, 2026-08-06, ruling D: a
validate-only protocol operation, so the dry run's prediction is the
engine's verdict; the mirror retired. | "the maintainer's 2026-08-06
ruling D (a validate-only protocol operation, so the dry run's
prediction is the engine's verdict by construction)" |
| `4484`, `5540`, `6011` | The `findStream`, `IStorageService.list` and
`actor-user-roles-to-positions` retirements. | named by their surfaces,
which the sentence already carried |
| `6536` | The eight keys left read by nothing after the mirror retired,
deferred to their own sweep. | "this is the removal the dry-run change
deliberately deferred to a sweep of its own" |
| `17158` | Maintainer, 2026-09-12, ruling A: retire the family,
`IExportService` and `ScheduleExportInput`; `ScheduleState` with it
unless a live consumer is measured. Landing route A, 2026-09-24; scope
note, 2026-09-25. | "maintainer ruling A of 2026-09-12 (…), the landing
route the maintainer ruled on 2026-09-24 (route A: …), and a scope note
the maintainer agreed on 2026-09-25" |
| `objectui#10247` | The console retires its own async-export path
first. | "objectui retires its own side of the unimplemented
async-export path first"; "which carries objectui's own retirement" |
| `19543` | Three sibling list doors declared `limit` / `cursor` and
never read them; the export-job list's door folded into this retirement.
| "one of three sibling list doors found declaring them and never
reading them" |
| `16320` | The seven cron-typed positions nothing read, retired (three
on these defs). | "once the retirement of the cron-typed positions
nothing read had deleted theirs"; "Those earlier cron-position
deletions" |

**`field-` (20 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7893` | Maintainer, 2026-08-12: retire the runtime `field` write
channel rather than build a read path. | the measurement and the ruling
were already in the sentence; the id is dropped |
| `5488`, `4052` | The `api` and `validateOnly` withdrawals. | "the
`api` withdrawal's rationale reused (`api-runtime-create-withdrawn`)";
named by key |
| `7743`, `7894` | The field overlay lock (`NOT_OVERRIDABLE`); the
plural `/meta/fields/` spelling folded onto the singular. | "The field
overlay refusal"; the plural door named by its path |
| `8169` | The `_diagnostics` envelope asserts well-formedness only; it
has no "in effect" axis. | "(the envelope has no "in effect" axis)" |
| `11566`, `11989` (PR), `11950` | Maintainer, 2026-08-24: tighten both
halves of `maxLength` (value shape and applicable types); shipped on
17.x; registered in a follow-up. | "Maintainer ruling of 2026-08-24,
tightening both halves — the value's shape and the types the key applies
to"; "registration was deferred to a follow-up" |
| `11875` | Maintainer, 2026-08-25, option 1: the write seam enforces
`maxLength` for `signature` / `qrcode`, then both join the
bounded-string set. | "which joined once the write seam enforced a
declared bound on them" |
| `11431` | The SQL driver stops reading a malformed bound as
authoritative (the `varchar(0)` plan). | "until it was taught to stop
reading a malformed bound as authoritative" |
| `8321` | `scale` / `precision` refused as non-integer or negative —
the house pattern. | "the house pattern the `precision`/`scale` integer
refusal set" |
| `11949` | Maintainer, 2026-08-25, option B: `minLength` is
`int().min(1)`, zero refused, the `maxLength` template in full. |
"Maintainer ruling of 2026-08-25 (option B, the lower bound at 1) … the
defect pair the 2026-08-24 ruling closed for `maxLength`" |
| `17469`, `11437` | Maintainer, 2026-09-13, option 1′: `multiple: true`
refused outside the multi-capable types — the earlier `radio` rule
generalised; the driver derives its JSON column from the spec predicate.
| "Maintainer ruling of 2026-09-13, option 1′ (the earlier rule refusing
an authored `radio` with `multiple: true`, generalised)" |
| `objectui#8886`, `objectui#8937` | The console's related list shaped
its parent filter from the spec predicate; its follow-up recorded the
driver half as owed. | "the console's related list pinned the divergence
on the consumer side when it began shaping that filter from the spec
predicate, and its follow-up recorded the driver half as owed and not
filed" |
| `20078` | Triage, 2026-09-25, remedy A: refuse the traversal at
authoring with a prescription; hydrating the field level is a capability
of its own. | "Triage routed this on 2026-09-25 to remedy A: refuse the
traversal at authoring, with a prescription." |
| `18682` | A validation rule or visibility predicate reads one hop
through a lookup. | "is served, one hop deep, and stays accepted" |
| `7501` | `scale` enforced at write time: an over-scale write refused,
never rounded. | "when `scale` was made enforced at write time (an
over-scale write refused, never rounded)"; "the write-time `scale`
enforcement" |

**`hook-` (12 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4839`, `5049` (PR) | Both `session.roles` admin exemptions removed;
the record lock and the delegation guard back on the one permission
vocabulary. | "An earlier fix removed both readers, returning the record
lock and the delegation guard to the one permission vocabulary" |
| `4579`, `4657` | The `openApi31` and `activationEvents` retirements. |
named by their surfaces, which the sentence already carried |
| `3733` | Measured: a key removed from a non-strict schema parses clean
and is silently dropped. | "as a removed field key was measured to be" |
| `5050` | This retirement's own card. | trailing id dropped |
| `4281` (PR) | An empty hook target is not "no target": closed at the
two metadata doors. | "An earlier breaking fix established that an empty
hook target is not "no target""; "that fix's headline failure mode" |
| `5928` | The `excludeObjects` face (global except named objects); it
declined to change the matcher's read in passing. | "The later
`excludeObjects` face (a hook global except for the objects it names)";
"the `excludeObjects` change declined to do it in passing" |
| `6573` | **404** — see Acceptance notes. | trailing id dropped; the
entry states the change |
| `4001` | The unknown-key strictness campaign (ADR-0078). | trailing id
dropped (ADR-0078 kept) |
| `3195` | The hook taxonomy collapsed from 18 events to the 8
dispatched; a registration guard warns on the rest. | "the change that
collapsed the hook taxonomy to the eight dispatched events made this
branch a warn" |
| `17713` | This refusal's own card. | trailing id dropped |

**`metadata-` (11 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `12057` | Maintainer, 2026-08-29: retirement adopted, re-scope
rejected (the card's ruling comment is no longer on it; `main` records
it). | "the maintainer's ruling of 2026-08-29 adopted retirement and
rejected a re-scope" |
| `13135` | **404** — see Acceptance notes. | "executed widened to the
full coupling set the fork report on that ruling measured" |
| `11513` | **404** — see Acceptance notes. | "the 2026-08-24
lock-and-clone ruling (lock the packaged base, customize a clone) left
deliberately unchartered" |
| `15542`, `15854` | Maintainer, 2026-09-06, ruled together (2 + A):
every `endpoints.*` switch gates exactly the face its name states; the
whole-store family gets its own key. | "The maintainer ruled the two
together on 2026-09-06 as one principle: …" |
| `15543` | No shipped boot path constructs a `RestServerConfig`. | the
measurement was already in the sentence; the id is dropped |
| `15624` | The outer cache keys read by nothing, retired on their own
(the owning seat's ruling). | "(the owning seat's ruling, conditioned on
the measurement below and re-taken on the merged ref)"; "retired on its
own under ADR-0049" |
| `14478` | Maintainer ruling B of 2026-09-02 on duration units. |
"Maintainer ruling B of 2026-09-02 on duration-shaped keys"; "the
duration-unit rename" |
| `8586`, `8421` | Maintainer, 2026-08-14, jointly: remove
`additionalTypes`, and refuse unknown `/meta` types by the static
registry. | "maintainer ruling of 2026-08-14: remove the key, jointly
with refusing unknown types at the `/meta` boundary by the static
registry" |
| `4212` | Four of five declared plugin lifecycle hooks were never
invoked, `onInstall` among them. | "the plugin lifecycle's `onInstall`
(a documented hook with no invocation site)" |

**`dataset-` (9 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `19889` | Ruling A of 2026-09-24: the schema door refuses what the
compile face refuses; a field spec with no `$` key stays undescended. |
"ruling A of 2026-09-24, which made the schema door refuse what the
compile face refuses, drew the line there" |
| `20080` | Triage, 2026-09-25, remedy A: refine the two analytics
carriers; remedy B (stop the analytics door descending) changes what a
nested list means. | "Triage on 2026-09-25 routed the fix to the two
analytics carriers instead, rather than stop the analytics door
descending, which would change what a nested list means" |
| `16737`, `16099` | The measured defect: `AVG()` over a datetime is an
average year on SQLite and an error on Postgres. | the measurement was
already in the sentence; the ids are dropped |
| `16353` | The aggregate × field-type table, declared in the spec. |
named by the table, which the sentence already carried |
| `16099` (in `surface` and `acceptanceCriteria`) | The widening of the
refusal to `sum` / `avg` over every field class, registered
`not-required` against this entry. | "followed in a later change";
"widened by a later change" |
| `17560` | Director ruling B of 2026-09-13: the compile door enforces
the table for every aggregate. | "Director ruling B of 2026-09-13: …";
the sibling entry named by id |
| `15768`, `16236` | `measureResultType` typing `min` / `max` over
strings and over a `formula` return type. | the sentence already states
both |
| `17513` | Closed as a duplicate with zero rulings on it. | "the card
it cited is closed as a duplicate with zero rulings on it" |

## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`,
widened

`COVERED_PREFIXES` gains `field-`, `export-`, `api-`, `dataset-`,
`hook-` and `metadata-` (17 prefixes; `data-` still selects neither
`datasource-` nor `dataset-`, and `api-` does not select `apimethod-`:
the match is `startsWith`). The `REWRITTEN` floor rises from **88 to
113** ids: the 25 entries this stage rewrote. The three `it` blocks are
textually unchanged. The file keeps its stage-1 name; the header lists
the seventeen covered families.

## Ablation — the widened pin can fail on a new-family block

From committed state, HEAD `d24a253bd0`, in one lock turn, with
`scripts/ablation-replace.mjs` in wrap mode (it owns the mutation's
restore; the leg script adds its own `trap … EXIT INT TERM` that
restores `registry.ts` from `HEAD` by absolute path and checks the blob)
and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is
built from the generated `registry.ts`, so that is the file mutated.
- **Mutation.** In `registry.ts`, the `reason` of
`hook-register-undispatched-lifecycle-event-refused`: anchor `made this
branch a warn — ` → `made this branch a warn (objectstack-ai#3195) — `. The tool read
anchor 1 → 0 and replacement 0 → 1, blob `94bc4938` → `b045dbfd`.
- **Mutate leg.** Spec build exit 0. Preflight: marker present in 4
built files. Pin: **red**, `1 failed | 2 passed` —
`hook-register-undispatched-lifecycle-event-refused: the printed
guidance cites a tracker id: expected 'objectstack-ai#3195' to be undefined`.
- **Restore.** Tool-proven: blob `94bc4938` == HEAD, `git diff HEAD`
empty.
- **Restore leg.** Spec build exit 0. The `--absent` preflight found the
marker in none of 224 built files, with the working tree clean against
HEAD. Pin: **green**, `3 passed`. Whole tree afterwards: 0 dirty paths.

## Verification

Final head **`d24a253bd0`**; every reading below was taken there. Every
heavy run went through `scripts/pm/os-verify-lock.sh`, with per-step
exit codes recorded separately.

- **Build:** `pnpm exec turbo run build --concurrency=2
--filter='@objectstack/cli^...'` gives `Tasks: 58 successful, 58 total`;
the ten packages outside that closure (for `check:dual-build-cjs-loads`)
give `Tasks: 68 successful, 68 total`.
- **Pin with its neighbour:** `pnpm --filter @objectstack/cli exec
vitest run --project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`,
`Tests 10 passed | 1 skipped` (the skip is the default-range file's own
`skipIf`).
- **CLI unit:** `test/vitest-tiers-partition.test.ts` and
`src/utils/spec-release-changes.test.ts` give `Test Files 2 passed`,
`Tests 28 passed`.
- **Spec, the whole `local` project:** `pnpm --filter @objectstack/spec
exec vitest run --project local --maxWorkers=2` gives `Test Files 573
passed (573)`, `Tests 16801 passed | 1 todo`. **The `repo` project:**
`Test Files 38 passed`, `Tests 690 passed`.
- **Typecheck:** `pnpm --filter @objectstack/spec typecheck` exits 0
(test layer: 53 files / 251 errors held in its ledger); `pnpm --filter
@objectstack/cli typecheck` exits 0 (3 files / 28 errors held).
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives **89** families. `--ran` over
the recorded exit codes reads **89 derived, 89 run, 0 NOT-MEASURED, 0
UNRUN**, all exit 0. They include `check:doc-authoring` ("16735
customer-facing string(s) across 1168 spec sources clean"),
`check:issue-citations`, `check:migration-registry` ("registry.ts is
current (311 semantic, 230 retired-key, 206 retired-def)"),
`check:spec-changes`, `check:upgrade-guide`, `check:generated` ("All 15
generated artifacts are up to date"), `check:org-identifier` ("no
removed session.tenantId alias"), `check:nul-bytes`,
`check:dual-build-cjs-loads` (104 require entry points across 66
packages load), `check:type-check-debt`, `check:adr-0087-registration`
and `check:changeset-no-major`. `check:dual-build-cjs-loads` first
exited 3 (`PREREQUISITE NOT MET`: ten packages had no `dist/`); after
the ten were built it exits 0, and that is the reading recorded.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 27 changed `.ts`
files reports 27 files, 0 errors, 0 warnings.
- The population is read from `eslint.config.mjs`:
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 27
are in it (no file-ignored notice).
- Invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so a text edit cannot move the
verdict on a file it does not touch.
- **Mergeability:** `main` moved one commit past the base, to
`0bbe4005`: the landed `20511` (a `qa-` entry, a retired key and
`registry.ts`). A driver-free bare-clone `merge-tree --write-tree` of
`d24a253bd0` against `0bbe4005` exits 0 with no conflicted path, and the
census over that merged tree reads 0 sites in all six families (whole
tree 461), so `main` was not merged in; CI's merge ref runs the registry
gates on the merged tree.

## Acceptance notes

- **Four dead ids, rewritten from what `main` records.** Each answers
404 on both the issues and the pulls endpoint, with a 200 control
(`14478`).
- `6148`: `scripts/check-adr-0087-registration.mjs`'s header (a
declared-breaking changeset must state its ADR-0087 disposition in
writing) — the same reading stage 4 used.
- `6573`: the objectql CHANGELOG entry "`engine.registerHook` refuses an
empty `object` target and a scope whose two faces cancel out", and
`engine.ts`'s refusal docblocks. It is this entry's own change, so the
trailing id is dropped; the entry already states the change.
- `13135`: `packages/metadata/CHANGELOG.md` ("retire the paper
metadata-customization protocol with its full coupling set … re-charter
of" the `12057` ruling, "the maintainer adopted retirement … 2026-08-29
… and" it "charters the full coupling set the fork report measured").
- `11513`: ADR-0126 names it the lock-and-clone ruling of 2026-08-24
(Salesforce-style: lock the packaged base, customize a clone), and its
section 9 records the per-field overlay layer as explicitly not
chartered.
- **One ruling read from `main`, not from its card.** `12057` answers
200 but carries only its triage comment; the 2026-08-29 ruling the entry
names is recorded in `packages/metadata/CHANGELOG.md`, and the sentence
says only what that record says.
- **Short numbers, ruling-record ids and acknowledgements went too
(invisible to the regex).** Eleven decision-batch numbers (`objectstack-ai#43` ×2,
`objectstack-ai#59` ×2, `objectstack-ai#122`, `objectstack-ai#127`, `objectstack-ai#128`, `objectstack-ai#145`, `objectstack-ai#215`, `objectstack-ai#218`, `objectstack-ai#221`) and
five ruling-record comment ids (in `field-currency-scale-refused`,
`field-predicate-reference-traversal-refused` and
`dataset-filter-nested-relation-equality-array-refused-at-save`) are
numbers an author is shown and cannot follow, so each is dropped. So are
five maintainer acknowledgements (「同意,其他也同意」 in
`api-assembled-entry-split`, 「同意」 ×3 in `export-job-family-retired`,
「同意」 in `metadata-customization-protocol-retired`): they record only
that a batch was approved, and each sentence now states the ruling's
date and content instead. The two quotes that carry the lesson stay
verbatim: "both legs, table in spec" and 「`min`/`max` numeric plus
`date`/`datetime`; everything else refused」.
- **Three `Prime Directive objectstack-ai#12` / `PD objectstack-ai#12` spellings are kept.** They
name a rule in this repository's AGENTS.md, not a tracker item, like the
ADR ids; the earlier stages kept the same spellings in the `ui-`,
`plugin-` and `system-` families.
- **`surface`, per ruling A.**
`18.dataset-measure-aggregate-field-type-refused` was the only entry of
these families whose `surface` carried tracker ids (two). Its header now
names the later widening and the sibling entry in words, and the AST
comparison shows nothing else in it moved. No test or tool reads that
`surface`: outside the migration tree, the pin and the generated
projections, the id appears only in three earlier changesets' ADR-0087
disposition markers (and this PR's changeset).
- **"issue NNNN" / "PR NNNN" spellings, checked by hand.** A scan of the
six families' evaluated prose for any run of three or more digits and
for `issue` / `card` / `PR` / `batch` / `record` / `summon` / `item`
plus a number now finds only HTTP statuses, ports, byte counts,
durations, dates, commit shas, SQLSTATE and TS error codes, ADR ids and
example values.
- **No test pinned a removed tracker number of these entries.** A search
of test files for the 25 entry ids finds only the pin,
`export-job-family-retirement.test.ts` (it asserts `not a D2 conversion`
and the backtick-free `surface`, both unchanged) and comment lines; a
search for the 75 cited numbers in `toMatch` / `toContain` assertions
finds only unrelated digit runs and runtime strings outside this card
(`api-endpoint-step.test.ts` and `endpoint-executor.test.ts` assert a
`5040` hint, which is objectstack-ai#20513's surface).
- **No open PR touches these six families.** Read twice: at the start of
this stage, 10 open PRs and 634 file rows; again just before opening
this one, 13 open PRs and 633 rows (the Version Packages PR `17076`
included both times). None carries a
`migrations/entries/semantic/NN.(field|export|api|dataset|hook|metadata)-*`
file. PR `20512` adds a retired-key file
`18.api__RestApiConfig__documentation.version.ts`; a retired key has no
id and is not in `step.semantic`, so the widened `api-` prefix does not
select it. PRs `20512`, `20504`, `20460` and `20458` add entries in
other families (`rest-`, `turso-`, `stack-`, `cube-`) and regenerate
`registry.ts`: ordinary concurrency, regenerate on merge.
- **Generated projections** (`spec-changes.json`,
`docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1–4;
only the protocol-17 entries appear in them.
- **What later stages pick up** (whole tree at this head, same
instrument): **461** prose-field sites in the other families, **50**
short numbers, **5** `surface` sites.

## Line budget

Entry files: **265 changed lines** (+147 / −118) across 25 files,
against the stage-1 ≈400 budget. The whole diff is **631 lines** (+372 /
−259) in 30 files. Of the rest, `registry.ts` is 265, the two
projections are 44 (`spec-changes.json` 24, the upgrade guide 20), the
widened pin is 29 and the changeset 28.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…-*, package-*, object-*, sharing-*, audit-*, flow-* and http-* migration entries states each lesson in words, not tracker numbers (stage 6) (objectstack-ai#20536)

Part of objectstack-ai#20233
Stage 6: the rest-, analytics-, view-, package-, object-, sharing-,
audit-, flow- and http- families.

Clause-②: no

**Stage 6 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `from` / `to`,
conversion or matching logic moves, and the chain rewrites exactly what
it rewrote before. One `surface` moves, under ruling A of the stage-1
ACCEPT (`5858839916`): it carried two tracker numbers.

## What this does

`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on card `19123` (`5749154545`) sets the shape:
the lesson in words, and no number, dead or alive; a cross-repo number
is still a tracker number.

This stage covers the nine families `rest-`, `analytics-`, `view-`,
`package-`, `object-`, `sharing-`, `audit-`, `flow-` and `http-`: **122
sites → 0** in the three prose fields and **2 → 0** in `surface`, across
33 entry files. It also takes the two carry-overs the stage-5 record
(`5880299859`) named:

- `18.api-error-retry-after-unit-in-key`: the clause on the ~16
runtime-emitted measurements and `ApiError.retryAfter` now dates the
ruling that decided it — "its 2026-09-05 population ruling" — instead of
reading under ruling B's 2026-09-02 date alone.
- `18.inline-grid-column-currency-scale-refused`: the two ruling-record
ids and the batch / item numbers are replaced by the rulings' dates and
options, the same rewrite stage 5 gave its `field-` sibling.

Each site now says what the cited ruling, measurement or fix decided.
ADR ids stay, and so does `Prime Directive objectstack-ai#10` in
`18.package-manifest-version-grammar-enforced` (a rule in AGENTS.md, as
stages 2–5 kept `objectstack-ai#10` / `objectstack-ai#12`). `registry.ts`, `spec-changes.json` and
`docs/protocol-upgrade-guide.md` are regenerated from the entries
(`gen:migration-registry`, `gen:spec-changes`, `gen:upgrade-guide`),
never hand-edited. The pin now holds twenty-seven families.

## Census — tracker ids in the author-shown fields

**Instrument.** The stage-4 / stage-5 TypeScript-AST census, the same
script: for each entry object literal under
`packages/spec/src/migrations/entries/**` it evaluates `replacement`,
`reason`, `acceptanceCriteria` and (separately) `surface`, joining
string literals with `+`, and counts `#` followed by 4 or 5 digits at a
word boundary. On base `fb386074` it reads the whole tree at **461**
sites / 5 `surface` / 50 short, which is the stage-5 record's
after-count. Unevaluable fields: 0. 313 semantic entries.

**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), before and after.
- **Dark (comment lines):** 832 `//` / docblock lines in entry files
carry a tracker id, and none is counted; 832 before and after. Comment
lines are the sibling card's surface (the one that owns every comment
and docblock line), and this PR touches none (proved below).

**Base `fb386074`:** `rest-` 6 entries, **17** sites (0 / 17 / 0);
`view-` 10, **15** (0 / 13 / 2); `analytics-` 6, **14** (2 / 12 / 0);
`audit-` 2, **14** (2 / 12 / 0); `sharing-` 2, **14** (1 / 13 / 0);
`http-` 2, **13** (0 / 13 / 0); `object-` 7, **13** (1 / 11 / 1);
`flow-` 6, **11** (0 / 11 / 0) plus **2** in `surface`; `package-` 6,
**11** (0 / 10 / 1). **122** sites (6 / 112 / 4) in 31 of the 47
entries; 91 distinct ids read (82 in this repository, 8 in objectui, 1
`hotcrm#`), plus five ruling-record comment ids. Short numbers in the
nine families: 9 (one kept, the Prime Directive).

**After this PR:** all nine families **0**, `surface` 0; the seventeen
earlier families still 0; whole tree **461 → 339**, `surface` **5 → 3**,
short **50 → 40** (the two `inline-` batch numbers included). The PM's
rough line count (about 133 sites, about 30 files) is a wider line
instrument; the AST reading is 122 in 31 files.

| entry | sites (replacement / reason / acceptanceCriteria) | surface |
short numbers | record ids |
|---|---|---|---|---|
| `17.analytics-query-request-envelope-retired` | 1 (0 / 1 / 0) | | | |
| `17.audit-log-action-enum-retired` | 4 (0 / 4 / 0) |  |  |  |
| `17.audit-log-action-restore-retired` | 10 (2 / 8 / 0) |  |  |  |
| `17.flow-retry-max-retries-required` | 1 (0 / 1 / 0) |  |  |  |
| `17.http-request-errors-total-retired` | 7 (0 / 7 / 0) |  |  |  |
| `17.http-server-runtime-vocabulary-retired` | 6 (0 / 6 / 0) |  |  |  |
| `17.package-uninstall-explicit-all-tenants` | 3 (0 / 2 / 1) | | 1 | |
| `17.rest-server-openapi31-block-removed` | 2 (0 / 2 / 0) |  |  |  |
| `17.sharing-execution-context-retired` | 11 (1 / 10 / 0) |  |  |  |
| `17.sharing-rule-recipient-reconcile` | 3 (0 / 3 / 0) |  |  |  |
| `17.view-filter-rule-value-shaped-by-operator` | 4 (0 / 3 / 1) | | | |
| `17.view-management-protocol-retired` | 3 (0 / 2 / 1) |  |  |  |
| `18.analytics-authorable-unknown-keys-refused` | 3 (0 / 3 / 0) | | | |
| `18.analytics-date-range-array-two-bounds-required` | 7 (2 / 5 / 0) |
| 1 | |
| `18.analytics-time-dimension-date-range-vocabulary-closed` | 3 (0 / 3
/ 0) | | 1 | |
| `18.api-error-retry-after-unit-in-key` | 0 |  |  |  |
| `18.flow-decision-branch-expression-absent-refused` | 1 (0 / 1 / 0) |
| | |
| `18.flow-decision-edge-branching-first-match` | 1 (0 / 1 / 0) | | | |
| `18.flow-edge-condition-evaluated-slot-source-required` | 4 (0 / 4 /
0) | 2 | | 1 |
| `18.flow-predicate-slot-blank-string-refused` | 4 (0 / 4 / 0) | | | 1
|
| `18.inline-grid-column-currency-scale-refused` | 0 |  | 2 | 2 |
| `18.object-block-sort-item-array` | 3 (0 / 3 / 0) |  | 1 |  |
| `18.object-grid-data-view-data-converged` | 4 (0 / 3 / 1) |  |  |  |
| `18.object-grid-default-filters-rule-array` | 2 (0 / 2 / 0) |  |  |  |
| `18.object-index-unknown-keys-refused` | 4 (1 / 3 / 0) |  |  |  |
| `18.package-api-contracts-unmounted-entries-retired` | 3 (0 / 3 / 0) |
| 1 | |
| `18.package-install-request-unknown-keys-refused` | 0 |  | 1 | 1 |
| `18.package-rollback-response-retired` | 5 (0 / 5 / 0) |  |  |  |
| `18.rest-api-endpoint-handler-status-retired` | 7 (0 / 7 / 0) | | 1 |
|
| `18.rest-api-plugin-durations-unit-in-key` | 3 (0 / 3 / 0) |  | 1 |  |
| `18.rest-server-config-dead-keys-retired` | 5 (0 / 5 / 0) |  |  |  |
| `18.view-filter-rule-absent-value-refused` | 2 (0 / 2 / 0) |  |  |  |
| `18.view-filter-rule-scalar-operator-array-refused` | 4 (0 / 4 / 0) |
| | |
| `18.view-overlay-options-bag-judged` | 0 |  |  |  |
| `18.view-pagination-page-size-default-50` | 2 (0 / 2 / 0) |  |  |  |
| **total, 35 entries** | **122 (6 / 112 / 4)** | **2** | **10 (0
kept)** | **5** |

The fourteen entries of these families that carried no number are
untouched: `analytics-cube-public-default-visible-enforced`,
`analytics-query-request-format-retired`,
`flow-node-config-required-keys-refused`,
`object-grid-default-sort-retired`, `object-kanban-quick-add-retired`,
`object-tenancy-organization-field-retired`,
`package-manifest-version-grammar-enforced` (its one short number is the
kept Prime Directive), `package-version-row-semver-2-0-0`,
`rest-api-config-dead-keys-retired`,
`rest-api-documentation-version-retired` (the new entry from the landed
`rest-` change: its prose was read and is clean),
`view-filter-rule-operator-input-canonical`,
`view-item-owner-hidden-retired`, `view-overlay-judged-by-viewkind-arm`,
`view-overlay-owner-hidden-retired`. Four of the 35 changed entries
carried no four- or five-digit id: `view-overlay-options-bag-judged` (a
provenance-only acknowledgement quote),
`package-install-request-unknown-keys-refused` (a batch number and a
ruling-record id) and the two carry-overs.

## Text only — proved by a base-vs-head AST comparison

For every entry file this PR changes, both versions (`fb386074` and the
head) are parsed and compared: every import declaration; every property
other than the three prose fields, by evaluated value (so `id`, `from` /
`to` and any matcher); every comment token in the file; and the code
skeleton, token by token with each run of joined string literals
collapsed to one. `surface` is allowed to differ only where the base
value carried a tracker id and the head value carries none. **35 files
compared, 0 with a non-prose change**; one note, the ruling-A `surface`
of `18.flow-edge-condition-evaluated-slot-source-required`. The
instrument is shown able to fail first: on an in-memory copy it reports
DETECTED for a mutated `id`, a mutated comment, a mutated `surface`
whose base carried no tracker id, a mutated code token and a mutated
import, and stays dark on a prose-only mutation. So none of the sibling
card's comment lines moved, and no entry's identity or matching moved.

`registry.ts`, compared the same way: comment tokens, imports and code
skeleton identical; all 1,572 non-prose properties identical by value
(container literals compared through their children); exactly the 35
changed ids differ; and all 313 head registry entries equal the head
entry files on `surface` / `replacement` / `reason` /
`acceptanceCriteria`.

## Every citation read, and what the text now says

Each cited id was read with a single-card REST read (body plus the
ruling, measurement or landing comments), resolved against the
repository its sentence names: 82 in this repository and 8 in
`objectstack-ai/objectui` (one bare id resolves there: the sort ruling's
consumer change `8758` is "objectui PR" in its sentence). The five
ruling-record comment ids were read by id. `hotcrm#1555` answers 403 to
this session and is rewritten from what `main` records. Ids are in code
spans so this body posts no cross-references. **8 of this repository's
ids answer 404** on the issues endpoint and again on the pulls endpoint
(`6206`, `6239`, `6511`, `6523`, `10004`, `14369`, `14691`, `17124`;
control `6209` answers 200); their sentences are rewritten from what
`main` records, listed in Acceptance notes.

**`rest-` (14 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `3197` | An audit: several event / subscription / connector webhook
enums are schema-only, declared with no runtime consumer. | "the
declared-but-unconsumed shape an earlier audit found in the connector
webhook and event enums one layer up" |
| `4579` | This retirement's own card (`openApi31` declared, never
enforced). | trailing id dropped; "(the `openApi31` precedent)" |
| `13823` | Maintainer, 2026-09-01: remove `handlerStatus` with a
tombstone; enforce excluded; the class direction recorded for two
sibling cards. | "maintainer ruling of 2026-09-01 on this key: remove it
with a tombstone; enforce excluded"; trailing id dropped |
| `5384` | `ApiEndpointSchema` was still an open object after `api`
became a registered metadata type; closed strictly. | "the same endpoint
vocabulary whose ApiEndpointSchema had already been closed strictly once
`api` became a registered metadata type" |
| `13808` (PR) | A factual sweep of the automation skill; one corrected
sentence taught `handlerStatus` as working machinery. | "this finding
came out of correcting that skill sentence, in a factual sweep of the
automation skill" |
| `3950` (PR) | The precedent that an exported value schema with no
consumer reads as a capability, so it leaves with its key. | "an
exported value schema with no consumer reads as a capability, so it
leaves with its key" |
| `13612`, `13613` | The unbound branded identifier schemas;
`EventNameSchema`'s binding schemas with no runtime consumer. | "two
sibling ADR-0049 findings (the unbound branded identifier schemas and
the event-name schema no runtime reads; not ruled by it)" |
| `14478`, `15677` | Maintainer ruling B on duration units (2026-09-02),
its population widened 2026-09-05; the `api/` stack of that ruling. |
the stage-3 wording, naming both dates; trailing ids dropped |
| `14369` | **404** — see Acceptance notes. | "The liveness census that
enrolled the four `RestServerConfig` sub-objects" |
| `11984` | `normalizeConfig` cast the four sub-objects instead of
parsing them; it parses them since. | "(which parses them, rather than
casting them, since an earlier fix)" |
| `14796` | A closed-set sweep of the cloud repository for the 15 dead
keys: zero hits. | "A closed-set sweep of the cloud repository at
9b6abe0f2fd5: zero hits" |
| `14691` | **404** — this retirement's own card. | trailing id dropped
|

**`analytics-` (10 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `3891` | The degraded analytics shim (the fallback serving
`/analytics/query` with no analytics service installed) dropped the
caller's identity and the contract's `where` filter at its door. | "the
retired degraded analytics shim (the fallback that answered
/analytics/query when no analytics service was installed, and dropped
the caller's identity and its `where` filter at the door)" |
| `4001` | The unknown-key strictness campaign: silent stripping of
undeclared keys ends as the default, schema family by schema family. |
"The unknown-key strictness campaign (the sweep that ended silent
stripping of undeclared keys as the default, one schema family at a
time), its data/ batch" |
| `3878` | One URL, two request bodies (the shim's envelope vs the bare
query); the envelope dialect was retired. | "strict since the degraded
shim's envelope dialect was retired (one URL, one request body)" |
| `10414` | `MetricSchema.filters` was authorable with zero consumers;
removed. | "removed later in this major, because nothing ever read it:
`metric-filters-removed`" |
| `17598` | Maintainer ruling A, 2026-09-12, re-affirmed 2026-09-13: the
array arm refuses anything but exactly two string bounds. | "Maintainer
ruling A of 2026-09-12, re-affirmed 2026-09-13, which tightened the
array arm to exactly two string bounds" |
| `16322` | The driver half of the closed preset vocabulary; its
migration table is the vocabulary entry's, unchanged (single day as the
same date twice). | "the shipped migration table for the closed preset
vocabulary"; "in a driver change of their own" |
| `17593` (PR) | All four analytics faces read the array arm through one
rule and refuse the rest with the ADR-0112 envelope. | "since the fix
that made them read the array arm one way"; "The fix that followed made
all four faces refuse it"; "Since that fix" |
| `17124` | **404** — see Acceptance notes. | "a measurement of one
authored document on each face found what that bought" |
| `16041` | Maintainer, 2026-09-06, option A (contract first): the
string arm closes to the declared preset vocabulary. | "Maintainer
ruling of 2026-09-06 on the analytics date-range string (option A —
contract first)" |
| `4614` | The preset list, once three copies (spec, objectui, docs),
became one source of truth. | "since the dashboard date filter's three
copies of the list were folded into it" |

**`view-` (10 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `5869`, `6209` (PR) | A scalar comparand on `in` / `not_in` answered
500; now a named 400 INVALID_FILTER. | "An earlier fix closed the
RUNTIME half"; the trailing `(5869)` in `acceptanceCriteria` dropped |
| `5685` | The ordering operators' comparand was widened to the strings
the platform itself produces (a schema stricter than the runtime was the
wrong side). | "an earlier fix already settled the opposite error (the
ordering operators' comparand widened to the strings the platform itself
produces)" |
| `5948` | The issue asking what `GET /ui/view/:object/:type` answers,
and its 2026-08-07 ruling, both read `GetViewResponseSchema`. | "The
issue asking what `GET /ui/view/:object/:type` answers AND its
2026-08-07 maintainer ruling"; "the shapes that ruling meant" |
| `6239` | **404** — this removal's own change (recorded in the client
and spec CHANGELOGs). | trailing id dropped |
| `19751`, `19514` | The absent-value and scalar-array findings (this
and its sibling entry's own cards). | leading ids dropped |
| `6227` | `ViewFilterRuleSchema.value` shaped by its operator (the
`view-filter-rule-value-shaped-by-operator` entry). | "since the value
was first shaped by its operator"; "from then until this change" |
| `objectui#9050` | Maintainer ruling C′, 2026-09-20: the protocol is
the only refusal set; render time never throws on a protocol-valid
document. | "the maintainer's ruling C-prime of 2026-09-20 on objectui's
render-time filter converter — the protocol is the only refusal set, so
a document it accepts never throws at render time"; the lesson quote
「the differences are the protocol's to close」 kept verbatim |
| `objectui#9853` | Maintainer, 2026-09-24: the display default page
size is 50, declared once in the protocol; objectui reads the spec
default and never hardcodes it. | "the maintainer's ruling of 2026-09-24
set the platform display page size to 50, declared once in the
protocol"; "(an earlier ruling on the grid's page size, which the
page-size ruling restated)" |
| (no id) `view-overlay-options-bag-judged` | Maintainer, 2026-09-24
(objectui's overlay-options card), option A: judge each `options.KIND`
at the door. | 「其他同意」 replaced by "the maintainer's ruling of
2026-09-24" |

**`package-` (8 ids and one ruling record)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7705`, `7780` | Uninstall left orphaned rows (repaired); measured
there: an org-less uninstall deleted every organization's rows. |
"measured at 5 of 5 deleted, including a foreign org's, while
uninstall's orphaned-row defect was being repaired"; "exactly as the
orphaned-row repair left it" |
| `objectstack-ai#12` (short) | Not a tracker id: `rest-requireauth-default-flip`
lived in protocol 12. | "(protocol 12)", the spelling four sibling
entries use |
| `19116` | Maintainer, 2026-09-23, option A: retire the three
contract-map entries naming paths nothing mounts. | "Maintainer ruling
of 2026-09-23 (option A: retire the three contract-map entries that name
paths nothing mounts)" |
| `18604` | The measurement on one `HttpDispatcher` over a real
`SchemaRegistry`. | the measurement was already in the sentence; the id
is dropped |
| `18058` | `installPackage` rebound onto the serving `POST
/api/v1/packages`. | "rebound by an earlier fix onto the serving POST
/api/v1/packages" |
| `5856869656` (record) | Maintainer, 2026-09-27, option A: the wrapped
install form refuses an unknown top-level key by name. | "the
maintainer's ruling of 2026-09-27, option A: the wrapped form refuses an
unknown top-level key by name" |
| `12038` | Maintainer, 2026-08-27, sub-question 3A: retire the false
rollback declaration first, then author the true one. | "Maintainer
ruling of 2026-08-27 on the client SDK's unbound response contracts,
sub-question 3A: retire this false declaration first, then author the
true one"; "the ruling's own survey" |
| `11925` | Typed the SDK's un-annotated return values, keeping
compile-time guards against a wrong-contract substitution. | "the change
that typed the SDK's un-annotated return values left a compile-time
guard"; "that negative guard" |
| `3877` | Response bodies are never checked against the schemas that
declare them. | "the hazard of response bodies never checked against the
schemas that declare them, realised in the opposite direction" |

**`object-` (11 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `objectui#8221` | Maintainer, 2026-09-07, option B: the legacy string
`sort` clause retired, one spelling (the array); its fourth item routes
the `ComponentPropsMap` pull-back here. | "the maintainer's ruling of
2026-09-07 (option B) retired the legacy string `sort` clause"; "One
item of that ruling"; the item's quote kept verbatim |
| `8758` (objectui PR) | The consumer half: the string arm dropped from
`convertSortToQueryParams`. | "the objectui change that drops the string
arm from `convertSortToQueryParams`" |
| `7751` | Maintainer, 2026-08-12, direction A: the `object-*` block
family's props schemas enter `ComponentPropsMap`. | "a read-point record
from the change that brought the `object-*` blocks into
`ComponentPropsMap` (the maintainer's ruling of 2026-08-12)" |
| `objectui#6207` | Found by objectui's declared-arm parity gate: two
spec authorities disagreed on `data`'s kind. Ruled 2026-08-25, option A.
| "(contract-vs-contract, found by objectui's declared-arm parity
gate)"; "The maintainer's ruling of 2026-08-25 (option A)"; "closes the
objectui finding that the two authorities disagreed" |
| `objectui#5090` | The grid's registry declaration of `data` was
aligned to `ViewData`. | "the authority objectui aligned the grid's
registry declaration to" |
| `objectui#4648` | Its deprecated-alias carve-out: object-grid's
deprecated spellings (`staticData` among them) are not published as
authoring surface. | "the deprecated `staticData` shortcut that
objectui's deprecated-alias carve-out already refuses to publish as
authoring surface" |
| `19514`, `objectui#9050` | As in `view-`. | as in `view-` |
| `objectui#4772` | The console's index fallback editor converged onto
`IndexSchema`, dropping `where`. | "removed when objectui converged that
editor onto `IndexSchema`"; "objectui then converged that editor" |
| `4001` | As in `analytics-`. | "The unknown-key strictness campaign
held this site open" (its internal batch and site numbers dropped) |
| `5114` | The console's filter save answered 422: a strict schema
refused a key the console itself writes. | "a measured risk, the kind
that had already made a console save answer 422 (a strict schema
refusing a key the console itself writes)" |

**`sharing-` (11 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `6206` | **404** — see Acceptance notes. | "completing the
maintainer's ruling of 2026-08-07 on the share-link context (enforcement
adjudicates on the WHOLE envelope, never a per-site subset)" |
| `6430`, `6511` | The share-link enforcement path moved onto the full
context under that ruling (`6511` answers 404). | "the share-link twin,
which that same ruling moved onto the whole context" |
| `6523`, `7068` (PR) | The sharing, approval and report contracts
converged onto the full envelope (`6523` answers 404). | "the envelope
the sharing, approval and report contracts have declared since they
converged onto it"; "One change converged the contracts" |
| `7140` (PR), `7206` (PR), `7070` | The four implementations
re-annotated (sharing and audit, then approvals and reports); the split
that deferred the type's deletion. | "two more re-annotated the four
implementations (sharing and audit, then approvals and reports)"; "the
deletion that split had deferred" |
| `7218` | This retirement's own card. | trailing ids dropped |
| `1878` | The metadata property liveness audit: security properties
parsed but never enforced. | "The change came out of the metadata
property liveness audit, which found security properties parsed but
never enforced" |
| `6350` | The stock reconciliation of the v17 train's breaking
changesets, which backfilled this entry. | "registered late, by the
stock reconciliation that compared the breaking changesets already on
the v17 release train against this ledger" |

**`audit-` (8 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7675` | Maintainer, 2026-08-12: two halves — build the cheap writers,
retire the enum values with no feature; principle recorded 「空 widget +
永远查不到东西的过滤器是可见产品缺陷;审计面宁窄勿谎」 (kept verbatim, it is the lesson). |
"Maintainer ruling 2026-08-12 on the audit log's writerless actions";
"that ruling's own survey" |
| `8144`, `8145` | The `login` / `logout` writers on the auth session
hooks; `config_change` from the settings service. | "(`login` / `logout`
on the auth session hooks, `config_change` from the settings service)" |
| `8147`, `8315` | The two retirements' own cards. | trailing ids
dropped; "(triage 2026-08-13)" kept |
| `1883`, `3146` | The undelete / purge permission lifecycle and the
soft-delete recycle bin: both open, held, not declined. | "(an undelete
/ purge permission lifecycle and a soft-delete recycle bin, neither
built yet)"; "both held open, not declined" |
| `8011` | A credential-storage audit had to re-measure two "hashed at
rest" comments; resolved by making the declaration cite its mechanism. |
"(the shape a credential-storage audit had to settle by re-measuring two
"hashed at rest" comments)" |

**`flow-` (11 ids and two ruling records)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4247` | `maxRetries` had two defaults (schema 0, engine 3). | the
measurement was already in the sentence; the id is dropped |
| `19961` | This refusal's own card. | leading id dropped |
| `hotcrm#1555` | **403** — see Acceptance notes. | "a CRM application's
lead-conversion flow rendered a refusal screen AND ran the conversion in
one execution" |
| `17322`, `17495` (in `surface`) | The node slot rebound to the edge
door's rule at `registerFlow`, then at `objectstack validate`. | "The
node slot joined this entry with the two later changes that rebound
AutomationEngine.registerFlow and objectstack validate to the edge
door's own rule" |
| `15807`, `15430`, `15662` | The evaluated-slot rule: an `ast`-only
envelope no engine can evaluate refused; a non-string node predicate
refused at registration rather than answered a silent `false`; carried
to the edge. | "The evaluated-slot rule, carried to the edge condition —
the line that first refused an `ast`-only envelope no engine can
evaluate, and refused a non-string node predicate at registration
instead of letting the evaluator answer it a silent `false`" |
| `5550509137` (record) | 2026-09-05: an `ast`-only envelope driven
through `AutomationEngine.evaluateCondition` answers `false`. |
"(measured on 2026-09-05 by driving an `ast`-only envelope through
`AutomationEngine.evaluateCondition` directly)" |
| `17493`, `5651023407` (record) | Maintainer ruling A, 2026-09-13: the
two sibling predicate slots refuse a blank string at authoring. |
"Maintainer ruling A of 2026-09-13: the two sibling predicate slots
refuse a blank string at authoring" |
| `15572` | Its pin had held the blank admission correct because parser
and evaluator agreed. | "An earlier fix had pinned that admission as
correct" |
| `17322`, `15811` | The structural `config.condition` and every
evaluated `source` refuse a blank. | "after the structural
`config.condition` and a blank evaluated `source`" |

**`http-` (11 ids)**

| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `9650`, `9835`, `9834`, `10004` | The request counter, then the
latency histogram, moved to the transport through the response-observing
hook (`10004` answers 404). | "(the request counter, then the latency
histogram, both through the response-observing hook the transport was
given)" |
| `5122`, `3977` | The origin cards of the two named sibling entries. |
named by those entries' ids, which the sentence already carried |
| `9834`, `5295` | This retirement's own cards. | trailing ids dropped |
| `4938` | The CONFIG half of `system/http-server.zod.ts` removed. |
"The first removed the CONFIG half"; "the config half's removal in this
very file" |
| `4834`, `4988`, `5055` | The dynamic plugin-loading family, the `ui/`
interaction configs, the widget / i18n shapes — each removed by route 3.
| "the earlier removals of the dynamic plugin-loading family, the `ui/`
interaction configs and the widget / i18n shapes" |

**Beyond the four- and five-digit regex.** Nine decision-batch numbers
(`objectstack-ai#27`, `objectstack-ai#43`, `objectstack-ai#57`, `objectstack-ai#77`, `objectstack-ai#117`, `objectstack-ai#215`, `objectstack-ai#217`, `objectstack-ai#218`, `objectstack-ai#227`),
their item numbers, the campaign's internal batch and site numbers,
`batch adjudication batch 4`, 「五问一批」 and "C′ item 1" were dropped; each
sentence now carries the ruling's date and content. The provenance-only
acknowledgements 「同意」 (×2), 「其他同意」 (×2), 「217 同意」, 「其他接受」 and 「9853
默认页大小改为50」 (the ruling's content, 50, is stated in words; the quote
itself carried a tracker number) were replaced by the ruling's date and
content. The lesson-bearing quotes are kept verbatim: 「the differences
are the protocol's to close」 (×2), the sort ruling's fourth item, 「every
other operator takes a scalar」, 「lowers to a deep-equality comparand」,
「`persistViewPatch` 只存 patch,不存 merged base」 and the audit ruling's
原则记录.

## Pin — widened, not weakened

`packages/cli/test/migrate-meta-engine-guidance.test.ts`:
`COVERED_PREFIXES` 17 → **27** (`rest-`, `analytics-`, `view-`,
`package-`, `object-`, `sharing-`, `audit-`, `flow-`, `http-`, and
`inline-` for the carry-over entry, the `inline-` family's only entry,
now tracker-free); `package-` selects no `packages-` entry. `REWRITTEN`
113 → **147**: the 34 ids this stage rewrote for the first time (the 33
nine-family entries plus `inline-grid-column-currency-scale-refused`;
`api-error-retry-after-unit-in-key` was already listed). The header
comment names the new families; the three `it` blocks and every `expect`
are textually unchanged.

**Ablation, from committed HEAD `472ee29b82`, one lock turn**
(`scripts/ablation-replace.mjs` wrap mode, a restore trap by absolute
path with a blob check, `scripts/ablation-dist-preflight.mjs`):
`registry.ts`, `http-server-runtime-vocabulary-retired`'s `reason`,
anchor `behind it, vocabulary second. ADR-0049.` → `behind it,
vocabulary second. ADR-0049, objectstack-ai#5295.` (anchor 1 → 0, replacement 0 → 1,
blob `06c06741` → `702e7370`). Mutate leg: spec build exit 0; the marker
in 4 `dist` files; the pin **RED**, 1 failed | 2 passed:
`http-server-runtime-vocabulary-retired: the printed guidance cites a
tracker id: expected 'objectstack-ai#5295' to be undefined`. Restore proven by the
tool (blob == HEAD `06c06741`, `git diff HEAD` empty). Restore leg: spec
build exit 0; the marker absent from all 224 `dist` files with the tree
clean; the pin **GREEN**, 3 passed; whole tree 0 dirty paths.

No other test pins a removed number: on `main`, the tests naming any of
the 35 entry ids (`sys-audit-log-retired-actions`,
`plugin-rest-api.handler-status-retirement`,
`rest-api-config-dead-keys-retirement`,
`inline-grid-column-currency-scale-refused`,
`component-object-grid-default-filters.pin`,
`view-overlay-owner-hidden-retirement`, and two that name them in
passing) assert ids, surfaces and prescriptions this PR does not move,
not the prose it rewrites.

## Generated artifacts

- `registry.ts`: 313 semantic, 232 retired-key, 206 retired-def; exactly
the 35 ids differ (see the AST comparison above).
- `spec-changes.json` and `docs/protocol-upgrade-guide.md`: only the
protocol-17 entries appear in them, as the generators project; every
changed line is a fragment of a changed entry's field.
-
`.changeset/20233-rest-analytics-view-package-object-sharing-audit-flow-http-migration-guidance-tracker-free.md`:
`'@objectstack/spec': patch`, `Clause-②: no`.

## Verification (head `472ee29b82`)

Every heavy run through `scripts/pm/os-verify-lock.sh`, each exit code
written to disk before it was read.

- **Build:** `turbo run build --concurrency=2
--filter='@objectstack/cli^...'` → Tasks: 58 successful, 58 total
(VERDICT command-exit 0); plus the 10 packages outside that closure for
the dual-build gate → Tasks: 68 successful, 68 total.
- **Pin + neighbour:** `pnpm --filter @objectstack/cli exec vitest run
--project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` → Test Files 2 passed (2),
Tests 10 passed | 1 skipped (the default-range file's own `skipIf`).
- **CLI unit (the tests that read the registry or
`spec-changes.json`):** `spec-release-changes`, `meta.stored-flags`,
`doctor-deprecation-hint-commands`, `vitest-tiers-partition` → Test
Files 4 passed (4), Tests 48 passed (48).
- **Spec:** `--project local` → Test Files 573 passed (573), Tests 16835
passed | 1 todo; `--project repo` → Test Files 39 passed (39), Tests 701
passed (701); `src/migrations/migrations.test.ts` alone → Tests 150
passed (150).
- **Typecheck:** `pnpm --filter @objectstack/spec typecheck` exit 0
(test layer 53 files / 251 errors held); `pnpm --filter @objectstack/cli
typecheck` exit 0 (3 files / 28 errors held).
- **Gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 89 families over this diff; all 89
run, all exit 0; `--ran` → "89 derived, 89 run, 0 NOT-MEASURED, 0
UNRUN". Among them: `check:doc-authoring` ("16759 customer-facing
string(s) across 1172 spec sources clean"), `check:generated` ("All 15
generated artifacts are up to date"), `check:migration-registry`
("registry.ts is current (313 semantic, 232 retired-key, 206
retired-def)"), `check:spec-changes`, `check:upgrade-guide`,
`check:issue-citations` ("no issue citations added"),
`check:org-identifier`, `check:nul-bytes`, `check:api-surface`,
`check:authorable-surface`, `check:dual-build-cjs-loads` (104 require
entry points across 66 packages load), `check:type-check-debt`,
`check:adr-0087-registration`, `check:changeset-no-major`,
`check:empty-changeset`.
- **Lint, a proven narrowing:** `eslint --no-inline-config --format
json` over the 37 changed `.ts` files → 37 files, 0 errors, 0 warnings,
no file-ignored notice. Population read from `eslint.config.mjs`
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`);
invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so this diff cannot move any
untouched file's verdict. The full `pnpm lint` is CI's.
- **Mergeability:** `origin/main` is `1378ec7c`, three commits past the
base, none touching the migration ledger or its projections; a
driver-free bare-clone `merge-tree --write-tree` of the head against it
exits 0 with no conflicted path. `registry.ts` is shared with open PRs
`20504`, `20460` and `20458`, ordinary concurrency; no open PR touches
any of the 35 entry files or the pin.

## Acceptance notes

**404 and 403 ids, rewritten from what `main` records.**
- `6206` (the share-link ruling of 2026-08-07):
`packages/core/src/security/assemble-execution-context.ts` (the
share-link copies omitted `accessible_org_ids`; both surfaces converted
to pass the whole envelope) and
`packages/plugins/plugin-approvals/CHANGELOG.md` ("applying the ...
ruling — enforcement adjudicates on the whole envelope, never a per-site
subset").
- `6523` / `6511`: the same CHANGELOG ("converged 36 contract signatures
onto the complete `resolveAuthzContext` envelope"); `6430` (200) names
the share-link half.
- `6239`: `packages/client/CHANGELOG.md` records it as this removal
itself (retire `ViewProtocol`'s five viewId-addressed methods); dropped.
- `8758` as a bare id: its sentence names objectui, and `objectui#8758`
answers 200 (the string `sort` clause retired).
- `10004`: `packages/observability/src/semconv.ts` and the observability
CHANGELOG pair it with `9834` as the histogram's move to the transport
seam.
- `14369`: `docs/qa/platform-checklist/areas/api-backend.json` ("the
liveness census that found the block read by nothing") and the `14640`
changeset (it enrolled four of the five sub-objects). `14691`: the same
file records it as this retirement; dropped.
- `17124`: `.changeset/17124-daterange-array-arm-arity.md` records the
measurement (one authored document, four faces, three readings).
- `hotcrm#1555` (403 cross-repo):
`.changeset/15429-decision-edge-branching-first-match.md` records the
case and the node (`lead_conversion.decision_duplicate`).

**Observations, not filed.**
- Carrier: this card's later stages · 339 prose-field sites, 40 short
numbers and 3 `surface` sites remain in the other families (the largest:
`actor-` 13, `hot-` 12, `external-` 11, `query-` 11, `delete-` 10,
`stack-` 10); `stack-` and `turso-` have entries in open PRs. The pin
file keeps its stage-1 name while holding twenty-seven families.
- Carrier: the sibling card for comment and docblock lines · 832 comment
lines in entry files still cite tracker ids (unchanged), including the
header comments of `18.view-pagination-page-size-default-50.ts`,
`18.view-overlay-options-bag-judged.ts`,
`18.flow-decision-edge-branching-first-match.ts` (`hotcrm#1555`) and
`18.analytics-authorable-unknown-keys-refused.ts`.

Implemented-by: `claude/issue-20233-migrate-meta-tracker-free-stage-6` ·
`domain:spec` seat 4 dispatch, session
`session_01ARcDurZ5j34RdqsGgc4jgH`.

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

---------

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

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants