Skip to content

feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) - #20458

Merged
objectstack-fleet[bot] merged 15 commits into
mainfrom
claude/issue-20300-cube-member-inner-name-retired
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 15 commits into
mainfrom
claude/issue-20300-cube-member-inner-name-retired

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20300

Clause-②: no (narrowing)

Retires the inner name on analytics cube measures and dimensions (MetricSchema.name, DimensionSchema.name). measures and dimensions are records, and the record key was always the member's identity: GET /api/v1/analytics/meta publishes every member as CUBE.KEY, and every consumer resolves a member by indexing the bag with its key. The inner copy was REQUIRED, read by nothing, and silently ignored when it disagreed with its key.

ADR-0049 enforce-or-remove, by triage's verdict 5859547666 (RETIRE) under the maintainer's criterion, verbatim: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」. Cube.dev and LookML key a member by its declared name, with no second inner name that can disagree.

Tier H. The diff touches skills/objectstack-ui/rules/dashboards.md (a deletion only; see Deviations 1). It lands on the maintainer's word, then the seat lands it. 103 files, +1446 / -423 (1869 changed lines, under the 5,000 line class).

Patch round after #20390 (head f639af5f3)

The sections below describe c4771e604. This head adds two commits and nothing else:

The net diff is 103 files, +1447 / −423: the stamp is the one added line. CI is green on this head. At-tier record 5875291969: PASS.

What this head carries (c4771e604)

surface change
schema retiredKey() tombstones on MetricSchema.name and DimensionSchema.name (both strictObjects, the action.aria posture). tsc types the key never, and the parse raises the prescription at measures.KEY.name / dimensions.KEY.name. The measures / dimensions describes now state that the record key IS the member's name.
D2 cube-member-inner-name-removed (protocol 18, retiredFromLoadPath), chained into step18.conversionIds with a rationale paragraph. It strips the inner name from every member of every analyticsCubes[] entry, and its notice names the cube.
D3 semantic entry cube-member-inner-name-retired: the judgement a DISAGREEING value still owes its author (which spelling was meant).
registration RETIRED_KEYS_BY_MAJOR[18] gains data/Metric:name and data/Dimension:name (per-file entries, generated region).
ledger both analytics_cube.json rows STAY dead (the tombstone keeps the key in the walked shape) with a REMOVED 2026-09-28 note and a re-measured verifiedAt. The README row is updated; the counts do not move.
producers dataset-compiler.ts stops writing it (the triage line), and so do the two untyped internal mints tsc cannot see (CubeRegistry.inferFromObject, and inferCubeFromQuery / inferMeasure in analytics-service.ts).
authors the showcase cube (8 members), the service-analytics README example (3), and the published objectstack-ui skill example (6)
fixtures about 300 member literals and map-built members across 84 test and fixture files in eight packages; the three existing step-18 cube conversion fixtures are trimmed so the whole-table replay stays disjoint
pins packages/spec/src/data/cube-member-inner-name-retirement.test.ts covers every door (schema, /meta binding, defineCube, defineStack with its STACK_SCHEMA_INVALID/422 envelope, and a @ts-expect-error tsc leg), the D2 legs (stored row, boot door with a lit control, a disagreeing value, idempotence, load-path retirement), the registration, and a tree-scoped structural absence pin over the declared five-root radius. The flipped analytics.test.ts blocks (the snake_case pins on a value nothing read) are now tombstone pins.
changeset @objectstack/spec minor (BREAKING banner, FROM → TO, the one-line fix, what an author sees, and the ADR-0087 registered marker); @objectstack/service-analytics patch

What an author who still writes it sees: tsc fails at the authoring site. The parse refuses it with: "measures.METRIC.name was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it never had an effect: the record key is the metric's name. … Delete the key. To rename a metric, rename its key in measures — and every query, dashboard and report that names CUBE.KEY. Run os migrate meta --from 17 to list the mechanical edits for existing sources; apply them by hand." A stored or built cube heals at rehydration and at the artifact door.

