Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/20595-metadata-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@objectstack/metadata': patch
---

Provenance comments in `@objectstack/metadata` cite the commits that decided them, not tracker numbers that no longer resolve

Clause-②: no

Docblocks and comments across the package cited issue-tracker numbers that now answer 404 on GitHub.
Each now cites the commit in this repository's history that made the decision it describes, except two
that meant an objectui issue and now spell `objectui#6111`. Some of these
docblocks sit on exported members, so the reworded text appears in the published `index.d.ts` /
`index.d.cts`, `node.d.ts` / `node.d.cts` and `view-container.d.ts` / `view-container.d.cts`, and
comments that esbuild keeps appear in the JavaScript output (`index.js` / `index.cjs`, `node.js` /
`node.cjs`).

Comment only: no export, type, error code, status, message text or runtime behaviour changes.
4 changes: 2 additions & 2 deletions packages/metadata/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* `@objectstack/spec/shared`). Architecturally attractive and explicitly
* *not* precluded by this module — but out of scope on the round that
* needed it (spec was frozen; types was under concurrent change).
* ⇒ **TAKEN, by the maintainer's 2026-08-30 ruling on #13279.** The
* ⇒ **TAKEN, by the maintainer's 2026-08-30 ruling (commit 6a180e42d).** The
* predicate now lives in `@objectstack/types`
* (`driver-error-classification.ts`); what forced it was a consumer this
* file could never serve — `resolveAuthzContext` in `@objectstack/core`,
Expand All @@ -37,7 +37,7 @@
* Option 2 is taken, so this is the compatibility seam it always said it would
* become — "a single, greppable seam to delete if the maintainer later takes
* option 2". It is NOT deleted: `@objectstack/metadata/errors` is a published
* subpath with out-of-repo consumers, and #13279 is a fix, not a removal. It
* subpath with out-of-repo consumers, and commit 6a180e42d is a fix, not a removal. It
* re-exports the one symbol it always exported, from the new home. Everything
* below still describes why the subpath exists and why it stays narrow.
*
Expand Down
4 changes: 2 additions & 2 deletions packages/metadata/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export { MemoryLoader } from './loaders/memory-loader.js';
export { RemoteLoader } from './loaders/remote-loader.js';
export { DatabaseLoader, type DatabaseLoaderOptions } from './loaders/database-loader.js';

// [#14921] The ambiguous-stem refusal. Published from the ROOT entry, not only
// [commit c1d274de7] The ambiguous-stem refusal. Published from the ROOT entry, not only
// from `./node` beside `FilesystemLoader`: the error reaches consumers through
// `MetadataManager.listNames()` / `list()`, which live here, and a caller that
// wants to tell "this deployment's metadata tree names one item twice" apart
Expand Down Expand Up @@ -54,7 +54,7 @@ export { TypeScriptSerializer } from './serializers/typescript-serializer.js';

