Skip to content

Commit fe463b4

Browse files
fix(metadata-protocol): refusals, hints and log lines state each decision in words instead of a tracker number (stage 2) (#20830)
Part of #20513 Clause-②: no **Stage 2 of 5 of this lane (`metadata-protocol`), under the maintainer's A / A ruling on the card.** The card stays open for stages 3-5, so this PR carries no closing keyword. Text only: no error `code`, field name, HTTP status, export or control flow moves. Every changed source line is a string-literal line (108 changed lines in 11 `.ts` files, checked line by line against the merge base). ## What this does The metadata protocol's refusals, hints and log lines sent the reader to a tracker number for the reason behind them. Each rewritten string now says that reason in words (form D, as the migration-entry rewrite and stage 1 applied it). Where the sentence already stated what was decided, only the citation goes. Where it did not, the decision is added in words: | Where | Cited | The sentence now says | |---|---|---| | `protocol.ts` `insertManyData` refusal (thrown) | 3172 | `insertMany` is the partial-success batch insert: an outcome per row, so a bad row neither fails the whole batch nor makes the good rows run their `beforeInsert` hooks twice. | | `protocol.ts` unknown metadata type refusal (400) | 8586 | A plugin cannot declare a type either: `additionalTypes` was retired because nothing ever read it. | | `protocol.ts` stored non-canonical type refusals on publish and on revert (`STORED_TYPE_NOT_CANONICAL`) | 7894, 8957 | The `/meta` URL door now folds a type to its canonical spelling before it writes, so such a row predates that; the stored migration's `skipped` report "with that same reason" loses only its citation. | | `runtime-authoring-gate.ts` schedule-flow `organization_id` hint | 6153 | The author's value wins, and the engine fills only an organization the run resolved, so for a schedule run the author is the one source. | | `plugin.ts` the three `kernel:ready` "migration skipped" warnings | 5839, 8629, 8686 | What the migration that did not run would have ensured: view-name uniqueness among ACTIVE rows only; the NULL-safe `sys_setting` row identity on tenant and global rows; the adoption of untenanted seed rows and their autonumber counter. | | `sys-metadata-repository.ts` history-counter abort (`error`) | 4867 | The old path answered 1 because it took a failed read for an empty table. | | `protocol.ts` publish-closure degrade (`warn`) | 10377 | Validation falls back to the LIVE declarations without the batch's own drafts in the closure. | | `protocol.ts` cold-boot org-scoped audit (`warn`) | 6190, 6992 | The write refusal it points at covers declared types only; the trailing "See" keeps ADR-0005. | | everything else: the overlay, `sys_view_definition` and `sys_setting` index migration messages (ADR-0120 D4 stays), the seed/API tenancy repair, its receipt and its skips, the batch-row withhold, the object-existence gate's no-registry warning, the nested-select, overlay and non-canonical-registry refusals, and the live-MySQL testkit error | 6418, 8725, 8629, 5839, 6417, 8686, 9451, 9261, 8502, 3770, 4196, 6190, 4432, 10382 | The sentence already said what was decided; only the citation goes. | Each claim was checked against today's code, not only against the cited card: `saveMetaItem` folds the request type before it persists; `additionalTypes` is a `retiredKey()` tombstone in `packages/spec`; the org-scoped write refusal is live in `orgScopedWriteRefusal`. One cited number (10382) answers 404 and was read through its landing commit `ee09d2119`; the testkit sentence already says what it guards. ## Order inside the stage All 46 id-bearing literals (50 occurrences) fit one PR, under the stop line, so the stage lands whole, in three commits in the ordered sequence: 1. `dc8a1a112` author-visible text: 10 literals (thrown refusals, the stored-type refusals, the hint); 2. `730cbcaf6` log lines: 35 literals, plus the two re-pinned tests; 3. `442473234` the `src/`-shipped testkit string, and the changeset. Each commit recomputes the ledger, so every commit on the branch is green on `check:doc-authoring`. ## Pins re-pinned: 3 assertion lines in 2 test files - `migrations/view-definition-active-index.test.ts` 342-343 asserted the two numbers in the MySQL degradation line. They now assert the two gaps in words: "an archived view keeps occupying its name slot" and "two same-name ACTIVE shared views (owner NULL)". - `protocol.batch-row-driver-text.test.ts` 396 asserted the number in the withhold warning. It now asserts "must not be quoted back on response data", the decision itself. A one-off mutation proves each new pin can fail, run on the committed head with `scripts/ablation-replace.mjs` (anchor hit once, disk-verified) under a shell trap. Changing "name slot" gives 1 failed / 27 passed. Changing "(owner NULL)" gives 1 failed / 27 passed. Changing "quoted back" gives 2 failed / 14 passed: the re-pin, and a knock-on in the next test, because the failed test never reached its `mockRestore`. After each leg, the blob equals HEAD and `git diff HEAD` is empty. The tree is clean after the run, and no test file was left behind. No string here is compared byte for byte with a twin in another package. The consumer pins outside the package read unchanged fragments: `runtime`'s `batch-row-driver-text-real-driver.integration.test.ts` reads the withhold prefix, `seed-tenancy-autonumber-split.integration.test.ts` reads "backfill skipped", and `meta-field-overlay-lock.test.ts`, `objectql`'s `protocol-meta.test.ts` and `rest`'s `meta-unknown-type-read-refusal.test.ts` read "is not a metadata type". I ran the three `runtime` files against the rebuilt `dist/`, and they passed. ## Ledger burn-down `scripts/doc-authoring-prose-id.baseline.json` was regenerated with `node scripts/check-doc-authoring.mjs --census-ledger` into a scratch file, so the growth refusal ran against the checked-in baseline, and then copied into place. Only `metadata-protocol` rows moved, and every one of them leaves: | File | Occurrences before | after | (file, id) pairs before | after | |---|---:|---:|---:|---:| | `metadata-protocol/src/protocol.ts` | 15 | 0 (row leaves) | 11 | 0 | | `metadata-protocol/src/migrations/seed-tenancy-backfill.ts` | 14 | 0 (row leaves) | 3 | 0 | | `metadata-protocol/src/migrations/view-definition-active-index.ts` | 7 | 0 (row leaves) | 3 | 0 | | `metadata-protocol/src/migrations/overlay-index.ts` | 4 | 0 (row leaves) | 2 | 0 | | `metadata-protocol/src/migrations/sys-setting-identity-index.ts` | 4 | 0 (row leaves) | 1 | 0 | | `metadata-protocol/src/plugin.ts` | 3 | 0 (row leaves) | 3 | 0 | | `metadata-protocol/src/migrations/live-mysql-database.testkit.ts` | 1 | 0 (row leaves) | 1 | 0 | | `metadata-protocol/src/runtime-authoring-gate.ts` | 1 | 0 (row leaves) | 1 | 0 | | `metadata-protocol/src/sys-metadata-repository.ts` | 1 | 0 (row leaves) | 1 | 0 | | **whole ledger** | 861 | 811 | 572 | 546 | The ledger's file count goes from 224 to 215, and other packages' rows moved: 0. The census's 40 messages reconcile with the ledger's 50 occurrences. The gate counts 46 string literals: 45 in the census population plus the testkit string, which the census filed as test-facing. The census folds a `+` chain into one message, so 5 two-literal chains make 45 literals into 40 messages. Four literals carry two ids each, which makes 46 literals into 50 occurrences. ## Verification (head `442473234`) - build: turbo over `@objectstack/runtime...` (30/30), then the whole workspace (72/72), then `@objectstack/metadata-protocol` directly. The new sentences are in `dist/index.js`, and the only citations left in `dist/` are docblocks. - `@objectstack/metadata-protocol` test: Test Files 190 passed, 3 skipped (193); Tests 2792 passed, 19 skipped. The skips are the live MySQL/PostgreSQL files: this container has no server. - `@objectstack/metadata-protocol` typecheck: exit 0; `tsc --listFiles` reads 193 of 193 test files. - `@objectstack/runtime`, the three consumer files above: 3 files, 33 tests passed. - `node scripts/pm/dispatch-gates.mjs --commands` (no paths; 13 paths against merge base `261c529f0`): 70 commands, all exit 0. `check:dual-build-cjs-loads` and `check:type-check-debt` first answered exit 3 PREREQUISITE NOT MET on the partial build. After the full build (and a direct rebuild of this package, whose `dist` a turbo cache hit had left older than the restored sources), both exit 0; `check:dts-closure` and `check:lean-entry-closure` were re-run there too. `--ran`: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. - `check:doc-authoring`: sibling-package prose ids hold the baseline, no growth, no burn-down unrecorded. - narrowed lint: `eslint --no-inline-config --format json` over the 11 touched `.ts` files reports 11 files, 0 errors and 0 warnings. The resolved `parserOptions` for these files are `ecmaVersion` and `sourceType` only, with no `project` or `projectService`. So no type-aware rule can move an untouched file. The repo-wide `pnpm lint` is CI's. - NOT MEASURED locally (CI's): the live PG/MySQL files, whose messages changed only ahead of the pinned MySQL statement lead-in; the Test Core shards; Dogfood; the workspace type-check lanes. ## Acceptance notes - PR #20817 (another lane, draft) also edits `protocol.ts`. Whichever lands second merges `main` and recomputes the ledger with `--census-ledger`. - Docblocks and `//` comments in this package still cite numbers. They are out of scope here: comments are the sanctioned home for internal anchors, and a separate card owns stale ones. --- _Generated by [Claude Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 22e584c commit fe463b4

13 files changed

Lines changed: 84 additions & 98 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
metadata-protocol refusals, hints and log lines no longer cite tracker numbers; each states the reason in words
6+
7+
Clause-②: no
8+
9+
Many messages the metadata protocol shows to authors, administrators and operators ended with an
10+
issue-tracker number where the reason belonged. The number goes, and where the sentence did not
11+
already say what was decided, it now does:
12+
13+
- Refusals: `insertManyData` without an engine `insertMany` now names what that method is (the
14+
partial-success batch insert, so a bad row neither fails the whole batch nor runs the good rows'
15+
`beforeInsert` hooks twice); the unknown-metadata-type refusal says a plugin cannot declare a type
16+
because `additionalTypes` was retired, having never been read; the stored non-canonical type
17+
refusals on publish and revert say the `/meta` URL door now folds a type to its canonical spelling
18+
before it writes, so such a row predates that.
19+
- The schedule-flow `organization_id` hint says why the author's value is the only source: the engine
20+
fills only an organization the run resolved, and a schedule resolves none.
21+
- Log lines: the three `kernel:ready` "migration skipped" warnings now say what the migration that did
22+
not run would have ensured; the history-counter abort says the old path took a failed read for an
23+
empty table; the publish-closure degrade says the batch's own drafts are left out of the closure;
24+
the cold-boot org-scoped audit calls the write refusal it points at declared-types-only. The
25+
overlay, `sys_view_definition` and `sys_setting` index messages, the seed/API tenancy repair and its
26+
receipt, the batch-row withhold and the object-existence gate's no-registry warning lose only the
27+
citation, because their sentences already said it.
28+
- The live-MySQL testkit's isolation error loses its citation.
29+
30+
Text only: no error code, field name, status or behaviour changes.

‎packages/metadata-protocol/src/migrations/live-mysql-database.testkit.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export function currentLiveMysqlDatabase(): string {
216216
const testPath = expect.getState().testPath;
217217
if (!testPath) {
218218
throw new Error(
219-
'live-mysql isolation (#10382): vitest reported no testPath, so this live connection ' +
219+
'live-mysql isolation: vitest reported no testPath, so this live connection ' +
220220
'cannot be given a per-file database and would fall back to sharing one with every ' +
221221
'other live file in this package — including its `drop database` in afterAll. Call ' +
222222
'currentLiveMysqlDatabase() from a test file.',

‎packages/metadata-protocol/src/migrations/overlay-index.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export async function ensureOverlayStateIndex(
271271
logger,
272272
`[metadata-protocol] could not create '${indexName}' on "${OVERLAY_TABLE}" after the probe ` +
273273
`succeeded — the table may currently have NO unique index over ` +
274-
`(${overlayIndexKeyParts().join(', ')}) among state='${state}' rows. Restart to retry (#6418).`,
274+
`(${overlayIndexKeyParts().join(', ')}) among state='${state}' rows. Restart to retry.`,
275275
detail,
276276
);
277277
return { status: 'failed', detail, fallback: 'not-attempted' };
@@ -374,7 +374,7 @@ function reportDegradation(
374374
`(${columns}) is enforced only as far as it was before; ADR-0005 overlay uniqueness is NOT ` +
375375
`enforced until the duplicates are resolved, and getMetaItem has no defined answer for ` +
376376
`which of the colliding rows wins. List them with: ${duplicateQuery} — or run ` +
377-
`"os migrate duplicates" — then restart (ADR-0120 D4, #6418, #8725).`,
377+
`"os migrate duplicates" — then restart (ADR-0120 D4).`,
378378
detail,
379379
);
380380
return;
@@ -390,7 +390,7 @@ function reportDegradation(
390390
`[metadata-protocol] could not rebuild '${indexName}' on "${OVERLAY_TABLE}" as the ` +
391391
`state='${state}' partial UNIQUE index; the existing index is unchanged, so two ${state} ` +
392392
`overlay rows for one (${columns}) can still coexist while everything else looks healthy. ` +
393-
`Fix the cause below and restart (#6418).`,
393+
`Fix the cause below and restart.`,
394394
detail,
395395
);
396396
}

‎packages/metadata-protocol/src/migrations/seed-tenancy-backfill.ts‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,15 +1147,15 @@ export async function recordSeedTenancyReceipt(
11471147
): Promise<void> {
11481148
const ledger = seam?.ledger;
11491149
const notRecorded =
1150-
`[metadata-protocol] the seed/API tenancy repair (#8686) ran and rewrote stored rows, but this ` +
1150+
`[metadata-protocol] the seed/API tenancy repair ran and rewrote stored rows, but this ` +
11511151
`deployment has NO durable record that it did`;
11521152
if (!ledger) {
11531153
// Functional absence, not a durability failure: this host is not an engine
11541154
// (a raw seam built by hand, a test double), so there is no ledger to miss.
11551155
// `warn` per AGENTS.md — the system is visibly smaller, not silently lying.
11561156
logger?.warn?.(
11571157
`${notRecorded} — no engine was resolved beside the raw-SQL seam, so ${DATA_MIGRATION_FLAG_OBJECT} ` +
1158-
`could not be written. Capture this boot's log before restarting (#9451).`,
1158+
`could not be written. Capture this boot's log before restarting.`,
11591159
);
11601160
return;
11611161
}
@@ -1164,15 +1164,15 @@ export async function recordSeedTenancyReceipt(
11641164
logger?.warn?.(
11651165
`${notRecorded} — ${DATA_MIGRATION_FLAG_OBJECT} is not registered on this kernel, so the ` +
11661166
`deployment ledger does not exist here. Compose PlatformObjectsPlugin (it carries the ledger ` +
1167-
`every served kernel gets) or capture this boot's log before restarting (#9451).`,
1167+
`every served kernel gets) or capture this boot's log before restarting.`,
11681168
);
11691169
return;
11701170
}
11711171
const flag = buildSeedTenancyReceipt(result, new Date().toISOString());
11721172
const outcome = await persistSeedTenancyReceiptRow(ledger, flag);
11731173
logger?.info?.(
11741174
`[metadata-protocol] seed/API tenancy repair recorded in ${DATA_MIGRATION_FLAG_OBJECT} ` +
1175-
`(id '${SEED_TENANCY_MIGRATION_ID}', ${outcome}) — the run survives this process (#9451).`,
1175+
`(id '${SEED_TENANCY_MIGRATION_ID}', ${outcome}) — the run survives this process.`,
11761176
{ id: SEED_TENANCY_MIGRATION_ID, outcome, details: flag.details },
11771177
);
11781178
} catch (e: unknown) {
@@ -1184,7 +1184,7 @@ export async function recordSeedTenancyReceipt(
11841184
`Nothing else reports it: capture this boot's log NOW, before the container is replaced. Fix: make ` +
11851185
`${DATA_MIGRATION_FLAG_OBJECT} writable on this deployment (it is provisioned by ` +
11861186
`PlatformObjectsPlugin) and verify with ` +
1187-
`SELECT * FROM ${DATA_MIGRATION_FLAG_OBJECT} WHERE id = '${SEED_TENANCY_MIGRATION_ID}' (#9451).`;
1187+
`SELECT * FROM ${DATA_MIGRATION_FLAG_OBJECT} WHERE id = '${SEED_TENANCY_MIGRATION_ID}'.`;
11881188
if (logger?.error) logger.error(message, e instanceof Error ? e : new Error(detail));
11891189
else logger?.warn?.(message, { error: detail });
11901190
}
@@ -1291,7 +1291,7 @@ export async function backfillSeedTenancy(
12911291
if (postureEnforcesWall(resolveTenancyPosture())) {
12921292
logger?.warn?.(
12931293
`[metadata-protocol] seed/API tenancy split detected on a MULTI-ORGANIZATION install — ` +
1294-
`backfill skipped (#8686). Affected: ${affected}. ` +
1294+
`backfill skipped. Affected: ${affected}. ` +
12951295
`Seed rows carry ${ORGANIZATION_FIELD} = NULL while API rows carry a real organization, so each ` +
12961296
`object runs two autonumber counters and can mint the same "unique" identifier twice — the ` +
12971297
`partitioned unique index (COALESCE(${ORGANIZATION_FIELD}, '${GLOBAL_TENANT}'), <field>) does not ` +
@@ -1369,7 +1369,7 @@ export async function backfillSeedTenancy(
13691369
if (organizationIds.length === 0 && organizationProbeError === undefined) {
13701370
logger?.info?.(
13711371
`[metadata-protocol] seed/API tenancy split detected on an install with no organization yet — ` +
1372-
`nothing to adopt, and nothing at risk (#8686). Affected: ${affected}. ` +
1372+
`nothing to adopt, and nothing at risk. Affected: ${affected}. ` +
13731373
`${ORGANIZATION_TABLE} is empty, so each of these objects runs exactly ONE counter (its ` +
13741374
`'${GLOBAL_TENANT}' row) and no "unique" identifier can be minted twice while there is only ` +
13751375
`one partition. No operator action: this self-heals at the first sign-up, when the ` +
@@ -1384,7 +1384,7 @@ export async function backfillSeedTenancy(
13841384
if (organizationIds.length !== 1) {
13851385
logger?.warn?.(
13861386
`[metadata-protocol] seed/API tenancy split detected but the target organization is not ` +
1387-
`derivable — backfill skipped (#8686). Affected: ${affected}. ` +
1387+
`derivable — backfill skipped. Affected: ${affected}. ` +
13881388
`The install reports tenancy posture 'single' but holds ${organizationIds.length} rows in ` +
13891389
`${ORGANIZATION_TABLE} (exactly 1 is required to adopt one without guessing). Until this is ` +
13901390
`resolved these objects run two autonumber counters and can mint the same "unique" identifier ` +
@@ -1399,7 +1399,7 @@ export async function backfillSeedTenancy(
13991399
`NOTE: the ${ORGANIZATION_TABLE} probe FAILED` +
14001400
(organizationProbeError === '' ? '' : ` (${organizationProbeError})`) +
14011401
`, so the count above is "unknown", not a measured zero — an unreadable probe is ` +
1402-
`reported here rather than through the benign no-organization-yet path (#9261). `) +
1402+
`reported here rather than through the benign no-organization-yet path. `) +
14031403
SNAPSHOT_CAVEAT,
14041404
// `organizationProbeError` is `undefined` — and so serializes AWAY — when
14051405
// the probe answered; a probe that failed carries its text, `''` and all.
@@ -1431,7 +1431,7 @@ export async function backfillSeedTenancy(
14311431
} catch (e) {
14321432
logger?.warn?.(
14331433
`[metadata-protocol] could not list already-minted duplicates for ${split.object}.${split.field} ` +
1434-
`(#8686) — the backfill continues; verify manually with: ` +
1434+
`— the backfill continues; verify manually with: ` +
14351435
`${buildCollisionProbeSql(split.object, split.field, client)}`,
14361436
{ error: operatorFacingErrorText(e) },
14371437
);
@@ -1456,7 +1456,7 @@ export async function backfillSeedTenancy(
14561456
} catch (e) {
14571457
stampFailures.push(object);
14581458
logger?.warn?.(
1459-
`[metadata-protocol] seed tenancy backfill could not stamp ${object} (#8686) — its rows keep ` +
1459+
`[metadata-protocol] seed tenancy backfill could not stamp ${object} — its rows keep ` +
14601460
`${ORGANIZATION_FIELD} = NULL and the counter merge below is SKIPPED for it, so the split ` +
14611461
`survives and the next boot retries. Nothing was lost; nothing was repaired for this object.`,
14621462
{ error: operatorFacingErrorText(e) },
@@ -1490,7 +1490,7 @@ export async function backfillSeedTenancy(
14901490
} catch (e) {
14911491
logger?.warn?.(
14921492
`[metadata-protocol] seed tenancy backfill could not merge the counter for ` +
1493-
`${split.object}.${split.field} (#8686) — the '${GLOBAL_TENANT}' counter is left in ` +
1493+
`${split.object}.${split.field} — the '${GLOBAL_TENANT}' counter is left in ` +
14941494
`place, so the high-water mark is intact and the next boot retries the repair`,
14951495
{ error: operatorFacingErrorText(e) },
14961496
);
@@ -1499,8 +1499,8 @@ export async function backfillSeedTenancy(
14991499

15001500
if (objectsStamped > 0) {
15011501
logger?.info?.(
1502-
`[metadata-protocol] seed/API tenancy split repaired for ${objectsStamped} object(s) ` +
1503-
`(#8686): untenanted seed rows adopted organization ${organizationId} and the ` +
1502+
`[metadata-protocol] seed/API tenancy split repaired for ${objectsStamped} object(s): ` +
1503+
`untenanted seed rows adopted organization ${organizationId} and the ` +
15041504
`'${GLOBAL_TENANT}' counter was merged into the organization-scoped one` +
15051505
(collisions.length > 0
15061506
? `. ${collisions.length} row(s) could NOT be adopted because their identifier is already ` +
@@ -1517,7 +1517,7 @@ export async function backfillSeedTenancy(
15171517
// will NOT rewrite, so an operator has to decide what happens to them.
15181518
logger?.warn?.(
15191519
`[metadata-protocol] ${collisions.length} business identifier(s) were already minted TWICE before ` +
1520-
`this repair (#8686) — reported, NOT renumbered. These values each exist on both a seeded row and ` +
1520+
`this repair — reported, NOT renumbered. These values each exist on both a seeded row and ` +
15211521
`an API-created row: ` +
15221522
collisions.map((c) => `${c.object}.${c.field}=${c.value} (${c.rows} rows)`).join(', ') +
15231523
`. The platform does not renumber them: a record number that has already appeared on a document, ` +

‎packages/metadata-protocol/src/migrations/sys-setting-identity-index.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ export async function ensureSysSettingIdentityIndex(
534534
logger,
535535
`[metadata-protocol] could not create '${SYS_SETTING_IDENTITY_INDEX_NAME}' on ` +
536536
`"${SYS_SETTING_TABLE}" after the probe succeeded — the table may currently have NO unique ` +
537-
`index on (${sysSettingIdentityKeyParts().join(', ')}). Restart to retry (#8629).`,
537+
`index on (${sysSettingIdentityKeyParts().join(', ')}). Restart to retry.`,
538538
detail,
539539
);
540540
return { status: 'failed', detail };
@@ -592,7 +592,7 @@ function reportDegradation(
592592
`over (${columns}). The system keeps looking healthy while the declared row identity is void on ` +
593593
`every row that is not scope='user' — user_id is NULL there — so two tenant-scope rows for one ` +
594594
`(namespace, key) in ONE organization, or two platform defaults on the global layer, can coexist ` +
595-
`and SettingsService has no defined answer for which one wins (#8629). MySQL/MariaDB before ` +
595+
`and SettingsService has no defined answer for which one wins. MySQL/MariaDB before ` +
596596
`8.0.13 has no functional key parts, so there is no in-dialect fix: run this platform on ` +
597597
`SQLite/PostgreSQL for the guarantee, and meanwhile watch for duplicates with this MySQL ` +
598598
`statement: ${buildSysSettingDuplicateProbeSqlMysql()}`,
@@ -615,7 +615,7 @@ function reportDegradation(
615615
`enforced until the duplicates are resolved: settings rows are admin-authored configuration, so ` +
616616
`no row is discarded automatically and this migration will keep refusing until an operator ` +
617617
`decides which row survives. List them with: ${duplicateQuery} — or run "os migrate duplicates" — ` +
618-
`then restart (ADR-0120 D4, #8629).`,
618+
`then restart (ADR-0120 D4).`,
619619
detail,
620620
);
621621
return;
@@ -631,7 +631,7 @@ function reportDegradation(
631631
`[metadata-protocol] could not rebuild '${SYS_SETTING_IDENTITY_INDEX_NAME}' on ` +
632632
`"${SYS_SETTING_TABLE}" as the NULL-safe row-identity index; the existing index is unchanged, so ` +
633633
`duplicate tenant-scope and global-scope settings rows can still be created while everything else ` +
634-
`looks healthy. Fix the cause below and restart (#8629).`,
634+
`looks healthy. Fix the cause below and restart.`,
635635
detail,
636636
);
637637
}

‎packages/metadata-protocol/src/migrations/view-definition-active-index.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,8 @@ describe('sys_view_definition active-row uniqueness (#5839) on a NULL-safe key (
339339
const note = String(logger.error.mock.calls[0]![0]);
340340
expect(note).toContain('UNRESTRICTED and NULL-distinct');
341341
expect(note).toContain('keeps looking healthy');
342-
expect(note).toContain('#5839');
343-
expect(note).toContain('#6417');
342+
expect(note).toContain('an archived view keeps occupying its name slot');
343+
expect(note).toContain('two same-name ACTIVE shared views (owner NULL)');
344344
// The fix, and the query that surfaces the duplicates meanwhile.
345345
expect(note).toContain('SQLite/PostgreSQL');
346346
expect(note).toContain(buildDuplicateProbeSql());

‎packages/metadata-protocol/src/migrations/view-definition-active-index.ts‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ export async function ensureViewDefinitionActiveIndex(
327327
logger,
328328
`[metadata-protocol] could not create '${VIEW_ACTIVE_INDEX_NAME}' on ` +
329329
`"${VIEW_DEFINITION_TABLE}" after the probe succeeded — the table may currently have NO ` +
330-
`unique index on (${viewActiveIndexKeyParts().join(', ')}). Restart to retry (#5839).`,
330+
`unique index on (${viewActiveIndexKeyParts().join(', ')}). Restart to retry.`,
331331
detail,
332332
);
333333
return { status: 'failed', detail };
@@ -378,9 +378,9 @@ function reportDegradation(
378378
`'${VIEW_ACTIVE_INDEX_NAME}' on "${VIEW_DEFINITION_TABLE}" stays UNRESTRICTED and NULL-distinct ` +
379379
`over (${columns}), the bare-composite degradation of ADR-0120 D3. The system keeps looking ` +
380380
`healthy while two consequences hold on this dialect: an archived view keeps occupying its ` +
381-
`name slot (#5839), and two same-name ACTIVE shared views (owner NULL) or environment-level ` +
382-
`views (organization_id NULL) can still coexist even though the platform states they cannot ` +
383-
`(#6417). MySQL/MariaDB has no partial indexes, so there is no in-dialect fix: run this ` +
381+
`name slot, and two same-name ACTIVE shared views (owner NULL) or environment-level ` +
382+
`views (organization_id NULL) can still coexist even though the platform states they cannot. ` +
383+
`MySQL/MariaDB has no partial indexes, so there is no in-dialect fix: run this ` +
384384
`platform on SQLite/PostgreSQL for the guarantee, and meanwhile watch for duplicates with: ` +
385385
`${buildDuplicateProbeSql()}`,
386386
detail,
@@ -402,7 +402,7 @@ function reportDegradation(
402402
`existing rows violate (${keyParts}) among state='active'. The previous index is left in ` +
403403
`place, so (${columns}) is enforced only as far as it was before; the NULL-safe key is NOT ` +
404404
`enforced until the duplicates are resolved. List them with: ${buildDuplicateProbeSql()} — or ` +
405-
`run "os migrate duplicates" — then restart (ADR-0120 D4, #6417, #8725).`,
405+
`run "os migrate duplicates" — then restart (ADR-0120 D4).`,
406406
detail,
407407
);
408408
return;
@@ -416,8 +416,7 @@ function reportDegradation(
416416
logger,
417417
`[metadata-protocol] could not rebuild '${VIEW_ACTIVE_INDEX_NAME}' on "${VIEW_DEFINITION_TABLE}" as ` +
418418
`the active-row NULL-safe index; the existing index is unchanged, so two same-name ACTIVE shared ` +
419-
`views can still coexist while everything else looks healthy. Fix the cause below and restart ` +
420-
`(#5839 / #6417).`,
419+
`views can still coexist while everything else looks healthy. Fix the cause below and restart.`,
421420
detail,
422421
);
423422
}

0 commit comments

Comments
 (0)