Zone 2, measured

  • A1 holds. Zero reads of a member's inner name in non-test source (analytics-service.ts#getMeta and memory-analytics.ts#getMeta publish CUBE.KEY; native-sql-strategy.ts#lookupMember and memory-analytics.ts#resolveMeasure / #resolveDimension index the bag by key). Lit control: four reads of measure.label / dimension.label in the same two projections. The one measure.name hit (dataset-compiler.ts:383) is a DatasetMeasure, not a cube member. objectui at pin f8a9d0fb05: no cube-member authoring. CubeSchema is used only in clientValidation.ts (control: that hit resolves at the same sha).
  • A2 holds, and is wider than the card. Non-test producers: dataset-compiler.ts (2 sites), CubeRegistry.inferFromObject (3) and the ad-hoc mint in analytics-service.ts (4, plus inferMeasure's 3 returns), the showcase, the README and the skill. Every one wrote the name EQUAL to its key, so no producer writes a disagreeing value. Test fixtures: 21 disagreed, all in driver-memory (e.g. totalAmount: { name: 'total_amount' }), and every one was queried by its key (orders.totalAmount). That is the trap, live in-repo. No platform-objects or template authors any cube. Stored rows: analytics_cube wraps as analyticsCubes at applyConversionsToStoredItem, and the pin's stored-row leg replays it.
  • A3. Worked on the merged cube contract (public enforced, feat(analytics): enforce analytics_cube.public and default it to visible #20348 landed). Line numbers are from the merged tree.
  • A4. Merged origin/main 6e3e5462c (which carries feat(spec)!: retire the list view's own tabs key; named presets are listViews entries #20357's step-18 appends) with bash scripts/pm/os-regen-merge.sh.
    • The driverless merge-tree (a bare shared clone with no merge.os-regen.driver registered) answered exit 0 with no conflicted paths.
    • The script's step 2 took main's side of content/docs/references/data/analytics.mdx, which was regenerated from the merged tree in its own commit (e19628132).
    • After the merge: both sides' ids are present in both step-18 lists (view-list-tabs-removed and cube-member-inner-name-removed) and in the rationale.
    • After the merge: check:generated reported all 15 artifacts current, measured right after a spec build of the merged tree.

Deviations

  1. The skills/** split was ordered, then withdrawn. The seat ordered the skill hunk split into a companion PR, and withdrew that on this measurement:
    • The example is an os:check block that check:skill-examples type-checks inside typecheck-consumers, a member of the required TypeScript Type Check aggregator.
    • Putting one inner name back into the example with ablation-replace (restored to the HEAD blob, git diff HEAD empty) gave exit 1: dashboards.md:450:15 error TS2322: Type 'string' is not assignable to type 'undefined'. So this PR without the hunk is red.
    • A companion PR alone on main would be red too: at base dbddf02c1, MetricSchema.name is a REQUIRED z.string(). That half is derived from the schema, not built.
    • No landing order is green, so the hunk stays here, as a pure deletion.
  2. File surface wider than the claim, same package and same defect class. CubeRegistry.inferFromObject and the analytics-service.ts mints are untyped (a Record of any) producers that tsc cannot see; A2 put every producer in scope. Fixture edits span service-analytics, driver-memory, spec, client, objectql, runtime, qa/dogfood and qa/downstream-contract.
  3. Route. The spec-property-retirement skill's route table maps .strict() to deletion plus a guidance map. I took the triage's retiredKey() route instead, which shared/retired-key.ts documents for closed shapes (strictly stronger than a guidance entry) and which action.aria used this week. As a result the ledger rows stay, per the card's acceptance. The CubeJoinSchema docblock line that said cube shapes never take a tombstone is corrected.
  4. D2 strips a disagreeing value too. Triage: "lossless when it equals the key; a disagreeing value gets a D3 entry". The D3 entry exists. The strip still removes a disagreeing value because the key already won everywhere, so no answer changes, and leaving it would stop the cube loading at the boot door. The notice prints both spellings (from: name "total_amount", to: (removed; the record key "totalAmount" is the name)).
  5. service-analytics is graded patch. Its members are filed under the same keys, and every /analytics/* answer is unchanged. @objectstack/spec carries minor: a published narrowing ships minor in the launch window, and the changeset declares it as Clause-②: no (narrowing) under its BREAKING banner.

Tests (head c4771e604 unless stated)

  • @objectstack/spec:
    • test 564 files / 16641 tests green (merged tree e19628132; spec src/ is unchanged since).
    • test:repo 37 / 675 green (pre-merge c1cae40df). Post-merge, its three tree-reading legs this diff owns were re-run green: the retirement pin, retired-key-migrate-sentence, and build-schemas-check-mode (107 tests together with the pin).
    • typecheck green (src, scripts, and the test layer under its shrink-only ledger). tsc -p tsconfig.test.json --listFilesOnly lists the new pin, so its @ts-expect-error legs are live.
  • @objectstack/service-analytics: typecheck green, 132 files / 3093 tests green (14cac89f4).
  • @objectstack/driver-memory: typecheck (which reaches the test layer) green, 57 / 1374 green (14cac89f4). tsc found the two map-built members there; the same shape was then swept in service-analytics and runtime.
  • Touched test files in other packages: client 7/7, objectql protocol-meta 95/95, runtime cross-field-refusal-operand-withhold 11/11, downstream-contract contract.test.ts 13/13 plus typecheck exit 0.
  • Reverse verification. Putting name: m.name back in dataset-compiler.ts via ablation-replace gave src/dataset-compiler.ts(673,7): error TS2322: Type 'string' is not assignable to type 'undefined' against the rebuilt spec .d.ts. Restored: blob equals HEAD, git diff HEAD empty. The direction was the predicted one (red).
  • Gates. dispatch-gates.mjs --commands at c4771e604 derives 126 families. All 126 were run and recorded, and --ran reports 0 UNRUN.
    • 123 exit 0.
    • 2 exit 3, PREREQUISITE NOT MET: check:dual-build-cjs-loads and check:type-check-debt (both need the whole-repo build).
    • 1 exit 1: check:platform-checklist, inherited from main (see Acceptance notes). Its inputs are byte-identical to main, and it is not a per-PR CI gate.
  • Lint (a proven narrowing).
    • Scope: eslint --no-inline-config --format json over exactly the 95 changed code files returned 0 errors and 0 warnings.
    • Population: read from eslint.config.mjs (files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']); the file count comes from the JSON output.
    • Invariance: the config itself records that it never enables type-aware linting (no parserOptions.project), so no untouched file's verdict can move.

NOT MEASURED (CI runs these):

  • qa/dogfood: the two touched dogfood tests and expression-conformance. The package does not resolve @objectstack/verify unbuilt, so no test ran. Static reading: 0 analytics.zod references in that ledger, against 11 .zod.ts references as the control.
  • The showcase typecheck: 7 TS2307 for unbuilt connectors and plugins, and 0 diagnostics in showcase.cube.ts.
  • downstream-contract's consumer-specifier-ledger needs @objectstack/cli built.
  • The two exit-3 gates above.

Skills readings

skills/objectstack-ui/rules/dashboards.md goes 468 → 468 lines. The whole package (every SKILL.md) goes 4404 → 4404. Against base the hunk is 6 lines modified and nothing added: each change deletes a name: 'KEY', fragment.

Acceptance notes (noted, not filed)

  • check:platform-checklist is red on main at 3cf644938:
    • The failure: areas/identity-auth.json anchors plugin-auth/src/auth-plugin.ts#twoFactor, and 7d6308895 (fix(plugin-auth): a refused auth setting no longer drops the settings saved with it #20429) turned that line-start key into an inline nested object key (plugins: { twoFactor: true }), which the shared resolver reads as absent by design.
    • It is independent of this diff: both files are byte-identical to main.
    • The gate is run by hand, not per PR. Carrier: the next PR to touch identity-auth.json or auth-plugin.ts, or the checklist owner.
  • The inner name carried a snake_case regex. The record key never had one, and it legitimately takes camelCase and dotted spellings in-repo (driver-memory fixtures; 'owner.amount_sum' in dotted-measure-refusal.test.ts). Nothing is enforced on the key today; this is an observation, not a change here.
  • The absence pin states its blind spot: members built under computed keys (Object.fromEntries(… { name: n, … })). tsc found the typed ones in driver-memory, and the rest of that shape was swept by an AST scan (object literals holding name, sql and type). What remains is only this pin's own refusal specimens and the schema shape.

维护者速读(草稿)

改了什么:分析立方体(cube)的度量与维度不再接受内部 name 字段;成员的名字就是它在 measures / dimensions 里的键。写了 name 会在编译期和解析期被明确拒绝,并给出迁移提示。已存储的立方体在加载时自动去掉该字段,照常可用。

为什么改:这个字段从来没有任何代码读取,系统一律按键识别成员;当 name 与键不一致时,作者写的值被静默忽略。Cube.dev、LookML 等主流方案也只有一个名字。按您「主流平台有没有这个能力」的判据,判定退役。

风险与代价(含回滚):对外行为不变 —— /analytics/meta 与查询接口的成员名仍是 立方体.键。仍写 name 的作者源码需要删除该字段(os migrate meta --from 17 列出改动)。回滚:还原本 PR 即可,无数据迁移需要撤销。本 PR 同时改了一个对外发布的 skill 示例(仅删除 name),因此需要您的批准。

席位意见:

你要做的:审阅后批准(Approve)本 PR。

Line 3 and Deviation 5 were amended by the domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) before the at-tier review: this diff widens no accept set and adds no export, so Clause-② is no (narrowing), as most retirements of this family on main declare. 20323-action-aria-removed.md declared yes; the definition in clause2-line.mjs decides, not the precedent. The changeset line moved with it in 2252e5728, and the claim on #20300 was amended in place.

The patch-round section above was added by the same seat after the at-tier record 5875291969.

…the record key is the member's name (WIP: schema, registry, ledger)

Claude-Session: https://claude.ai/code/session_01B3TqpoQbTAfG7G74GMDWNW
Co-authored-by: Claude <noreply@anthropic.com>
… changeset for the retired inner member name (WIP)

Claude-Session: https://claude.ai/code/session_01B3TqpoQbTAfG7G74GMDWNW
Co-authored-by: Claude <noreply@anthropic.com>
tsc found the two in driver-memory; the same map-builder shape recurs in
service-analytics and runtime fixtures, and a spec scripts fixture carried
the key into a leg that would then fail for the tombstone's reason.

Claude-Session: https://claude.ai/code/session_01B3TqpoQbTAfG7G74GMDWNW
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/downstream-contract, @objectstack/service-analytics, @objectstack/spec, touching 29 documentable anchor(s). ⚠️ 7 changed file(s) yielded no anchor (packages/services/service-analytics/README.md, packages/spec/authorable-surface/data.json, packages/spec/liveness/README.md, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/natural-language-queries.mdx (via count_distinct (literal, a string literal in inferMeasure))
  • content/docs/api/data-api.mdx (via DimensionSchema (symbol, a top-level const), MetricSchema (symbol, a top-level const))
  • content/docs/automation/hook-bodies.mdx (via issued_on (literal, a string literal in fixture))
  • content/docs/data-modeling/field-type-decision-tree.mdx (via total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))
  • content/docs/data-modeling/fields.mdx (via total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))
  • content/docs/data-modeling/queries.mdx (via closed_at (literal, a string literal in fixture), count_distinct (literal, a string literal in inferMeasure), order_count (literal, a string literal in fixture), total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))
  • content/docs/deployment/validating-metadata.mdx (via closed_at (literal, a string literal in fixture), count_distinct (literal, a string literal in inferMeasure))
  • content/docs/getting-started/quick-start.mdx (via analyticsCubes (literal, a string literal in apply))
  • content/docs/kernel/contracts/data-engine.mdx (via count_distinct (literal, a string literal in inferMeasure))
  • content/docs/protocol/objectql/query-syntax.mdx (via count_distinct (literal, a string literal in inferMeasure), order_count (literal, a string literal in fixture), total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))
  • content/docs/protocol/objectql/schema.mdx (via total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))
  • content/docs/protocol/objectql/state-machine.mdx (via closed_at (literal, a string literal in fixture))
  • content/docs/ui/dashboards.mdx (via count_distinct (literal, a string literal in inferMeasure), total_amount (literal, a string literal in a comment on a changed line; a string literal in fixture))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via count_distinct (literal, a string literal in inferMeasure))
  • content/docs/releases/v17/17-0.mdx (via count_distinct (literal, a string literal in inferMeasure))
  • content/docs/releases/v17/17-2.mdx (via MetricSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-5.mdx (via count_distinct (literal, a string literal in inferMeasure))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 7 changed file(s) yielded no anchor (packages/services/service-analytics/README.md, packages/spec/authorable-surface/data.json, packages/spec/liveness/README.md, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 35 pages)
  • 11 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c876a7426d930e9e8d310fdffb1f64ae836cdced → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f7f04049f450ca34255c66ffe905ddecff3abfcf — the merge of head d977ee667de307147c95e08c2abfc958ac680493 into base c876a7426d930e9e8d310fdffb1f64ae836cdced, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f7f04049f450ca34255c66ffe905ddecff3abfcf && git checkout f7f04049f450ca34255c66ffe905ddecff3abfcf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c876a7426d930e9e8d310fdffb1f64ae836cdced d977ee667de307147c95e08c2abfc958ac680493 && git checkout -B drift-repro c876a7426d930e9e8d310fdffb1f64ae836cdced && git merge --no-ff d977ee667de307147c95e08c2abfc958ac680493

node scripts/docs-audit/affected-docs.mjs --json c876a7426d930e9e8d310fdffb1f64ae836cdced

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c876a7426d930e9e8d310fdffb1f64ae836cdced → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2252e57287d7ca731155ce266e38cdf80aaae902
Local-runs: none

Head confirmed against the API at review time (head.sha equals the sha above; 9 commits, 103 files, +1446 / −423, merge base with main 6e3e5462c). Inputs: card #20300 with every comment, the PR body, the paginated file list, the three-dot diff, the check-runs on this head, and origin/main files by git show / git grep. Nothing was built, run or re-run.

① Derived judgments

  1. The two tombstones — right. MetricSchema.name and DimensionSchema.name become retiredKey(...) on both strictObjects. Read against shared/retired-key.ts on main: z.never({ error }).optional() types the key never on z.input and raises the guidance itself at the key's path. The prescription opens with the fully-qualified key in backticks, names @objectstack/spec 17.5.0 (main is 17.4.0; the action.aria and view.pageName tombstones already on main cite 17.5.0 the same way), gives the one-line fix and the rename route, and closes with the house os migrate meta --from 17 sentence byte-identical to CUBE_JOIN_MIGRATE. A cube parse yields one invalid_type issue per member at measures.KEY.name / dimensions.KEY.name; the new pin covers the schema, the analytics_cube registry binding (asserted to BE CubeSchema), defineCube, defineStack (STACK_SCHEMA_INVALID / 422, with a control), the did-you-mean exclusion, and a @ts-expect-error tsc leg in a file tsconfig.test.json compiles per the dev's --listFilesOnly reading. No tracker number in the prescription, the D2 summary or the D3 texts.

  2. The D2 conversion cube-member-inner-name-removed — right. toMajor: 18, retiredFromLoadPath: true, walks analyticsCubes[] with mapCollection, per member of both bags, copy-on-write. The notice path is spelled analyticsCubes[i](CUBE).BAG.KEY.name, the cube-naming convention cube-join-sql-and-relationship-removed established. It strips whenever the key is present: an equal value emits from: name, to: (removed); a disagreeing value emits from: name "VALUE" and to: (removed; the record key "KEY" is the name). Stripping the disagreeing value is right and behaviour-lossless: no reader of the inner name exists (item 5), and leaving it would make the boot door refuse the stored cube — the pin shows the artifact refused before the conversion and accepted after it, beside a lit nme control refused on both sides. Idempotent by construction (second replay hands the input back by reference, pinned). Fixture expectedNotices: 4 equals the stripped keys (three equal on orders, one disagreeing on events, the canonical events.kind untouched). Placement: appended after actionAriaRemoved in CONVERSIONS_BY_MAJOR[18] and in the same slot of step18.conversionIds; the step-18 rationale is extended. The three existing step-18 cube fixtures (metric-filters-removed, cube-sub-day-granularities-removed, cube-join-sql-and-relationship-removed) lose their inner names so the whole-table replay stays disjoint — required by the skill's fixture rule, not optional.

  3. The D3 entry cube-member-inner-name-retired — right. One file under migrations/entries/semantic/18.*.ts in the README's shape (surface without backticks, replacement, reason naming the D2 by its whole id, non-empty acceptanceCriteria). Its copy in the generated region of migrations/registry.ts is byte-identical and lands in id order (cube-join… before cube-member… before the metric-filters entry); check:migration-registry holds the region equal to the directory.

  4. The retired-keys table — right. data/Dimension:name and data/Metric:name as per-file entries under entries/retired-keys/18.*.ts, landed sorted in the generated RETIRED_KEYS_BY_MAJOR[18] region (after data/CurrencyConfig:precision, after data/Metric:filters), spelled exactly as the authorable-surface/data.json rows minus the [RETIRED] mark. Registered under 18 while the tombstone names 17.5.0 is the ADR-0087 level-half amendment applied, not a mismatch.

  5. The producers — right, and every /analytics/* answer stays unchanged, verified on main. Both getMeta projections (analytics-service.ts 2152–2172, memory-analytics.ts 1280–1298) build { name: CUBE.KEY, type, title: label } from Object.entries with no spread and no read of the inner name; resolveMeasure / resolveDimension and the service's assert*Fields index the bags by key. My own grep of non-test packages/**/src for .name reads on measure / metric / dimension / member variables found only dataset-measure and dataset-dimension reads (DatasetMeasure.name, drill dimensions carrying field), never a cube member — A1 holds. My grep of non-test literal producers found exactly the sites the diff corrects: analytics-service.ts (the default count, two dimension mints, the time-dimension mint, inferMeasure's three returns), cube-registry.ts (four), dataset-compiler.ts (two), the README, the showcase cube and the skill example — A2 holds. inferMeasure's return type narrows, but it is not re-exported from index.ts and the package has a single . export, so no public type moves; dataset-compiler.ts types its members Metric / Dimension from spec, so tsc forced those two removals.

  6. The measures / dimensions describes — right. Regenerated analytics.mdx carries the new descriptions and renders the name rows as never / optional / [REMOVED] …, the row shape action.mdx already uses for aria.

  7. The published skill example — right as a deletion-only hunk. Six lines of skills/objectstack-ui/rules/dashboards.md, each dropping a name: 'KEY', fragment inside an os:check fence (verified on main at line 440), which check:skill-examples type-checks in the typecheck-consumers lane.

  8. The authorable-surface baseline — right. Both rows become [RETIRED]. No api-surface/, json-schema.manifest/, spec-changes.json or protocol-upgrade-guide.md change, which matches the footprints of the action.aria (dcd3bceaa) and list-view tabs (6e3e5462c) retirements; spec-changes.json folds only up to PROTOCOL_MAJOR 17, and step 18 is the pending major.

  9. The liveness rows — right. Both rows stay dead, re-verified 2026-09-28, notes in the house template (REMOVED, tombstoned at the schema, stripped by the protocol-18 conversion, entry stays because the tombstone keeps the key in the walked shape, what to do instead). The README row is amended; state-counts.md is untouched because 9 dead stays 9. Spec property liveness is green on this head.

  10. Pins and radius — right. The new cube-member-inner-name-retirement.test.ts is registered in vitest.repo-tests.json; CI's Test Core runs turbo run test test:repo, so it is a CI test. Its tree-scoped absence walk covers packages, examples, skills, content, scripts with the extensions the RLS tags pin uses, and every one of those roots and extensions is declared under @objectstack/spec in scripts/cross-package-test-inputs.mjs; exclusions are reasoned inline, no allowlist file. The flipped analytics.test.ts blocks now pin the tombstone with a lit control instead of a snake_case regex on a value nothing read.

  11. The objectui pin. The body cites f8a9d0fb05, which is the pin at the merge base and on this head; main bumped to dd3f7e1be3 (3cf644938) after the merge base. Re-read at dd3f7e1be3: CubeSchema is referenced only by clientValidation.ts (a dynamic import for the metadata-admin validator); no cube-member authoring at either pin, so the tombstone cannot break the pinned sibling.

② Semver level

  • @objectstack/spec minor with the BREAKING banner — right. ADR-0087's level-half amendment: pre-GA, a metadata-facing retirement ships minor, with breaking-ness carried by the banner and the disposition; check-changeset-no-major refuses major. The changeset body carries the FROM → TO table, the one-line fix, what an author sees, the stored-row story, and the disposition marker.
  • @objectstack/service-analytics patch — acceptable. No public export changes (item 5); the behaviour change is three producers ceasing to write an inert key, and the members stay filed under the same keys. Both packages sit in the one fixed group in .changeset/config.json, so the group publishes 17.5.0 regardless; the per-package grade is a label, and patch is the honest one.
  • The declaration, applying clause2-line.mjs's definition. The diff widens no accept set (it removes an authorable key) and adds no export (api-surface/ untouched; D2 and D3 are registry data), so the value is no; it is breaking, so the arm is (narrowing) — the reader's own table calls that combination "NOT a widening, but breaking". The three carriers, tested:
    • the changeset, line 14: no (narrowing) — reads declared, arm narrowing.
    • the PR body, line 3: no (narrowing) — reads declared, arm narrowing; Check Changeset re-ran after the body edit and is green.
    • the claim 5868952850, line 16: the declaration line reads no with NO parenthetical, so the reader answers declared with arm: null — by its own rule "an ABSENT arm declares NO DIRECTION". The arm appears only in the amendment paragraph, which is prose and not a key-initial line. So the correction reached two of the three carriers, not three. No CI gate reads the claim (the carrier sweep was retired by ruling 5770886272), so this is not a gate input; it is an unfinished amendment with a one-line remedy — edit line 16 of the claim to no (narrowing).
    • One overstatement to correct in passing: the body's "the shape of every retirement of this family on main" is not true of 20323-action-aria-removed.md, which declares yes; list-view-tabs-retired.md declares no (narrowing). By the definition, no (narrowing) is the right reading here whatever the aria changeset chose.
  • ADR-0087 registration — right. The changeset's adr-0087 marker reads registered cube-member-inner-name-removed, cube-member-inner-name-retired; the D2 id resolves in CONVERSIONS_BY_MAJOR[18], the D3 id in MIGRATIONS_BY_MAJOR[18].semantic, and neither exists at the merge base, so both are new in the diff as registered requires.

③ Boundary flags

The five deviations.

  1. Skills split ordered, then withdrawn — accepted. The example sits in an os:check fence and check:skill-examples runs in the typecheck-consumers lane of the required TypeScript Type Check aggregator. With the tombstone, a name: in the example types never (the TS2322 the dev measured); without the tombstone, name is REQUIRED on main, so a companion PR alone would be red. No green landing order exists for a split; the hunk must ride with the schema, and the PR is therefore Tier H.
  2. File surface wider than the claim — accepted. The untyped mints are producers Prime Directive Add comprehensive test suite for Zod schema validation #12 says to fix at the source; the fixture edits are forced by the never type. Same package, same defect class, and the card's acceptance text is unchanged by it.
  3. Route: retiredKey() on a strictObject rather than the skill table's delete-plus-guidance — accepted, with one escalation. The route matches the card's acceptance text, triage note 1, retired-key.ts's own docblock (on a closed shape a bare delete is loud but cannot carry the prescription or the tsc channel), and two precedents landed this week (action.aria dcd3bceaa, list-view tabs 6e3e5462c). The CubeJoinSchema docblock correction is right. Escalation: the skill's §2 route table and AGENTS.md's Post-Task Checklist step 3 both still route a strict schema to the guidance map; both are governed documents (Tier S and Tier H) and not this PR's to edit — the seat should file the reconciliation as its own card so the next retirement does not re-argue it.
  4. D2 strips a disagreeing value — accepted. See ① item 2: triage's "a disagreeing value gets a D3 entry" is met, the strip is what keeps a stored cube loading, and the notice keeps both spellings for the author.
  5. service-analytics graded patch, spec minor — accepted. See ②.

The two out-of-scope findings.

  • check:platform-checklist red on main. The claim is consistent with the tree: docs/qa/platform-checklist/areas/identity-auth.json:1599 anchors packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor, and on main that file spells twoFactor only inside the inline nested plugins: { twoFactor: true } (line 1622) plus a comment. Neither file is in this diff, and I could not run the gate (read-only). Escalation: under Prime Directive chore: version packages #10 a located, reproducible red on main is a defect that owes a card, not an acceptance note whose carrier is "the next PR to touch either file", which may not come — the seat should file it. Not an input to this verdict.
  • The member-key regex observation. Observation only, and correctly so: Prime Directive Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3 wants machine names snake_case, the record key now carries the only identity and no spelling rule, and in-repo fixtures already key members camelCase (totalAmount), so enforcing it would be its own accept-set narrowing card for triage. No regression here — the regex it replaces guarded a value nothing read.

The NOT MEASURED items are all CI-covered on this head: the two dogfood tests and expression-conformance by Dogfood Regression Gate; the showcase typecheck by Type Check · workspace; downstream-contract's consumer-specifier-ledger by Type Check · consumer gates; check:dual-build-cjs-loads and check:type-check-debt by Type Check · debt ledger and Lint & Repo Gates. Their conclusions are read below.

Check-runs on this head, read at 2026-09-28T15:19Z, after the re-triggers from the push and the body edit had all completed — the seven required contexts:

  • Lint & Repo Gates — success
  • TypeScript Type Check — success
  • Test Core — success
  • Dogfood Regression Gate — success
  • Build Core — success
  • Temporal Conformance (live PG + MySQL) — success
  • Governed Surface Queue Guard — success

Every other check-run on the head is success (24 of them, including the four Type Check · lanes, the six Test Core shards, the three Dogfood Regression Gate shards, Check Changeset on both triggers, Spec property liveness, Build Docs and Dogfood Verify CLI); skipped by their path filters: Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke (opt-in).

Tier H status. skills/objectstack-ui/rules/dashboards.md is a skills/** path, so the whole PR is Tier H by the register: it lands only on an APPROVED review by a GOVERNED_APPROVERS account, after which the owning seat lands it. The PR is a draft with mergeable_state: blocked, Governed Surface Queue Guard is green, and this record is the contract input the maintainer reads — no seat approves, readies, queues or arms it.

Fixes #20300 — right. The card's acceptance (both keys tombstoned with the prescription, both ledger rows staying dead, a D2 conversion and a D3 entry, docs regenerated, authoring the key a tsc and parse error, check:liveness green) is delivered whole by this diff; the branch-claim and single-writer checks are green, and nothing of the card is left for a follow-up.

Implemented-by: claude/issue-20300-cube-member-inner-name-retired
Reviewed-by: session_01B3TqpoQbTAfG7G74GMDWNW

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

改了什么:分析立方体(cube)的度量和维度不再接受内部的 name 字段。成员的名字就是它在 measures / dimensions 里的键。

  • 作者如果还写 name,会在编译期(tsc)和解析期被明确拒绝,并得到迁移提示。
  • 已存储的立方体在加载时由迁移(D2 cube-member-inner-name-removed)自动去掉该字段,照常可用。
  • 三处内部生成立方体的代码也不再写这个字段。

为什么改:

  • 这个字段从来没有任何代码读取,系统一律按键来识别成员。
  • 当 name 和键不一致时,作者写的值会被静默忽略;仓内 driver-memory 的测试夹具里就有 21 处这种不一致。
  • Cube.dev、LookML 等主流方案也只有一个名字。
  • 按您「主流平台有没有这个能力」的判据,triage 判定退役(5859547666)。

风险与代价(含回滚):

  • 对外行为不变:/analytics/meta、/analytics/query、/analytics/sql 返回的成员名仍是 立方体.键。达档复审在 main 上重新核实过:没有任何非测试代码读取内部 name。
  • 仍写 name 的作者源码需要删掉这个字段,os migrate meta --from 17 会列出要改的地方。
  • 回滚:还原本 PR 即可,没有数据迁移需要撤销。
  • 本 PR 同时改了一个对外发布的 skill 示例 skills/objectstack-ui/rules/dashboards.md(只删了 6 处 name:,行数不变),因此属于 Tier H,需要您亲手合并或批准。

席位意见:建议合并。

你要做的:审阅后直接合并本 PR。您的人工合并本身就是审核记录。本席今日下班,如果改为点 Approve,就由接任席位落地。


Generated by Claude Code

…be-member-inner-name-retired

# Conflicts:
#	packages/spec/vitest.repo-tests.json
…ublished, so the package label 17.4.0

Claude-Session: https://claude.ai/code/session_01B3TqpoQbTAfG7G74GMDWNW
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review September 29, 2026 04:36
@os-zhuang
os-zhuang enabled auto-merge September 29, 2026 04:36
…red inner member name

Two files landed on main after this branch retired the key: four literal
members and one map builder. The tree-scoped absence pin found the literals;
the AST sweep found the builder, the pin's declared blind spot.

Claude-Session: https://claude.ai/code/session_01B3TqpoQbTAfG7G74GMDWNW
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f6948961b8a4973335540a1ca8eaf917b2237b37
Local-runs: none

The head moved during this review. The brief named 2bd3b8dee9; the dev pushed f6948961b8 at 04:42Z on top of it, and that push cancelled the CI run on 2bd3b8dee9. This record is on f6948961b8, confirmed against the API when the write began and again before posting (head.sha equals the sha above; 14 commits, 105 files, +1452 / -428; the merge base with main is main's own tip f11b5f20a, so the head contains current main whole and the two-dot and three-dot diffs coincide). The prior records 5873023860 and 5875291969 were read as inputs, not adopted. Inputs: the PR body, the PR comments (the two prior records and the maintainer quick-reads 5873137496 and 5875315415), the reviews, the paginated file list, the net diff against main, the patch round's own diffs (f639af5f3..40f1f68721 for the merge, 40f1f68721..2bd3b8dee9 for the regen, 2bd3b8dee9..f6948961b8 for the fixture sweep), card #20300 with every comment, the check-runs on this head, and origin/main files by git show. Nothing was checked out, built, run or re-run; the one read beyond diffs and git show is the tree sweep in ① item 9, which read blobs through git and executed nothing of the repository.

① Derived judgments

  1. The patch round, commit by commit. 40f1f68721 merges origin/main f11b5f20a (602 files of main's own delta) faithfully: at 2bd3b8dee9 the net file list against main is exactly the PR's 103 files, so the merge dropped nothing of main's and added nothing of its own. Six of the PR's files were also touched by main in the window, every overlap disjoint from the PR's hunks: migrations/registry.ts (the stage 4 to 7 rewrites and main's new entries), analytics-service.ts (the analytics: service-analytics' two filter faces answer $empty by the field's declared type (read-scope SQL, the analytics where) — ruling A on #20399 #20445 declaredValueShape hook and sourceFieldMeta.multiple at lines 697 to 1051; the PR's hunks sit at 2795 to 3078), the two $empty conformance fixtures memory-operator-key-clobber.test.ts and native-sql-filter-logic-conformance.test.ts (main's filter: the engine's compile surfaces answer $empty by the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444 additions beside the PR's name removals), vitest.repo-tests.json (main's count-shards-merge.test.ts and requires-plugins-retirement.test.ts stacked beside the PR's pin; 41 entries, sorted), and liveness/README.md (item 5). 2bd3b8dee9 regenerates authorable-surface/data.json over the merged tree: four rows, all main's (EngineAggregateOptions:search and :searchFields from feat(spec,objectql,metadata-protocol): grouped and aggregated queries honour search #20487, FieldOperators:$empty and SpecialOperator:$empty from feat(spec): declare the staged $empty filter operator and its per-type expansion (#20311) #20442); net against main the file carries only the PR's two [RETIRED] marks. f6948961b8 is two fixtures, +5 / -5 (item 9).

  2. The step-18 registries equal main plus this PR's entries. Net diff on conversions/registry.ts is +158 / -24, every deletion a name: fixture line in the three sibling cube fixtures, and CONVERSIONS_BY_MAJOR[18] gains exactly cubeMemberInnerNameRemoved, after actionAriaRemoved. Net diff on migrations/registry.ts is +73 / -0: step18.conversionIds gains exactly 'cube-member-inner-name-removed', the step-18 rationale gains its paragraph, the D3 entry lands in id order between cube-join-sql-and-relationship-retired and cube-metric-filters-retired, and RETIRED_KEYS_BY_MAJOR[18] gains data/Dimension:name (after data/CurrencyConfig:precision, before data/DriverOptions:timeout) and data/Metric:name (after data/Metric:filters, before data/NoSQLQueryOptions:timeout). The entries/ directory delta against main is exactly the three new files, and the D3 file is byte-identical to its registry copy. main's step-18 siblings from the window (feat(spec,core,cli)!: a scenario's requires is checked before it runs — unmet params or services skip it with a reason; requires.plugins retires into requires.services #20511's requires.plugins retirement, feat(spec,rest)!: the served OpenAPI info carries the publisher's api.documentation identity; api.documentation.version retired #20512's api.documentation.version retirement, ba5927f71's stack-config-default-export-unbuilt-refused) are present because the head contains main whole.

  3. retiredAfter: '17.4.0' is still the one value the census pin accepts. retired-after.census.test.ts on main: an UNPUBLISHED entry takes the package label whenever the census's last release equals the label. packages/spec/package.json reads 17.4.0 on main and on the head; the census's last release is 17.4.0 (11 releases; the census file is byte-identical between main and the head, and cube-member-inner-name-removed appears in none). The tolerance is closed, so the only accepted value is 17.4.0, which the head carries. The version pass 1c761c0d71 did not move the label: it is test-only, making two version-sensitive tests hold across the pending 17.5.0 Version Packages PR chore: version packages #17076. If chore: version packages #17076 lands before this PR, the label becomes 17.5.0 while the census trails at 17.4.0, and the pin's pending range [17.4.0, 17.5.0] still accepts 17.4.0; once the 17.5.0 tarball is censused the entry becomes PUBLISHED and the value the tarballs say is the release before it, 17.4.0 again. So the stamp is right under every landing order. No retiredAfter value on main moved in the window.

  4. This PR's migration-entry texts pass what main now enforces on tracker numbers. What main enforces: packages/cli/test/migrate-meta-engine-guidance.test.ts (queue tier, run by Test Core) spawns os migrate meta and holds every printed block of the COVERED families, selected by id prefix, to no # followed by four or five digits; cube- is not among its 41 prefixes, so it does not select this family. migrations.test.ts pins wording per entry, none of them this family. check-issue-citations.mjs (lint.yml, diff-scoped) judges that every citation a change ADDS resolves, and fix(scripts): check-issue-citations reads a qualifier only when it names a known repository, so pre-#N / post-#N are judged and framework#N is this repository #20554 made pre-#N and post-#N judged and framework#N this repository. This diff: the D2 summary, the D2 notice from / to / path strings, the D3 surface, replacement, reason and acceptanceCriteria, the two retired-key entry values and both tombstone prescriptions carry no # plus four or five digits. Every #20300 in the diff sits in a // or docblock comment, the two liveness notes, the README row or two test titles: comment and ledger prose, never author-shown text, and all cite the live card. No pre-#, post-# or framework# form is added. The family would also pass the CLI pin as written if cube- were added to its prefixes.

  5. The liveness README resolution — right. main's 05077d4c2 sharded the generated counts into state-counts/ and rewrote 21 README lines; the merge took main's text (the README's merge diff is main's +17 / -12), and the net diff against main is one line: the analytics_cube row gains the PR's clause (RETIRED by spec(analytics): retire the inner name on cube measures and dimensions; the record key is the identity (2 keys) #20300 as retiredKey() tombstones, both rows STAY dead, the count does not move) and nothing else. state-counts/analytics_cube.md on main reads 18 live, 9 dead, 27 classified, and the head does not touch it; the deleted state-counts.md is not resurrected; analytics_cube.json net diff is the two rows' verifiedAt and note. check-generated.ts on main now names liveness/state-counts/ as a directory, and nothing in this diff moves a count. Spec property liveness is success on this head.

  6. ba5927f71's one-stack-authoring-shape rule touches nothing in this diff. That rule makes os validate and os build refuse a default export no producer built, and composeStacks refuse an unbuilt input. No file in this diff exports a stack default or calls composeStacks: the two dogfood tests build with defineStack, the showcase cube is a defineCube() (a cube, not a stack), additional-domains.fixtures.ts exports a typed Cube literal the downstream-contract test reads, and the service-analytics and driver-memory fixtures hand typed Cube objects to the service directly. No fixture or showcase here can raise STACK_PROVENANCE_MISSING.

  7. The tombstones, producers, describes, reference page and changeset — unchanged since 5875291969, re-read against current main. main did not touch analytics.zod.ts, retired-key.ts, cube-registry.ts or dataset-compiler.ts in the window, and its packages/spec/scripts changes are the count-sharding and strictness-ledger scripts, not the reference-docs generator; the tombstone prescriptions and the house sentence are byte-identical to the prior head and still match the pin in retired-key-migrate-sentence.test.ts; analytics.mdx net diff is the six describe and [REMOVED] rows, and Build Docs is success. The D4 artifacts fold only graduated steps (protocol-upgrade-guide.md header: current protocol 17.0.0; no step-18 id appears in it or in spec-changes.json on main), so no regeneration is owed for the D3 entry.

  8. The objectui pin. .objectui-sha is not in this diff, so Console Pin Gate is skipped by its path filter on this head, as before; the merge carried main's pin text into the migrations registry.

  9. The f6948961b8 sweep, and the residue question answered. The merge brought two files from main that still wrote the inner name: memory-20444-empty-operator.test.ts (filter: the engine's compile surfaces answer $empty by the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444; three literal members) and where-empty-operator.test.ts (analytics: service-analytics' two filter faces answer $empty by the field's declared type (read-scope SQL, the analytics where) — ruling A on #20399 #20445; one literal member and one Object.fromEntries builder). Read against the pin's matcher (lexOffenders: a frame whose own keys include name, sql and type, closed under a parent frame opened by measures or dimensions), the four literals are offenders in a walked root with a scanned extension, so 2bd3b8dee9 would have failed the pin in test:repo (Test Core) had its run not been cancelled; the builder is the pin's declared blind spot and inert at runtime (the fixture is handed to the service, never parsed), and the dev's AST sweep found it. f6948961b8 removes both and nothing else. My own read of the head tree, over the five walked roots and the pin's extensions: every innermost object literal whose own keys include name, sql and type is inside the retirement kit's own excluded set (the D2 fixture in conversions/registry.ts, the pin's anti-vacuity specimens, packages/spec/CHANGELOG.md) except one TypeScript type annotation in metadata-protocol naming a runtime index shape, not a cube member; the 21 dimensions: Object.fromEntries(...) builders on the head all build { label, type, sql }; no bag[key] = { name, ... } assignment exists. So no other fixture main brought in still writes an inner member name.

② Semver level

  • @objectstack/spec minor with the BREAKING banner — right, unchanged. The changeset opens with the banner and carries the FROM / TO table, the one-line fix, what an author sees, the stored-row story, and the marker registered cube-member-inner-name-removed, cube-member-inner-name-retired; both ids resolve in the head's registries and neither exists on main. check-changeset-no-major refuses major in the launch window.
  • @objectstack/service-analytics patch — acceptable. No export changes; three producers stop writing an inert key; the members stay under the same keys and every /analytics/* answer is unchanged.
  • Clause-②: no (narrowing) in all three carriers. The diff widens no accept set and adds no export, and it is breaking. The changeset, line 8: no (narrowing). The PR body, line 3: no (narrowing). The claim 5868952850, its declaration line: no (narrowing). Check Changeset is success on this head. ba5927f71's own Clause-②: yes is main's, not this PR's.

③ Boundary flags

The prior records' flags, answered. The claim's declaration line, the body's overstatement, the two escalations (#20464, #20465), the objectui pin and the five deviations stand as 5875291969 answered them; nothing in this round reopens one. The prose drift persists and widens: the body's patch-round section still ends at f639af5f3 (103 files, +1447) and names neither 40f1f68721, 2bd3b8dee9 nor f6948961b8; the head is 105 files, +1452 / -428. Not a gate; one paragraph would spare the maintainer the reconciliation.

What the merge introduced. main's 602-file delta, six disjoint overlaps with the PR's files (① item 1), and two fixtures that undid the retirement on arrival (① item 9), now swept. Nothing else: no registry, census, generator, tombstone or docs surface moved in a way this diff has to answer.

Does the head merge into current main typecheck- and test-clean? The head contains main's tip f11b5f20a whole, and main has not moved since (re-fetched before posting), so the CI run on this head is the merged-tree run. As far as a read of main shows, nothing on main refuses this diff: the census pin accepts the stamp (① item 3), the tracker-number pins do not select the family and its texts are clean regardless (① item 4), the stack-shape rule reaches none of its files (① item 6), and the fixture residue that would have reddened Test Core on 2bd3b8dee9 is gone (① item 9). Check-runs on this head, read at 2026-09-29T04:52Z: 33 runs, none red, 25 concluded. Success: Build Core, Build Docs, Temporal Conformance (live PG + MySQL), Dogfood Verify CLI, the three Dogfood Regression Gate shards, Test Core (2/6) and Test Core (4/6), Type Check · source gates, Type Check · debt ledger, Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Spec property liveness, Auto Label, filter, and the four branch-claim, part-of and single-writer checks; skipped by path filter: Console Pin Gate, Packed-tarball smoke (opt-in). Still in progress at that reading: Lint & Repo Gates, Type Check · consumer gates, Type Check · workspace, and Test Core (1/6), (3/6), (5/6) and (6/6); the aggregate Dogfood Regression Gate was queued, and the aggregates TypeScript Type Check and Test Core had not yet been published. Of the seven required contexts, Build Core, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard are success, and Lint & Repo Gates, TypeScript Type Check, Test Core and Dogfood Regression Gate had not yet concluded. A red required check is a FAIL input; none is red at posting, and the queue guard refuses to land the PR until every check is green, so the verdict below stands on the read and on the checks that have concluded, with the in-progress contexts named.

Tier H status, now approved. skills/objectstack-ui/rules/dashboards.md is unchanged in this round (six deletion-only fragments inside an os:check fence, as before), so the PR is Tier H by the register. os-zhuang is in GOVERNED_APPROVERS (check-governed-queue-guard.mjs, with hotlong). The APPROVED review 5347753804 was given on 2bd3b8dee9 at 04:36:47Z and is not dismissed; under Prime Directive #14 the word holds on any commit, later pushes included, so it covers f6948961b8. The maintainer also readied the PR and armed auto-merge (merge_method: merge), so the PR enters the queue when every check is green; Governed Surface Queue Guard is success on this head. For the maintainer's eye: the head the approval was given on would have failed Test Core on the absence pin (① item 9), and the one post-approval push is that fix, test-fixture only, +5 / -5, touching no governed path. This record approves, readies, queues, arms or merges nothing.

Fixes #20300 — right. The card's acceptance is delivered whole as before; the two files this round adds are fixture sweeps of the same class the PR already swept, and nothing of the card is left for a follow-up.

Observation, not a gate. The CLI guidance pin's covered prefixes do not include cube-; this family's texts are already tracker-free, so adding the prefix would be a one-line widening for the seat that owns that pin.

Implemented-by: claude/issue-20300-cube-member-inner-name-retired
Reviewed-by: session_01B3TqpoQbTAfG7G74GMDWNW

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · 补充二(最终 head f6948961b)


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Sep 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 29, 2026
…be-member-inner-name-retired

# Conflicts:
#	packages/spec/src/migrations/registry.ts
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Director note · record at head · 2026-09-29T05:29Z

The head moved again after the PASS 5883890131 at f6948961b8: d977ee667d (pushed at 05:20 UTC) is a merge of origin/main c876a7426d, 48 files of main's own delta — including #20572's step-18 registry refactor (conversions/registry.ts, migrations/registry.ts), the exact family that conflicted on this branch twice today. A driver-free merge-tree against main is clean at this read, and os-zhuang's APPROVED review (04:36 UTC, on 2bd3b8dee9) stands. Two things before the landing, for the owning domain:spec seat: (1) a delta record at d977ee667d (a fresh full record is not needed — the PR-own diff against the merge base, file by file, against the f6948961b8 record's, plus how the step-18 tails look after #20572's refactor), so the merged head carries a record, as #20357 did not; (2) the API reads auto_merge: no at this read, so re-arm it if the push dropped it. Director seat, session_01AsCNgFBs8HCjwhyHQsFbx3 (objectstack#12708).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d977ee667de307147c95e08c2abfc958ac680493
Local-runs: none

A delta record on d977ee667d, as the director note 5884275219 asks; the full record 5883890131 (PASS on f6948961b8) and the earlier 5875291969 were read as inputs, not adopted. Head confirmed against the API when the review began and again before posting (head.sha equals the sha above; 105 files, +1456 / -428; mergeable: true, mergeable_state: clean). Since f6948961b8 the branch has exactly one commit, d977ee667d, a merge of origin/main c876a7426d, which brought four commits of main's own (#20572 24d521e53, #20576, #20577, #20504; 48 files) and the one hand conflict the merge message names, packages/spec/src/migrations/registry.ts. Inputs: the PR body, comments, reviews and paginated file list; the net diff against the merge base c876a7426d; the patch diff f6948961b8..d977ee667d; #20572's diff; the check-runs on this head; and origin/main files by git show. Nothing was checked out, built, run or re-run; the fragment parse and the tree sweep in ① read blobs through git and executed nothing of the repository; the merge check is an in-memory git merge-tree.

① Derived judgments

  1. The PR-own diff, file by file, against the one 5883890131 judged. The file list is the same 105 files. Comparing each file's PR-own hunks at d977ee667d (against c876a7426d) with its hunks at f6948961b8 (against f11b5f20a), 104 files carry byte-identical added and removed lines; the one file whose hunks changed is packages/spec/src/migrations/registry.ts, +73 / -0 before and +77 / -0 now. So the merge dropped nothing of main's and nothing of the PR's in the other 104, including skills/objectstack-ui/rules/dashboards.md (the Tier H hunk), conversions/registry.ts (+158 / -24 at both heads) and vitest.repo-tests.json (the PR's one line, with main's scripts/step18-rationale-merge.test.ts entry present beside it).

  2. migrations/registry.ts: what moved, and it is right. Against main, the head's four hunks are the new STEP18_RATIONALE fragment (+17), the D3 entry in the generated semantic region (+32, unchanged from the prior head), and the two RETIRED_KEYS_BY_MAJOR[18] entries (+9 and +19, unchanged). Gone from the PR-own diff are the two tails refactor(spec): step 18 rationale as key-sorted fragments, conversionIds derived, so two retirements merge clean #20572 dissolved: the twelve-line chain append to step18.rationale and the one-line 'cube-member-inner-name-removed' append to step18.conversionIds. The net +4 is 17 minus 13. That is the resolution the dev reports, and it is the right one: after refactor(spec): step 18 rationale as key-sorted fragments, conversionIds derived, so two retirements merge clean #20572 a retirement adds its conversion in CONVERSIONS_BY_MAJOR[18] only and its sentences as a fragment only.

  3. The step18 block is main's. From const step18: MigrationStep = { down to semantic: [, the head is byte-identical to main at c876a7426d: rationale: joinRationale(STEP18_RATIONALE), the derivation comment, and conversionIds mapped off CONVERSIONS_BY_MAJOR[18]. No conversionIds: [ literal remains in step 18; the one left on the head (line 1091) is step 17's. Below semantic: [ the block differs from main only by the D3 entry inside the generated region.

  4. The fragment follows refactor(spec): step 18 rationale as key-sorted fragments, conversionIds derived, so two retirements merge clean #20572's rule, read against the doc comment on STEP18_RATIONALE and the pin scripts/step18-rationale-merge.test.ts. Parsed with the pin's own element regex, the head's list is 47 elements with no residue (the pin's whole-list check), strictly sorted by id, every id kebab-case, every text non-empty and equal to its trim, and every order unique. The new element: id is cube-member-inner-name-retired, the D3 entry's id; it sits between cron-positions-deleted and cube-metric-filters-retired, which is where the id sorts; order: 47, one more than main's highest, 46; no leading or trailing space. Its text is the branch's former paragraph with the chain's trailing space removed and nothing else changed. The 46 fragments main carries are unchanged in id, order and text. The pin on the head is byte-identical to main's and is in the repo-tests list, so Test Core on this head ran it over this list.

  5. The rendered rationale includes the fragment. joinRationale renders by order, ties by id, joined with one space. main's 46 fragments render to a 48953-character paragraph; the head's 47 render to 49942 characters, which is main's paragraph, one space, then the fragment (988 characters), and nothing else. The paragraph now renders after list-view-tabs-retired (order 46) rather than before the decision-mode sentences where the chain had it; that is what an order one past the highest means, and no reader depends on position within the paragraph.

  6. The four step-18 sets equal main plus exactly this PR's entries. CONVERSIONS_BY_MAJOR[18]: main's 45 in main's order, plus cubeMemberInnerNameRemoved after actionAriaRemoved and before flowDecisionModeInclusiveExplicit. The derived conversionIds is that list's ids, so it gains exactly cube-member-inner-name-removed in the same place. The semantic list: main's 238 plus cube-member-inner-name-retired between cube-join-sql-and-relationship-retired and cube-metric-filters-retired, and the region stays sorted. RETIRED_KEYS_BY_MAJOR[18]: main's 203 plus data/Dimension:name (after data/CurrencyConfig:precision, before data/DriverOptions:timeout) and data/Metric:name (after data/Metric:filters, before data/NoSQLQueryOptions:timeout), sorted. The entries/ delta against main is exactly the three new files, and the D3 file equals its registry copy after de-indent, comment and literal both.

  7. No fixture main brought still writes an inner cube-member name. Of the 48 files in main's window, three mention measures or dimensions: migrations/registry.ts (prose), ui/dashboard.zod.ts and ui/view.zod.ts (a widget's dimensions: z.array(z.string()), not a cube member); none authors a cube. A sweep of the whole head tree (107 files that open a measures or dimensions frame, blobs read through git) finds six innermost literals whose own keys include name, sql and type, all in the retirement kit's own excluded set: the four members of the D2 fixture in conversions/registry.ts and the two refusal specimens in cube-member-inner-name-retirement.test.ts. Zero elsewhere, which agrees with the dev's "no new fixture needed a fix" and with Test Core green.

② Semver level

Nothing moved. .changeset/20300-cube-member-inner-name-retired.md is byte-identical between f6948961b8 and the head: @objectstack/spec minor under the BREAKING banner, @objectstack/service-analytics patch, Clause-②: no (narrowing) on line 8. The PR body's line 3 reads Clause-②: no (narrowing). Check Changeset is success on this head. The breaking marker on main's #20504 and its own Clause-② line are main's, not this PR's; the merge adds no export and widens no accept set.

③ Boundary flags

Merges clean into current main. origin/main has moved one commit past the merge base, to e666636fd9 (#20581, a create-objectstack test), which touches none of the PR's 105 files. git merge-tree --write-tree origin/main d977ee667d exits 0 with tree 13564fde01 and names no conflicted path. The API agrees: mergeable: true, mergeable_state: clean.

Check-runs on this head: 35 runs, none in progress, none red (re-polled at 2026-09-29T05:59Z). 33 success, including every required context: Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard, Lint & Repo Gates (the last to finish), TypeScript Type Check, Test Core (six shards and the aggregate) and Dogfood Regression Gate (three shards and the aggregate). Also success: Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Spec property liveness, Check Changeset, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Build Docs, Dogfood Verify CLI, Auto Label, filter, and the four branch-claim, part-of and single-writer checks. Skipped by path filter or opt-in: Console Pin Gate, Packed-tarball smoke (opt-in). The head contains main at c876a7426d whole and main's one later commit touches none of the PR's files, so this run is the merged-tree run for everything but that one test file.

Tier H. dashboards.md is unchanged in this round (① item 1), so the PR is Tier H by the register as before. os-zhuang's APPROVED review 5347753804, given on 2bd3b8dee9 at 04:36Z, is not dismissed; under Prime Directive #14 it holds on later pushes, so it covers d977ee667d. For the maintainer's eye: the two pushes since the approval are the fixture sweep f6948961b8 (+5 / -5, test files) and this merge, whose only PR-own change is the PR's own rationale paragraph reshaped into the fragment form main now requires; no governed path moved.

auto_merge reads null at this read, as the director note anticipated. This record approves, readies, queues, arms or merges nothing; re-arming is the seat's act.

Fixes #20300 — right, unchanged. Body line 1. The card's acceptance is delivered as before; this round adds nothing to it and takes nothing away.

Observation, not a gate. The body's patch-round section still ends at f639af5f3 and names neither 40f1f68721, 2bd3b8dee9, f6948961b8 nor d977ee667d; the head is 105 files, +1456 / -428.

Implemented-by: claude/issue-20300-cube-member-inner-name-retired
Reviewed-by: session_01B3TqpoQbTAfG7G74GMDWNW

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Re-queued by the domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) at 2026-09-29T06:01Z, answering the director note 5884275219.

维护者:无需操作,合入后席位收尾。


Generated by Claude Code

Merged via the queue into main with commit 92fe081 Sep 29, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20300-cube-member-inner-name-retired branch September 29, 2026 06:48
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… commits that decided them (stage 4) (objectstack-ai#20548)

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

## What changed

This is stage 4 of the staged sweep: the `data/` remainder. It covers
the six `packages/spec/src/data/` files stage 3 (PR objectstack-ai#20533, landed
`03b19d9cfd`) left out because an open PR held them, and nothing else.
They are `object.zod.ts`, `filter-logic-conformance.ts`,
`object.form.ts`, `data-engine.zod.ts`, `data-engine.test.ts` and
`hook.form.ts`. Later stages cover the other areas, so this PR says
`Part of`.

The census below measured all six. Three of them carry comment or
docblock sites that cite a tracker number answering 404.
`data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts` carry
none, so they are not in the diff.

Every such site has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123). That is **19 sites on 19 lines in 3 files,
covering 9 numbers**. Each rewritten line now cites the commit in
`origin/main` history that decided what the line describes, and it says
in its own words what that commit decided. Where a PR number was already
on the line (`PR objectstack-ai#13529`), it stays beside the commit as the link.

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of the 9 numbers: a search for each
number, with and without `#`, finds nothing there. So every anchor is a
commit: **9 distinct shas**. Stage 3 had already read these commits and
recorded them in PR objectstack-ai#20533's body. They were not copied from there. Each
one was re-read against the current line it anchors: its own message or
diff names the number it replaces, and it made the change the line
describes. `object.zod.ts` and `filter-logic-conformance.ts` moved on
`main` after stage 3 read them (PRs objectstack-ai#20521 and objectstack-ai#20523). Each site was
therefore re-read at this base, `03b19d9cfd`.

Only comments changed. Every source file keeps its line count (20 lines
out, 20 in, over 3 files), so no line citation into these files moves.
One of the 20 lines held no dead citation:
`filter-logic-conformance.ts:249`, the first half of a sentence reflowed
onto `:250`. No code token moves (see the guard below).

**No tracker number is added.** Every tracker number on an added line
was already in the hunk it replaces. `PR objectstack-ai#13529` stands on three added
lines, and on the three removed lines of the same hunks. It is the link
beside commit `9dac1ae01`, which stage 3 recorded the same way.

No reference page under `content/docs/references/` moved: none of the
rewritten docblocks projects into one (`check:docs` at the head: `226
generated files in sync`). The PR adds one `patch` changeset for
`@objectstack/spec` (see Changeset below).

## Census: the six files, before and after

**Instrument.** This is the instrument of stages 1 to 3. It sends REST
`GET /repos/objectstack-ai/objectstack/issues/N` without following
redirects, for every distinct number cited in `packages/spec/src/data`.
The population is:
- the citation gate's own exported `CITATION_RE` and
`NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`,
`objectstack-ai/objectstack`, `framework`, `pre-` or `post-`;
- widened case-insensitively to `Pre-`, `POST-` and `Framework`, as in
stage 3;
- N of 100 or more, excluding `summon` heads.

Each site is classified by the TypeScript parser as a line comment, a
docblock, a block comment or a string.

Two cross-checks close the population. First, a raw `#N` count in each
of the six files equals the census rows plus the cross-repo rows in five
files. In the other two it is one higher, and the extra is a second
number after a slash inside a string (`objectstack-ai#5322/objectstack-ai#5134` in a `note`,
`objectstack-ai#6262/objectstack-ai#6433` in a test title). Both answer 200. Second, no spelled
citation (`issue N`, `PR N`, `card N`) occurs in any of the six.

**Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The
dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at
the start, after every 100 numbers and at the end: 24 of 24 lit (200)
and 24 of 24 dead (404) over 8 checkpoints in the base run, and 21 of 21
lit and 21 of 21 dead over 7 checkpoints in the head run.

| reading | tree | numbers probed | 200 | 404 | 301 or other | dead
sites, all of `data/` | dead sites, the six files | lines | files |
numbers |
|---|---|---|---|---|---|---|---|---|---|---|
| before | base `03b19d9cfd`, probed 2026-09-29T01:11:59Z to 01:15:49Z |
601 | 572 | 29 | 0 | **77** | 19 | 19 | 3 | 9 |
| after | head `53c9070dfd`, probed 2026-09-29T01:25:55Z to 01:29:35Z |
597 | 572 | 25 | 0 | **58** | 0 | 0 | 0 | 0 |

The head probe found no number newly dead since the base probe: the same
572 numbers answer 200. The base reading of 77 equals stage 3's after
reading at `96fd49caa2`.

**Per file.** Cited sites here are every in-repo citation the population
reads, live or dead.

| file | cited sites (base) | dead sites before | by class | dead sites
after |
|---|---|---|---|---|
| `object.zod.ts` | 120 | 15 | 8 docblock, 7 line comment | 0 |
| `filter-logic-conformance.ts` | 97 | 3 | 2 docblock, 1 line comment |
0 |
| `object.form.ts` | 31 | 1 | 1 line comment | 0 |
| `data-engine.zod.ts` | 48 | 0 | | 0 |
| `data-engine.test.ts` | 29 | 0 | | 0 |
| `hook.form.ts` | 0 | 0 | | 0 |

None of the 19 sites is a string, so this stage leaves no string token
behind.

## Per-number table

| number | sites / lines | anchor: what it decided |
|---|---|---|
| `objectstack-ai#8772` | 4 / 4, `object.zod.ts:2718`, `:2731`, `:2744`, `:2910` |
`75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling.
`ObjectSchema.create()` forces `required: true` on a `master_detail`
reference under `controlled_by_parent` and refuses an explicit
`required: false`. Raw parse stays tolerant, and runtime tolerance is
the ruling's other half. Its changeset records the measurement that only
the security gate closed that shape while the declaration surface
accepted it (`:2731`). ADR-0055 stays cited beside it. It is the same
anchor stage 3 gave `object.test.ts` |
| `objectstack-ai#10165` | 2 / 2, `object.zod.ts:818`, `:1036` | `801296050`:
`ttl.onlyWhen` with the canonical null predicate (maintainer ruling
2026-08-20, option A). One shared `onlyWhen` union, and both of
`retention.onlyWhen`'s conflicts mirrored. Its diff wrote both
`[objectstack-ai#10165]` blocks |
| `objectstack-ai#10347` | 3 / 3, `object.zod.ts:1006`, `:1042`, `:1049` |
`530c1df65`: the Archiver honours a declared `ttl`. It selects by the
ttl cutoff on `ttl.field` when `ttl` is declared, and by `created_at` /
`archive.after` otherwise (maintainer ruling 2026-08-20) |
| `objectstack-ai#10527` | 1 / 1, `object.zod.ts:1005` | `5649efbf9`: refuses a
diverging retention + ttl + archive triple at parse time. Its diff wrote
this very paragraph |
| `objectstack-ai#11195` | 1 / 1, `object.zod.ts:1791` | `b37231883`:
`UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor`
(the "last three" the line names) |
| `objectstack-ai#11408` | 1 / 1, `object.zod.ts:2189` | `f11fc61c5`: declares
`editMode` on the object document (maintainer ruling 2026-08-24, the
`objectstack-ai#10144` declare-or-rule-out family, which stays cited) |
| `objectstack-ai#13608` | 3 / 3, `object.zod.ts:2317`, `:2354`, `:2366` |
`fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only
at mint, fail-closed, with the undifferentiated `null` refusal. Its
changeset heads with objectstack-ai#13608. It is the same anchor stage 1 gave
`contracts/share-link-service.ts` |
| `objectstack-ai#13195` | 3 / 3, `filter-logic-conformance.ts:190`, `:250`, `:525` |
`9dac1ae01`, PR objectstack-ai#13529's squash commit, which stays as the link:
`$exists` means has-a-value on driver-memory's live mingo path, its
analytics face and driver-mongodb's `translateFilter` (the "last three
key-presence exits") |
| `objectstack-ai#12868` | 1 / 1, `object.form.ts:256` | `c459da6bc`: narrows the
per-option `default` key out of the form-view options vocabulary, which
offered a key nothing on that surface read. Commit `e808890958`, which
wrote this line, names objectstack-ai#12868 as the same offer-vs-door class |

The shas were checked at the base and again at `origin/main`
`288611e3e5`. Every one matches exactly one commit (`git rev-parse
--disambiguate`, count 1). Every one is an ancestor (`git merge-base
--is-ancestor`, exit 0 for 9 of 9). The control leg `e9584681a4` also
exits 0, and the repository is not shallow. For each commit, a grep of
its own message or diff finds the number it replaces. Seven of the nine
name it in the message. `fc9ba76a5` names it in its diff (20 lines,
including its changeset heading), and so does `c459da6bc` (8 lines,
including its changeset heading).

Wordings to check, each true of its commit:
- `object.zod.ts:2731` now reads 「closes that shape, and commit
75b7c24 records that the declaration and the enforcement disagree」.
The measurement was the card's. The commit's changeset records it: "only
the security gate closed that shape while the declaration surface
accepted it".
- `object.zod.ts:2189` reads 「Declared here by commit f11fc61's
maintainer ruling」, and `:2744` reads 「the other half of commit
75b7c24's ruling」. This is stage 3's wording for the same relation
(`object.test.ts`, 「the other half of commit 75b7c24's ruling」): the
commit that landed the ruling and quotes it.
- `object.zod.ts:1049` reads 「That is the whole of what [commit
530c1df] changed here」. Commit `52db1d1f2a` wrote the paragraph.
`530c1df65` is the change it describes.

## Mechanical guard: no code token moves

The check compares leaf tokens with comments stripped, base `03b19d9cfd`
against head `53c9070dfd`. It uses the TypeScript parser's leaf tokens
(TypeScript from the head's lockfile), so template literals are scanned
in context, and it excludes JSDoc nodes. It ran over all 3 touched `.ts`
files. It is the stage-3 instrument, unchanged.

- Real run: 13,624 base tokens (object.zod.ts 8,774, object.form.ts
3,226, filter-logic-conformance.ts 1,624), **0 files with a token
change** (exit 0).
- Comment-insertion control (`object.form.ts`): 0 files changed, as
expected (exit 0).
- Positive control (a declaration inserted into `object.zod.ts`): 1 file
reads DIFFER at token 1629 (exit 1).
- Positive control (one digit changed inside the `objectstack-ai#5322/objectstack-ai#5134` `note`
string in `filter-logic-conformance.ts`): 1 file reads DIFFER at token
889 (exit 1).

Line balance: `object.zod.ts` +15 / -15, `filter-logic-conformance.ts`
+4 / -4, `object.form.ts` +1 / -1. Line counts are equal at base and
head: 3,240, 621 and 751.

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/spec`
is included. It says only that the provenance comments were re-anchored.
`Clause-②: no`: no export, key, value or type moves (the guard above).

Measured on the head's built package: `object.zod.ts` is
`src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten
comments also reach `dist`:
- `9dac1ae01` appears in `dist/data/index.d.ts` (the
`filter-logic-conformance.ts` docblock) and in 4 bundled `.js` files;
- `fc9ba76a5`, `f11fc61c5` and `b37231883` each appear in 22 bundled
`.js` files, and `c459da6bc` in 12;
- the positive control, the pre-existing `object.zod.ts` sentence
「Fail-CLOSED at both points」, appears in 11 bundled `.js` files.

## Gates (head `53c9070dfd`)

- **Citation judging pass, run as CI runs it:** `pnpm
check:issue-citations && node scripts/check-issue-citations.mjs` exits
0. The self-test passes 73 cases in 7 batteries. The live run judged 6
citations across 3 files: 3 resolve (`objectstack-ai#9138` twice, `objectstack-ai#11410`) and 3
resolve as a pull request (`objectstack-ai#13529`, the link).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at the head derived 79 families, and
all 79 exit 0. `--ran` reports 79 run, 0 NOT MEASURED, 0 unrun, and
exits 0. A full `turbo run build` of `./packages/*` ran first, under the
shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate
met an unbuilt prerequisite.
- `pnpm --filter @objectstack/spec run check:generated`: under the lock
against that build, `All 15 generated artifacts are up to date`, VERDICT
command-exit 0.
- **Tests and typecheck:**
- `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2
src/data` under the lock: Test Files 107 passed (107), Tests 3527
passed, 1 todo (3528), VERDICT command-exit 0. It covers every test in
`data/`, among them `object.test.ts`, which reads these schemas.
- The 13 spec suites outside `src/data` that read the touched files'
source text or pin their line numbers, under the lock: Test Files 13
passed (13), Tests 544 passed (544). They are stage 3's 12
(`scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`,
`src/api/api-entry-graph.pin.test.ts`,
`src/contracts/scoped-context.test.ts`,
`src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`,
`src/system/constants/platform-object-names.test.ts`,
`src/type-alias-convention.pin.test.ts`, `src/ui/dashboard.test.ts`)
plus `src/shared/union-author-message-pins.test.ts`, which pins
`data/object.zod.ts:855`.
- `pnpm --filter @objectstack/spec typecheck` under the lock exits 0,
including `check:test-typecheck` (53 files, 251 errors, 138 pinned
signatures held).
- **Lint, as a proven narrowing at the head:** `eslint
--no-inline-config --format json` over the 3 touched `.ts` files gives 3
files, 0 errors and 0 warnings. All 3 are in eslint's own population
(`isPathIgnored` is false for each). `eslint.config.mjs` never enables
type-aware linting (no `parserOptions.project`, which its own line 328
states), so a comment edit here cannot move the verdict on any untouched
file. The repo-wide `pnpm lint` is CI's run.

## Acceptance notes

- **Base.** The branch forked from `03b19d9cfd`, stage 3's landing.
`origin/main` then moved two commits (`05077d4c26`, PR objectstack-ai#20532, and
`288611e3e5`, PR objectstack-ai#20536), and neither touches `data/`. `dispatch-gates`
flagged its derivation as stale because `scripts/regen-artifacts.mjs`
had moved, so `origin/main` was merged in (`53c9070dfd`, a clean merge
with no driver-deferred path) before the gates ran. The PR's delta
against `origin/main` is exactly its 4 files. `origin/main` has since
moved two more commits: `7e36a3cd7c` (PR objectstack-ai#20531) and `ba5927f714` (PR
objectstack-ai#20460). Neither touches `data/` or anything the gate derivation reads,
and a re-derivation prints the same 79 commands. A no-driver
`merge-tree` of the head onto `ba5927f714`, from a bare shared clone,
exits 0. So there is no second merge.
- **Open PRs, re-read at 2026-09-29T02:01Z:** 9 open PRs, and none
touches any of the six files. The `data/` files open PRs touch are
objectstack-ai#20458's `analytics*` files, objectstack-ai#20504's `driver/turso.*`, and objectstack-ai#20545's
`filter-number-comparand-declared-type.*`, which is disjoint. Since the
claim, PR objectstack-ai#20460 has landed (`ba5927f714`) without touching
`filter-subtree-provenance.ts`. That file's 3 dead sites are outside
this claim's fence, so they are left for a later stage.
- **The rung.** Two anchored changes also have ADR-0087 entries in
`packages/spec/src/migrations`: `cbp-master-detail-required-forced` for
objectstack-ai#8772, and `form-view-option-default-retired` for objectstack-ai#12868. The second
entry's own header names commit `c459da6bc`. This PR takes the commit
rung, as stages 1 to 3 did. The D3 id is the more durable in-repo
record, if the ruling's first rung is later read to include those
entries.
- **What stays in `data/` after this stage: 58 dead sites.**
- **12 comment sites in files other open work still holds.**
`analytics.zod.ts`, `analytics-strictness-batchd.test.ts` and
`analytics-date-range-two-bound-window.test.ts` hold 5 (objectstack-ai#20300, PR
objectstack-ai#20458). `driver/turso.zod.ts` and `driver/turso.test.ts` hold 4
(objectstack-ai#20437, PR objectstack-ai#20504). `filter-subtree-provenance.ts` holds 3. It was held
by objectstack-ai#20367 and is now free (see above).
- **3 comment sites stage 3 left on purpose.** They are the test-read
`[objectstack-ai#6259]` marker at `api-derivation.ts:163`, the test comment at
`api-derivation.test.ts:232` that names it, and `field.zod.ts:370`,
whose `objectstack-ai#6111` is objectui's number.
- **43 string sites**, left as tokens: 41 test strings (2 of them in the
held analytics and turso test files) and the 2 exported
`AGGREGATION_CASES` note strings in `aggregation-conformance.ts`
(`:398`, `:407`, objectstack-ai#11065), which objectstack-ai#20489's claim holds.
- **Outside `data/`,** the card's other remaining items are unchanged:
the migrations and ui areas, the `liveness/**` notes, the `why` strings,
the `PROVENANCE_WAIVERS` reason, and `rest-server.zod.ts`.
- **The citation gate's reach.** It defers `packages/**/*.test.ts`. No
test file is touched here, so all 3 touched files are in its judging
population.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…/src remainder to the commits and ADRs that decided them (stage 6) (objectstack-ai#20606)

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

Stage 6 of the staged sweep: the `packages/spec/src` remainder outside
`migrations/` and the held files. Its claim is `5884233505`, with 22
files named there. Every comment or docblock line in those files that
cited a tracker number answering 404 now cites what decided its rule, in
ruling C+D's form C. That is the commit on `main` that decided the rule,
or, for one number, the ADR amendment that records the ruling. Each line
says in its own words what was decided. Comments only: 66 lines out, 66
in, across 21 files. No code token, string literal, `describe()` text or
message-catalog string moves. Two dead sites stay byte-identical,
because a test reads each one by literal.

The census is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to the 22 paths. Before: base `c876a7426d`,
board enumerated (185 pages, frontier objectstack-ai#20590). After: head `9d63cb6548`,
frontier objectstack-ai#20604.

## Measurement

| file (under `packages/spec/src/`) | dead before | after | numbers,
then anchor |
|---|---:|---:|---|
| `api/rest-server.zod.ts` | 11 | 0 | objectstack-ai#14691 ×9 to `b3a63d32c`; objectstack-ai#14369
×2 to `a3d5724c8` |
| `system/i18n-resolver.ts` | 14 | 0 | objectstack-ai#12961 ×6 to `901355c3b`; objectstack-ai#13218
×4 to `c45d8e6b4`; objectstack-ai#8460 ×2 to ADR-0029 D9.2a; objectstack-ai#10926 to `d173125fb`;
objectstack-ai#13109 to `8b236c826` |
| `system/operation-message.ts` | 4 | 0 | objectstack-ai#12493 ×4 to `aa5994e17`
(docblock lines only) |
| `system/translation.zod.ts` | 2 | 0 | objectstack-ai#10926 ×2 to `d173125fb` |
| `system/core-services.zod.ts` | 1 | 0 | objectstack-ai#6604 to `d127ff002` |
| `system/dev-login.zod.ts` | 1 | 0 | objectstack-ai#17081 to `24d622b94` (objectstack-ai#17556
stays, 200) |
| `system/environment-artifact.zod.ts` | 1 | 0 | objectstack-ai#11333 to `e58ea8b38`
(objectstack-ai#14865 and objectstack-ai#13457 stay, 200) |
| `shared/identifiers.zod.ts` | 7 | 0 | objectstack-ai#12245 ×2 to `c41b42e8d`; objectstack-ai#12144
×2 to `3a04b0125`; objectstack-ai#12194 and objectstack-ai#12176 (:140-141) to `311433f6b`; objectstack-ai#12176
(:189) to `7986d973f` |
| `shared/metadata-collection.zod.ts` | 1 | 0 | objectstack-ai#10485 to `35ad101bc` |
| `index.ts` (package root) | 6 | 0 | objectstack-ai#11350 ×5 to `ece4dad31` (objectstack-ai#11709
stays, 200); objectstack-ai#10485 to `35ad101bc` |
| `automation/control-flow.zod.ts` | 1 | 0 | objectstack-ai#14419 to `c5a7448d5`
(objectstack-ai#14954 stays, 200) |
| `automation/execution.zod.ts` | 1 | 0 | objectstack-ai#13681 to `18d816a50` |
| `automation/index.ts` | 1 | 0 | objectstack-ai#16659 to `ecdfc9411` |
| `automation/schedule-organization.zod.ts` | 1 | 0 | objectstack-ai#16659 to
`ecdfc9411` |
| `ai/index.ts` | 1 | 0 | objectstack-ai#11350 to `ece4dad31` |
| `identity/identity.zod.ts` | 1 | **1** | objectstack-ai#8715 kept at :230 (a test
reads it); `2c86fe3ea` added on :231 |
| `security/explain.zod.ts` | 1 | 0 | objectstack-ai#8714 to `42b05af89` |
| `security/public-form.ts` | 1 | 0 | objectstack-ai#6640 to `2ab1257c9` |
| `data/api-derivation.ts` | 1 | **1** | objectstack-ai#6259 kept at :163 (two tests
read it); `6968885ef` already on :164; file untouched |
| `data/driver/turso.zod.ts` | 2 | 0 | objectstack-ai#6345 ×2 to `e2798fab7` |
| `conversions/walk.ts` | 1 | 0 | objectstack-ai#13031 to `b799ac553` |
| `meta-spelling/metadata-url-spelling.ts` | 2 | 0 | objectstack-ai#10485 ×2 to
`35ad101bc` |
| **22 files** | **62** | **2** | 26 numbers, 24 removed: 24 distinct
shas and 1 ADR |

Per-file counts at base equal the claim's (census `5884031174` at
`f11b5f20a2`) in all 22 files. A second instrument agrees site for site:
every `#N` in the 22 files, classified by the TypeScript parser, and
each of 201 distinct numbers probed by REST `issues/N` without
redirects. It found 484 sites, all in comments and none in a string, 26
dead numbers and 62 dead sites. Its string-class positive control found
19 string sites in `api/rest-server.test.ts`. Head: 424 sites and 177
numbers, 175 answer 200 (the same 175), and 2 answer 404 (the two kept
sites). Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every
checkpoint (4 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and
objectstack-ai#16697 answered 404 at every checkpoint.

## Why each anchor decides its line

Each sha resolves uniquely, is an ancestor of `origin/main` (and of the
base), has one parent, and names the number it replaces in its own
message or diff. Each was read for the rule its line states.

- **objectstack-ai#14691 to `b3a63d32c`**: the retirement of the ten inert
`RestServerConfig` keys under ADR-0049 enforce-or-remove. Its own
`rest-server.zod.ts` diff wrote all nine `objectstack-ai#14691` lines: the tombstones,
the dropped `CrudEndpointPatternSchema` and the `routes` block.
- **objectstack-ai#14369 to `a3d5724c8`**: seeded the four `RestServerConfig` liveness
ledgers "from the census filed with objectstack-ai#14369" (its changeset heading names
the number). It records both facts the two lines state: every CRUD route
is mounted from hard-coded method/path pairs, and `routes` is parsed,
defaulted and normalized, then never read.
- **objectstack-ai#12961 to `901355c3b`**: "Ruled 2026-08-29 (option A)".
`translatePage` descends into declared `properties.children`; on an id
collision a region-level component wins outright, and among nested
matches document order decides. Its diff wrote the `objectstack-ai#12961` lines being
replaced.
- **objectstack-ai#13218 to `c45d8e6b4`**: exports `walkAddressedPageComponents` and
consumes it from both sides; its changeset reads "(objectstack-ai#13218, ruled
2026-08-30)".
- **objectstack-ai#13109 to `8b236c826`**: its changeset says the extractor OMITTED
keys the resolver reads, and "This matches the second half". The line
now says the second half went live and this commit repaired it.
- **objectstack-ai#8460 to ADR-0029 D9.2a**: ruling C's first rung. The amendment
"D9.2a — AMENDMENT (2026-08-13)" records the option-A ruling: an
extender's scalar applies only while the fold's base still carries the
packaged owner's value. It also records that the mechanism is
deliberately the same comparison-based one the catalog uses one layer
up. The heading marker `[objectstack-ai#8460]` becomes `[ADR-0029 D9.2a]`.
- **objectstack-ai#10926 to `d173125fb`**: "Option A per the maintainer ruling on
objectstack-ai#10926 (2026-08-22): drop the key", the `submitLabel` retirement all
three lines describe.
- **objectstack-ai#12493 to `aa5994e17`**: adds `record_write_denied` and
`approval_recall_not_submitter` ahead of their emitters, with no
placeholders. Its diff wrote the four docblock lines. The catalog's
rendered strings are untouched, per hypothesis 5.
- **objectstack-ai#6604 to `d127ff002`**: "Per the maintainer's 2026-08-08 Option-B
ruling the kernel side takes the domain-specific name", matching
`KernelServiceMapSchema`.
- **objectstack-ai#17081 to `24d622b94`**: lands objectstack-ai#17556 as "Suggestion 1 of objectstack-ai#17081".
The line keeps objectstack-ai#17556 and names the parent card in words.
- **objectstack-ai#11333 to `e58ea8b38`**: declares `grantedPermissions`, described by
the commit itself as "the artifact-contract half of objectstack-ai#11333 option A /
the objectstack-ai#13457 batch ruling". The line keeps objectstack-ai#14865 and objectstack-ai#13457 and states
the ruled option in words.
- **objectstack-ai#12245 to `c41b42e8d`**: rewrote this docblock from "the per-surface
census (its os-dev-report comment, measured on origin/main @ e2debee)"
and carries the 1218-values measurement. The report comment lived on the
deleted card, so the commit is now the record, and the line says so.
- **objectstack-ai#12144 to `3a04b0125`**: wrote the storage-owned length-ceiling note
and its storage-column pin; its changeset heads "(objectstack-ai#12144)".
- **objectstack-ai#12194 / objectstack-ai#12176 to `311433f6b`** (:140-141): stage 1, the item-name
grammar declared and refused at the publish door; its message reads
"Stage 1 of objectstack-ai#12176". **objectstack-ai#12176 to `7986d973f`** (:189): "Retire
compound-name metadata addressing", stage 3 of the maintainer-ruled
retirement.
- **objectstack-ai#10485 to `35ad101bc`**: retires the `themes` carrier, `ThemeSchema`
and the `PLURAL_TO_SINGULAR` fold ("Ruled B"). Its own diff wrote all
four lines.
- **objectstack-ai#11350 to `ece4dad31`**: "Invariant recorded (maintainer ruling
2026-08-23)". It also points the premise-delta note at objectstack-ai#11709, which is
what `index.ts:148` now says. This is stage 1's wording for
`kernel/index.ts:53`.
- **objectstack-ai#14419 to `c5a7448d5`**: `create_record` surfaces the engine's
`DUPLICATE_RECORD` code and the engine binds it on `$error`, the
founding case the line names. Its message names objectstack-ai#14419 as the card it
lands.
- **objectstack-ai#13681 to `18d816a50`**: declares the run-level
`FlowRunSummary.failed`, the spec half of the contained-failure
contract.
- **objectstack-ai#16659 to `ecdfc9411`**: declares the start-node
`config.organization` key and "the one refusal sentence every
enforcement point says". Its sub-commits pin "the three objectstack-ai#16659
consequences", so it is also the commit that closed the defect the
second line describes.
- **objectstack-ai#8714 to `42b05af89`**: "ONE closed contributor-state enumeration",
maintainer-ruled 2026-08-18.
- **objectstack-ai#6640 to `2ab1257c9`**: `preserveAudit` is UPDATE-only (stage 1's
anchor for the same rule).
- **objectstack-ai#6345 to `e2798fab7`**: its own `turso.zod.ts` diff wrote both lines
("The maintainer's objectstack-ai#6345 ruling closes it…", "(objectstack-ai#6345 fork 2)"). The
wording is stage 3's in `config-registry.zod.ts`.
- **objectstack-ai#13031 to `b799ac553`**: adds `mapViewPayloads` to `walk.ts`, the
centralized walk the heading describes. Its message names objectstack-ai#13031 as the
card it lands.
- **objectstack-ai#8715, kept**: `2c86fe3ea` ("Maintainer ruling 2026-08-15
(disposition B: delete)"; its diff wrote :230) now sits on :231.

## Mechanical proof

- **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc
kinds excluded, controls mutate the head text in memory only). Base
`c876a7426d` against the head, 21 files, 37,539 base tokens:
  - Real run: 0 files with a token change (exit 0).
  - Comment-insertion control (`ai/index.ts`): 0 (exit 0).
