diff --git a/.changeset/liveness-symbol-anchors-batch-2.md b/.changeset/liveness-symbol-anchors-batch-2.md new file mode 100644 index 0000000000..835c0633b8 --- /dev/null +++ b/.changeset/liveness-symbol-anchors-batch-2.md @@ -0,0 +1,44 @@ +--- +"@objectstack/spec": patch +--- + +chore(spec): re-anchor the `manifest`, `datasource`, `permission`, `dataset` and `webhook` liveness ledgers to consuming symbols (#13003) + +Adoption batch 2 of the symbol-anchor citation grammar landed by #12516, following +batch 1's `action` / `object` pass. The `liveness/` ledgers ship inside this +package's npm tarball (they are named in `files`), so this is a published-data +change even though no runtime behaviour moves and no schema key changes. + +One hundred and five `path:NNN` evidence citations across +`liveness/manifest.json` (34), `datasource.json` (28), `permission.json` (25) and +`dataset.json` (18) are now written `path#symbol`, each re-closed by reading the +code on the current tree rather than by shifting a line number. A symbol moves +with its consumer, so the pointer survives the in-file drift that rots a line, +and goes red when the consumer is renamed or deleted — a direction a stale line +can never produce. Every entry touched is stamped `verifiedAt: 2026-08-28`, which +dates `datasource.json`, `dataset.json` and `webhook.json` for the first time. + +What the re-closure found, which is again the reason the migration is not +mechanical: 88 of the 105 citations were pointing at the wrong place already, +every one of them IN RANGE and so invisible to all three existing checks. They +had drifted onto docblocks, blank lines, a `variant: 'secondary'` UI action, a +`return m.aggregate` belonging to a different key, and — for `datasource.ssl` — +a contiguous run of five wrong pointers into a docblock about an unrelated +environment variable. Two prose claims were falsified outright and are withdrawn: +`external.validation`'s note credited `checkOnBoot` with gating the boot sweep +(nothing reads that key anywhere; the sweep is unconditional), and +`objects.viewAllRecords` cited a reader named `hasViewAllData` that no longer +exists in the tree. + +`webhook.json` is the batch's separate case. All eleven of its `live` entries +cited bare FILENAMES with line numbers (`auto-enqueuer.ts:266`), which the +evidence scanner cannot parse as citations at all — so that ledger contributed +zero resolvable paths and was never actually asked about by any check. Rewriting +them as repo-rooted anchors made them askable, and the key-mention check +immediately found a real answer: the dispatcher reads the remapped `active` +column and never the authored `isActive`, so that call site now lives in the +entry's note rather than in its evidence. + +`tool.json` is deliberately unchanged: every one of its citations points into +`packages/services/service-ai`, the closed cloud runtime, which is absent from +this checkout — there is no code here to re-close it against. diff --git a/packages/spec/liveness/dataset.json b/packages/spec/liveness/dataset.json index c4cda37d8f..314031503e 100644 --- a/packages/spec/liveness/dataset.json +++ b/packages/spec/liveness/dataset.json @@ -4,13 +4,15 @@ "props": { "name": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:178", - "note": "registry key + compiled Cube name." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`name: dataset.name` on the emitted Cube; the same value names the dataset in every join-validation diagnostic)", + "note": "registry key + compiled Cube name. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:178` had rotted onto a bare `);` inside `aggregateToMetricType`'s throw, ~290 lines from the read. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "label": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:179", - "note": "compiled into Cube title for presentations." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`title: resolveI18nLabel(dataset.label, REGISTRY_LOCALE)` — the Cube's display title, #6761 inline-locale-map aware)", + "note": "compiled into Cube title for presentations. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:179` had rotted onto a bare `}`. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "description": { "status": "live", @@ -18,46 +20,54 @@ }, "object": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:180", - "note": "base table — Cube sql + join resolution + draft-rows resolver." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (three reads: `declaredDatasource(dataset.object)` and `isExternal(dataset.object)` for the same-datasource join gate, and `fromObject = dataset.object` as the join walk's root)", + "note": "base table — Cube sql + join resolution + draft-rows resolver. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:180` had rotted onto `return m.aggregate as Metric['type']`, a DIFFERENT key's read, which is the rot shape hardest to catch by eye because it is still plausible compiler code. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "include": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:92", - "note": "compiled into Cube joins + the NativeSQLStrategy join allowlist (ADR-0021)." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`const include = dataset.include ?? []`, walked into Cube joins and the NativeSQLStrategy join allowlist)", + "note": "compiled into Cube joins + the NativeSQLStrategy join allowlist (ADR-0021). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:92` had rotted onto a BLANK LINE. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "filter": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-executor.ts:205", - "note": "dataset-level WHERE, ANDed with runtime + measure-scoped filters." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`filter: dataset.filter` — where the authored key is actually read, onto the compiled artifact); packages/services/service-analytics/src/dataset-executor.ts#DatasetExecutor (`combineFilters(compiled.filter, selection.runtimeFilter)` — where the compiled form is ANDed at query time)", + "note": "dataset-level WHERE, ANDed with runtime + measure-scoped filters. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and the entry gains its missing half. `:205` had rotted onto a docblock sentence about a missing operand spreading a blank. More to the point, the executor only ever sees `compiled.filter`: the read of the AUTHORED key is in the compiler, and this entry cited only the downstream consumer — so a change that dropped `dataset.filter` from the compiler would have left this citation resolving happily against code that reads a name the author never writes. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "dimensions": { "children": { "name": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:141", - "note": "dimension identifier (Cube dimension key)." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`name: d.name`, the `dimensions[d.name]` key, the `?? d.name` label fallback, and the `assertDeclared(..., d.name)` diagnostic)", + "note": "dimension identifier (Cube dimension key). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:141` had rotted into a docblock about a query-time diagnostic boundary (#5288), ~280 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "label": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:142", - "note": "compiled into Cube dimension for presentations." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`label: resolveI18nLabel(d.label, REGISTRY_LOCALE) ?? d.name` — #6761: an inline locale map is a label, not a missing one)", + "note": "compiled into Cube dimension for presentations. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:142` had rotted into the same docblock as its sibling. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "field": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:144", - "note": "emitted as the dimension's SQL column reference (relationship-validated)." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`sql: d.field`, guarded by `assertDeclared(d.field, 'dimension', d.name)` — the relationship-path validation)", + "note": "emitted as the dimension's SQL column reference (relationship-validated). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:144` had rotted onto a docblock terminator `*/`. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "type": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:143", - "note": "dimensionType() maps to Cube time/number/boolean/string; drives grouping." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#dimensionType (`switch (d.type)` → Cube time/number/boolean/string); packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`type: dimensionType(d)`, and the `dim.type === 'time'` branch that granularity depends on)", + "note": "dimensionType() maps to Cube time/number/boolean/string; drives grouping. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:143` had rotted onto a `{@link compileDataset}` docblock line, which is a near-miss of the funniest kind: the citation landed on a cross-reference to its own real consumer. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "dateGranularity": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:146", + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`dim.granularities = d.dateGranularity ? [d.dateGranularity] : ['day','week','month','quarter','year']` — a declared granularity narrows the Cube dimension to exactly one); packages/services/service-analytics/src/dataset-executor.ts#resolveDimensionGranularity (`selection.dateGranularity ?? datasetDefault` — the dataset's own value is limb 3 of the query-time precedence)", "proof": "packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts#analytics-tz-bucketing", - "note": "ADR-0054 high-risk class (analytics): a time dimension's single granularity auto-buckets at query time (dataset-executor.ts:272-287); the proof asserts the day bucket SHIFTS with the org timezone (2024-03-01T03:00Z buckets to 2024-02-29 in America/Los_Angeles) — guarding the analytics-strategy ↔ in-memory count ↔ REST exec-context integration that #2018 fixed." + "note": "ADR-0054 high-risk class (analytics): a time dimension's single granularity auto-buckets at query time (dataset-executor.ts:272-287); the proof asserts the day bucket SHIFTS with the org timezone (2024-03-01T03:00Z buckets to 2024-02-29 in America/Los_Angeles) — guarding the analytics-strategy ↔ in-memory count ↔ REST exec-context integration that #2018 fixed. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:146` had rotted onto a docblock opener `/**`, and the note's `dataset-executor.ts:272-287` onto a closing brace and a comment line about the PRESENTATION's choice; the precedence resolver is at ~:315. On a row that carries an ADR-0054 high-risk proof, both pointers were wrong. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" } } }, @@ -65,43 +75,51 @@ "children": { "name": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:166", - "note": "measure identifier + derived-measure reference." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`name: m.name`, the `measures[m.name]` / `measureFilters[m.name]` keys, and the derived spec's own `name`); packages/services/service-analytics/src/dataset-compiler.ts#aggregateToMetricType (names the measure in both refusals)", + "note": "measure identifier + derived-measure reference. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:166` had rotted onto a `[#5367]` comment about the DATASET_INVALID refusal, ~280 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "label": { "status": "live", - "evidence": "packages/services/service-analytics/src/analytics-service.ts:479", - "note": "compiled into Cube metric + enriched onto result fields." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`label: resolveI18nLabel(m.label, REGISTRY_LOCALE) ?? m.name` — compiled into the Cube metric); packages/services/service-analytics/src/analytics-service.ts#AnalyticsService (`queryDataset` re-resolves `m.label` in the REQUEST locale and enriches it onto the result field, which is why the compile-time and request-time resolutions are two reads and not one)", + "note": "compiled into Cube metric + enriched onto result fields. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `analytics-service.ts:479` had rotted onto a blank docblock line in this 2338-line file; the enrichment is at ~:1345. The compiler half was never cited at all. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "aggregate": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:60", - "note": "aggregateToMetricType() → Cube metric type (sum/count/avg/…); unsupported aggregates throw." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#aggregateToMetricType (`if (!m.aggregate)` refuses a non-derived measure without one, `UNSUPPORTED_AGGREGATES.has(m.aggregate)` refuses the rest by name, and the value otherwise becomes the Cube metric type)", + "note": "aggregateToMetricType() → Cube metric type (sum/count/avg/…); unsupported aggregates throw. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:60` had rotted onto `export interface DerivedMeasureSpec {`, ~100 lines above the function. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "field": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-compiler.ts:170", - "note": "SQL aggregate operand (count omits field → '*'); relationship-validated." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`sql: m.field ?? '*'` — `count` with no field aggregates over rows — guarded by `assertDeclared(m.field, 'measure', m.name)`)", + "note": "SQL aggregate operand (count omits field → '*'); relationship-validated. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:170` had rotted into a comment about members the spec now refuses at parse. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "filter": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-executor.ts:225", - "note": "measure-scoped WHERE, applied via a supplementary per-measure query." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`if (m.filter) measureFilters[m.name] = m.filter` — the read of the authored key); packages/services/service-analytics/src/dataset-executor.ts#splitMeasuresByFilter (splits the selection into the unfiltered batch and the per-measure supplementary queries `DatasetExecutor` then runs)", + "note": "measure-scoped WHERE, applied via a supplementary per-measure query. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and the entry gains its missing half — `:225` had rotted onto a docblock sentence about not writing `?? 0` in the widget, and, as with the dataset-level `filter`, the executor reads only the COMPILED `measureFilters`, never this key. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "format": { "status": "live", - "evidence": "packages/services/service-analytics/src/analytics-service.ts:480", - "note": "compiled into Cube metric + enriched onto result fields for the renderer." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`if (typeof m.format === 'string') metric.format = m.format`); packages/services/service-analytics/src/analytics-service.ts#AnalyticsService (`queryDataset`: `if (f.format == null && m.format) f.format = m.format` — enriched onto the result field for the renderer)", + "note": "compiled into Cube metric + enriched onto result fields for the renderer. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `analytics-service.ts:480` had rotted onto a docblock sentence about driver selection. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "currency": { "status": "live", - "evidence": "packages/services/service-analytics/src/analytics-service.ts:531", - "note": "measure-declared currency (ISO 4217) enriched onto result fields alongside label/format, so the renderer formats the amount with a locale-correct Intl symbol rather than a '$' baked into format." + "evidence": "packages/services/service-analytics/src/analytics-service.ts#AnalyticsService (`queryDataset`: the ADR-0053 chain — a measure is monetary if `mc.currency` is set OR it aggregates a `currency`-type field, and the display code resolves explicit measure `currency` → the source field's `defaultCurrency` → the tenant default `context.currency`)", + "note": "measure-declared currency (ISO 4217) enriched onto result fields alongside label/format, so the renderer formats the amount with a locale-correct Intl symbol rather than a '$' baked into format. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `analytics-service.ts:531` had rotted onto a comment about a `400 INVALID_FIELD` refusal, ~830 lines above the chain. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" }, "derived": { "status": "live", - "evidence": "packages/services/service-analytics/src/dataset-executor.ts:247", - "note": "post-aggregation arithmetic (ratio/sum/difference/product) over base measures; {op, of} both consumed." + "evidence": "packages/services/service-analytics/src/dataset-compiler.ts#compileDataset (`derived.push({ name: m.name, op: m.derived.op, of: m.derived.of })` — both members read here, and a derived measure is deliberately NOT compiled into a Cube metric); packages/services/service-analytics/src/dataset-executor.ts#evaluateDerivedMeasures (post-aggregation arithmetic row by row, driven from `compiled.derived`)", + "note": "post-aggregation arithmetic (ratio/sum/difference/product) over base measures; {op, of} both consumed. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:247` had rotted onto a BLANK LINE, and the compiler leg (where `{op, of}` are actually read off the authored key) was uncited. Re-closed by hand against c459da6bc; DATED for the first time (this file carried no `verifiedAt` anywhere, which the #13003 ACCEPT ruling sorts as OLDEST).", + "verifiedAt": "2026-08-28" } } } diff --git a/packages/spec/liveness/datasource.json b/packages/spec/liveness/datasource.json index 727553117d..2f489b0b4c 100644 --- a/packages/spec/liveness/datasource.json +++ b/packages/spec/liveness/datasource.json @@ -4,8 +4,9 @@ "props": { "name": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts:674", - "note": "registry key + the driver name the engine routes on (`driver.name` must equal it)." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`name: record.name` — the record→`DatasourceConnectionSpec` projection every driver is built from); packages/services/service-datasource/src/datasource-connection-service.ts#connectDeclared (the same name keys the per-datasource connection state and every result row)", + "note": "registry key + the driver name the engine routes on (`driver.name` must equal it). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and DATED for the first time — `datasource-connection-service.ts:674` had rotted in range onto a docblock about an `onEnable` bridge and the default driver's idempotent replay; the projection is at ~:807. This file's entries carried NO `verifiedAt` at all, which the ACCEPT ruling on #13003 sorts as OLDEST (undated means never clock-checked, strictly worse than any date), and this row is why: an undated citation into an 823-line service had nothing asking whether it still pointed anywhere. Re-closed by hand against c459da6bc." }, "label": { "status": "live", @@ -13,68 +14,83 @@ "evidenceScope": "cross-repo", "evidence": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:96 takes `label` off the datasource draft with the datasource's own `name` as fallback, and packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:146 renders it as the preview card's title", "producer": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts:44 registers DatasourcePreview against the `datasource` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", - "note": "display metadata (Setup → Datasources list). ADR-0033 docs-shaped, deliberately kept, not authorWarn'd. PROSE RECONCILED 2026-08-10 (#7427): the old clause 'No runtime consumer by design' is superseded as a whole-system claim — a designer preview rendering a key to a human IS a runtime consumer (maintainer ruling 2026-08-10, #7131; README, 'Designer previews count as consumers'), and the metadata-admin datasource preview has been showing this value as the card title the whole time. What the old clause got right, and what is preserved: NO CONNECTION-PATH CONSUMER reads it — nothing in `ConnectableDatasource` / `DatasourceConnectionSpec` / the driver factory takes `label`, and `live` here does not mean a driver acquired a use for it. UNCHANGED by the reconciliation: still ADR-0033 docs-shaped, still deliberately KEPT, still not authorWarn'd. Status did not move (this row was already `live`); what it gained is a falsifiable pointer, which is the thing a `live` verdict is supposed to be. Reachability checked rather than assumed: `datasource` IS a registered metadata kind (packages/spec/src/kernel/metadata-type-schemas.ts:119), so the metadata-admin route that resolves this preview is a live authoring path — contrast `validation.label`, whose preview renders the same way into a kind ADR-0088 retired." + "note": "display metadata (Setup → Datasources list). ADR-0033 docs-shaped, deliberately kept, not authorWarn'd. PROSE RECONCILED 2026-08-10 (#7427): the old clause 'No runtime consumer by design' is superseded as a whole-system claim — a designer preview rendering a key to a human IS a runtime consumer (maintainer ruling 2026-08-10, #7131; README, 'Designer previews count as consumers'), and the metadata-admin datasource preview has been showing this value as the card title the whole time. What the old clause got right, and what is preserved: NO CONNECTION-PATH CONSUMER reads it — nothing in `ConnectableDatasource` / `DatasourceConnectionSpec` / the driver factory takes `label`, and `live` here does not mean a driver acquired a use for it. UNCHANGED by the reconciliation: still ADR-0033 docs-shaped, still deliberately KEPT, still not authorWarn'd. Status did not move (this row was already `live`); what it gained is a falsifiable pointer, which is the thing a `live` verdict is supposed to be. Reachability checked rather than assumed: `datasource` IS a registered metadata kind (the `datasource: DatasourceSchema` row of `BUILTIN_METADATA_TYPE_SCHEMAS` in packages/spec/src/kernel/metadata-type-schemas.ts), so the metadata-admin route that resolves this preview is a live authoring path — contrast `validation.label`, whose preview renders the same way into a kind ADR-0088 retired. 2026-08-28 (#13003): that reachability pointer was written `metadata-type-schemas.ts:119` and had drifted onto the `flow: FlowSchema` row of the same map — the right map, the wrong kind, which is the one rot shape a reader is least likely to catch by eye. Restated by symbol. The objectui-realm citations in `evidence` / `producer` are pinned to @e9ab52f9 and are not re-graded here (this batch reads the framework tree only)." }, "driver": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:345", - "note": "factory dispatch; `resolveDriverId` normalizes aliases before the switch. Since #6345 that table is also what both boot hosts read for `OS_DATABASE_DRIVER` / `--database-driver`, and the canonical mongo id is `mongodb` (stored `mongo` converges via the ADR-0087 conversion `datasource-driver-mongo-to-mongodb`; the alias stays accepted). Every builtin id now has an explicit construction arm — the trailing `memory` fall-through is gone." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveKind (`return resolveDriverId(driverId)` — the alias normalization); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (`const kind = resolveKind(spec.driver)`, then the per-driver construction arms; an id that normalizes to nothing is refused by name)", + "note": "factory dispatch; `resolveDriverId` normalizes aliases before the switch. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:345` had rotted in range onto a docblock line about `datasource.ssl` in this 1314-line file; the dispatch is at ~:1037. Since #6345 that table is also what both boot hosts read for `OS_DATABASE_DRIVER` / `--database-driver`, and the canonical mongo id is `mongodb` (stored `mongo` converges via the ADR-0087 conversion `datasource-driver-mongo-to-mongodb`; the alias stays accepted). Every builtin id now has an explicit construction arm — the trailing `memory` fall-through is gone. Re-closed by hand against c459da6bc; DATED for the first time (this file's rows were all undated, which the #13003 ACCEPT ruling sorts as oldest)." }, "config": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:129", - "note": "per-driver connection config. Validated against the driver contract since #4410 (data/driver/config-registry.zod.ts). NOTE the walk boundary: `config` is a `z.record`, so the gate cannot see inside it — the keys an author actually writes (`host`, `port`, `filename`) are governed by the per-driver zod schemas in data/driver/*.zod.ts, not by this ledger. That is a real gap in coverage, recorded here rather than left implicit." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`config: record.config ?? {}` — how the block reaches the factory at all); packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlConnection (the pg / better-sqlite3 arm reads `spec.config` for host, port, filename …); packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMysqlConnection; packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMemoryConfig; packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMongoUrl (with `buildMongoAuth` beside it for the credential half)", + "note": "per-driver connection config. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:129` had rotted in range onto an error-message helper's `const cause = …` line, ~360 lines from the nearest read. The single line was also understating the entry: `config` has no ONE consumer, it has one per driver arm plus the projection that carries it there, and a single rotted pointer hid five real ones. Re-closed by hand against c459da6bc. Validated against the driver contract since #4410 (data/driver/config-registry.zod.ts). NOTE the walk boundary: `config` is a `z.record`, so the gate cannot see inside it — the keys an author actually writes (`host`, `port`, `filename`) are governed by the per-driver zod schemas in data/driver/*.zod.ts, not by this ledger. That is a real gap in coverage, recorded here rather than left implicit." }, "pool": { "children": { "min": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:191, packages/services/service-datasource/src/default-datasource-driver-factory.ts:482, packages/services/service-datasource/src/datasource-pool-support.ts:79", - "note": "knex pool floor on `postgres` / `mysql` (`buildSqlPool` :191, handed to `SqlDriver` at :403 and :458), and the MongoClient's `minPoolSize` on `mongodb` (:482). Live only since #4465 — the factory used to hardcode `{ min: 0, max: 5 }` over the carried value. NOT live on `memory` / `sqlite` / `sqlite-wasm`, and not silently dropped there either: declaring it is an authoring ERROR since #5714 (the SQLite pair) and #5931 (`memory`) — `POOL_UNSUPPORTED_DRIVER_IDS` (datasource-pool-support.ts:79). Also an authoring ERROR on `turso` since #7243 (maintainer ruling 2026-08-11, whole-arm — `TursoDriverConfig` has no `min` / `max` and neither libSQL transport pools). See the block note below." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (the knex pool floor on `postgres` / `mysql`); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (the mongodb arm maps it onto the MongoClient's `minPoolSize`, and the same function calls `assertDatasourcePoolSupported` at the last door); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS (the four arms on which declaring it is an authoring ERROR)", + "note": "knex pool floor on `postgres` / `mysql` (`buildSqlPool`, handed to `SqlDriver` in the pg and mysql arms), and the MongoClient's `minPoolSize` on `mongodb`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — all three citations had rotted in range. `:191` and `:482` in the driver factory landed in a docblock about import-error interpolation and on a `throw new Error(unparseablePostgresDsnMessage(...))` respectively; the reads are at ~:623 and ~:1182. `datasource-pool-support.ts:79` landed on a docblock line, ~36 lines above the constant it names. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — the factory used to hardcode `{ min: 0, max: 5 }` over the carried value. NOT live on `memory` / `sqlite` / `sqlite-wasm`, and not silently dropped there either: declaring it is an authoring ERROR since #5714 (the SQLite pair) and #5931 (`memory`) — `POOL_UNSUPPORTED_DRIVER_IDS` (datasource-pool-support.ts:79). Also an authoring ERROR on `turso` since #7243 (maintainer ruling 2026-08-11, whole-arm — `TursoDriverConfig` has no `min` / `max` and neither libSQL transport pools). See the block note below." }, "max": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:192, packages/services/service-datasource/src/default-datasource-driver-factory.ts:483, packages/services/service-datasource/src/datasource-pool-support.ts:79", - "note": "knex pool ceiling on `postgres` / `mysql` (`buildSqlPool` :192); also mapped onto the Mongo client's `maxPoolSize` (:483, #4465). Same driver qualification as `min`: an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (datasource-pool-support.ts:79 — #5714 / #5931 / #7243). See the block note below." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (the knex pool ceiling on `postgres` / `mysql`); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (mapped onto the Mongo client's `maxPoolSize`, #4465); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS", + "note": "knex pool ceiling on `postgres` / `mysql` (`buildSqlPool`); also mapped onto the Mongo client's `maxPoolSize` (#4465). Same driver qualification as `min`: an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — same three rots as `min`, one line over. Re-closed by hand against c459da6bc; DATED for the first time." }, "idleTimeoutMillis": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:193, packages/services/service-datasource/src/datasource-pool-support.ts:79", - "note": "passed through to knex verbatim on `postgres` / `mysql` (`buildSqlPool` :193) — the two SQL arms are the ONLY ones that read it. `mongodb` takes `min` / `max` out of the block and nothing else (:477-483), so this key reaches nothing on that arm; the unqualified `live` this row carried before #6214 overstated it. Since #7243 that reach-nothing is REJECTED BY NAME rather than dropped: `POOL_UNREAD_KEYS_BY_DRIVER.mongodb` lists this key, so a mongo datasource declaring it is refused at all three doors (maintainer ruling 2026-08-11 — rejected, not wired onto MongoClient's `maxIdleTimeMS`, for want of a measured consumer). An authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` too (datasource-pool-support.ts:79 — #5714 / #5931 / #7243; the whole-block rejection text names `min` / `max` and the timeouts together). See the block note below." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (passed through to knex verbatim — the two SQL arms are the only readers); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNREAD_KEYS_BY_DRIVER (the mongodb by-name rejection); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS (the four whole-block rejections)", + "note": "passed through to knex verbatim on `postgres` / `mysql` (`buildSqlPool`) — the two SQL arms are the ONLY ones that read it. `mongodb` takes `min` / `max` out of the block and nothing else, so this key reaches nothing on that arm; the unqualified `live` this row carried before #6214 overstated it. Since #7243 that reach-nothing is REJECTED BY NAME rather than dropped: `POOL_UNREAD_KEYS_BY_DRIVER.mongodb` lists this key, so a mongo datasource declaring it is refused at all three doors (maintainer ruling 2026-08-11 — rejected, not wired onto MongoClient's `maxIdleTimeMS`, for want of a measured consumer). An authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` too (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243; the whole-block rejection text names `min` / `max` and the timeouts together). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `default-datasource-driver-factory.ts:193` had rotted into an import-error docblock and `datasource-pool-support.ts:79` onto a docblock line, ~36 lines above the constant it meant; the entry also gains the `POOL_UNREAD_KEYS_BY_DRIVER` anchor its own note already named in prose but never cited, which is the by-name rejection this row's whole qualification rests on. Re-closed by hand against c459da6bc; DATED for the first time." }, "connectionTimeoutMillis": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:194, packages/services/service-datasource/src/datasource-pool-support.ts:79", - "note": "mapped onto knex's `acquireTimeoutMillis` (different name, same meaning) on `postgres` / `mysql` (`buildSqlPool` :194-196). Carries `idleTimeoutMillis`' qualification exactly: `mongodb` reads only `min` / `max` from the block, so this key reaches nothing there and is REJECTED BY NAME since #7243 (`POOL_UNREAD_KEYS_BY_DRIVER.mongodb`), and the whole block is an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (datasource-pool-support.ts:79 — #5714 / #5931 / #7243). See the block note below." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (`acquireTimeoutMillis: pool.connectionTimeoutMillis` — the rename happens here); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNREAD_KEYS_BY_DRIVER (the mongodb by-name rejection); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS", + "note": "mapped onto knex's `acquireTimeoutMillis` (different name, same meaning) on `postgres` / `mysql` (`buildSqlPool`). Carries `idleTimeoutMillis`' qualification exactly: `mongodb` reads only `min` / `max` from the block, so this key reaches nothing there and is REJECTED BY NAME since #7243 (`POOL_UNREAD_KEYS_BY_DRIVER.mongodb`), and the whole block is an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — same two rots as its sibling. Note what the anchor fixes that the line could not even in principle: this key's consumer RENAMES it on the way through, so `buildSqlPool` is the only place in the tree where the authored spelling and the knex spelling are both visible, and a citation that drifts off it leaves the rename undocumented. Re-closed by hand against c459da6bc; DATED for the first time." } }, - "note": "QUALIFIED BY DRIVER 2026-08-10 (#6214) — the spec half of the #5931 ruling. `live` on the four rows above means \"honoured by the pooled arms\", never \"honoured everywhere\", and the rows said so nowhere until this repair: an author reading them got `pool` presented as an unconditional knob on a block that four built-in drivers now REFUSE outright and a fifth (`mongodb`) honours only half of. HONOURED: `postgres` / `mysql` hand `buildSqlPool(spec)` to `SqlDriver` (packages/services/service-datasource/src/default-datasource-driver-factory.ts:188-198, applied at :403 and :458), and `mongodb` maps `min` / `max` — only those two — onto the MongoClient's `minPoolSize` / `maxPoolSize` (:477-483). LOUDLY REJECTED on `memory` / `sqlite` / `sqlite-wasm` / `turso` (#7243): `POOL_UNSUPPORTED_DRIVER_IDS` (packages/services/service-datasource/src/datasource-pool-support.ts:79) with one explanation per arm (:141-149), thrown at every door a `pool` block can come in through — the Setup wizard's create/update (datasource-admin-service.ts:243, :314), the boot-time auto-connect pre-pass (datasource-connection-service.ts:508), and the factory's last door (default-datasource-driver-factory.ts:374). Two arms, two reasons, one verdict: knex's better-sqlite3 dialect pins `{min:1,max:1}` on purpose because a second connection to `:memory:` opens a SEPARATE, empty database, so sizing the pool would split one datasource's data across several stores (#5714, maintainer ruling 2026-08-06 option B); `memory` opens no connection at all — its store is a plain data structure inside this process, reached by a direct call — so `min` / `max` and the timeouts have nothing to configure (#5931, maintainer ruling 2026-08-07, which also set the default that a silently-dropped key JOINS an existing rejection set rather than queueing for its own ruling, #6140). Measured through the real factory before the rejection existed (#5931): `postgres + pool{min:3,max:9}` → live `{min:3,max:9}`; `sqlite + pool{min:3,max:9}` → live `{min:1,max:1}`; `memory + pool{min:3,max:9}` → driver config `{\"persistence\":false}`, `pool` undefined. `examples/app-crm` was the live specimen — `CrmDatasource` declared `pool: { min: 1, max: 5 }` and ran on `{min:1,max:1}`. NOTHING IS SILENT HERE ANY MORE, as of #7243 (maintainer ruling 2026-08-11, both halves). The two faces this note recorded as still-dropped are now loud. (a) `turso` JOINS the rejected set whole-arm, with no fork by url mode: `TursoDriverConfig` has no `min` / `max`, a `file:` / `:memory:` url runs the very better-sqlite3 engine the set already rejects for, and a `libsql://` url is a remote request transport with no persistent connections, capped by `config.concurrency`. Measured before the change: the turso arm never referenced `spec.pool` at all. (b) `mongodb`'s two timeout keys are REJECTED BY NAME rather than wired — a new per-KEY shape (`POOL_UNREAD_KEYS_BY_DRIVER`, datasource-pool-support.ts), because rejecting the whole block on that arm would throw away the `min` / `max` it does honour. Measured before the change: `mongodb + pool{max:20,idleTimeoutMillis:30000,connectionTimeoutMillis:3000}` built a driver whose config was url + database + maxPoolSize:20 and nothing else — half the block honoured, which reads to the author as all of it. MongoClient does expose `maxIdleTimeMS` / `connectTimeoutMS`, so this one COULD have been implemented; the ruling declined for want of a measured consumer and left the wiring as a one-line pulled change for the day demand appears. A driver id the platform ships no contract for (`com.vendor.snowflake`) is deliberately NOT judged — \"we validate what we can construct\" (datasource-pool-support.ts:101-105)." + "note": "QUALIFIED BY DRIVER 2026-08-10 (#6214) — the spec half of the #5931 ruling. `live` on the four rows above means \"honoured by the pooled arms\", never \"honoured everywhere\", and the rows said so nowhere until this repair: an author reading them got `pool` presented as an unconditional knob on a block that four built-in drivers now REFUSE outright and a fifth (`mongodb`) honours only half of. HONOURED: `postgres` / `mysql` hand `buildSqlPool(spec)` to `SqlDriver`, and `mongodb` maps `min` / `max` — only those two — onto the MongoClient's `minPoolSize` / `maxPoolSize`, both inside `createDefaultDatasourceDriverFactory`'s per-driver arms. LOUDLY REJECTED on `memory` / `sqlite` / `sqlite-wasm` / `turso` (#7243): `POOL_UNSUPPORTED_DRIVER_IDS` (packages/services/service-datasource/src/datasource-pool-support.ts) with one explanation per arm in `POOL_UNSUPPORTED_REASONS`, thrown by `assertDatasourcePoolSupported` at every door a `pool` block can come in through — the Setup wizard's `createDatasource` / `updateDatasource`, the boot-time pre-pass (`DatasourceConnectionService.assertDeclaredPoolsAreHonoured` via `unsupportedPoolIssue`, and `attemptConnect` before the factory call), and the factory's last door in `createDefaultDatasourceDriverFactory`. 2026-08-28 (#13003): every line reference in this block note was re-read and replaced with the symbol it meant — they had drifted with the same files their sibling `evidence` citations did (`:188-198` landed in an import-error docblock, `:374` inside `resolveSslOption`, `datasource-pool-support.ts:79` on a docblock line), and a line inside a `note` is worse than one in `evidence` because no check reads `note` at all. Two arms, two reasons, one verdict: knex's better-sqlite3 dialect pins `{min:1,max:1}` on purpose because a second connection to `:memory:` opens a SEPARATE, empty database, so sizing the pool would split one datasource's data across several stores (#5714, maintainer ruling 2026-08-06 option B); `memory` opens no connection at all — its store is a plain data structure inside this process, reached by a direct call — so `min` / `max` and the timeouts have nothing to configure (#5931, maintainer ruling 2026-08-07, which also set the default that a silently-dropped key JOINS an existing rejection set rather than queueing for its own ruling, #6140). Measured through the real factory before the rejection existed (#5931): `postgres + pool{min:3,max:9}` → live `{min:3,max:9}`; `sqlite + pool{min:3,max:9}` → live `{min:1,max:1}`; `memory + pool{min:3,max:9}` → driver config `{\"persistence\":false}`, `pool` undefined. `examples/app-crm` was the live specimen — `CrmDatasource` declared `pool: { min: 1, max: 5 }` and ran on `{min:1,max:1}`. NOTHING IS SILENT HERE ANY MORE, as of #7243 (maintainer ruling 2026-08-11, both halves). The two faces this note recorded as still-dropped are now loud. (a) `turso` JOINS the rejected set whole-arm, with no fork by url mode: `TursoDriverConfig` has no `min` / `max`, a `file:` / `:memory:` url runs the very better-sqlite3 engine the set already rejects for, and a `libsql://` url is a remote request transport with no persistent connections, capped by `config.concurrency`. Measured before the change: the turso arm never referenced `spec.pool` at all. (b) `mongodb`'s two timeout keys are REJECTED BY NAME rather than wired — a new per-KEY shape (`POOL_UNREAD_KEYS_BY_DRIVER`, datasource-pool-support.ts), because rejecting the whole block on that arm would throw away the `min` / `max` it does honour. Measured before the change: `mongodb + pool{max:20,idleTimeoutMillis:30000,connectionTimeoutMillis:3000}` built a driver whose config was url + database + maxPoolSize:20 and nothing else — half the block honoured, which reads to the author as all of it. MongoClient does expose `maxIdleTimeMS` / `connectTimeoutMS`, so this one COULD have been implemented; the ruling declined for want of a measured consumer and left the wiring as a one-line pulled change for the day demand appears. A driver id the platform ships no contract for (`com.vendor.snowflake`) is deliberately NOT judged — \"we validate what we can construct\" (the docblock over `POOL_UNSUPPORTED_DRIVER_IDS` in datasource-pool-support.ts)." }, "ssl": { "children": { "enabled": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:111", - "note": "`enabled: false` short-circuits to `ssl: false`; otherwise the block is assembled into client TLS options." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`if (block.enabled === false) return false` — the short-circuit, ahead of the option assembly)", + "note": "`enabled: false` short-circuits to `ssl: false`; otherwise the block is assembled into client TLS options. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." }, "rejectUnauthorized": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:114" + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.rejectUnauthorized !== undefined ? { rejectUnauthorized: block.rejectUnauthorized } : {}` — present-or-absent, so `false` is carried rather than defaulted away); packages/services/service-datasource/src/default-datasource-driver-factory.ts#mysqlSslOption (re-reads the resolved option into the spelling mysql2 accepts, #8874)", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." }, "ca": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:115" + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.ca ? { ca: block.ca } : {}`)", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." }, "cert": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:116" + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.cert ? { cert: block.cert } : {}`)", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." }, "key": { "status": "live", - "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts:117" + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.key ? { key: block.key } : {}`)", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." } }, - "note": "Live only since #4465. Before that the whole block stopped at the record — nothing put it on the connection spec, so a TLS configuration with a CA certificate in it configured nothing while looking identical to one that worked. A security-shaped property that was silently inert; exactly the ADR-0078 class this ledger exists to catch, and it was found by hand rather than by a gate." + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED, whole block, and this is the batch's sharpest case of a security-shaped entry pointing at nothing. All five citations (`:111` / `:114` / `:115` / `:116` / `:117`) landed in a docblock about `OS_DATABASE_URL` and which host knob a connect-failure remedy should name — a contiguous run of five wrong pointers into a 1314-line file, in range, in a file that names `ssl` on dozens of lines, so nothing in the gate could see it. The real reads are all in ONE function, `resolveSslOption` at ~:357, which is why five separate lines collapse to one anchor apiece. The block's whole history is that it was silently inert once before (#4465); an evidence pointer that had quietly stopped resolving to the code is the same failure one level up. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465. Before that the whole block stopped at the record — nothing put it on the connection spec, so a TLS configuration with a CA certificate in it configured nothing while looking identical to one that worked. A security-shaped property that was silently inert; exactly the ADR-0078 class this ledger exists to catch, and it was found by hand rather than by a gate." }, "description": { "status": "live", @@ -86,53 +102,62 @@ }, "active": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts:296", - "note": "`active: false` skips the datasource in the boot auto-connect sweep, and datasource-admin-plugin.ts:424 excludes it from the runtime re-registration set. Genuinely enforced — unlike `flow.active` / `tool.active`, both of which were retired in v17 for claiming this and not delivering it." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#connectDeclared (`if (ds.active === false) continue` — the boot auto-connect sweep skips it); packages/services/service-datasource/src/datasource-connection-service.ts#assertDeclaredPoolsAreHonoured (the same skip on the pre-pass, so an inactive datasource is not refused for a pool it will never build); packages/services/service-datasource/src/datasource-admin-plugin.ts#rehydratePools (`r.origin === 'runtime' && (r.active ?? true)` — excluded from the runtime re-registration set)", + "note": "`active: false` skips the datasource in the boot auto-connect sweep, and `rehydratePools` excludes it from the runtime re-registration set. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `datasource-connection-service.ts:296` had rotted onto a bare closing brace between `getConnectionState` and `listConnectionStates`, and the note's `datasource-admin-plugin.ts:424` onto `this.config = config`. The entry also gains a second in-file consumer the single line hid: the pre-pass honours the same skip. Re-closed by hand against c459da6bc; DATED for the first time. Genuinely enforced — unlike `flow.active` / `tool.active`, both of which were retired in v17 for claiming this and not delivering it." }, "autoConnect": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts:233", - "note": "ADR-0062 D2(c): opts a managed, unrouted datasource into the boot connect sweep." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#isDatasourceAddressed (`if (ds.autoConnect === true) return true` — limb (c) of the three-way addressed test the boot sweep gates on)", + "note": "ADR-0062 D2(c): opts a managed, unrouted datasource into the boot connect sweep. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:233` had rotted onto a comment about operators finding a mis-routed datasource by going to look in the database, ~26 lines above the predicate. Re-closed by hand against c459da6bc; DATED for the first time." }, "schemaMode": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts:679", - "note": "carried onto the connection spec (#4410) and gates DDL at the driver; also read by objectql/src/engine.ts:620 for the federation write gate. Live only since #4465 — before that it was dropped between record and spec, so an `external` database ObjectStack must never run DDL against was constructed as `managed`. Security-shaped and silently inert; the second reason this ledger was seeded." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`...(record.schemaMode ? { schemaMode: record.schemaMode } : {})` — the #4410 repair, carried onto the connection spec); packages/services/service-datasource/src/datasource-connection-service.ts#isDatasourceAddressed (limb (a): a non-managed datasource is addressed whatever else it declares); packages/objectql/src/engine.ts#assertWriteAllowed (`if (!ds || !ds.schemaMode || ds.schemaMode === 'managed') return` — the federation write gate's first test); packages/objectql/src/engine.ts#registerDatasourceDef (what captures `schemaMode` into `datasourceDefs` so that gate has anything to read)", + "note": "carried onto the connection spec (#4410) and gates DDL at the driver; also read by the engine's federation write gate. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `datasource-connection-service.ts:679` had rotted onto `const names = Array.from(this.states.entries())`, and the note's `engine.ts:620` onto a bare docblock continuation line in a 13593-line file; the write gate is at ~:5267. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — before that it was dropped between record and spec, so an `external` database ObjectStack must never run DDL against was constructed as `managed`. Security-shaped and silently inert; the second reason this ledger was seeded." }, "external": { "children": { "allowedSchemas": { "status": "live", - "evidence": "packages/services/service-datasource/src/external-datasource-service.ts:145", - "note": "restricts which remote schemas browse/introspect will surface (ADR-0015)." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/external-datasource-service.ts#listRemoteTables (`const allowed = ds?.external?.allowedSchemas` — and the filter only bites on tables whose schema can be attributed, which the code says beside the read)", + "note": "restricts which remote schemas browse/introspect will surface (ADR-0015). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:145` had rotted onto a docblock about the `col.primaryKey` spelling, ~226 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." }, "allowWrites": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:620", - "note": "the enforced datasource-wide write gate (Gate 3). This — not `capabilities.readOnly` — is how a federated datasource is made read-only." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/engine.ts#assertWriteAllowed (`const dsAllows = ds.external?.allowWrites ?? false` — Gate 3, ANDed with the object's own `external.writable`, called from `insert` / `update` / `delete`); packages/objectql/src/engine.ts#registerDatasourceDef (captures `external` into `datasourceDefs` so the gate has a definition to read)", + "note": "the enforced datasource-wide write gate (Gate 3). This — not `capabilities.readOnly` — is how a federated datasource is made read-only. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:620` had rotted onto a bare docblock continuation in a 13593-line file; the gate is at ~:5267, roughly 4650 lines away. A security-shaped enforcement point whose only pointer had drifted that far is the reason this migration is not cosmetic. Re-closed by hand against c459da6bc; DATED for the first time." }, "validation": { "status": "live", - "evidence": "packages/runtime/src/external-validation-plugin.ts:153, packages/runtime/src/external-validation-plugin.ts:231", - "note": "`onMismatch` selects the drift policy (default 'fail'); `checkIntervalMs` schedules the recurring drift check; `checkOnBoot` gates the boot-time one. Also read by the degraded-boot classifier (packages/types/src/degraded-boot.ts:13)." + "verifiedAt": "2026-08-28", + "evidence": "packages/runtime/src/external-validation-plugin.ts#resolveOnMismatch (`ds?.external?.validation?.onMismatch ?? 'fail'` — the drift policy, defaulted fail-closed and re-read per sweep); packages/runtime/src/external-validation-plugin.ts#scheduleDriftChecks (`const interval = def?.external?.validation?.checkIntervalMs` — arms one background timer per datasource that declares it)", + "note": "`onMismatch` selects the drift policy (default 'fail'); `checkIntervalMs` schedules the recurring drift check. Also read by the degraded-boot classifier (packages/types/src/degraded-boot.ts, `validation.onMismatch:'fail'` as one of the two degraded-boot causes). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and one PROSE CLAIM WITHDRAWN. Both citations had rotted in range (`:153` onto a `schemaMode?: string` field of a local `DatasourceDef` interface, `:231` onto a comment about vitest workers paying the bill for un-unref'd timers). The withdrawal: this note said \"`checkOnBoot` gates the boot-time one\", and the re-closure falsifies it — `checkOnBoot` has ZERO readers anywhere in this repo (its only non-test occurrences are the schema declaration, its two alias spellings, and one example app that authors it). `ExternalValidationPlugin.start` hooks `kernel:ready` and calls `runValidation` UNCONDITIONALLY, so `checkOnBoot: false` still runs the boot sweep — and with the default `onMismatch: 'fail'` that aborts boot. The container entry stays `live` on the two readers that do exist; the inert member is below this ledger's one-level walk and is filed rather than flipped here, per ADR-0049 and the #13003 dispatch's no-status-changes rule. Re-closed by hand against c459da6bc; DATED for the first time." }, "credentialsRef": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts:457", - "note": "dereferenced through the SecretBinder to cleartext for the duration of one connect; never persisted or logged (ADR-0015 Addendum)." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#attemptConnect (`const credentialsRef = record.external?.credentialsRef`, resolved through the configured secret resolver into a `secret` spread onto the factory call and never stored on the record — three distinct failure states are named rather than swallowed: no secret store configured, resolver threw, resolved to nothing)", + "note": "dereferenced through the SecretBinder to cleartext for the duration of one connect; never persisted or logged (ADR-0015 Addendum). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:457` had rotted onto a docblock about threading a value through an instance field rather than the ConnectResult, ~95 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." }, "queryTimeoutMs": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-admin-service.ts:220", - "note": "carried into the external-datasource probe options as `timeoutMs`." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-admin-service.ts#testConnection (`const queryTimeoutMs = (input.external as …)?.queryTimeoutMs`, spread onto the probe options as `timeoutMs` only when it is a number)", + "note": "carried into the external-datasource probe options as `timeoutMs`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:220` had rotted onto a BLANK LINE, which is the most legible form of this rot: in range, resolvable, and pointing at nothing at all. Re-closed by hand against c459da6bc; DATED for the first time." } }, "note": "5 of 7 live. The two dead ones are opposite in risk: `external.label` is cosmetic, `external.requirePermission` is security-shaped — it reads as an access gate and is not one." }, "origin": { "status": "live", - "evidence": "packages/services/service-datasource/src/datasource-admin-plugin.ts:244", - "note": "server-stamped provenance (ADR-0015 Addendum). `code` marks a GitOps-owned datasource read-only in the UI; `runtime` marks one editable and re-registrable (datasource-admin-plugin.ts:424). Never accepted from client input." + "verifiedAt": "2026-08-28", + "evidence": "packages/services/service-datasource/src/datasource-admin-plugin.ts#rehydratePools (`records.filter((r) => r.origin === 'runtime' && (r.active ?? true))` — the value that decides which datasources are re-registered at boot); packages/services/service-datasource/src/datasource-admin-plugin.ts#DatasourceAdminServicePlugin (the metadata adapter installed in `init` defaults an artefact-loaded row's missing `origin` to `'code'`, and the connect path stamps `origin: record.origin ?? 'runtime'` onto the lifecycle context)", + "note": "server-stamped provenance (ADR-0015 Addendum). `code` marks a GitOps-owned datasource read-only in the UI; `runtime` marks one editable and re-registrable (`rehydratePools`). Never accepted from client input. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:244` had rotted onto `variant: 'secondary'`, a UI action definition in this plugin's own metadata, and the note's `:424` onto `this.config = config`. Re-closed by hand against c459da6bc; DATED for the first time." } } } diff --git a/packages/spec/liveness/manifest.json b/packages/spec/liveness/manifest.json index 14871b6901..19a2230bd2 100644 --- a/packages/spec/liveness/manifest.json +++ b/packages/spec/liveness/manifest.json @@ -4,80 +4,87 @@ "props": { "id": { "status": "live", - "evidence": "packages/objectql/src/registry.ts:3492 (initialDisabledPackageIds lookup), :3512-3518 (the key the installed-package collection is stored under), :3505 (namespace ownership record); packages/objectql/src/engine.ts:4378", - "verifiedAt": "2026-08-23", + "evidence": "packages/objectql/src/registry.ts#installPackage (three reads in one verb — the `initialDisabledPackageIds.has(manifest.id)` lookup that decides whether the record installs disabled, the `package` collection key the record is stored under, and the owner id handed to `registerNamespace`); packages/objectql/src/engine.ts#registerApp (`const id = manifest.id || manifest.name` — the package id every registration below it is stamped with); packages/rest/src/package-routes.ts#publishRoute (publish refuses a manifest without it: `if (!manifest.id || !manifest.version)` ⇒ 400 PACKAGE_MANIFEST_INVALID)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "The package identity the whole install path is keyed on. `installPackage` uses it as the collection key, as the namespace owner, and as the disabled-set lookup; the REST publish route refuses a manifest without it (packages/rest/src/package-routes.ts:532)." + "note": "The package identity the whole install path is keyed on. `installPackage` uses it as the collection key, as the namespace owner, and as the disabled-set lookup; the REST publish route refuses a manifest without it (packages/rest/src/package-routes.ts#publishRoute). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — all four citations had rotted IN RANGE. `registry.ts:3492` landed in `removeOverlayEntry`'s plain-key branch (`plain._packageId !== 'sys_metadata'`), an overlay-removal path that reads no manifest at all; `:3505` and `:3512-3518` both landed in `listItems` (the `'object'|'objects'` special case, and the package filter plus its docblock about hiding a disabled package's metadata); `engine.ts:4378` landed in the autonumber-counter resync (`this.autonumberCounters.set(counterKey, supplied)`), ~320 lines short of the read. WHY THE GATE COULD NOT SEE IT: registry.ts is 3921 lines and engine.ts is 13593, so every one of those lines is comfortably in range, and `id` is a word both files carry on hundreds of lines, so the #11457 key-mention check anchors on the coincidence and passes. Re-closed by hand against c459da6bc." }, "namespace": { "status": "live", - "evidence": "packages/objectql/src/registry.ts:3473-3486 (shareable-namespace check + NamespaceConflictError), :3504-3505 (registerNamespace), :3542-3543 (unregister on removal); packages/objectql/src/engine.ts:4379", - "verifiedAt": "2026-08-23", + "evidence": "packages/objectql/src/registry.ts#installPackage (the install-time gate — `manifest.namespace && !isShareableNamespace(...)`, then `getNamespaceOwners(...).find(owner => owner !== manifest.id)` ⇒ NamespaceConflictError, then `registerNamespace(manifest.namespace, manifest.id)` on the way through); packages/objectql/src/registry.ts#uninstallPackage (`unregisterNamespace(pkg.manifest.namespace, id)` — the release leg); packages/objectql/src/engine.ts#registerApp (`const namespace = manifest.namespace as string | undefined`, carried into the registration)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Enforced, not merely read: a non-shareable namespace already owned by another package raises NamespaceConflictError at install. Also the ADR-0121 carve-out segment for a stack's endpoints — packages/spec/src/api/endpoint-publish-gate.ts:175 refuses a stack that declares `apis:` without an explicit namespace." + "note": "Enforced, not merely read: a non-shareable namespace already owned by another package raises NamespaceConflictError at install. Also the ADR-0121 carve-out segment for a stack's endpoints — packages/spec/src/api/endpoint-publish-gate.ts#validateApiEndpointDeclarations refuses a stack that declares `apis:` without an explicit namespace. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — every citation had rotted in range. `registry.ts:3473-3486` landed in `removeOverlayEntry`'s discriminated-overlay loop, `:3504-3505` in `listItems`, and `:3542-3543` in a docblock about `resolveObjectKey` reuse; `engine.ts:4379` landed in the autonumber-counter resync. The three registry reads had all been pulled into two verbs (`installPackage` / `uninstallPackage`) that the line numbers no longer reach. Re-closed by hand against c459da6bc." }, "defaultDatasource": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:6337 (packageDatasource — the package-level default an object without its own `datasource` inherits)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/engine.ts#resolveDatasourceBinding (limb 4 of the routing precedence — `const packageDatasource = manifest?.defaultDatasource`, taken only when the object declares none of its own and no mapping or lifecycle rule already claimed it, and only when a driver of that name is registered)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:6337` is in range of this 13593-line file and lands in a docblock about secret-field resolution for privileged server-side consumers, ~400 lines short of the read. The consuming symbol is the same `resolveDatasourceBinding` the `object.datasource` entry was re-anchored to in batch 1 of this card: one precedence function reads the object key at limb 1 and this manifest key at limb 4, which the two rotted lines obscured by pointing at unrelated places in different files. Re-closed by hand against c459da6bc." }, "version": { "status": "live", - "evidence": "packages/rest/src/package-routes.ts:532 (publish refuses a manifest without it), :541-544 (published version echoed back); packages/services/service-package/src/index.ts:394,401; packages/cli/src/commands/plugin/publish.ts:91", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/rest/src/package-routes.ts#publishRoute (publish refuses a manifest without it — `if (!manifest.id || !manifest.version)` ⇒ 400 — and echoes the accepted version back in the 2xx body); packages/services/service-package/src/index.ts#PackageServicePlugin (the `publish` verb of the composed package service forwards `data.manifest.version` to the driver and logs the published coordinate); packages/cli/src/commands/plugin/publish.ts#PluginPublish (`const version = String(manifest.version ?? '').trim()`, refused when empty)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — a mixed entry, and the mix is the point. The CLI citation was accurate (`publish.ts:91` still names the read). Both REST citations had rotted in range: `:532` lands inside the #8016 docblock about `INTERNAL_ERROR` vs a coded refusal, and `:541-544` runs off the end of that docblock onto the `export function registerPackageRoutes(` signature — the declaration site, not a read. And the service-package citation was written `index.ts:394,401`, a comma-joined pair the evidence scanner cannot parse as a citation at all (the token stops matching PATH_RE), so that consumer has never been resolved by any check — unfalsifiable prose standing beside two checked pointers. Its two lines were in fact correct; it is now an anchor of its own. Re-closed by hand against c459da6bc." }, "type": { "status": "live", - "evidence": "packages/spec/src/stack.zod.ts:855 (an app-only derivation returns [] unless the manifest type is `app`); packages/runtime/src/domains/packages.ts:275 (package listing filter)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/spec/src/stack.zod.ts#validateSingleApp (`if (config.manifest?.type !== 'app') return []` — the single-app rule applies to consumer packages only); packages/runtime/src/domains/packages.ts#handlePackagesRequest (`packages.filter((p) => p.manifest?.type === query.type)` — the package listing filter)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) — both citations were still ACCURATE (`stack.zod.ts:855` and `domains/packages.ts:275` each name their read on the nose), so this is a pure grammar migration with no repair. Worth recording as one of the batch's control cases: both consumers sit in files under 2300 lines that have not been restructured since the 2026-08-23 seeding, which is the shape under which a line citation survives. Re-closed by hand against c459da6bc." }, "scope": { "status": "live", - "evidence": "packages/services/service-settings/src/settings-service.ts:800 (the default settings scope a package's settings land in); packages/metadata-protocol/src/package-writability.ts:79 (write-path gate); packages/platform-objects/src/apps/studio.app.ts:72 (Studio hides system/cloud-scoped packages)", - "verifiedAt": "2026-08-23", + "evidence": "packages/services/service-settings/src/settings-service.ts#registerManifest (`const defaultScope = manifest.scope ?? 'tenant'` — the scope every specifier without its own inherits); packages/metadata-protocol/src/package-writability.ts#isWritablePackage (write-path gate — an installed package whose manifest scope is in READ_ONLY_PACKAGE_SCOPES is refused); packages/platform-objects/src/apps/studio.app.ts#STUDIO_APP (the package picker filters `manifest.scope nin ['system','cloud']`)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Three consumers of three different kinds — a default, a writability gate and a UI filter — so the deployment scope is read on both the runtime and the admin path." + "note": "Three consumers of three different kinds — a default, a writability gate and a UI filter — so the deployment scope is read on both the runtime and the admin path. 2026-08-28: RE-ANCHORED (#13003) — all three citations were still ACCURATE, so this is a grammar migration. The anchor is what the entry gains: `settings-service.ts` is 2586 lines and its read sits ~800 lines in, which is exactly the shape that rots next." }, "name": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:4378 (package id fallback when `id` is absent), :4467-4471 (manifest-as-app registration uses it as the app name); packages/cli/src/commands/plugin/publish.ts:92 (marketplace display name)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/engine.ts#registerApp (`const id = manifest.id || manifest.name` — the package-id fallback when `id` is absent; the same function then registers manifest-as-app under it: `if (manifest.name && manifest.navigation && !manifest.apps?.length)`); packages/cli/src/commands/plugin/publish.ts#PluginPublish (`flags['display-name'] ?? manifest.name ?? id` — the marketplace display name)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the CLI citation was accurate; both engine citations had rotted in range. `engine.ts:4378` lands in the autonumber-counter resync and `:4467-4471` in the docblock for `createWithAutonumberResync` about the `ERR_AUTONUMBER_COLLISION` contract — neither anywhere near a manifest read. The two reads this entry names are both inside `registerApp`, ~230 and ~320 lines further down, so the two separately-cited lines collapse to one anchor. Re-closed by hand against c459da6bc." }, "description": { "status": "live", - "evidence": "packages/cli/src/commands/plugin/publish.ts:121 (copied onto the published package body); packages/cli/src/commands/validate.ts:354-355", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/cli/src/commands/plugin/publish.ts#PluginPublish (`if (typeof manifest.description === 'string') pkgBody.description = manifest.description` — copied onto the published package body); packages/cli/src/commands/validate.ts#Validate (echoed under the validated package's name in the human-facing summary)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `publish.ts:121` was accurate; `validate.ts:354-355` had rotted in range onto a comment about which advisories the `--json` face could structurally reach, ~130 lines short of the read (`config.manifest.description`, in the summary block). Re-closed by hand against c459da6bc." }, "permissions": { "status": "live", - "evidence": "packages/plugins/plugin-security/src/suggested-audience-bindings.ts:252 (declared permission strings from every enabled installed package feed the suggested audience bindings)", - "verifiedAt": "2026-08-23", + "evidence": "packages/plugins/plugin-security/src/suggested-audience-bindings.ts#collectDeclaredSuggestions (declared permission strings from every enabled installed package feed the suggested audience bindings — `Array.isArray(manifest?.permissions) ? manifest.permissions : []`, which is also where the legacy-arm-only reading below is measured)", + "verifiedAt": "2026-08-28", "evidenceScope": "cross-repo", - "note": "LIVE ON ONE ARM ONLY, and the split matters. `ManifestPermissionsSchema` (manifest.zod.ts:54) is a union of the legacy flat `string[]` and the structured `PluginPermissionsSchema` (services / hooks / network / fs, ADR-0025 §3.2). The single reader is guarded by `Array.isArray(manifest?.permissions)`, so it reads the LEGACY arm and skips the structured one entirely; no other reader exists in objectstack or objectui. PluginPermissionEnforcer (packages/core/src/security/plugin-permission-enforcer.ts:94-104) is not the missing consumer — it registers the set the install-time consent flow persisted to `sys_package_installation.granted_permissions`, explicitly \"independent of whatever the manifest *requested*\", and nothing in this repo feeds the manifest declaration into it. The verdict is `live` because the key is read; the structured arm's zero is filed as #11333 rather than being flattened into this one-level row. Cloud unmeasured (see `_note`) — the consent flow ADR-0025 §3.5 describes lives there." + "note": "LIVE ON ONE ARM ONLY, and the split matters. `ManifestPermissionsSchema` (manifest.zod.ts:54) is a union of the legacy flat `string[]` and the structured `PluginPermissionsSchema` (services / hooks / network / fs, ADR-0025 §3.2). The single reader is guarded by `Array.isArray(manifest?.permissions)`, so it reads the LEGACY arm and skips the structured one entirely; no other reader exists in objectstack or objectui. PluginPermissionEnforcer (packages/core/src/security/plugin-permission-enforcer.ts:94-104) is not the missing consumer — it registers the set the install-time consent flow persisted to `sys_package_installation.granted_permissions`, explicitly \"independent of whatever the manifest *requested*\", and nothing in this repo feeds the manifest declaration into it. The verdict is `live` because the key is read; the structured arm's zero is filed as #11333 rather than being flattened into this one-level row. Cloud unmeasured (see `_note`) — the consent flow ADR-0025 §3.5 describes lives there. 2026-08-28: RE-ANCHORED (#13003) — the citation was still ACCURATE (`suggested-audience-bindings.ts:252` names the read), so this is a grammar migration. What the anchor buys here is specific to this row's shape: the whole `live` verdict rests on ONE reader, so if `collectDeclaredSuggestions` is deleted or renamed the entry now goes red instead of pointing at whatever line 252 has become. Re-closed by hand against c459da6bc." }, "objects": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:4405-4420 (both the array and the map spelling are registered)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/engine.ts#registerApp (`if (manifest.objects)` — both the array spelling and the `Object.entries` map spelling are walked and registered)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:4405-4420` had rotted in range onto the `createWithAutonumberResync` docblock (the unique-violation retry contract and its storage-dependence warning), ~320 lines short of the read. Re-closed by hand against c459da6bc." }, "datasources": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:4391-4394", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/engine.ts#registerApp (`if (manifest.datasources)` — array or map, normalized to a list and registered)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:4391-4394` had rotted in range into the autonumber-collision docblock (a comment about counters below the store's real max), ~320 lines short of the read. Re-closed by hand against c459da6bc." }, "dependencies": { "status": "live", - "evidence": "packages/metadata-protocol/src/protocol.ts:4290-4300 (the declared map drives the transitive package-closure walk)", - "verifiedAt": "2026-08-23", + "evidence": "packages/metadata-protocol/src/protocol.ts#resolveWritePackageScope (`const declared = manifest?.dependencies` inside the frontier loop — the declared map drives the transitive package-closure walk that narrows a write's reference scope)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Read as a declaration rather than as an install instruction: an unresolvable dependency still counts toward the closure (the code says so at :4293-4296), the transitive walk just stops there." + "note": "Read as a declaration rather than as an install instruction: an unresolvable dependency still counts toward the closure (the loop's own comment says so — the package declared it, so an object stamped with it is reachable by declaration), the transitive walk just stops there. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `protocol.ts:4290-4300` had rotted in range of this 20255-line file onto a docblock about `evaluateRuntimeAuthoringGate` staying pure and about advisories riding the 2xx, ~210 lines short of the read. The note's own inner citation (`the code says so at :4293-4296`) had rotted with it and is now stated symbol-relative instead of by line — a line inside a note is exactly as unfalsifiable as one the scanner cannot parse, since no check reads `note` at all. Re-closed by hand against c459da6bc." }, "configuration": { "children": { @@ -99,10 +106,10 @@ "children": { "kinds": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:4504-4518 → packages/objectql/src/registry.ts:3748 (`registerKind`)", - "verifiedAt": "2026-08-25", + "evidence": "packages/objectql/src/engine.ts#registerApp (`if (manifest.contributes?.kinds)` then `for (const kind of manifest.contributes.kinds)` — the block's one and only reader monorepo-wide) → packages/objectql/src/registry.ts#registerKind (which keys the entry on `id` via `registerItem`)", + "verifiedAt": "2026-08-28", "evidenceScope": "cross-repo", - "note": "THE ONLY LIVE MEMBER OF THIS BLOCK, and the entire reason the block is not uniformly dead: #10627 measured that the whole monorepo contains exactly one non-test read of `manifest.contributes` and it reads `kinds`. Line drift re-verified for this seeding — #10627 cited engine.ts:4499-4501 on 299b85e9d; on b9e9227e3 the same three lines are :4504-4506. BELOW THE WALK: `globs` was RETIRED (#11169, maintainer ruling 2026-08-24, ADR-0087 D3 `plugin-manifest-kind-globs-retired`) — zero value reads anywhere (the only non-test occurrences were the schema declaration and the two type positions on `registerKind`/`getAllKinds`, which now drop it); the promised file-type discovery runs off the metadata type registry's `filePatterns`, which `contributes.kinds` does not extend. A kind entry is `{ id, description? }`; `registerKind` keys the item on `id`, and `description` is not read. CAVEAT ON THE LIVE VERDICT, filed as #10729: the typed accessor `getAllKinds()` (registry.ts:3752) has zero callers, so what the registered kind is consumed FOR downstream is unclear — the registration itself is real, the consumption of the bucket is not established." + "note": "THE ONLY LIVE MEMBER OF THIS BLOCK, and the entire reason the block is not uniformly dead: #10627 measured that the whole monorepo contains exactly one non-test read of `manifest.contributes` and it reads `kinds`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — and this row is the strongest argument in the batch for the anchor grammar, because its line has now been chased THREE times and rotted every time. #10627 cited engine.ts:4499-4501 on 299b85e9d; the 2026-08-23 seeding re-measured it to :4504-4506 on b9e9227e3; on c459da6bc `:4504-4518` lands in the autonumber-collision retry (`code: 'ERR_AUTONUMBER_COLLISION'`) and the read is at ~:4826, ~320 lines away. The registry half rotted too: `:3748` now lands in `getAllApps`, while `registerKind` sits at ~:3860. Chasing the line was the whole maintenance cost this migration removes. BELOW THE WALK: `globs` was RETIRED (#11169, maintainer ruling 2026-08-24, ADR-0087 D3 `plugin-manifest-kind-globs-retired`) — zero value reads anywhere (the only non-test occurrences were the schema declaration and the two type positions on `registerKind`/`getAllKinds`, which now drop it); the promised file-type discovery runs off the metadata type registry's `filePatterns`, which `contributes.kinds` does not extend. A kind entry is `{ id, description? }`; `registerKind` keys the item on `id`, and `description` is not read. CAVEAT ON THE LIVE VERDICT, filed as #10729: the typed accessor `getAllKinds()` (registry.ts#getAllKinds, re-read here — the `:3752` this note gave has drifted to ~:3864) has zero callers, so what the registered kind is consumed FOR downstream is unclear — the registration itself is real, the consumption of the bucket is not established." }, "events": { "status": "dead", @@ -168,10 +175,10 @@ }, "data": { "status": "live", - "evidence": "packages/runtime/src/app-plugin.ts:946-947 (manifest datasets are pushed onto the seed set applied at install)", - "verifiedAt": "2026-08-23", + "evidence": "packages/runtime/src/app-plugin.ts#seedDatasets (`if (manifest && Array.isArray(manifest.data)) seedDatasets.push(...manifest.data)` — leg 2 of the seed collection, after the preferred top-level `bundle.data`)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Deprecated in favour of `defineStack({ data })` but still read, so `live` rather than `dead`. Its subtree is SeedSchema, classified in full by the governed `seed` type — recorded as a RESOLVED deferral (`manifest/data` → `seed`) in scripts/liveness/undrilled-containers.baseline.json rather than duplicated here, so a key added to SeedSchema cannot get a verdict on one side and not the other." + "note": "Deprecated in favour of `defineStack({ data })` but still read, so `live` rather than `dead`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `app-plugin.ts:946-947` had rotted in range onto the JOB scheduling block (`ok++` after a `svc.schedule(...)` with its retry policy), ~55 lines short of the read. That is the SAME rot batch 1 recorded one file over for `object.actions`, which cited `app-plugin.ts:929` and landed in the same job block: two ledger entries, two different keys, one drifted region. ANCHOR CHOICE, stated because it is weaker than the batch's others: the read is inline in `AppPlugin.start`, which has no enclosing named helper (contrast `collectBundleActions`, which batch 1 could anchor for `object.actions`), so the anchor names `seedDatasets` — the local the spread lands in, unique in the file and specific to this collection — rather than the 1840-line class. Re-closed by hand against c459da6bc. Its subtree is SeedSchema, classified in full by the governed `seed` type — recorded as a RESOLVED deferral (`manifest/data` → `seed`) in scripts/liveness/undrilled-containers.baseline.json rather than duplicated here, so a key added to SeedSchema cannot get a verdict on one side and not the other." }, "capabilities": { "children": { @@ -217,29 +224,31 @@ "children": { "app": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:4477-4480 → packages/objectql/src/registry.ts:3659-3667 (`registerAppNavContribution` keys the contribution list on it and returns early without it)", - "verifiedAt": "2026-08-23", + "evidence": "packages/objectql/src/engine.ts#registerApp (walks `manifest.navigationContributions` and hands each entry to the registry) → packages/objectql/src/registry.ts#registerAppNavContribution (keys the contribution list on it and returns early without it) → packages/objectql/src/registry.ts#applyNavContributions (the merge reads `app?.name` against that same key)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "The target app id — required in practice, not just in the schema: `if (!contribution || !contribution.app) return;` drops a contribution that omits it." + "note": "The target app id — required in practice, not just in the schema: `if (!contribution || !contribution.app) return;` drops a contribution that omits it. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — both citations had rotted in range. `engine.ts:4477-4480` landed on the `createWithAutonumberResync` parameter list (~350 lines short of the walk at ~:4799) and `registry.ts:3659-3667` landed in `uninstallPackage`'s package-record removal, ~100 lines short of `registerAppNavContribution`. Re-closed by hand against c459da6bc." }, "group": { "status": "live", - "evidence": "packages/objectql/src/registry.ts:3663 (stored on the contribution), :3690-3691 (`applyNavContributions` merges by group id)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/registry.ts#registerAppNavContribution (`group: contribution.group` — stored on the contribution and echoed in the registration log); packages/objectql/src/registry.ts#applyNavContributions (`if (c.group)` ⇒ `findNavGroup(nav, c.group)`, and an unmatched group id appends at top level with a warning rather than dropping the items)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `registry.ts:3663` had rotted onto the `// Remove package record` comment inside `uninstallPackage`, and `:3690-3691` onto the `return pkg;` of `enablePackage`. Both real consumers are ~100-140 lines further down. Re-closed by hand against c459da6bc." }, "priority": { "status": "live", - "evidence": "packages/objectql/src/registry.ts:3664 (`contribution.priority ?? 200` — the merge order, defaulted rather than ignored)", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/objectql/src/registry.ts#registerAppNavContribution (`priority: contribution.priority ?? 200` — defaulted rather than ignored); packages/objectql/src/registry.ts#applyNavContributions (`[...contributions].sort((a, b) => a.priority - b.priority)` — lower priority applied first)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `registry.ts:3664` had rotted onto `const collection = this.metadata.get('package')` in `uninstallPackage`. The entry also gains its second consumer: the value is not merely stored with a default, it is the sort key `applyNavContributions` orders the merge by, which the single stored-at line never said. Re-closed by hand against c459da6bc." }, "items": { "status": "live", - "evidence": "packages/objectql/src/registry.ts:3665 (stored), :3690 onward (merged into the target app's navigation tree, read through `getApp` / `getAllApps` and the protocol serving path)", - "verifiedAt": "2026-08-23", + "evidence": "packages/objectql/src/registry.ts#registerAppNavContribution (`items: Array.isArray(contribution.items) ? contribution.items : []` — stored, non-array coerced to empty); packages/objectql/src/registry.ts#applyNavContributions (pushed into the target group's `children` or onto the nav root, on a structuredClone so the stored app is never mutated); packages/objectql/src/registry.ts#getApp (the read path that applies the merge, `getAllApps` the same)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "BELOW THE WALK: the per-item nav shape is NavigationContributionSchema's item type (packages/spec/src/ui/app.zod.ts), merged into the app's own navigation tree — the same surface the `app` ledger governs on the owning side. The merge is lazy on read and never mutates the stored app, so registration order does not matter (ADR-0029 D7)." + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `registry.ts:3665` had rotted onto `if (collection) {` in `uninstallPackage`, and the `:3690 onward` half onto `enablePackage`'s return. BELOW THE WALK: the per-item nav shape is NavigationContributionSchema's item type (packages/spec/src/ui/app.zod.ts), merged into the app's own navigation tree — the same surface the `app` ledger governs on the owning side. The merge is lazy on read and never mutates the stored app, so registration order does not matter (ADR-0029 D7). Re-closed by hand against c459da6bc." } } }, @@ -253,10 +262,10 @@ "children": { "objectstack": { "status": "live", - "evidence": "packages/metadata-core/src/protocol-handshake.ts:87 (the legacy range, read when `engines` is absent); packages/cli/src/commands/lint.ts:388", - "verifiedAt": "2026-08-23", + "evidence": "packages/metadata-core/src/protocol-handshake.ts#resolveDeclaredRange (`const legacy = manifest.engine?.objectstack?.trim()` — limb 3, reached only when neither `engines` field is declared, and tagged `source: 'engine.objectstack'`); packages/cli/src/commands/lint.ts#lintConfig (the `hasRange` disjunction — a package declaring none of the three ranges is flagged)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "The legacy single-field compatibility range. Read only as the fallback leg of the handshake — `engines.protocol` then `engines.platform` are consulted first (protocol-handshake.ts:83-87), which is exactly the precedence manifest.zod.ts documents." + "note": "The legacy single-field compatibility range. Read only as the fallback leg of the handshake — `engines.protocol` then `engines.platform` are consulted first, in `resolveDeclaredRange`, which is exactly the precedence manifest.zod.ts documents. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the lint citation was accurate; the handshake one had rotted, and rotted in the way this grammar is built for: `protocol-handshake.ts:87` now lands on the `export function resolveDeclaredRange(` SIGNATURE line, three lines above the read. A citation that drifts onto its own consumer's declaration line still passes existence, still passes the line bound, and still passes the key-mention check — and reads, to a human opening the file, almost right. Re-closed by hand against c459da6bc." } } }, @@ -264,16 +273,17 @@ "children": { "platform": { "status": "live", - "evidence": "packages/metadata-core/src/protocol-handshake.ts:85; packages/cli/src/commands/lint.ts:387", - "verifiedAt": "2026-08-23", - "evidenceScope": "in-repo" + "evidence": "packages/metadata-core/src/protocol-handshake.ts#resolveDeclaredRange (`const platform = manifest.engines?.platform?.trim()` — limb 2, tagged `source: 'engines.platform'`); packages/cli/src/commands/lint.ts#lintConfig (second term of the `hasRange` disjunction)", + "verifiedAt": "2026-08-28", + "evidenceScope": "in-repo", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the lint citation was accurate; `protocol-handshake.ts:85` had rotted into the docblock above the consumer (the #12772 sentence about not having two readers with two priority orders), seven lines short of the read. Re-closed by hand against c459da6bc." }, "protocol": { "status": "live", - "evidence": "packages/metadata-core/src/protocol-handshake.ts:83; packages/cli/src/commands/lint.ts:386,396", - "verifiedAt": "2026-08-23", + "evidence": "packages/metadata-core/src/protocol-handshake.ts#resolveDeclaredRange (`const protocol = manifest.engines?.protocol?.trim()` — limb 1, consulted first and tagged `source: 'engines.protocol'`); packages/cli/src/commands/lint.ts#lintConfig (first term of the `hasRange` disjunction, and the `path: 'manifest.engines.protocol'` the advisory points authors at)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Checked BEFORE `platform` (ADR-0025 §3.10 #3), so a plugin keeps working across platform releases that preserve the protocol — the precedence is real in the code, not just in the describe()." + "note": "Checked BEFORE `platform` (ADR-0025 §3.10 #3), so a plugin keeps working across platform releases that preserve the protocol — the precedence is real in the code, not just in the describe(). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the lint citation was accurate but written `lint.ts:386,396`, the comma-joined form the evidence scanner cannot parse, so that consumer had never resolved against any check; `protocol-handshake.ts:83` had rotted into the docblock, seven lines short of the read. All three legs of this handshake are read in ONE function, so the three sibling entries now share one anchor apiece rather than three lines that drift together. Re-closed by hand against c459da6bc." } } }, @@ -285,10 +295,10 @@ }, "packaging": { "status": "live", - "evidence": "packages/cli/src/commands/plugin/build.ts:126 (read), :159-161 (`manifest-deps` keeps the declared dependencies external instead of bundling them)", - "verifiedAt": "2026-08-23", + "evidence": "packages/cli/src/commands/plugin/build.ts#PluginBuild (`const packaging = manifest.packaging ?? 'bundled'`, then two `if (packaging === 'manifest-deps')` forks — one that pushes the package.json dependencies onto esbuild's `external` list instead of bundling them, one further down)", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Live on a real behavioural fork, not on a read: the value selects whether esbuild bundles dependencies into the artifact or externalizes them for install-time resolution, so the two tiers produce different artifacts. Contrast its neighbour `runtime`, which is read only to be printed." + "note": "Live on a real behavioural fork, not on a read: the value selects whether esbuild bundles dependencies into the artifact or externalizes them for install-time resolution, so the two tiers produce different artifacts. Contrast its neighbour `runtime`, which is read only to be printed. 2026-08-28: RE-ANCHORED (#13003) — both citations were still ACCURATE (`:126` names the read, `:159-161` the externalization), so this is a grammar migration with no repair. The file is 233 lines end to end, which is the batch's clearest correlation: every accurate citation in it sits in a small or structurally quiet file, and every rotted one in a file of 1800 to 20255 lines. Re-closed by hand against c459da6bc." }, "integrity": { "status": "dead", diff --git a/packages/spec/liveness/permission.json b/packages/spec/liveness/permission.json index 212ea7d419..22ac24a2b8 100644 --- a/packages/spec/liveness/permission.json +++ b/packages/spec/liveness/permission.json @@ -48,43 +48,43 @@ "children": { "allowCreate": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION insert→allowCreate)", - "note": "Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the persona × CRUD matrix runs the create verb over real HTTP for every showcase permission set, in BOTH directions per cell — an allowed create returns an id and the row is really there, a denied one is 403 PERMISSION_DENIED and persisted nothing. Cells are judged as a UNION with the everyone-baseline set (ADR-0090 D5). One proof binds all four allow* verbs; multi-entry binding has precedent in `semantic-roles`." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#OPERATION_TO_PERMISSION (the map row: insert→allowCreate); packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`const permKey = OPERATION_TO_PERMISSION[operation]` in `checkObjectPermission` — the gate that reads the bit); packages/plugins/plugin-security/src/permission-evaluator.ts#crudBucketForOperation (the same map switched into the object-capability buckets)", + "note": "Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the persona × CRUD matrix runs the create verb over real HTTP for every showcase permission set, in BOTH directions per cell — an allowed create returns an id and the row is really there, a denied one is 403 PERMISSION_DENIED and persisted nothing. Cells are judged as a UNION with the everyone-baseline set (ADR-0090 D5). One proof binds all four allow* verbs; multi-entry binding has precedent in `semantic-roles`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:15` had rotted onto a docblock line about the 2026-08-26 ruling that retired `allowRestore`/`allowPurge`; the map itself begins at ~:23. All five `:15` citations in this file rotted together, which is what a shared constant does when its file gets a new header: one edit above it invalidates every entry that cites it, and none of them can notice. Anchored to the constant instead, plus the two verbs that consume it. Re-closed by hand against c459da6bc." }, "allowRead": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION find/findOne/count/aggregate→allowRead)", - "note": "Proof-bound 2026-08-23 (#10959) — the read verb of the same persona × CRUD matrix, both directions per cell (a readable list answers 200 with rows; a denied one is 403 PERMISSION_DENIED). See `allowCreate` for the binding rationale." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#OPERATION_TO_PERMISSION (the map row: find/findOne/count/aggregate→allowRead); packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`const permKey = OPERATION_TO_PERMISSION[operation]` in `checkObjectPermission` — the gate that reads the bit); packages/plugins/plugin-security/src/permission-evaluator.ts#crudBucketForOperation (the same map switched into the object-capability buckets)", + "note": "Proof-bound 2026-08-23 (#10959) — the read verb of the same persona × CRUD matrix, both directions per cell (a readable list answers 200 with rows; a denied one is 403 PERMISSION_DENIED). See `allowCreate` for the binding rationale. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:15` had rotted onto a docblock line about the 2026-08-26 ruling that retired `allowRestore`/`allowPurge`; the map itself begins at ~:23. All five `:15` citations in this file rotted together, which is what a shared constant does when its file gets a new header: one edit above it invalidates every entry that cites it, and none of them can notice. Anchored to the constant instead, plus the two verbs that consume it. Re-closed by hand against c459da6bc." }, "allowEdit": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION update→allowEdit)", - "note": "Proof-bound 2026-08-23 (#10959) — the edit verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the allowed edit persisted; the denied edit changed nothing). See `allowCreate` for the binding rationale." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#OPERATION_TO_PERMISSION (the map row: update→allowEdit); packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`const permKey = OPERATION_TO_PERMISSION[operation]` in `checkObjectPermission` — the gate that reads the bit); packages/plugins/plugin-security/src/permission-evaluator.ts#crudBucketForOperation (the same map switched into the object-capability buckets)", + "note": "Proof-bound 2026-08-23 (#10959) — the edit verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the allowed edit persisted; the denied edit changed nothing). See `allowCreate` for the binding rationale. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:15` had rotted onto a docblock line about the 2026-08-26 ruling that retired `allowRestore`/`allowPurge`; the map itself begins at ~:23. All five `:15` citations in this file rotted together, which is what a shared constant does when its file gets a new header: one edit above it invalidates every entry that cites it, and none of them can notice. Anchored to the constant instead, plus the two verbs that consume it. Re-closed by hand against c459da6bc." }, "allowDelete": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION delete→allowDelete)", - "note": "Proof-bound 2026-08-23 (#10959) — the delete verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the deleted row is gone; the denied delete left the row standing). See `allowCreate` for the binding rationale." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#OPERATION_TO_PERMISSION (the map row: delete→allowDelete); packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`const permKey = OPERATION_TO_PERMISSION[operation]` in `checkObjectPermission` — the gate that reads the bit); packages/plugins/plugin-security/src/permission-evaluator.ts#crudBucketForOperation (the same map switched into the object-capability buckets)", + "note": "Proof-bound 2026-08-23 (#10959) — the delete verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the deleted row is gone; the denied delete left the row standing). See `allowCreate` for the binding rationale. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:15` had rotted onto a docblock line about the 2026-08-26 ruling that retired `allowRestore`/`allowPurge`; the map itself begins at ~:23. All five `:15` citations in this file rotted together, which is what a shared constant does when its file gets a new header: one edit above it invalidates every entry that cites it, and none of them can notice. Anchored to the constant instead, plus the two verbs that consume it. Re-closed by hand against c459da6bc." }, "allowExport": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/rest/src/rest-server.ts (enforceExportPermission — caller-level 403 gate on the bulk-egress route, fail-closed when the security service cannot answer) + packages/plugins/plugin-security/src/security-plugin.ts (canExport → checkObjectPermission('export'), posture-unresolvable → deny) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:493-502 (annotateEffectiveApiOperations reads the per-object allowExport bit and the `'*'` wildcard for the /me/permissions projection the frontend renders)", - "note": "#3544 — user-level export axis over read. Re-verified 2026-07-30: enforcement is SERVER-side, not only the projection — the export route calls enforceExportPermission (403), separate from the object-level 405; the annotate path is the display half. Optional/no-default = backward-compatible opt-out (unset inherits read); `false` denies export while keeping read. 2026-08-25: the annotate pointer was REPOINTED — `annotateEffectiveApiOperations` moved out of hono-plugin.ts into current-user-endpoints.ts, the same repos-internal code movement that rotted systemPermissions and tabPermissions. The old citation carried no line, so the #11210 line bound could not see it; the key-mention signal is what found it." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#enforceExportPermission (caller-level 403 gate on the bulk-egress route, fail-closed when the security service cannot answer); packages/plugins/plugin-security/src/security-plugin.ts#canExport (→ checkObjectPermission('export'), posture-unresolvable → deny); packages/plugins/plugin-hono-server/src/current-user-endpoints.ts#annotateEffectiveApiOperations (`const exportBit = acc.allowExport ?? wildExport` — the per-object bit and the `'*'` wildcard, for the /me/permissions projection the frontend renders)", + "note": "#3544 — user-level export axis over read. Re-verified 2026-07-30: enforcement is SERVER-side, not only the projection — the export route calls enforceExportPermission (403), separate from the object-level 405; the annotate path is the display half. Optional/no-default = backward-compatible opt-out (unset inherits read); `false` denies export while keeping read. 2026-08-25: the annotate pointer was REPOINTED — `annotateEffectiveApiOperations` moved out of hono-plugin.ts into current-user-endpoints.ts, the same repos-internal code movement that rotted systemPermissions and tabPermissions. The old citation carried no line, so the #11210 line bound could not see it; the key-mention signal is what found it. 2026-08-28: RE-ANCHORED (#13003) — the `:493-502` half was still ACCURATE (`:502` names the read), so this leg is a grammar migration; the two path-only legs are upgraded to anchors in the same pass, which is what the 2026-08-25 repoint recorded as impossible to falsify (\"the old citation carried no line, so the #11210 line bound could not see it\"). Re-closed by hand against c459da6bc." }, "allowTransfer": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION transfer→allowTransfer; DESTRUCTIVE_OPERATIONS fail-closed backstop) + modifyAllRecords bypass via MODIFY_ALL_WRITE_KEYS", - "note": "#1883 — RBAC gate pre-mapped, deny unless granted; the `transfer` ObjectQL operation is pending M2, so granting delivers nothing until it ships. Re-verified 2026-07-30: M2 still unshipped (no transfer/restore/purge operations in packages/objectql), the gate mapping stands." + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#OPERATION_TO_PERMISSION (the map row transfer→allowTransfer); packages/plugins/plugin-security/src/permission-evaluator.ts#DESTRUCTIVE_OPERATIONS (the fail-closed backstop — an operation in this set is denied when the map yields no bit); packages/plugins/plugin-security/src/permission-evaluator.ts#MODIFY_ALL_WRITE_KEYS (the modifyAllRecords bypass, which folds the destructive class in)", + "note": "#1883 — RBAC gate pre-mapped, deny unless granted; the `transfer` ObjectQL operation is pending M2, so granting delivers nothing until it ships. Re-verified 2026-07-30: M2 still unshipped (no transfer/restore/purge operations in packages/objectql), the gate mapping stands. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:15` had rotted onto a docblock line about the 2026-08-26 ruling that retired `allowRestore`/`allowPurge`; the map itself begins at ~:23. All five `:15` citations in this file rotted together, which is what a shared constant does when its file gets a new header: one edit above it invalidates every entry that cites it, and none of them can notice. Anchored to the constant instead, plus the two verbs that consume it. Re-closed by hand against c459da6bc." }, "allowRestore": { "status": "dead", @@ -100,28 +100,30 @@ }, "viewAllRecords": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts (read bypass at the allowRead check; getEffectiveScope read→'org'; hasViewAllData)" + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`checkObjectPermission`: `permKey === 'allowRead' && (objPerm.viewAllRecords || objPerm.modifyAllRecords)` — the read bypass); packages/plugins/plugin-security/src/permission-evaluator.ts#getEffectiveScope (read → 'org' when either super-user bit is held); packages/plugins/plugin-security/src/permission-evaluator.ts#superuserBypassSets (the sets whose bypass a caller actually holds)", + "note": "2026-08-28: RE-ANCHORED (#13003) and PROSE CORRECTED — this citation carried no line at all, so nothing could ever falsify it; re-closing it by hand found the evidence naming `hasViewAllData`, a symbol that no longer exists anywhere in `packages/**` (the reader is `superuserBypassSets`, with `hasSuperuserReadBypass` / `hasSuperuserWriteBypass` beside it). A path-only citation is the weakest form in this ledger: it passes the existence check by naming a file, and the line bound has nothing to bound. Re-closed by hand against c459da6bc." }, "modifyAllRecords": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts#owner-anchor-and-bulk-writes", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts (MODIFY_ALL_WRITE_KEYS bypass over edit/delete + destructive class; getEffectiveScope write→'org')" + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#MODIFY_ALL_WRITE_KEYS (the bypass key set over edit/delete plus the destructive class); packages/plugins/plugin-security/src/permission-evaluator.ts#PermissionEvaluator (`MODIFY_ALL_WRITE_KEYS.has(permKey) && objPerm.modifyAllRecords` in `checkObjectPermission`); packages/plugins/plugin-security/src/permission-evaluator.ts#getEffectiveScope (write → 'org')", + "note": "2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to anchors; the two consumers its prose already named are now separately falsifiable. Re-closed by hand against c459da6bc." }, "readScope": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts#showcase-scope-depth", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts (getEffectiveScope) + packages/plugins/plugin-sharing/src/sharing-service.ts (owner-match widened by readScope; hierarchy values delegated to IHierarchyScopeResolver)", - "note": "ADR-0057 D1 — read access DEPTH (own/own_and_reports/unit/unit_and_below/org). own/org enforced in open edition; hierarchy values via the enterprise hierarchy-scope-resolver (fail-closed to owner-only when absent; defineStack requires 'hierarchy-security'). Proven: packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#getEffectiveScope (the read branch — ranks the caller's sets and takes the widest); packages/plugins/plugin-sharing/src/sharing-service.ts#buildReadFilter (owner-match widened by the stamped `__readScope`; hierarchy values delegated to IHierarchyScopeResolver)", + "note": "ADR-0057 D1 — read access DEPTH (own/own_and_reports/unit/unit_and_below/org). own/org enforced in open edition; hierarchy values via the enterprise hierarchy-scope-resolver (fail-closed to owner-only when absent; defineStack requires 'hierarchy-security'). Proven: packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts. 2026-08-28: RE-ANCHORED (#13003) — path-only citations upgraded to anchors. Re-closed by hand against c459da6bc." }, "writeScope": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-scope-depth-write.dogfood.test.ts#showcase-scope-depth-write", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:224 (getEffectiveScope write branch; absent → 'own') + packages/plugins/plugin-sharing/src/sharing-service.ts:230 (canEdit owner-set widened by __writeScope; 'org' short-circuits to unrestricted)", - "note": "ADR-0057 D1 — write access DEPTH (same enum as readScope). Re-verified 2026-07-30 and proof-bound the same day: the proof asserts POST-STATE in three postures — 'unit' edits a BU co-member's row and does NOT descend into a child BU; an ABSENT writeScope stays owner-only even under a 'unit' readScope (the axes gate independently); and the hierarchy seam fails CLOSED without the enterprise resolver." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#getEffectiveScope (the write branch; absent → 'own'); packages/plugins/plugin-sharing/src/sharing-service.ts#buildWriteFilter (`const writeScope = (context as any).__writeScope`; 'org' short-circuits to unrestricted); packages/plugins/plugin-sharing/src/sharing-service.ts#matchesOwnerScope (the by-id write leg behind `canEdit` / `canDelete`, same short-circuit, otherwise the owner-id set is resolved at that depth)", + "note": "ADR-0057 D1 — write access DEPTH (same enum as readScope). Re-verified 2026-07-30 and proof-bound the same day: the proof asserts POST-STATE in three postures — 'unit' edits a BU co-member's row and does NOT descend into a child BU; an ABSENT writeScope stays owner-only even under a 'unit' readScope (the axes gate independently); and the hierarchy seam fails CLOSED without the enterprise resolver. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — both citations had rotted in range. `permission-evaluator.ts:224` landed on the `MODIFY_ALL_WRITE_KEYS` bypass inside `checkObjectPermission` — a DIFFERENT key's enforcement, which is the most misleading rot in this file because it still reads as permission-evaluation code; `sharing-service.ts:230` landed on `noun: 'share'`, ~240 lines above the read. The entry also gains the second sharing consumer its prose named as `canEdit`: the read is in `matchesOwnerScope`, the fast path `canEdit` and `canDelete` share. Re-closed by hand against c459da6bc." } } }, @@ -129,39 +131,40 @@ "children": { "readable": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-fls-read-mask-strip.dogfood.test.ts#showcase-fls-read-mask-strip", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:301", - "note": "FLS read-mask. Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the proof AUTHORS a scratch permission set carrying `readable: false` and asserts the runtime outcome both ways on the same field, row and request — the key is ABSENT from the wire (`'budget' in record` is false, which `toBeUndefined()` cannot distinguish from a mask), while the entitled caller gets the real value. The sibling `editable` is deliberately NOT bound to this proof: that file authors the key but asserts its refusal as a consequence of unreadability, not as the write-deny axis (`showcase-permission-zoo` pins that half)." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#getFieldPermissions (`if (perm.readable) result[fieldName].readable = true` — the most-permissive merge across the caller's sets, keyed `object.field`)", + "note": "FLS read-mask. Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the proof AUTHORS a scratch permission set carrying `readable: false` and asserts the runtime outcome both ways on the same field, row and request — the key is ABSENT from the wire (`'budget' in record` is false, which `toBeUndefined()` cannot distinguish from a mask), while the entitled caller gets the real value. The sibling `editable` is deliberately NOT bound to this proof: that file authors the key but asserts its refusal as a consequence of unreadability, not as the write-deny axis (`showcase-permission-zoo` pins that half). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:301` had rotted onto a blank docblock line (the `explain-engine.ts` bypass table), ~78 lines above the merge. Re-closed by hand against c459da6bc." }, "editable": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:302", - "note": "FLS write-deny." + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#getFieldPermissions (`if (perm.editable) result[fieldName].editable = true` — the same merge, write half)", + "note": "FLS write-deny. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `permission-evaluator.ts:302` had rotted onto a docblock line naming the `vama_bypass` explain section. The two FLS keys are merged one line apart inside one function, so both entries now share one anchor and the pair can no longer drift independently. Re-closed by hand against c459da6bc." } } }, "systemPermissions": { "status": "live", - "verifiedAt": "2026-08-23", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/sharing-rule-org-less-caller.dogfood.test.ts#sharing-rule-org-less-caller", - "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:267 (getSystemPermissions — ADR-0066 D3 union of the capabilities a caller's permission sets grant, into ExecutionContext.systemPermissions) + packages/plugins/plugin-sharing/src/sharing-rule-service.ts:136 (assertCanManageRules — ADR-0111 D6: every sharing-rule verb, list and get included, refuses PERMISSION_DENIED unless the caller holds `manage_sharing` OR the legacy `manage_platform_settings` admin override, with system contexts bypassing; enforced in the SERVICE so non-REST callers are covered) + packages/plugins/plugin-sharing/src/sharing-plugin.ts:993 + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:897 (/auth/me/apps — `AppSchema.requiredPermissions ⊆ ctx.systemPermissions`, the app-entry/nav half)", - "note": "Re-verified 2026-08-23 (#10959) and the note CORRECTED — the previous text (\"PARTIAL — app-entry/nav visibility only, not a general capability gate\") was stale in both halves. (a) Its evidence pointer `plugin-hono-server/src/hono-plugin.ts:1222` no longer exists: that file is 717 lines and contains no `systemPermissions` reference at all — the app-entry consumer moved to `current-user-endpoints.ts`. (b) The scoping claim is falsified by ADR-0111 D6 (Accepted 2026-07-30, P0 implemented): `SharingRuleService.assertCanManageRules` reads `context.systemPermissions` and refuses the whole sharing-rule surface unless the caller holds `manage_sharing` or the legacy `manage_platform_settings` admin override (system contexts bypass), which is a DATA-LAYER authorization gate, not nav visibility — the admit set is wider than one capability, but every member of it is read from `systemPermissions`, which is what this entry classifies. The bound proof measures exactly that: it authors `system_permissions: ['manage_sharing']` on a permission set and asserts the refusal it gets back is the ORG-scope refusal and explicitly NOT `/requires the manage_sharing capability/` — i.e. the authored capability really did clear the service gate — with the org-bound holder of the same grant reading its own tenant (200) as the entitled contrast, over two organizations so a single-tenant fixture cannot pass on the broken build." + "evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts#getSystemPermissions (ADR-0066 D3 union of the capabilities a caller's permission sets grant, into ExecutionContext.systemPermissions); packages/plugins/plugin-sharing/src/sharing-rule-service.ts#assertCanManageRules (ADR-0111 D6: every sharing-rule verb, list and get included, refuses PERMISSION_DENIED unless the caller holds `manage_sharing` OR the legacy `manage_platform_settings` admin override, with system contexts bypassing; enforced in the SERVICE so non-REST callers are covered); packages/plugins/plugin-sharing/src/sharing-plugin.ts#buildSharingMiddleware (the same capability read on the middleware path); packages/plugins/plugin-hono-server/src/current-user-endpoints.ts#registerCurrentUserEndpoints (/auth/me/apps — `AppSchema.requiredPermissions ⊆ ctx.systemPermissions`, the app-entry/nav half)", + "note": "Re-verified 2026-08-23 (#10959) and the note CORRECTED — the previous text (\"PARTIAL — app-entry/nav visibility only, not a general capability gate\") was stale in both halves. (a) Its evidence pointer `plugin-hono-server/src/hono-plugin.ts:1222` no longer exists: that file is 717 lines and contains no `systemPermissions` reference at all — the app-entry consumer moved to `current-user-endpoints.ts`. (b) The scoping claim is falsified by ADR-0111 D6 (Accepted 2026-07-30, P0 implemented): `SharingRuleService.assertCanManageRules` reads `context.systemPermissions` and refuses the whole sharing-rule surface unless the caller holds `manage_sharing` or the legacy `manage_platform_settings` admin override (system contexts bypass), which is a DATA-LAYER authorization gate, not nav visibility — the admit set is wider than one capability, but every member of it is read from `systemPermissions`, which is what this entry classifies. The bound proof measures exactly that: it authors `system_permissions: ['manage_sharing']` on a permission set and asserts the refusal it gets back is the ORG-scope refusal and explicitly NOT `/requires the manage_sharing capability/` — i.e. the authored capability really did clear the service gate — with the org-bound holder of the same grant reading its own tenant (200) as the entitled contrast, over two organizations so a single-tenant fixture cannot pass on the broken build. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — ALL FOUR citations had rotted in range, five days after the 2026-08-23 by-hand re-verification that set them: `permission-evaluator.ts:267` landed inside `getEffectiveScope`'s rank lookup (the reader is `getSystemPermissions` at ~:279), `sharing-rule-service.ts:136` on `private readonly inertRuleSeen = new Set()` (~20 lines above the gate), and `sharing-plugin.ts:993` in a docblock about the i18n service being contributed by another plugin (~80 lines above the read). The fourth, `current-user-endpoints.ts:897`, had rotted too — onto the `/me/apps` handler's own header comment, five lines above the `1. AppSchema.requiredPermissions ⊆ ctx.systemPermissions` line it was clearly meant to name and ~31 above the `const sysPerms = new Set(execCtx.systemPermissions ?? [])` that does it; a near-miss onto prose that DESCRIBES the read is the single most convincing form of this rot, because anyone who opens the file to check finds the right words there. So all four were wrong, and this entry's own note already records the file-move that killed their predecessors. Five days from a by-hand re-verification to four dead pointers is the measurement this card was filed on. Re-closed by hand against c459da6bc." }, "tabPermissions": { "status": "live", - "verifiedAt": "2026-08-23", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/me-apps-and-everyone-baseline.dogfood.test.ts#me-apps-and-everyone-baseline", - "evidence": "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:947 (tabRank ranks all four values — hidden 0 < default_off 1 < default_on 2 < visible 3) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:952-958 (the merge across the caller's resolved permission sets, most-visible wins; an unrecognised value is skipped rather than defaulted) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:963 (the /me/apps drop — tabs[app.name] === 'hidden' ⇒ the app is not returned, and this filter runs BEFORE the fail-open branch, so it survives a missing SecurityPlugin) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:772 (the same rank table for the /auth/me/permissions half) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:862 (projected onto that response) + packages/plugins/plugin-security/src/permission-set-projection.ts:287 (the sys_permission_set.tab_permissions column parsed onto the resolved set) + packages/core/src/security/assemble-execution-context.ts:328 (onto ExecutionContext.tabPermissions)", - "note": "Re-verified 2026-08-23 (#11210) and every pointer REPLACED — all three of the previous ones were dead, in two different ways, and the gate could see neither. (a) `plugin-hono-server/src/hono-plugin.ts:1200` was past EOF: that file is 717 lines and contains no `tabPermissions` reference at all — the same code movement that killed this entry's sibling `systemPermissions` pointer (#10959/#11209) moved /me/apps and /auth/me/permissions into `current-user-endpoints.ts`. (b) `runtime/src/security/resolve-execution-context.ts:205` and `rest/src/rest-server.ts:1551` were dead WITHIN bounds — both files are long enough for the cited line, and neither mentions `tabPermissions` anywhere (0 occurrences, measured); the handler's own comment at current-user-endpoints.ts:922-926 records why, namely that resolveCtx() carries neither systemPermissions nor tabPermissions, so /me/apps re-resolves the sets itself. THE VERDICT IS UNCHANGED and was never in question — `live` with a bound dogfood proof, and the behaviour still ships; what was dead is the citation, which is exactly the rot #11210's line bound now makes impossible to leave behind silently. The 2026-07-30 reading survives the re-measurement verbatim: the rank merge reads all four values, not only 'hidden'. Two consumers, deliberately both cited: /me/apps ENFORCES (a hidden app is dropped), /auth/me/permissions PROJECTS the merged map for the renderer." + "evidence": "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts#registerCurrentUserEndpoints (both consumers live in this registrar's route closures: /auth/me/permissions builds the rank table and the most-visible merge across the caller's resolved sets and projects the map onto the response, and /me/apps applies the same merge and DROPS an app whose merged value is 'hidden' — a filter that runs before the fail-open branch, so it survives a missing SecurityPlugin); packages/plugins/plugin-hono-server/src/current-user-endpoints.ts#tabRank (the four-value rank table itself — hidden 0 < default_off 1 < default_on 2 < visible 3, built once per handler; an unrecognised value is skipped rather than defaulted); packages/plugins/plugin-security/src/permission-set-projection.ts#permissionSetBodyFromRow (the sys_permission_set.tab_permissions column parsed onto the resolved set, with `permissionSetRowFields` writing it back and `mergeRowPatchIntoBody` patching it); packages/core/src/security/assemble-execution-context.ts#entryFields (`tabPermissions: authz.tabPermissions` onto ExecutionContext)", + "note": "Re-verified 2026-08-23 (#11210) and every pointer REPLACED — all three of the previous ones were dead, in two different ways, and the gate could see neither. (a) `plugin-hono-server/src/hono-plugin.ts:1200` was past EOF: that file is 717 lines and contains no `tabPermissions` reference at all — the same code movement that killed this entry's sibling `systemPermissions` pointer (#10959/#11209) moved /me/apps and /auth/me/permissions into `current-user-endpoints.ts`. (b) `runtime/src/security/resolve-execution-context.ts:205` and `rest/src/rest-server.ts:1551` were dead WITHIN bounds — both files are long enough for the cited line, and neither mentions `tabPermissions` anywhere (0 occurrences, measured); the handler's own comment at current-user-endpoints.ts:922-926 records why, namely that resolveCtx() carries neither systemPermissions nor tabPermissions, so /me/apps re-resolves the sets itself. THE VERDICT IS UNCHANGED and was never in question — `live` with a bound dogfood proof, and the behaviour still ships; what was dead is the citation, which is exactly the rot #11210's line bound now makes impossible to leave behind silently. The 2026-07-30 reading survives the re-measurement verbatim: the rank merge reads all four values, not only 'hidden'. Two consumers, deliberately both cited: /me/apps ENFORCES (a hidden app is dropped), /auth/me/permissions PROJECTS the merged map for the renderer. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED. This entry had SEVEN line citations, more than any other in the batch, and the re-closure is a study in how that ages: `assemble-execution-context.ts:328` was still exact; the five hono lines had all slid a few lines each (`:947` onto `await resolvePermissionSets(...)`, `:963` onto the `const apps = [...]` filter head, `:772` onto the `systemPermissions` set one line above the rank table) — near-misses that read as right to anyone who opens the file; and `permission-set-projection.ts:287` had rotted 30 lines onto `cachedSpecBodyKeys`, a different mechanism entirely. ANCHOR RESIDUAL, stated because it is the batch's weakest: both hono consumers are anonymous route closures inside ONE exported registrar, so the distinction the seven lines carried survives only in this prose. `tabRank` is cited beside the registrar for that reason — it is specific to the tab machinery, so deleting the merge reds the anchor even though the registrar would survive. Re-closed by hand against c459da6bc." }, "rowLevelSecurity": { "children": { "name": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts:203 (skip-diagnostics name the operator sees)" + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#compileFilter (the skip diagnostic the operator sees — `policy '${name ?? '(unnamed)'}' on '${object}'` in the warn line for an uncompilable predicate)", + "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `rls-compiler.ts:203` had rotted onto a comment about failing closed when every applicable policy depended on an unpopulated `current_user.*` variable; the only read of this key is the warn line ~11 lines above it. Re-closed by hand against c459da6bc." }, "label": { "status": "dead", @@ -177,38 +180,41 @@ }, "object": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts:289 (applicability: exact object or '*')" + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#getApplicablePolicies (`policy.object !== objectName && policy.object !== '*'` — the applicability test)", + "note": "2026-08-28: RE-ANCHORED (#13003) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "operation": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts:304 (applicability: 'all' or the mapped RLS op)" + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#getApplicablePolicies (`policy.operation === 'all'`, else compared against the mapped RLS op from `mapOperationToRLS`)", + "note": "2026-08-28: RE-ANCHORED (#13003) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "using": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts", + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#compileFilter (`policy.using` is the predicate on the read pass, and the fallback when a `check` pass finds no `check`)", "proof": "packages/qa/dogfood/test/rls-fixture.dogfood.test.ts#rls-by-id-write", - "note": "compiled into find + analytics SQL. ADR-0054 high-risk class (RLS): the proof boots an owner-isolated fixture so a fresh member cannot read an admin-created row, then asserts the runner's verdict in both directions — `rls-consistent` when the owner predicate also gates the by-id write (#1994 pre-image check) and `rls-hole` when it doesn't. Guards read AND by-id-write enforcement, not just the read predicate." + "note": "compiled into find + analytics SQL. ADR-0054 high-risk class (RLS): the proof boots an owner-isolated fixture so a fresh member cannot read an admin-created row, then asserts the runner's verdict in both directions — `rls-consistent` when the owner predicate also gates the by-id write (#1994 pre-image check) and `rls-hole` when it doesn't. Guards read AND by-id-write enforcement, not just the read predicate. 2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc." }, "check": { "status": "live", - "verifiedAt": "2026-07-30", + "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/showcase-d3-d4-capabilities.dogfood.test.ts#showcase-d3-d4-capabilities", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts" + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#compileFilter (`(policy as { check?: string }).check ?? policy.using` — the post-image pass prefers `check` and falls back to `using`)", + "note": "2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc." }, "positions": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts:297 (applicability domain vs caller's held positions)", - "note": "flat match — no subordinate rollup (ADR-0090 D3 rename)." + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#getApplicablePolicies (`const domain = (policy as { positions?: string[] }).positions` — the applicability domain, matched flat against the caller's held positions)", + "note": "flat match — no subordinate rollup (ADR-0090 D3 rename). 2026-08-28: RE-ANCHORED (#13003) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "enabled": { "status": "live", - "verifiedAt": "2026-07-30", - "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts (getApplicablePolicies: `enabled === false` excluded before object/positions/operation matching; absent = active per the schema default) — pinned by the enabled-gate cases in packages/plugins/plugin-security/src/security-plugin.test.ts", - "note": "ENFORCED 2026-07-30, by the security-subset re-verification this stamp records. Until then the entry claimed live citing rls-compiler.ts, and NOTHING read the property: policies OR-combine (any match allows access), so a policy an admin disabled kept CONTRIBUTING ITS GRANT — the schema's own describe ('Disabled policies are not evaluated') was false in the over-share direction, the #3896 shape. Same enforce-or-remove resolution as `positions` (ADR-0049)." + "verifiedAt": "2026-08-28", + "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#getApplicablePolicies (`(policy as { enabled?: boolean }).enabled === false` excluded BEFORE object / positions / operation matching; absent = active per the schema default) — pinned by the enabled-gate cases in packages/plugins/plugin-security/src/security-plugin.test.ts", + "note": "ENFORCED 2026-07-30, by the security-subset re-verification this stamp records. Until then the entry claimed live citing rls-compiler.ts, and NOTHING read the property: policies OR-combine (any match allows access), so a policy an admin disabled kept CONTRIBUTING ITS GRANT — the schema's own describe ('Disabled policies are not evaluated') was false in the over-share direction, the #3896 shape. Same enforce-or-remove resolution as `positions` (ADR-0049). 2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to an anchor, on the entry whose whole history is that its citation once named a file nothing in it read. Re-closed by hand against c459da6bc." }, "priority": { "status": "dead", diff --git a/packages/spec/liveness/webhook.json b/packages/spec/liveness/webhook.json index a4039eabaf..974f4699c3 100644 --- a/packages/spec/liveness/webhook.json +++ b/packages/spec/liveness/webhook.json @@ -1,62 +1,73 @@ { "type": "webhook", - "_note": "WebhookSchema (outbound webhook — packages/spec/src/automation/webhook.zod.ts). Governed via a spec-only schema override in the gate (SPEC_ONLY_SCHEMAS): webhook is still NOT a registered metadata type (absent from kernel/metadata-type-schemas.ts) — registering it would turn on Studio webhook CRUD + saveMetaItem overlay + create-seeds; that reassessment is tracked in #3490 and deliberately deferred. TWO THINGS CLOSED THE OLD 'entire surface is dead' classification: (1) #3494 PRUNED the aspirational dead props outright — body / payloadFields / includeSession / retryPolicy / tags / authentication are gone from the schema; (2) the #3461 materializer bridge (PR #3489) makes every REMAINING prop live. `bootstrapDeclaredWebhooks` (plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts) materializes each stack/connector-authored webhook into a `sys_webhook` DATA row on boot — WebhookSchema.parse (:114) → mapWebhookToRow (:191): `object`→`object_name` (:195), `isActive`→`active` (:202), same-named `name`/`label`/`triggers`/`url`/`method`/`description`, `secret`→the encrypted `signing_secret` column (#7799), `headers`→the encrypted `headers_secret` column (#7986), and the REST of the envelope → `definition_json`. The dispatcher (AutoEnqueuer) reads those rows (auto-enqueuer.ts:175 `where:{active:true}`) and fans out on data.record.* events (plus the aggregate data.records.* a predicate write publishes, dispatched under the opt-in bulk_update/bulk_delete triggers — #4639), reading `object_name`/`name`/`url`/`method`/`triggers` off the row, `timeoutMs` back out of `definition_json`, and BOTH credentials through `engine.resolveSecretField()` against their encrypted columns — the signing key from `signing_secret` (#7799) and the custom headers from `headers_secret` (#7986). So all 11 remaining props are LIVE; there is no dead/experimental prop left, so nothing carries an authorWarn (the old per-webhook `url` heads-up is gone — authoring is no longer a no-op). Seed-not-clobber: an admin-edited row (`customized`) is never re-seeded (bootstrap-declared-webhooks.ts:143). The `object` prop carries the ADR-0054 runtime proof for the whole materialization pipeline (bound high-risk class `webhook-materialization`). Field-level line refs: materializer bootstrap-declared-webhooks.ts, runtime object plugins/plugin-webhooks/src/sys-webhook.object.ts, dispatcher auto-enqueuer.ts.", + "_note": "WebhookSchema (outbound webhook — packages/spec/src/automation/webhook.zod.ts). Governed via a spec-only schema override in the gate (SPEC_ONLY_SCHEMAS): webhook is still NOT a registered metadata type (absent from kernel/metadata-type-schemas.ts) — registering it would turn on Studio webhook CRUD + saveMetaItem overlay + create-seeds; that reassessment is tracked in #3490 and deliberately deferred. TWO THINGS CLOSED THE OLD 'entire surface is dead' classification: (1) #3494 PRUNED the aspirational dead props outright — body / payloadFields / includeSession / retryPolicy / tags / authentication are gone from the schema; (2) the #3461 materializer bridge (PR #3489) makes every REMAINING prop live. `bootstrapDeclaredWebhooks` (plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts) materializes each stack/connector-authored webhook into a `sys_webhook` DATA row on boot — WebhookSchema.parse (:114) → mapWebhookToRow (:191): `object`→`object_name` (:195), `isActive`→`active` (:202), same-named `name`/`label`/`triggers`/`url`/`method`/`description`, `secret`→the encrypted `signing_secret` column (#7799), `headers`→the encrypted `headers_secret` column (#7986), and the REST of the envelope → `definition_json`. The dispatcher (AutoEnqueuer) reads those rows (auto-enqueuer.ts:175 `where:{active:true}`) and fans out on data.record.* events (plus the aggregate data.records.* a predicate write publishes, dispatched under the opt-in bulk_update/bulk_delete triggers — #4639), reading `object_name`/`name`/`url`/`method`/`triggers` off the row, `timeoutMs` back out of `definition_json`, and BOTH credentials through `engine.resolveSecretField()` against their encrypted columns — the signing key from `signing_secret` (#7799) and the custom headers from `headers_secret` (#7986). So all 11 remaining props are LIVE; there is no dead/experimental prop left, so nothing carries an authorWarn (the old per-webhook `url` heads-up is gone — authoring is no longer a no-op). Seed-not-clobber: an admin-edited row (`customized`) is never re-seeded (bootstrap-declared-webhooks.ts:143). The `object` prop carries the ADR-0054 runtime proof for the whole materialization pipeline (bound high-risk class `webhook-materialization`). Field-level line refs: materializer bootstrap-declared-webhooks.ts, runtime object plugins/plugin-webhooks/src/sys-webhook.object.ts, dispatcher auto-enqueuer.ts. 2026-08-28 — WHOLE FILE RE-ANCHORED (#13003, adoption of the #12516 symbol-anchor grammar), and this file was the batch's worst starting position by a distance. Every citation in it was written as a BARE FILENAME with a line (`bootstrap-declared-webhooks.ts:193`, `auto-enqueuer.ts:266`, `sys-webhook.object.ts:160`), which `PATH_RE` cannot parse: a citation must be repo-rooted to be a citation at all. So all eleven `live` entries scanned as ZERO local paths and ZERO line citations — no existence check, no line bound, no key-mention anchoring, nothing. The gate was not passing this file; it was never asked about it, and the ledger's own counters could not say so. Combined with the second fact — no entry carried `verifiedAt`, which the #13003 ACCEPT ruling sorts as OLDEST — these rows were unfalsifiable and unclocked at once. Re-closed by hand against c459da6bc: every consumer read on the current tree, no line carried forward. What the re-closure found: the claims were all TRUE and the pointers were all merely unresolvable — the opposite of `object.json` in batch 1, where the pointers resolved and the claims were wrong. Both are the same defect at different ends. Two prose repairs fell out of it: the parenthetical line numbers are gone in favour of the symbols that own each read, and the dispatcher's credential legs are named as `attachSecret` / `attachHeaders`, which REFUSE a subscription whose credential cannot be recovered rather than delivering without it — a fail-closed behaviour the old prose did not record.", "props": { "name": { "status": "live", - "evidence": "Materialized to sys_webhook.name by the bridge (bootstrap-declared-webhooks.ts:193, #3489); the dispatcher reads row.name for the dedup key + logging (auto-enqueuer.ts:266).", - "note": "Was dead pre-bridge (admin-CRUD-only). Now fed by authoring." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`name: wh.name` — materialized to sys_webhook.name, #3489); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (the same value is the idempotency key: `where: { name: wh.name }` decides seed vs patch vs skip); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`name: (row.name as string) ?? row.id` — the dispatcher's dedup/log key)", + "note": "Was dead pre-bridge (admin-CRUD-only). Now fed by authoring. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "label": { "status": "live", - "evidence": "Materialized to sys_webhook.label (bootstrap-declared-webhooks.ts:194, #3489); consumed by the sys_webhook Studio list views (sys-webhook.object.ts listViews — `columns: ['label', …]`).", - "note": "Display-only — surfaced in Studio, never sent on the wire." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`label: wh.label ?? wh.name` — materialized to sys_webhook.label, #3489); packages/plugins/plugin-webhooks/src/sys-webhook.object.ts#SysWebhook (every one of the object's four listViews leads with the `label` column)", + "note": "Display-only — surfaced in Studio, never sent on the wire. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "object": { "status": "live", - "evidence": "Bridge remaps spec `object` → sys_webhook.object_name (bootstrap-declared-webhooks.ts:195, #3489); the dispatcher keys its per-object subscription on it (auto-enqueuer.ts:267).", + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`object_name: wh.object ?? null` — the spec→runtime remap this entry's proof exists for); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`objectName: row.object_name ? String(row.object_name) : undefined` — what the per-object subscription keys on)", "proof": "packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts#webhook-materialization", - "note": "ADR-0054 high-risk class (webhook-materialization): the spec `object`→runtime `object_name` remap is the representative check for the whole authoring→dispatcher pipeline that crosses manifest-decomposition → materializer → sys_webhook row → dispatcher subscription — the exact multi-layer seam that broke in integration (the materializer was first gated behind the realtime guard) despite green unit tests. The proof authors a stack `webhooks:` entry against the real in-process stack and asserts the sys_webhook row materialized with object_name set." + "note": "ADR-0054 high-risk class (webhook-materialization): the spec `object`→runtime `object_name` remap is the representative check for the whole authoring→dispatcher pipeline that crosses manifest-decomposition → materializer → sys_webhook row → dispatcher subscription — the exact multi-layer seam that broke in integration (the materializer was first gated behind the realtime guard) despite green unit tests. The proof authors a stack `webhooks:` entry against the real in-process stack and asserts the sys_webhook row materialized with object_name set. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "triggers": { "status": "live", - "evidence": "Materialized to sys_webhook.triggers (bootstrap-declared-webhooks.ts:196, #3489); the dispatcher parses + maps them to create/update/delete from data.record.* and to bulk_update/bulk_delete from the aggregate data.records.* a predicate write publishes (auto-enqueuer.ts, unknown values dropped with a warning #3196, bulk pair #4639).", - "note": "Was dead pre-bridge. Salvaged 1:1." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`triggers: wh.triggers ?? []`); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (accepts array / comma-string / JSON-string shapes, maps them onto the dispatchable set, warns by name on values the engine never emits (#3196), and refuses a subscription whose set is empty under rule `webhook/without-triggers`)", + "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "url": { "status": "live", - "evidence": "Materialized to sys_webhook.url (bootstrap-declared-webhooks.ts:197, #3489); the dispatcher delivers to it (auto-enqueuer.ts:269).", - "note": "Was dead pre-bridge and carried the per-webhook authorWarn; the bridge (#3489) makes authoring live, so the warning is removed." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`url: wh.url`); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`if (!row?.id || !row?.url) return null` — a row without one is not a subscription at all — then `url: String(row.url)` for delivery)", + "note": "Was dead pre-bridge and carried the per-webhook authorWarn; the bridge (#3489) makes authoring live, so the warning is removed. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "method": { "status": "live", - "evidence": "Materialized (lowercased) to sys_webhook.method (bootstrap-declared-webhooks.ts:200, #3489); the dispatcher upper-cases + uses it (auto-enqueuer.ts:274).", - "note": "Was dead pre-bridge. Salvaged 1:1." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`method: String(wh.method ?? 'POST').toLowerCase()` — stored lowercase to match the object's select option values); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`String(row.method ?? defn.method ?? 'POST').toUpperCase()` — upper-cased back for the wire, so both spellings survive the select change)", + "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "headers": { "status": "live", - "evidence": "Bridge writes the authored map to sys_webhook.headers_secret — a `type: 'secret'` column the engine encrypts into sys_secret and masks on read (bootstrap-declared-webhooks.ts mapWebhookToRow + headersPatch, #7986); the dispatcher recovers and re-parses it via engine.resolveSecretField() on each cache refresh (auto-enqueuer.ts attachHeaders) and attaches the headers to the outbound request.", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7986: the same column and the same absent `enable` block as #7799's signing secret, one key of the blob later. It no longer rides in that blob; the authored map itself is unchanged. The WHOLE map moves because only some entries are credentials and the platform cannot tell which." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (the envelope it returns is the authored one MINUS its two credential passengers — `splitWebhookHeaders` lifts this map out before `definition_json` is serialized, #7986); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (`headersPatch` / the `WEBHOOK_HEADERS_FIELD` insert write the map into the encrypted `headers_secret` column); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#attachHeaders (recovers and re-parses it per cache refresh, and REFUSES the subscription when recovery fails rather than delivering header-less)", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7986: the same column and the same absent `enable` block as #7799's signing secret, one key of the blob later. It no longer rides in that blob; the authored map itself is unchanged. The WHOLE map moves because only some entries are credentials and the platform cannot tell which. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "timeoutMs": { "status": "live", - "evidence": "Bridge folds the envelope into sys_webhook.definition_json (bootstrap-declared-webhooks.ts:203, #3489); the dispatcher reads defn.timeoutMs and applies it to the delivery (auto-enqueuer.ts:277 → http-outbox.ts:96).", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows). Now materialized from authoring." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`definition_json: JSON.stringify(envelope)` — this key rides the envelope rather than a column of its own); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`timeoutMs: defn.timeoutMs`, read back out of that JSON onto the cached subscription and passed to the outbox on both enqueue paths); packages/services/service-messaging/src/http-outbox.ts#assertEnqueueDeliverable (the outbox side of the same field)", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows). Now materialized from authoring. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "secret": { "status": "live", - "evidence": "Bridge writes the authored key to sys_webhook.signing_secret — a `type: 'secret'` column the engine encrypts into sys_secret and masks on read (bootstrap-declared-webhooks.ts mapWebhookToRow + secretPatch, #7799); the dispatcher recovers the plaintext via engine.resolveSecretField() on each cache refresh (auto-enqueuer.ts attachSecret) and passes it as signingSecret to the outbox, which consumes it for one HMAC without persisting it (#7722).", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7799. It no longer rides in that blob; the authored key itself is unchanged." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`splitWebhookSecret` lifts the key out of the envelope before serialization, #7799); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (`secretPatch` / the `WEBHOOK_SECRET_FIELD` insert write cleartext exactly once into the `secret`-typed `signing_secret` column, which the engine wraps into sys_secret); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#attachSecret (recovers the plaintext per cache refresh and hands it to the outbox as `signingSecret`, consumed for one HMAC and never persisted, #7722)", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7799. It no longer rides in that blob; the authored key itself is unchanged. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "isActive": { "status": "live", - "evidence": "Bridge remaps spec `isActive` → sys_webhook.active (bootstrap-declared-webhooks.ts:202, #3489); the dispatcher gates delivery on it (`where: { active: true }`, auto-enqueuer.ts:176).", - "note": "Was dead pre-bridge (NAME MISMATCH spec `isActive` vs runtime `active`, now remapped). Seed-not-clobber preserves an admin's `active:false` across redeploys (bootstrap-declared-webhooks.ts:132-145)." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`active: wh.isActive !== false` — the NAME MISMATCH remap, spec `isActive` → runtime `active`); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (seed-not-clobber: an admin-authored (`managed_by === 'admin'`) or admin-edited (`customized === true`) row is skipped, so an operator's `active: false` survives redeploys)", + "note": "Was dead pre-bridge (NAME MISMATCH spec `isActive` vs runtime `active`, now remapped). Seed-not-clobber preserves an admin's `active:false` across redeploys. The dispatcher gates delivery on the REMAPPED column — `AutoEnqueuer.doRefresh` loads `where: { active: true }`, so only armed rows are ever cached — and that call site is kept here in PROSE rather than as a citation, on the `object.tenancy.organizationField` precedent: a consumer that never names the key belongs in the note, not in `evidence`. 2026-08-28: RE-ANCHORED (#13003) — and this row is the one that PAID for the migration inside the same run. Rewriting this file's bare-filename citations as repo-rooted anchors made them askable for the first time, and the key-mention check immediately answered: `webhook/isActive -> auto-enqueuer.ts` UNANCHORED, because that file names `active` and never `isActive` in any case fold. The check was right — the remap happens at the materializer and nothing downstream of it sees the authored spelling — so the citation moved to prose rather than being exempted. Recording the direction because it is the useful one: an unparseable citation does not fail, it is simply never asked, and eleven of those had been sitting here reading as evidence. Re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" }, "description": { "status": "live", - "evidence": "Materialized to sys_webhook.description (bootstrap-declared-webhooks.ts:201, #3489); Studio-editable/displayed (sys-webhook.object.ts:160).", - "note": "Display-only — surfaced/editable in Studio, inert in dispatch." + "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`description: wh.description ?? null` — materialized to sys_webhook.description, #3489); packages/plugins/plugin-webhooks/src/sys-webhook.object.ts#SysWebhook (the column the Studio editor renders and edits)", + "note": "Display-only — surfaced/editable in Studio, inert in dispatch. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "verifiedAt": "2026-08-28" } } }