From c3a0c542387cdaf461dd941c7bd1d8d83c99e6a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 19:58:27 +0000 Subject: [PATCH] fix(metadata-protocol): the sealed-overlay boot report does not count a served .custom override The boot census behind [metadata_sealed_overlay_unserved] counted the shipping package's declared .custom override with the unserved rows, so the line said no read serves it and prescribed deleting the stored row, the live customization. The census now skips a row isServedOverrideRowOf admits on its package_id column. Unserved rows, the legacy package-less row a declared override shadows included, are still reported with their remedies. What the boot loads, serves and arms is unchanged. Co-Authored-By: Claude --- ...1-sealed-overlay-report-served-override.md | 14 ++++++ .../protocol.reads-environment-only.test.ts | 50 +++++++++++++++++++ packages/metadata-protocol/src/protocol.ts | 16 +++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 .changeset/28771-sealed-overlay-report-served-override.md diff --git a/.changeset/28771-sealed-overlay-report-served-override.md b/.changeset/28771-sealed-overlay-report-served-override.md new file mode 100644 index 00000000000..92c100b1798 --- /dev/null +++ b/.changeset/28771-sealed-overlay-report-served-override.md @@ -0,0 +1,14 @@ +--- +'@objectstack/metadata-protocol': patch +--- + +fix(metadata-protocol): the boot's sealed-overlay report no longer counts a served `.custom` override or tells the operator to delete it (#28771) + +Clause-②: no +Metadata-protocol: none + +At boot, `[metadata_sealed_overlay_unserved]` lists the environment rows that overlay sealed managed content, says no read serves them, and prescribes deleting each stored row. Since #28412 the shipping package's declared override in its paired `.custom` package is served, and for a flow it is armed in place of the base. The report still counted that row, so it told the operator to delete the live customization. + +- **A served override is not counted.** The census skips a row that the one served-override predicate (`isServedOverrideRowOf`) admits, judged on the row's `package_id` column, never on a body's `_packageId`. +- **Every unserved row is still reported, with its remedies.** That includes the legacy package-less row a declared override leaves shadowed, the pair's undeclared row, a dependent package's declaration, and a package-less row whose body only names the pair. The count goes down by exactly the served rows. +- **Nothing else changes.** Which row is served, loaded or armed stays the same; only the report line changes. diff --git a/packages/metadata-protocol/src/protocol.reads-environment-only.test.ts b/packages/metadata-protocol/src/protocol.reads-environment-only.test.ts index 50c80ce19c7..43d80e3dfd9 100644 --- a/packages/metadata-protocol/src/protocol.reads-environment-only.test.ts +++ b/packages/metadata-protocol/src/protocol.reads-environment-only.test.ts @@ -310,6 +310,56 @@ describe('§3 the boot report names the sealed overlays, per type, with their re expect(sealed[0]).toContain('datasource×1 (pkg_ds)'); expect(sealed[0]).not.toContain('host_ds'); }); + + // [#28771, ADR-0146 D2] The shipping package's declared `.custom` override + // of a shipped flow is served (and armed) since #28412, so the census that + // says "no read serves them … delete the stored row" does not count it. The + // judgement is `isServedOverrideRowOf` on the row's `package_id` column. + describe('[#28771] a served .custom override is not counted as unserved', () => { + const FLOW = 'managed_flow'; + const PAIR = `${PKG}.custom`; + const DECLARED = { overrides: { package: PKG, basedOn: '1.0.0' } }; + const flowArtifact: Artifact = { type: 'flow', name: FLOW, body: { name: FLOW, _packageId: PKG } }; + const override = (over: Row = {}, body: Row = DECLARED) => + metaRow('flow', FLOW, { label: 'override', ...body }, { id: 'r_flow_override', package_id: PAIR, ...over }); + const legacy = () => metaRow('flow', FLOW, { label: 'legacy' }, { id: 'r_flow_legacy' }); + const sealedLines = (warns: string[]) => warns.filter((w) => w.includes('[metadata_sealed_overlay_unserved]')); + + it('a boot holding only the declared override prints no sealed-overlay line', async () => { + const { warns } = await boot({ sys_metadata: [override()] }, [flowArtifact]); + expect(sealedLines(warns)).toEqual([]); + }); + + it('a boot holding the override and a legacy package-less row reports the legacy row alone, with its remedies', async () => { + const { warns } = await boot({ sys_metadata: [override(), legacy()] }, [flowArtifact]); + const sealed = sealedLines(warns); + expect(sealed).toHaveLength(1); + expect(sealed[0]).toContain(`1 active environment sys_metadata row(s)`); + expect(sealed[0]).toContain(`flow×1 (${FLOW})`); + expect(sealed[0]).toContain('linkage-free clone'); + expect(sealed[0]).toContain('delete the stored row'); + }); + + it('control: a legacy row alone is reported as before', async () => { + const { warns } = await boot({ sys_metadata: [legacy()] }, [flowArtifact]); + const sealed = sealedLines(warns); + expect(sealed).toHaveLength(1); + expect(sealed[0]).toContain(`flow×1 (${FLOW})`); + }); + + for (const [label, row] of [ + ['the pair\'s undeclared row', () => override({}, {})], + ['a dependent package\'s declaration', () => override({ package_id: 'app.partner' })], + ['a package-less row whose body stamps the pair', () => metaRow('flow', FLOW, { label: 'stamped', _packageId: PAIR, ...DECLARED }, { id: 'r_flow_stamped' })], + ] as const) { + it(`control: ${label} is served by no rule, so it is still reported`, async () => { + const { warns } = await boot({ sys_metadata: [row()] }, [flowArtifact]); + const sealed = sealedLines(warns); + expect(sealed).toHaveLength(1); + expect(sealed[0]).toContain(`flow×1 (${FLOW})`); + }); + } + }); }); describe('§4 [ADR-0131 C7] the anonymous form doors\' legacy organization read is deleted', () => { diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 3a2f380e0b0..3f9cd578af3 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -29382,7 +29382,21 @@ export class ObjectStackProtocolImplementation implements // defines in code is named once, by the // datasource-admin boot restore's warning, which // carries its own remedy — not here as well. - if (!this.isHostCodeDatasource(normalizedType, String(record.name))) { + // + // [#28771, ADR-0146 D2] …nor a row the served-override + // rule serves: the shipping package's declared `.custom` + // override IS served (and, for a flow, armed), so a line + // saying "no read serves them … delete the stored row" + // would prescribe deleting the live customization. The + // one predicate, on the row's `package_id` column. The + // census only: what this loop loads is unchanged. + if ( + !this.isHostCodeDatasource(normalizedType, String(record.name)) + && !isServedOverrideRowOf( + record as ServedOverrideRow, + this.packagedArtifactOwner({ type: normalizedType, name: String(record.name) }), + ) + ) { sealedOverlays.push({ type: normalizedType, name: String(record.name) }); } if (normalizedType === 'object') continue;