- Code-insertion positive control (`system/i18n-resolver.ts`): DIFFER at
token 14452 (exit 1).
- String positive control (a real `StringLiteral` in
`system/operation-message.ts`, found by the parser): DIFFER at token 5
(exit 1).
- The first string-control attempt matched a quoted fragment inside a
comment and did not fire. It was a vacuous control, not a measurement,
and the parser-located control above replaced it.
- **Line balance**: every file is +N/−N (66/66 across 21 files); every
line count is equal at base and head.
- **Tracker numbers**: added-not-removed is empty in every file, and no
`PR #N` is on an added line. Net-removed: 60 sites, 24 numbers.
- **Shas**: 24 distinct on added lines, 0 on removed lines.
  - `rev-parse --disambiguate` answers 1 object for each.
- `merge-base --is-ancestor` exits 0 against `origin/main` `e666636fd9`
and against the base.
- Each is single-parent; the repository is not shallow; the control leg
`e9584681a4` exits 0.
- Each commit's own message (17 of 24) or diff (all 24) names the number
it replaces.
- **Literal readers**: every string literal in the repository that
carries one of the 26 numbers was matched against the 22 files' text.
Three hits read these files:
  - `data/api-derivation.test.ts:236` splits on `[objectstack-ai#6259]`;
  - `packages/runtime/src/api-exposure.test.ts:152` splits on `objectstack-ai#6259`;