// View container binding
//
// [#14399] `deriveViewContainerObject` is this package's ONE spelling of "which
// [commit 3c1bbd2a8] `deriveViewContainerObject` is this package's ONE spelling of "which
// object does an aggregated `defineView` container bind to" — the container's
// own top-level `object` first, then `list.data.object`, `form.data.object`,
// and the row's own `name` last (its own docblock carries the ruling). It is
Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/loaders/ambiguous-metadata-stem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* [#14921] The refusal a metadata source tree earns by naming one item twice.
* [commit c1d274de7] The refusal a metadata source tree earns by naming one item twice.
*
* ## The invariant this restores
*
Expand All @@ -21,7 +21,7 @@
* diagnostic anywhere, and `MetadataManager.admitLoaderItems()`'s documented
* "keep the first and say nothing" absorbs the collision a second time.
*
* ## The ruling (maintainer, via the director seat on #14921, 2026-09-05)
* ## The ruling (maintainer, via the director seat, 2026-09-05; landed as commit c1d274de7)
*
* Option 1 of three: **refuse the ambiguous stem loudly at list time.** Two
* files sharing a stem across the registered extensions is an AUTHORING ERROR,
Expand All @@ -37,7 +37,7 @@
*
* ## Why a brand and a predicate rather than bare `instanceof`
*
* `MetadataManager`'s plural reads catch per loader on purpose (#5108/#14423):
* `MetadataManager`'s plural reads catch per loader on purpose (#5108 and commit a56baa2bd):
* a storage outage must degrade to a short-but-served list rather than take the
* whole enumeration down. This refusal is the opposite kind of fact — an
* author's tree is malformed and no retry fixes it — so those seams have to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* ## Reachability is measured, not argued
*
* PR #14409 (landed `3ecb7dc1a`): mysql2 3.23.1 returns a module constant
* Commit `3ecb7dc1a`: mysql2 3.23.1 returns a module constant
* literally named `INVALID_DATE` for a zero `DATETIME`; postgres-date 1.0.7
* builds `new Date(NaN)` for every year in 275760..294276, which Postgres
* itself stores. Ruled option B by the maintainer on 2026-09-02, on all five
Expand Down
6 changes: 3 additions & 3 deletions packages/metadata/src/loaders/database-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type { IDataDriver, IDataEngine, DriverQuery } from '@objectstack/spec/co
import type { MetadataLoader, MetadataKeyedItem } from './loader-interface.js';
import { calculateChecksum } from '../utils/metadata-history-utils.js';
import { LRUCache } from '../utils/lru-cache.js';
// [#13279] Both predicates moved to `@objectstack/types` — see its
// [commit 6a180e42d] Both predicates moved to `@objectstack/types` — see its
// `driver-error-classification.ts` `## Home` section. The verdicts are
// byte-identical; only the import path changed.
import { isMissingTableError, isSchemaAlreadyExistsError } from '@objectstack/types';
Expand Down Expand Up @@ -79,7 +79,7 @@ import { migrateProjectIdToEnvironmentId } from '../migrations/migrate-project-i
* because a guard on some arms and not others re-opens the drift the single
* spelling closed.
*
* Reachability is MEASURED, not assumed (#14409, landed `3ecb7dc1a`): mysql2
* Reachability is MEASURED, not assumed (commit `3ecb7dc1a`): mysql2
* 3.23.1 returns a module constant literally named `INVALID_DATE` for a zero
* `DATETIME`, and postgres-date 1.0.7 builds `new Date(NaN)` for every year in
* 275760..294276 — years Postgres itself stores. Unguarded,
Expand Down Expand Up @@ -940,7 +940,7 @@ export class DatabaseLoader implements MetadataLoader {
* with its empty value.
*/
private rethrowUnlessTableUnprovisioned(error: unknown): void {
// [#13324] Every caller of this helper reads `this.tableName`, so that is
// [commit 4cda78c9b] Every caller of this helper reads `this.tableName`, so that is
// the relation whose emptiness they are about to trust — a failure naming
// any OTHER relation (a view over a dropped base table) is not evidence
// about it and stays loud.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* #14921 — two files sharing one stem are REFUSED, never resolved by
* Commit c1d274de7 — two files sharing one stem are REFUSED, never resolved by
* extension precedence.
*
* ---------------------------------------------------------------------------
Expand All @@ -25,7 +25,7 @@
* set.
*
* ---------------------------------------------------------------------------
* The rule this pins (maintainer ruling on #14921, via the director seat,
* The rule this pins (maintainer ruling, landed as commit c1d274de7, via the director seat,
* 2026-09-05 — option 1 of three, verbatim 「同意」)
* ---------------------------------------------------------------------------
* Two files sharing a stem across the REGISTERED extensions is an authoring
Expand Down Expand Up @@ -123,7 +123,7 @@ beforeAll(async () => {
await fs.writeFile(path.join(cleanDir, rel), body, 'utf-8');
}
// Nested twin of a FLAT name. Not a collision: a nested file is neither
// listed nor resolvable (#14486), so it derives no name to collide with.
// listed nor resolvable (commit 4b4d5a331), so it derives no name to collide with.
await fs.writeFile(
path.join(cleanDir, 'crm', 'solo.json'),
JSON.stringify({ name: 'nested-solo' }),
Expand Down Expand Up @@ -242,7 +242,7 @@ describe('#14921 PIN 2 — one file per stem is still listed AND loadable', () =

it('CONTROL: a NESTED file sharing a flat name is not a collision', async () => {
// `crm/solo.json` sits beside a flat `solo.json`. A nested file derives no
// resolvable name at all (#14486), so there is nothing for it to collide
// resolvable name at all (commit 4b4d5a331), so there is nothing for it to collide
// with — and treating basenames as the unit would refuse this good tree.
const loader = loaderFor(cleanRoot);

Expand Down Expand Up @@ -280,15 +280,15 @@ describe('#14921 PIN 3 — the loadMany() two-body symptom is gone', () => {
const bodies = await loaderFor(cleanRoot).loadMany<{ name?: string }>(TYPE);

// Flat AND nested, exactly as before: this walk was never narrowed to the
// listed set (#14486 RECORD), and this card does not narrow it either.
// listed set (commit 4b4d5a331's RECORD), and this card does not narrow it either.
expect(bodies.map(b => b.name).sort()).toEqual(['alpha', 'beta', 'nested-solo', 'solo']);
});
});

describe('#14921 CONTROL — the refusal reads the REGISTERED extension set', () => {
it('`dual.json` + `dual.js` is NOT ambiguous under the default set', async () => {
// `.js` carries no registered serializer there, so it derives no name —
// the same reason #14486 stopped listing it.
// the same reason commit 4b4d5a331 stopped listing it.
const loader = loaderFor(probeRoot);

expect(await loader.list(TYPE)).toEqual(['dual']);
Expand Down Expand Up @@ -349,7 +349,7 @@ describe('#14921 CONTROL — a storage OUTAGE still degrades, it is not swept up
const manager = new MetadataManager({ formats: ['json'], loaders: [] });
manager.registerLoader(new OutageLoader());

// Degraded, exactly as #14423 left it: an array, short and served.
// Degraded, exactly as commit a56baa2bd left it: an array, short and served.
expect(await manager.listNames(TYPE)).toEqual([]);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* #14341 — `FilesystemLoader.loadManyKeyed()`: a file-held item is keyed by the
* Commit 2e471e362 — `FilesystemLoader.loadManyKeyed()`: a file-held item is keyed by the
* name this loader can actually RESOLVE for it, and by nothing else.
*
* ---------------------------------------------------------------------------
Expand All @@ -15,7 +15,7 @@
* while `listDiagnosed()` called the short answer complete.
*
* ---------------------------------------------------------------------------
* The rule this pins (PM ruling on #14341, 2026-09-02 — option D)
* The rule this pins (PM ruling of 2026-09-02 — option D, landed as commit 2e471e362)
* ---------------------------------------------------------------------------
* An item is keyed by this loader's own name-to-path derivation (the basename
* minus extension, the same derivation `list()` reports) ONLY where that
Expand All @@ -39,7 +39,7 @@
* ---------------------------------------------------------------------------
* `RECORD:` cases pin behaviour this ruling deliberately LEAVES ALONE — the
* nested nameless file is still dropped. They exist so the derivation repair
* (#14486: one shared name-to-path function for `list()`, `findFile()` and
* (the card commit 4b4d5a331 took in part: one shared name-to-path function for `list()`, `findFile()` and
* `loadManyKeyed()`) inverts them deliberately, with the change visible in a
* diff, instead of silently.
*
Expand Down Expand Up @@ -177,7 +177,7 @@ describe('#14341 FilesystemLoader.loadManyKeyed() keys by the resolvable name',

it('RECORD: a nested NAMELESS file is still dropped — the honest drop, #14486', async () => {
// Not a repair this ruling makes: there is no name for it that any other
// door reports. #14486 (one shared name-to-path derivation) is where this
// door reports. The card commit 4b4d5a331 took in part (one shared name-to-path derivation) is where this
// inverts, deliberately.
const keyed = await loader().loadManyKeyed(TYPE);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* #14486 — `FilesystemLoader.list()` reports only names the resolve trio can
* Commit 4b4d5a331 — `FilesystemLoader.list()` reports only names the resolve trio can
* open: one shared name-to-path derivation for `list()`, `findFile()` and
* `loadManyKeyed()`.
*
Expand All @@ -23,7 +23,7 @@
* author (human or AI) reads it as their own typo and retries the same word.
*
* ---------------------------------------------------------------------------
* The rule this pins (maintainer ruling on #14486, via the director seat,
* The rule this pins (maintainer ruling recorded in commit 4b4d5a331, via the director seat,
* 2026-09-02 — option A, narrow)
* ---------------------------------------------------------------------------
* `list()` reports a file only where this loader's derivation is a bijection
Expand All @@ -47,8 +47,8 @@
* divergence that remains is visible in a diff rather than implicit. Both of
* them are the same half of the ruling — "a file that does not fit is neither
* listed NOR resolvable" — which could not be taken here: each would invert a
* landed #14341 pin in `filesystem-loader-keyed-items.test.ts`, a file under a
* concurrent claim (PR #14627) when this landed.
* landed pin of commit 2e471e362 in `filesystem-loader-keyed-items.test.ts`, a file under a
* concurrent claim (squashed as commit ac8ed476f) when this landed.
*
* `CONTROL:` cases pin the things that must NOT move, and one of them
* (`javascript` registered) is the reverse verification that the narrowing
Expand Down Expand Up @@ -232,10 +232,10 @@ describe('#14486 the repair reaches MetadataManager', () => {
describe('#14486 RECORD: the half of the ruling this PR could not take', () => {
it('RECORD: loadMany() still returns bodies for files list() no longer names', async () => {
// The ruling also pinned "nothing unlisted is returned by `loadMany()`
// either". Filtering the shared walk would invert three landed #14341 pins
// either". Filtering the shared walk would invert three pins commit 2e471e362 landed
// — `filesystem-loader-keyed-items.test.ts:113`, `:167`, `:187` — and its
// `loadMany()` CONTROL at `:196` ("with every file", length 7), in a file
// held by a concurrent claim (PR #14627). Recorded, not repaired.
// held by a concurrent claim (squashed as commit ac8ed476f). Recorded, not repaired.
const bodies = await loader().loadMany<{ name?: string; label?: string }>(TYPE);

expect(bodies).toContainEqual({ name: 'account', label: 'Account' });
Expand Down
Loading
Loading