- `identity/api-key-retirement.test.ts:118` asserts `are NOT declared
here (objectstack-ai#8715`.
- Those lines are the two kept sites. No test or script matches any
rewritten line by pattern.

## Tests and gates (at head `9d63cb6548`)

- `pnpm exec turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71
total, VERDICT command-exit 0.
- `pnpm --filter @objectstack/spec check:generated`: exit 1,
`check:docs` stale (1 of 15). `check:generated --fix` then regenerated
exactly that artifact: 2 pages, 3 lines, each its docblock line
verbatim.
  - `content/docs/references/automation/schedule-organization.mdx`
  - `content/docs/references/data/driver-turso.mdx`
  - The re-check in the gate run below is exit 0.
- `vitest run --maxWorkers=2` over the touched areas
(`src/api/rest-server.test.ts`,
`src/api/rest-api-config-dead-keys-retirement.test.ts`, `src/system`,
`src/shared`, `src/automation`, `src/ai`, `src/identity`,
`src/security`, `src/data/driver`, `src/conversions`,
`src/meta-spelling`): Test Files 159 passed (159), Tests 5163 passed
(5163).
- The 23 spec suites outside those areas that read a touched file's
source text or name it: Test Files 23 passed (23), Tests 540 passed
(540).
-
`scripts/{tombstoned-row-status,strictness-ledger,file-description,skill-map-guards,root-index,export-origins,category-title,split-entries,dist-freshness,dist-freshness-adoption,root-entry-type-nameability.pin}`
tests;
- `src/type-alias-convention.pin`,
`src/contracts/{automation-result-status.pin,automation-service,scoped-context}`,
`src/api/{export-job-family-retirement,api-entry-graph.pin}`,
`src/eager-entry-import`, `src/integration/connector-author-shape`,
`src/ui/{interaction-config-retirement,notification,strictness-batch14}`,
`src/migrations/migrations`.
- `scripts/build-schemas-check-mode.test.ts` is left to CI: it imports
rather than reads, and rebuilds schemas in a temp tree.
- `pnpm --filter @objectstack/spec typecheck`: exit 0;
`check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures
held).
- Lint, a proven narrowing: `eslint --no-inline-config --format json`
over the 21 touched `.ts` files gives 21 files, 0 errors, 0 warnings.
  - `isPathIgnored` is false for all 21, read through eslint's API.
- `eslint.config.mjs:327-328` says type-aware linting is never enabled,
so a comment edit cannot move an untouched file's verdict.
  - The repo-wide `pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 108 families derived and run, every one exit 0. `--ran`
reads "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN". Among them:
- `pnpm check:issue-citations` plus the live diff-scoped `node
scripts/check-issue-citations.mjs` judged 7 citations across 21 files, 7
resolve: the live numbers kept beside the anchors (objectstack-ai#9249, objectstack-ai#14954,
objectstack-ai#17556, objectstack-ai#14865, objectstack-ai#13457, and objectstack-ai#11709 twice).
- `pnpm check:doc-authoring`: 16,765 customer-facing strings across
1,175 spec sources clean; the sibling baseline holds.
- Changeset: `patch` for `@objectstack/spec`. 13 of the 21 touched
sources are `src/**/*.zod.ts`, which `files[]` ships verbatim, and the
rewritten docblocks reach `dist`. For example, `ruled collision
arbitration (commit 901355c)` is in 1 `.d.ts`, and the unchanged
neighbouring sentence in the same exported docblock (the positive
control) is in 1 `.d.ts`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`7a1faf1a5d`, from a bare shared clone, exits 0. The 3 commits `main`
gained since the base touch none of this diff's files. No merge was
made, as stages 1–4 did.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** The population is exactly the claim's 22 files: 62 dead
sites at the tip, equal per file to the census at `f11b5f20a2`.
2. **Holds, with nothing to respell.** No sibling-qualified pair occurs
among the 62 sites; no `pre-#N` spelling is dead here.
3. **Holds.** Re-read at 2026-09-29T06:23Z, after the last push and
before this PR was opened: all 14 open PRs' full file lists (1,116 files
in the version PR alone), and the newest `Claim:` on all 15
`pm:dispatched` cards. None names any of this PR's 24 paths. The five
exclusions stay excluded.
4. **Holds.** The two projected pages were regenerated by the generator,
never by hand. No other page under `content/docs/references/` carries
any of the 26 numbers.
5. **Holds.** No `#N` in the 22 files is inside a string; the two
literal-read sites stay as tokens. In `system/i18n-resolver.ts` and
`system/operation-message.ts` only docblock and line-comment lines
moved.

## Deviations

- The file surface is 21 of the 22 named files. `data/api-derivation.ts`
is untouched, because its one dead site is read by literal and its
commit already stands on the next line (stage 3's disposition).
- Six changed lines held no dead number. Each is the other half of a
rewritten sentence: `rest-server.zod.ts:756`, `identifiers.zod.ts:19`,
`index.ts:133`, `environment-artifact.zod.ts:137`,
`schedule-organization.zod.ts:82`, and `identity.zod.ts:231` (the commit
placed beside the kept :230).
- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`); the pre-push trailer check passed on
every push.

## Acceptance notes

**What stays for later stages.** At the tip `7a1faf1a5d` with this PR
applied, `packages/spec/src` holds **260** dead sites (34 numbers). This
is the gate's census on this head, with the four spec sources `main`
changed since the base re-extracted and re-probed at the tip. By area:
- `migrations/` **233**: objectstack-ai#20233 edits the same entry files; the
author-shown fields are its form D.
- `conversions/registry.ts` **12**: held by PRs objectstack-ai#20570 and objectstack-ai#20458.
- `stack.zod.ts` **9**: free now; PR objectstack-ai#20579 landed as `7a1faf1a5d` at
06:02Z, after the claim, so it stayed excluded here.
- `data/analytics.zod.ts` **3**: PR objectstack-ai#20458.
- `integration/connector.zod.ts` **1**: objectstack-ai#20287, PR objectstack-ai#20587.
- `data/api-derivation.ts:163` (objectstack-ai#6259) and
`identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because
`api-derivation.test.ts:236`,
`packages/runtime/src/api-exposure.test.ts:152` and
`api-key-retirement.test.ts:118` read them by literal. Removing them is
a test-string change, form D, outside this card's comment-only scope.

**Carried from earlier stages, outside the gate's census** (which blanks
strings and defers test files): the dead-number test-title strings, the
two `why` strings, the `PROVENANCE_WAIVERS` reason, the two
`AGGREGATION_CASES` notes, and the `liveness/**` notes.

**Outside `packages/spec/src`** (objectstack-ai#20556's lane):
`packages/spec/scripts/check-entry-nameability.ts` cites objectstack-ai#11350 in its
header (:14) and PRINTS "recorded on objectstack-ai#11350" in its failure text (:727);
`packages/spec/scripts/root-entry-type-nameability.pin.test.ts` cites it
too. Commit `ece4dad31` is the anchor, already verified here.

**Rung.** Five of the anchored retirements also have ADR-0087 D3/D2
entries: `identity-api-key-schema-retired`,
`metadata-item-name-grammar-enforced`,
`rest-server-config-dead-keys-retired`, `stack-themes-carrier-retired`
and `translation-component-submit-label-retired`. This PR takes the
commit rung, as stages 1–5 did. The D3 id is the more durable in-repo
record if the ruling's first rung is later read to include those
entries.

**Wording, each true of its commit.**
- `dev-login.zod.ts:10` names objectstack-ai#17081 in words ("suggestion 1 of its
parent card").
- `environment-artifact.zod.ts:136-137` states objectstack-ai#11333's option A as "the
option the objectstack-ai#13457 batch ruling chose".
- `identifiers.zod.ts:18` drops "its `os-dev-report` comment is the
measurement of record", since that comment went with the card, and names
the commit as the record.

**Observation, not filed** (a pre-existing live citation, not a tracker
number; carrier: none): `environment-artifact.zod.ts:137` and
`packages/runtime/src/security/artifact-granted-permissions.ts:6` cite
"ADR-0025 §3.5 step 2" for the granted set. At the tip, §3.5's numbered
step 2 is "Compatibility" and "Permission consent" is step 3.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…data/analytics.zod.ts to the commits that decided them (stage 7) (objectstack-ai#20616)

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

Stage 7 of the staged sweep: `packages/spec/src/stack.zod.ts` and
`packages/spec/src/data/analytics.zod.ts`, both freed by landings (PR
objectstack-ai#20579 and PR objectstack-ai#20458). Its claim is `5885635758`. Every comment or
docblock line in those two files that cited a tracker number answering
404 now cites the commit on `main` that decided its rule, in ruling
C+D's form C, and says in its own words what was decided. Comments only:
12 lines out, 12 in, across 2 files. No code token, string literal or
`describe()` text moves. No dead site stays: none of the 12 is read by
literal.

The census is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to the two paths. Before: base `0f6dcac5e9`,
board enumerated (185 pages, frontier objectstack-ai#20611). After: head `cc0580d404`,
board enumerated (185 pages, frontier objectstack-ai#20615).

## Measurement

| file (under `packages/spec/src/`) | dead before | after | numbers,
then anchor |
|---|---:|---:|---|
| `stack.zod.ts` | 9 | 0 | objectstack-ai#10485 ×2 (`:415`, `:1023`) to `35ad101bc`;
objectstack-ai#6238 (`:633`) to `c8d6f6e08`; objectstack-ai#14192 (`:1233`) to `4d0d9445a`; objectstack-ai#14686
×2 (`:3037`, `:3194`) to `279431e7a`; objectstack-ai#14662 ×3 (`:4510`, `:5070`,
`:5293`) to `35dffeace` |
| `data/analytics.zod.ts` | 3 | 0 | objectstack-ai#10194 ×3 (`:404`, `:407`, `:485`)
to `2306a765c` |
| **2 files** | **12** | **0** | 6 numbers removed, 6 distinct shas |

Per-file counts at base equal the claim's (9 and 3, from stage 6's
census). A second instrument agrees site for site: every `#N` in the two
files, classified by the TypeScript parser, and each of the 84 distinct
numbers of 100 or more probed by REST `issues/N` without following
redirects (the other 3 are the ordinals `Prime Directive objectstack-ai#12`, `batch
objectstack-ai#23`, `batch objectstack-ai#57`).
- Base: 244 sites, all in comments (0 strings, 0 code). 78 numbers
answer 200 and 6 answer 404: the same 6 numbers and the same 12 sites as
the gate.
- Its string-class positive control found 11 string sites in
`kernel/manifest-unknown-keys.test.ts` and
`packages/cli/src/utils/lower-callables.test.ts`.
- Head: 232 sites, 78 numbers, all 78 answer 200 (the same 78), none
answers 404.
- Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every
checkpoint (3 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and
objectstack-ai#16697 answered 404 at every checkpoint.

## Why each anchor decides its line

Each sha resolves uniquely, is an ancestor of `origin/main` (and of the
base), and has one parent. No file under `docs/adr/**`,
`docs/NORTH-STAR.md` or `scripts/adr-anchors/` names any of the six
numbers or records these rules, so each takes the commit rung, as stages
1–6 did.

- **objectstack-ai#10485 to `35ad101bc`** (`:415`, `:1023`): retires the `themes`
carrier key and `ThemeSchema` under ADR-0049. Its message records the
ruling, "Ruled B (退役授权面, 2026-08-21)", and its own `stack.zod.ts` diff
wrote both lines. `:415` keeps ADR-0049 and the ruling in its words; the
D3 entry `stack-themes-carrier-retired` it names on `:423` is unchanged.
This is the anchor stages 1, 5 and 6 used for the same retirement.
- **objectstack-ai#6238 to `c8d6f6e08`** (`:633`): widens the array member of
`functions` so its `handler` also takes the lowered string ref, which is
the fix for `objectstack build` refusing its own array output. Its
message names objectstack-ai#6238, and its own diff wrote the line. objectstack-ai#4343 and objectstack-ai#4976 on
the same line stay (both 200).
- **objectstack-ai#14192 to `4d0d9445a`** (`:1233`): turns `ManifestSchema` and its
nested blocks into `strictObject` and flips the assembled-body strip pin
to a refusal pin; each of its sub-commits names objectstack-ai#14192. The line itself
was written later by `c78c9180de`, whose own message says "objectstack-ai#14192 closed
ManifestSchema with strictObject", so the commit that closed it is the
anchor.
- **objectstack-ai#14686 to `279431e7a`** (`:3037`, `:3194`): "defineStack refuses two
actions that resolve to one scope-qualified runtime key". Its subject
names objectstack-ai#14686, and its diff adds `collectDuplicateActionKeyErrors` and
the changeset for that refusal. Both lines were written later by
`773a99960a` (PR objectstack-ai#15022), whose message describes the same "same-key
rule, which runs before the merge".
- **objectstack-ai#14662 to `35dffeace`** (`:4510`, `:5070`, `:5293`): "composeStacks
refuses two stacks whose actions resolve to one scope-qualified runtime
key". It checks the composed set with the rule `defineStack` applies
within one stack, with no `actionConflict` option (maintainer ruling
2026-09-03). Its message does not name objectstack-ai#14662; its own `stack.zod.ts`
diff wrote all three `(objectstack-ai#14662)` lines.
- **objectstack-ai#10194 to `2306a765c`** (`analytics.zod.ts:404`, `:407`, `:485`):
binds `analytics_cube` (and `theme`) in `UNREGISTERED_KIND_SCHEMAS`, so
`PUT /meta/analytics_cube/:name` parses through `CubeSchema`, and gives
`CubeSchema` the `...MetadataProtectionFields` spread. Its message names
objectstack-ai#10194, and its own diff wrote all three lines. The `[objectstack-ai#10194]` markers
become `[commit 2306a76]`, the spelling stages 1 and 5 already use in
`kernel/metadata-type-schemas.ts`.

## Mechanical proof

- **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc
kinds excluded, controls mutate the head text in memory only). Base
`0f6dcac5e9` against the head, 2 files, 17,249 base tokens:
  - Real run: 0 files with a token change (exit 0).
  - Comment-insertion control (`data/analytics.zod.ts`): 0 (exit 0).
- Code-insertion positive control (`stack.zod.ts`, a declaration
appended): DIFFER at token 15388 (exit 1).
- String positive control (the first `StringLiteral` the parser locates
in each file): DIFFER at token 5 (exit 1), once per file.
- `describe()` positive control (the first `.describe()` string argument
the parser locates: `stack.zod.ts:133`, `analytics.zod.ts:244`): DIFFER
at tokens 507 and 442 (exit 1).
- **Line balance**: `stack.zod.ts` +9/−9, `data/analytics.zod.ts` +3/−3;
line counts equal at base and head (5344 and 853).
- **Tracker numbers**: added-not-removed is empty in both files, and no
`PR #N` is on an added line. Net-removed: 12 sites, 6 numbers. The only
numbers on added lines are objectstack-ai#4343 and objectstack-ai#4976, which stay on `:633`.
- **Shas**: 6 distinct on added lines, 0 on removed lines.
  - `rev-parse --disambiguate` answers 1 object for each.
- `merge-base --is-ancestor` exits 0 for each, against `origin/main`
`7510663c87` and against the base; each is single-parent; the repository
is not shallow.
- **Literal readers**: all 26 string, template and regex literals in the
repository that carry one of the six numbers (42 code files) were
matched against the two files' base text: 0 occur there. Each removed
line was also cut into 4-word windows (96) and searched across the tree:
the 9 hits inside string literals are other files' own test titles
sharing a phrase ("the ADR-0010 protection envelope", "an assembled body
is"), and none reads either file. The source-text readers of the two
files read code, not these comments:
`compose-stacks-refusal-envelopes.test.ts` counts `throw new Error(`,
and `check-stack-collection-maps.mjs` and
`check-skill-top-level-keys.mjs` read the declared collections and keys.

## Tests and gates (at head `cc0580d404`)

- `pnpm exec turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71
total, VERDICT command-exit 0.
- `pnpm --filter @objectstack/spec check:generated` under the lock: all
15 generated artifacts up to date, `check:docs` over
`content/docs/references/**` included; VERDICT command-exit 0. No
reference page projects any of the 12 lines, so none is regenerated.
- `vitest run --maxWorkers=2` under the lock over the two files' own
suites (`src/stack*`, `src/compose-stacks*`, `src/define-stack*`,
`src/assembled-package-body`, `src/data/analytics*`, `src/data/cube*`):
Test Files 35 passed (35), Tests 976 passed (976).
- The 37 spec suites that read source text across `src/`, or carry one
of these numbers, under the lock: Test Files 37 passed (37), Tests 759
passed (759).
-
`scripts/{category-title,dist-freshness,dist-freshness-adoption,file-description,strictness-ledger,strictness-ledger-doc,root-index,skill-map-guards,export-origins,split-entries,root-entry-type-nameability.pin}`,
`scripts/liveness/{evidence,tombstoned-row-status}`;
- `src/type-alias-convention.pin`, `src/eager-entry-import`,
`src/api/{api-entry-graph.pin,auth,export-job-family-retirement}`,
`src/ai/tool-confirmation-prescription-tense.pin`,
`src/data/{currency-mode-family-closure.pin,external-lookup-retirement}`,
`src/identity/position-delegatable-enforcer.pin`,
`src/integration/{connector-connection-timeout-retirement,connector-resilience-keys-retirement}`,
`src/security/rls-tags-retirement`,
`src/shared/{alias-integrity,retired-key-migrate-sentence}`,
`src/system/{compliance-families-retirement,constants/platform-object-names,email-template-floor-locale-parity.pin,message-queue-retirement}`,
`src/ui/{action-requires-confirmation-docblock.pin,i18n,interaction-config-retirement,strictness-batch14}`,
`src/kernel/{manifest-unknown-keys,metadata-type-schemas}`.
- Left to CI:
`scripts/{build-schemas-check-mode,def-key-collisions,openapi-self-consistency}`
(each rebuilds artifacts in a temp tree) and
`scripts/{check-generated-ledger,check-generated-fix-rebuild.pin}` (read
the ledger and `dist`). None reads comment text.
- `pnpm --filter @objectstack/spec typecheck` under the lock: exit 0;
`check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures
held).
- Lint, a proven narrowing: `eslint --no-inline-config --format json`
over the 2 files gives 2 files, 0 errors, 0 warnings.
  - `isPathIgnored` is false for both, read through eslint's API.
- `eslint.config.mjs:327-328` says type-aware linting is never enabled,
so a comment edit cannot move an untouched file's verdict.
  - The repo-wide `pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 79 families derived and run, every one exit 0. `--ran`
reads "79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN". Among them:
- `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) and
the live diff-scoped `node scripts/check-issue-citations.mjs`: it judged
the 2 citations on added lines, objectstack-ai#4343 and objectstack-ai#4976, and both are live
issues.
- `pnpm check:doc-authoring`: 16,804 customer-facing strings across
1,179 spec sources clean; the sibling baseline holds.
- `pnpm check:stack-collection-maps`: 8 enumerations reconciled against
31 declared collections.
- Changeset: `patch` for `@objectstack/spec`. Both files are
`src/**/*.zod.ts`, which `files[]` ships verbatim, and the rewritten
docblocks reach `dist`: "posture: commit 4d0d944 closed" and "[commit
2306a76] This docblock used to say" are each in 2 `.d.ts`, their old
spellings in 0. Positive control: the unchanged neighbouring sentence
"BY INHERITANCE — an undeclared key on one is REFUSED" is in the same 2
`.d.ts`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`7510663c87`, from a bare shared clone, exits 0. The 3 commits `main`
gained since the base touch neither file nor the citation or derivation
scripts, and a re-derivation prints the same 79 commands. No merge was
made.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** 12 dead sites at the tip, 9 in `stack.zod.ts` and 3 in
`data/analytics.zod.ts`, equal per file to stage 6's census.
2. **Holds.** Read at 2026-09-29T07:36Z and again at 08:16Z, after the
last push and before this PR was opened: all open PRs' full file lists
(9 PRs, 166 files at the second read) and the newest `Claim:` on all 11
`pm:dispatched` cards. None names either file, except this card's own
claim.
3. **Holds, with nothing to keep.** All 12 sites are comments. No test
string, exported string or `describe()` text carries one, and no test or
script reads any of them by literal.
4. **Holds.** No generated reference page projects these lines;
`check:docs` is green with no regeneration.

## Deviations

- None to the file surface: the 12 claimed lines and one changeset, no
generated page needed.
- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`); the pre-push trailer check passed on
every push.

## Acceptance notes

**What stays for later stages.** The gate's census at this PR's head
(base `0f6dcac5e9` plus this PR) reads **248** dead sites (29 numbers)
in `packages/spec/src`. The only `packages/spec/src` change `main` has
made since the base (objectstack-ai#20610's migrations entry and registry) adds four
live numbers and removes none, so 248 also stands at the tip
`7510663c87` plus this PR:
- `migrations/` **233**: objectstack-ai#20233 edits the same entry files (PR objectstack-ai#20607
holds `migrations/registry.ts`).
- `conversions/registry.ts` **12**: PRs objectstack-ai#20570 and objectstack-ai#20587 hold it.
- `integration/connector.zod.ts` **1**: PR objectstack-ai#20587 (objectstack-ai#20287).
- `data/api-derivation.ts:163` (objectstack-ai#6259) and
`identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because tests
read them by literal, so removing them is form D.

**Outside the gate's census: test files.** The gate defers `*.test.ts`.
The same six dead numbers still stand at 15 comment sites and 10
test-title strings in `packages/spec/src` test files:
- `data/analytics-strictness-batchd.test.ts:96` (comment, objectstack-ai#10194) and
its title `:93`. This file is in the `analytics*` set stages 3 and 4
excluded while PR objectstack-ai#20458 held it;
`analytics-date-range-two-bound-window.test.ts` and
`cube-member-inner-name-retirement.test.ts` were in that set too and are
not re-measured here.
- The package root: `compose-stacks-action-echo.test.ts:20`, `:34`,
`:200` (objectstack-ai#14686) and titles `:176`, `:224`;
`compose-stacks-action-key-collision.test.ts:3` (objectstack-ai#14662);
`stack-top-level-strict.test.ts:103` (objectstack-ai#10485) and title `:128`;
`type-alias-convention.pin.test.ts:257`, `:1572`, `:1937` (objectstack-ai#10485).
- `shared/`: `metadata-collection.test.ts:250`,
`metadata-url-spelling.test.ts:51`, `:72`, `:168` (objectstack-ai#10485), `:257`
(objectstack-ai#10194), title `:254`. `automation/sync-retirement.test.ts:207`
(objectstack-ai#10485).
- `kernel/`: `manifest-unknown-keys.test.ts`, four titles (objectstack-ai#14192);
`metadata-type-schemas.test.ts:422`, a title (objectstack-ai#10194).
- Stage 6 took the package root, `shared/` and `automation/` through the
gate's census, which never lists a test file, so test-file comment lines
there may carry other dead numbers as well. That wider population is not
measured here.

**Outside `packages/spec/src`.** The same six numbers stand at 44 more
sites
(`packages/{metadata-protocol,objectql,rest,runtime,cli,core,metadata,qa}`,
`examples/`, `scripts/`, `packages/spec/scripts/`), and at 19 sites in
`migrations/` (the objectstack-ai#20233 area).

**Rung.** The objectstack-ai#10485 retirement also has the ADR-0087 D3 entry
`stack-themes-carrier-retired`, which `:423` already names. This PR
takes the commit rung, as stages 1–6 did.

**Wording, each true of its commit.** `:3037` and `:3194` now read
"commit 279431e's same-key refusal": the refusal that commit added, in
lines `773a99960a` wrote. `:1233` reads "commit 4d0d944 closed
`ManifestSchema`", in a line `c78c9180de` wrote.

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

---------

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

## What this is

The release-time half of the 17.5.0 release notes. The page
`content/docs/releases/v17/17-5.mdx` landed before the cut (objectstack-ai#20396) with
a `RELEASE-TIME TODO` comment listing four edits to make once 17.5.0 was
on npm. 17.5.0 was published on 2026-09-29 (`@objectstack/cli@17.5.0` at
07:58Z, the last package, `@objectstack/spec`, at 08:09Z). This PR makes
those edits, deletes both TODO comments, and updates
`content/docs/releases/v17/index.mdx`.

Docs-only: two files under `content/docs/releases/`, which is
release-owned, so this is the dedicated docs-only PR `AGENTS.md`
sanctions for that tree. It publishes nothing from any package, hence
`skip-changeset`.

## What changed

**`17-5.mdx`**

- **Publish date.** "What's new" now opens: 17.5.0 was published to the
`latest` tag on 2026-09-29, 20 days after 17.4.0.
- **Count.** The draft said it was compiled from "868 changesets pending
on `main` at `ab6fb027`". The version commit `8c87d26a` (objectstack-ai#17076)
actually consumed **958** changesets (the `.changeset/*.md` files it
deletes, README excluded). The page now states 958 as its measure and
cross-checks it against the CHANGELOGs: the 69 package `CHANGELOG.md`
files that carry a 17.5.0 section at `8c87d26a` list **1,372 per-package
entries** (703 minor, 669 patch, 0 major) in 56 of those files, and
those entries de-duplicate to exactly the same 958.
- **The 90 changesets the draft never read**, the ones consumed by
`8c87d26a` but not pending at `ab6fb027`, were each read in full and
folded in:
- **Breaking changes & migration: 45.** Two new subsections: *Written
values are held to the field's declared type* (date and datetime ISO
spellings on a real day, the year range 0001–9999, the numeric string
grammar, `precision`, `progress` bounds, `/import` thousands commas,
with a Migration table) and *An edge-branched decision takes its first
matching branch* (objectstack-ai#20344, with the stored-row caveat). The rest joined
existing subsections: RLS cross-class comparisons; org-less grants; cube
`public`; number comparands, `having` placeholders and double
accumulation; flow node config, `connector_action`, `api` flow secrets
and the connector resilience keys; list-view `tabs`, action `aria` and
view round-trip keys; `/diff` `/history` `/audit` as authoring doors and
OpenAPI `info`; remote Turso and unbuildable indexes; the one stack
authoring shape and new lint positions; QA `requires`, narrowed
published types and `retiredAfter`.
- **New capabilities: 11.** Studio form rows for 27 structured keys, the
staged `$empty` operator, the new `ComponentPropsMap` rows, and email
verification under `open`.
- **Notable fixes: 15.** Dispatcher-only hosts, `/diff` default range,
plain-text email faces, auth-settings sibling isolation, SQLite
`reclaimSpace()`, zh-CN/ja-JP/es-ES object labels, aggregate `search`,
and the OSV sweep.
- **New in Console: 2.** The fourth objectui pin move and the `trash-2`
→ `trash` icon.
- **Judged too minor to surface: 17.** Each is text only, with no
behaviour change an app or operator can reach: describe, docblock and
comment rewrites, `os migrate meta` guidance text, liveness-ledger data
and layout, a form row's declared language, a test-only import change in
`plugin-dev`, and the successor `Link` header of the deprecated
`?layers=true` flag on the environment-scoped mount.
- **Highlights** gain three bullets drawn from the above (decision
first-match, written values, the stack authoring shape). The "running
deployment" warning list gains five lines.
- Every breaking entry that needs an operator action has an
upgrade-checklist line, marked *Not exercised* unless the HotCRM upgrade
below exercised it.
- **Console.** Four pin moves now, not three: `f8a9d0fb0596 →
dd3f7e1be356` (`3cf6449`, objectstack-ai#20436) carries 325 releasing objectui
changesets, 41 of them declared breaking upstream. The Highlights,
"What's new" and Console sections all say four.
- **Dependencies.** `nodemailer` is `^10.0.2`, not `^9.1.1`. That is a
major bump for GHSA-6vj9-mwq6-2f5v, which has no 9.x fix. The line also
carries the operator-visible note from objectstack-ai#20564's changeset: from
nodemailer 10.0.12, `requireTLS` wins over `ignoreTLS`, so a
`transportOptions: { ignoreTLS: true }` override on a port other than
465 now upgrades to STARTTLS or fails the send, and `secure: false` is
the way to connect in the clear.
- **New subsection "Also shipped in 17.5.0 — not in its CHANGELOG".**
The publish ran from `main` at `0f6dcac5` (Release run 36536081716), 8
first-parent commits after the version commit, so the npm packages also
contain `6e3aa75e a093ce3 92fe081 3a89d45 7001918 c96beb2 ba4648d
0f6dcac`. Their changesets are still unconsumed in `.changeset/`. The
subsection gives one line per commit and says they will be listed again
in 17.6.0's CHANGELOG and that the cause is tracked in objectstack-ai#20613. The
breaking `92fe0814` (objectstack-ai#20458, cube member inner `name` retired) gets a
Migration note taken from its own changeset and a checklist entry, and
the checklist preface says where that note lives.

**`v17/index.mdx`** (following the 17.4.0 curation precedent `b11bfb9a`)

- frontmatter description: "17.0.0 through 17.5.0";
- status blockquote: 17.5.0 is released and current, published
2026-09-29, taking over from 17.4.0; a plain install resolves 17.5.0;
the minors warning names 17.5.0;
- a "17.5.0 stays in that register" paragraph drawn from the page's
Highlights, linking `#breaking-changes--migration-in-1750` and
`#upgrade-checklist`;
- the per-release list marks 17.5.0 current and 17.4.0 no longer
current;
- the checklist callout records that 17.4.0 → 17.5.0 has been exercised
only in part (seven lines, on HotCRM), and the per-release checklist
links lead with 17.5.0.

## Findings from a HotCRM 17.4.0 → 17.5.0 upgrade

These were folded in at the coordinator's request; the parent session
verified them.

- **Decision-mode flip** (objectstack-ai#20344): now a 17.4.0 → 17.5.0 table, a
standing warning that flows stored in `sys_metadata` take the new
meaning without being rewritten, and a checklist line. The line says to
review each `mode: 'inclusive'` that `os migrate meta --from 17` offers,
deleting it where the conditions partition, because applied blindly it
draws `flow-decision-inclusive-overlap`. It then says to review the
`--stored` list.
- **`specVersion` / `engines.protocol`**: the checklist now says what an
app does after a 17.x minor, from the code. `PROTOCOL_VERSION` is still
`17.0.0`, and the handshake compares only the major, so
`engines.protocol: '^17'` stays, a `^17.0.0` `specVersion` admits
17.5.0, and a `^18` range is refused `OS_PROTOCOL_INCOMPATIBLE`.
"Protocol 18" is the migration registry's next major; the 17.5.0 schemas
already refuse its shapes, which is why `os migrate meta --from 17` runs
to 18. The Breaking-changes intro carries the same sentence.
- **Seven checklist lines** are marked *Exercised on HotCRM (a 17.4.0
app with a 17.4.0-created SQLite DB), 2026-09-29* with the observed
result: `os doctor` scheduled-work reading, the `account-issuer`
pre-flight, `os migrate meta --from 17` (41 refusals in 874 lines, 240
of them generic protocol-18 notices, so filter the output), the decision
review with `--stored` (0 rows), `page.assignedProfiles`, lookup screen
field `reference`, and `chartConfig` (34 sites). Every other line stays
*Not exercised*, and the preface and the v17 index callout say the hop
was exercised only in part.

## Citations

Every added `#N` was resolved on the board: 144 candidate numbers from
the 90 commits and the 8 post-version commits, all resolving, and objectstack-ai#20613
is open. SHAs are 7-character short SHAs, and each was verified to
resolve unambiguously.

## Gates run (workspace installed)

The full sweep ran on `2b3b323b`. The head `664854a4` changes one phrase
in one checklist line, and on it the MDX parse, `check:doc-anchors`,
`check:role-word`, `check:issue-citations --base origin/main`, the
audit-scope gate and the release-page gates were re-run, all green.

Named in the task, all exit 0:

- `pnpm check:doc-anchors`: 391 internal fragment links, all resolve.
- `node scripts/check-issue-citations.mjs --base origin/main`: 119
citations judged (104 resolve as pull requests, 1 as an issue, 14
cross-repo `objectui#N` unjudged); every added citation resolves.
- `pnpm check:role-word`: no new occurrences.
- `node scripts/docs-audit/check-audit-scope.mjs`: in sync, and
release-owned pages are review-only.
- `check-release-page-status`, `check-release-section-coverage` (plain
and `--strict`) and `check-release-notes`: all OK.
- MDX parse: both pages compile with `@mdx-js/mdx` 3 + `remark-gfm`, and
all 7 tables on `17-5.mdx` parse with no ragged rows.

Derived with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`: 47 commands, **all 47 exit 0**.
The first sweep hit 5 prerequisite refusals (exit 3, or `check:docs` on
the missing gitignored `json-schema` tree) from unbuilt
`@objectstack/spec`, `@objectstack/formula`, `@objectstack/lint` and
`@objectstack/client-react`. None was a finding. Those packages were
built and the whole list was re-run. Among the 47:
`check:doc-authoring`, `check:docs-single-h1`, `check:docs-redirects`,
`check:corpus-claim-drift`, `check:docs-transcript-drift`,
`@objectstack/spec check:docs` / `check:skill-examples` /
`check:liveness`, `@objectstack/lint check:doc-formula-expressions` /
`check:doc-security-posture`, `check-doc-frontmatter`,
`check-docs-section-name`, `check-section-landing-index` and
`check:nul-bytes`.

The diff was also re-read by hand; the fixes from that pass are the
second commit (`da443bdb`).

## Not in this PR

`content/docs/upgrading.mdx`'s per-release table still reads "v17.4.0 —
⛔ checklist not written; machine-draft notes only" and has no 17.5.0
row. It is a hand-written tree outside `content/docs/releases/`, so it
is left for a separate change.


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

---------

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

Fixes objectstack-ai#20622

Clause-②: no

One new `patch` changeset (`@objectstack/cli`, in the fixed release
group) and nothing else. No code, no docs pages, no edit to any existing
changeset.

## What it carries

1. An upgrade line: the raised dependency floors cover what objectstack
loads; a lockfile-preserving upgrade can keep an older `hono` under
`@modelcontextprotocol/sdk` (via `@objectstack/cli` to
`@objectstack/mcp`), which objectstack never loads. `pnpm update hono`
clears a scanner. It states no version number.
2. A section naming, by PR number and title only, the 16 changesets (15
PRs) that shipped inside 17.5.0 without being consumed. Breaking entries
first: objectstack-ai#20458, objectstack-ai#20504, objectstack-ai#20567 (two changesets).

## Measurement

The brief's range command (`git log --diff-filter=A --name-only
8c87d26..0f6dcac -- .changeset/`) yields only 8 files. The other 8
were added BEFORE the version commit and were already left unconsumed by
it (the tree at `8c87d26a5d` still holds them). The set that shipped in
17.5.0 and is still pending is the changeset directory at `0f6dcac5e9`
intersected with `origin/main`: 16 files, all still pending, matching
the triage's 16 and its breaking set (3 PRs, 4 files). Breaking was
decided by each file's text (`BREAKING` banner / narrowing arm).

Code anchors for the upgrade line: `packages/mcp/package.json` depends
on `@modelcontextprotocol/sdk ^1.30.0`; `packages/cli/package.json`
depends on `@objectstack/mcp`;
`packages/plugins/plugin-hono-server/package.json` carries `hono
^4.13.5`; `packages/mcp/src` imports only `server/mcp`, `server/stdio`,
`server/webStandardStreamableHttp` and `types` from the SDK (no
`server/streamableHttp`).

## Gates

19 derived by `dispatch-gates.mjs --commands`, all 19 run and exit 0
(adr-0087-registration, changeset-no-major, closing-keyword-parity,
comment-mask-corpus, empty-changeset, gate self-tests, nul-bytes,
published-files and the rest); `--ran` reconciliation: 19 derived, 19
run, 0 NOT-MEASURED, 0 UNRUN. `check-changeset-fixed` green. Ordering:
must land before objectstack-ai#20639 (Version Packages, open at the time of writing).

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(analytics): retire the inner name on cube measures and dimensions; the record key is the identity (2 keys)

3 participants