From 8d31c8d8d401d4e9b66f15d618d22f17dab46a84 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 03:27:59 +0000 Subject: [PATCH 1/3] docs(spec): re-anchor the dead tracker citations in the manifest, dataset and permission liveness ledgers to the commits that decided them 134 note sites across three ledgers cited GitHub issues that answer 404. Each now names the commit that decided it (the ADR/ruling-record-else-commit order), and says the decision in words where the number alone carried it. Note strings only: every row's status, evidence, proof and verifiedAt is unchanged. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- ...0234-liveness-ledger-provenance-anchors.md | 14 ++++ packages/spec/liveness/dataset.json | 36 ++++----- packages/spec/liveness/manifest.json | 76 +++++++++---------- packages/spec/liveness/permission.json | 42 +++++----- 4 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 .changeset/20234-liveness-ledger-provenance-anchors.md diff --git a/.changeset/20234-liveness-ledger-provenance-anchors.md b/.changeset/20234-liveness-ledger-provenance-anchors.md new file mode 100644 index 00000000000..b5814d3b9c8 --- /dev/null +++ b/.changeset/20234-liveness-ledger-provenance-anchors.md @@ -0,0 +1,14 @@ +--- +'@objectstack/spec': patch +--- + +The `manifest`, `dataset` and `permission` liveness ledgers cite the commit that decided each note instead of a tracker number that no longer resolves + +Clause-②: no + +Notes in these three ledgers named GitHub issues that no longer exist, so a reader could +not tell why a row carries its verdict. Each such note now names the commit that made the +decision and, where the number alone carried the meaning, says what was decided. The +`liveness/` ledgers ship in this package's tarball, which is why this is a release note at +all. Note text only: no row's status, evidence, proof or date changes, and no schema, +export or runtime behaviour changes. diff --git a/packages/spec/liveness/dataset.json b/packages/spec/liveness/dataset.json index f36c1dc019a..bc2d93827ac 100644 --- a/packages/spec/liveness/dataset.json +++ b/packages/spec/liveness/dataset.json @@ -5,13 +5,13 @@ "name": { "status": "live", "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).", + "note": "registry key + compiled Cube name. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "label": { "status": "live", "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).", + "note": "compiled into Cube title for presentations. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "description": { @@ -21,19 +21,19 @@ "object": { "status": "live", "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).", + "note": "base table — Cube sql + join resolution + draft-rows resolver. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "include": { "status": "live", "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).", + "note": "compiled into Cube joins + the NativeSQLStrategy join allowlist (ADR-0021). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "filter": { "status": "live", "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).", + "note": "dataset-level WHERE, ANDed with runtime + measure-scoped filters. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "dimensions": { @@ -41,32 +41,32 @@ "name": { "status": "live", "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).", + "note": "dimension identifier (Cube dimension key). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "label": { "status": "live", "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).", + "note": "compiled into Cube dimension for presentations. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "field": { "status": "live", "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).", + "note": "emitted as the dimension's SQL column reference (relationship-validated). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "type": { "status": "live", "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).", + "note": "dimensionType() maps to Cube time/number/boolean/string; drives grouping. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "dateGranularity": { "status": "live", "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. 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).", + "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 (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" } } @@ -76,49 +76,49 @@ "name": { "status": "live", "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).", + "note": "measure identifier + derived-measure reference. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "label": { "status": "live", "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).", + "note": "compiled into Cube metric + enriched onto result fields. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "aggregate": { "status": "live", "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).", + "note": "aggregateToMetricType() → Cube metric type (sum/count/avg/…); unsupported aggregates throw. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "field": { "status": "live", "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).", + "note": "SQL aggregate operand (count omits field → '*'); relationship-validated. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "filter": { "status": "live", "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).", + "note": "measure-scoped WHERE, applied via a supplementary per-measure query. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "format": { "status": "live", "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).", + "note": "compiled into Cube metric + enriched onto result fields for the renderer. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" }, "currency": { "status": "live", "evidence": "packages/services/service-analytics/src/analytics-service.ts#AnalyticsService (`queryDataset`: the currency 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 FIXED currency `sourceFieldMeta().defaultCurrency` → the tenant default `context.currency`); packages/services/service-analytics/src/plugin.ts#AnalyticsServicePlugin (`sourceFieldMeta` relays `currencyConfig.defaultCurrency` only under `currencyMode: 'fixed'` — a `dynamic` field has no currency of its own, so its code is never read)", - "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). 2026-09-27: evidence RE-WORDED (#20126) — the chain no longer cites the date/datetime-semantics ADR, which never names currency, and its middle step is the field's FIXED currency, which the plugin relays only under `currencyMode: 'fixed'` (#20091). Verdict unchanged: LIVE. Re-closed by hand against 1c8b320a8.", + "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 (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST). 2026-09-27: evidence RE-WORDED (#20126) — the chain no longer cites the date/datetime-semantics ADR, which never names currency, and its middle step is the field's FIXED currency, which the plugin relays only under `currencyMode: 'fixed'` (#20091). Verdict unchanged: LIVE. Re-closed by hand against 1c8b320a8.", "verifiedAt": "2026-09-27" }, "derived": { "status": "live", "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).", + "note": "post-aggregation arithmetic (ratio/sum/difference/product) over base measures; {op, of} both consumed. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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 review accepting that re-anchoring sorts as OLDEST).", "verifiedAt": "2026-08-28" } } diff --git a/packages/spec/liveness/manifest.json b/packages/spec/liveness/manifest.json index f5ace655ac9..c10bc68a692 100644 --- a/packages/spec/liveness/manifest.json +++ b/packages/spec/liveness/manifest.json @@ -1,96 +1,96 @@ { "type": "manifest", - "_note": "ManifestSchema (packages/spec/src/kernel/manifest.zod.ts:132) — the plugin / package manifest an author writes as `objectstack.config.ts` or a packaged `manifest.json`, parsed by `ManifestSchema.parse` at packages/objectql/src/registry.ts:2950 and by `os plugin build` / `os plugin publish`. GOVERNED VIA THE GATE'S SPEC-ONLY OVERRIDE (SPEC_ONLY_SCHEMAS), seeded 2026-08-23. WHY THE OVERRIDE AND NOT THE REGISTRY: the manifest is not a metadata KIND (absent from BUILTIN_METADATA_TYPE_SCHEMAS) and not a stack collection either — the retired-key entry packages/spec/src/migrations/entries/retired-keys/17.kernel__Manifest__loading.ts records that `PLURAL_TO_SINGULAR` has no `packages` / `plugins` entry, so a manifest is never walked as a stack collection member. It is therefore the THIRD category the override has had to reach, after `query` (a request surface) and `qa` (a file surface): a ratchet rooted in the registry could not ask who reads any of it, and a ratchet extended to unregistered KINDS would not reach it either. Like `query` and `qa` there is no registry to fold it back onto — the override IS its governance. WHAT THAT COST BEFORE THIS FILE EXISTED, twice, both by hand and after the fact: `loading` carried ten inert keys, one of them (`sandboxing`) security-shaped — it isolated nothing while looking like isolation — retired at #4914; and the 11-member `contributes` block turned out to have exactly ONE reader monorepo-wide (#10627). No gate asked either question because the manifest was not in the denominator. MEASUREMENT PROVENANCE: the `contributes` children below are #10627's verdict table verbatim (measured on origin/main 299b85e9d), re-verified for line drift on 2026-08-25 at claim of #10724 — the cited read site now sits at engine.ts:4604-4606. Every other row was measured for this seeding on 2026-08-23 against b9e9227e3, by per-key probe with controls: the same probe finds real reads of sibling keys (`.packaging` at cli/src/commands/plugin/build.ts:126, `.data` at runtime/src/app-plugin.ts:946, `.contributes` at objectql/src/engine.ts:4504), so a zero here is about the path, not the pattern. CROSS-REPO LEG: ../objectui walked for every `dead` row (0 property reads of these keys; control — `manifest.(id|name|namespace|version)` returns 46 hits there, so manifest reads are findable). CLOUD LEG MEASURED CLEAN 2026-08-24 (#10812, discharging #10724's precondition): cloud `origin/main` @ 5b5925a has zero `manifest.contributes` reads (the single regex hit is an HTTP query parameter on the marketplace route; controls held — 15 manifest-property reads findable, member words present), completing the three-repo census at exactly one live read (engine.ts, member `kinds`). WALK BOUNDARY, recorded rather than silently skipped: the gate classifies one level and this file drills six containers one more, so keys BELOW a drilled child (e.g. `contributes.kinds[].id` / `.globs` / `.description`, `navigationContributions[].items[]`) sit outside the walk; where they were measured in the same pass their verdicts are recorded in the child's `note` instead of being fanned out into rows the gate would not check. `data` is not drilled here at all — it embeds SeedSchema, which the governed `seed` type classifies in full, so it is a RESOLVED deferral in scripts/liveness/undrilled-containers.baseline.json rather than a duplicated set of rows. DISPOSITIONS: #10724 EXECUTED — the nine mechanically-dead `contributes` members (events/menus/themes/translations/actions/drivers/fieldTypes/functions/commands) are retiredKey tombstones as of @objectstack/spec 17.x (D3 `plugin-manifest-contributes-dead-members-retired`); their rows below say so and STAY (tombstones keep the key in the drilled shape). #10726 EXECUTED 2026-08-26 (maintainer-ruled Option B 2026-08-22, cloud precondition discharged by #10812): `routes` is a retiredKey tombstone too (D3 `plugin-manifest-contributes-routes-retired`), leaving `kinds` the block's sole live member. `loading`'s tombstone landed earlier (#4914).", + "_note": "ManifestSchema (packages/spec/src/kernel/manifest.zod.ts:132) — the plugin / package manifest an author writes as `objectstack.config.ts` or a packaged `manifest.json`, parsed by `ManifestSchema.parse` at packages/objectql/src/registry.ts:2950 and by `os plugin build` / `os plugin publish`. GOVERNED VIA THE GATE'S SPEC-ONLY OVERRIDE (SPEC_ONLY_SCHEMAS), seeded 2026-08-23. WHY THE OVERRIDE AND NOT THE REGISTRY: the manifest is not a metadata KIND (absent from BUILTIN_METADATA_TYPE_SCHEMAS) and not a stack collection either — the retired-key entry packages/spec/src/migrations/entries/retired-keys/17.kernel__Manifest__loading.ts records that `PLURAL_TO_SINGULAR` has no `packages` / `plugins` entry, so a manifest is never walked as a stack collection member. It is therefore the THIRD category the override has had to reach, after `query` (a request surface) and `qa` (a file surface): a ratchet rooted in the registry could not ask who reads any of it, and a ratchet extended to unregistered KINDS would not reach it either. Like `query` and `qa` there is no registry to fold it back onto — the override IS its governance. WHAT THAT COST BEFORE THIS FILE EXISTED, twice, both by hand and after the fact: `loading` carried ten inert keys, one of them (`sandboxing`) security-shaped — it isolated nothing while looking like isolation — retired at #4914; and the 11-member `contributes` block turned out to have exactly ONE reader monorepo-wide (the census recorded in commit be21955ba). No gate asked either question because the manifest was not in the denominator. MEASUREMENT PROVENANCE: the `contributes` children below are that census's verdict table verbatim (measured on origin/main 299b85e9d), re-verified for line drift on 2026-08-25 when the retirement in commit be21955ba was claimed — the cited read site now sits at engine.ts:4604-4606. Every other row was measured for this seeding on 2026-08-23 against b9e9227e3, by per-key probe with controls: the same probe finds real reads of sibling keys (`.packaging` at cli/src/commands/plugin/build.ts:126, `.data` at runtime/src/app-plugin.ts:946, `.contributes` at objectql/src/engine.ts:4504), so a zero here is about the path, not the pattern. CROSS-REPO LEG: ../objectui walked for every `dead` row (0 property reads of these keys; control — `manifest.(id|name|namespace|version)` returns 46 hits there, so manifest reads are findable). CLOUD LEG MEASURED CLEAN 2026-08-24 (recorded in commit be21955ba, discharging that retirement's precondition): cloud `origin/main` @ 5b5925a has zero `manifest.contributes` reads (the single regex hit is an HTTP query parameter on the marketplace route; controls held — 15 manifest-property reads findable, member words present), completing the three-repo census at exactly one live read (engine.ts, member `kinds`). WALK BOUNDARY, recorded rather than silently skipped: the gate classifies one level and this file drills six containers one more, so keys BELOW a drilled child (e.g. `contributes.kinds[].id` / `.globs` / `.description`, `navigationContributions[].items[]`) sit outside the walk; where they were measured in the same pass their verdicts are recorded in the child's `note` instead of being fanned out into rows the gate would not check. `data` is not drilled here at all — it embeds SeedSchema, which the governed `seed` type classifies in full, so it is a RESOLVED deferral in scripts/liveness/undrilled-containers.baseline.json rather than a duplicated set of rows. DISPOSITIONS: EXECUTED in commit be21955ba — the nine mechanically-dead `contributes` members (events/menus/themes/translations/actions/drivers/fieldTypes/functions/commands) are retiredKey tombstones as of @objectstack/spec 17.x (D3 `plugin-manifest-contributes-dead-members-retired`); their rows below say so and STAY (tombstones keep the key in the drilled shape). EXECUTED 2026-08-26 in commit bc56e1881 (maintainer-ruled Option B 2026-08-22, cloud precondition discharged by the 2026-08-24 reading): `routes` is a retiredKey tombstone too (D3 `plugin-manifest-contributes-routes-retired`), leaving `kinds` the block's sole live member. `loading`'s tombstone landed earlier (#4914).", "props": { "id": { "status": "live", "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#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." + "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 (commit 9ee2dcfbd) 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#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#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." + "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 (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — 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#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. 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." + "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 (commit 9ee2dcfbd) — 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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. 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." + "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 recorded apart 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 (commit 9ee2dcfbd) — 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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 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." + "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 (commit 9ee2dcfbd) 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": { "status": "dead", "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#11332, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:configuration`. The container had ZERO reads in objectstack, objectui and cloud (#12400, cloud clean at 15f55df with positive controls), which settled both children at once — the per-child rows this row used to carry (title / properties, dead-by-container, verified 2026-08-23) leave the ledger with the children, because the tombstone removes the drilled shape beneath the key. `properties.*.secret` is why this was recorded as false compliance rather than tidying: its describe() promised \"value is encrypted/masked (e.g. API Keys)\" and nothing encrypted, masked or even parsed it. The enforced channel is host composition — the options object passed to the plugin's constructor in defineStack plugins." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit dce5cd4f0, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:configuration`. The container had ZERO reads in objectstack, objectui and cloud (#12400, cloud clean at 15f55df with positive controls), which settled both children at once — the per-child rows this row used to carry (title / properties, dead-by-container, verified 2026-08-23) leave the ledger with the children, because the tombstone removes the drilled shape beneath the key. `properties.*.secret` is why this was recorded as false compliance rather than tidying: its describe() promised \"value is encrypted/masked (e.g. API Keys)\" and nothing encrypted, masked or even parsed it. The enforced channel is host composition — the options object passed to the plugin's constructor in defineStack plugins." }, "contributes": { "children": { @@ -99,67 +99,67 @@ "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`. 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." + "note": "THE ONLY LIVE MEMBER OF THIS BLOCK, and the entire reason the block is not uniformly dead: the census recorded in commit be21955ba measured that the whole monorepo contains exactly one non-test read of `manifest.contributes` and it reads `kinds`. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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. That census 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, recorded at this row's seeding (commit 3fc606687): 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", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.events`. Prior verdict kept below as provenance: #10627 verdict. Its only in-repo author — packages/plugins/plugin-hono-server/objectstack.config.ts:233, declaring `kernel:ready` / `kernel:listening` — subscribes imperatively in plugin code; the declaration drives nothing, which makes it decorative rather than merely unused." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.events`. Prior verdict kept below as provenance: the census verdict that commit records. Its only in-repo author — packages/plugins/plugin-hono-server/objectstack.config.ts:233, declaring `kernel:ready` / `kernel:listening` — subscribes imperatively in plugin code; the declaration drives nothing, which makes it decorative rather than merely unused." }, "menus": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.menus`. Prior verdict kept below as provenance: #10627 verdict, and the tightest control in the set: the bare word `menus` has only three non-test hits monorepo-wide — this declaration, plus two alias maps that redirect the spelling to `navigation` (packages/spec/src/ui/app.zod.ts:1242, packages/spec/src/system/translation.zod.ts:428). The working surface is app `navigation` / `manifest.navigationContributions`." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.menus`. Prior verdict kept below as provenance: the census verdict that commit records, and the tightest control in the set: the bare word `menus` has only three non-test hits monorepo-wide — this declaration, plus two alias maps that redirect the spelling to `navigation` (packages/spec/src/ui/app.zod.ts:1242, packages/spec/src/system/translation.zod.ts:428). The working surface is app `navigation` / `manifest.navigationContributions`." }, "themes": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.themes`. Prior verdict kept below as provenance: #10627 verdict, re-verified there independently rather than inherited from the filer: the non-spec `themes` hits all reach the registry through TOP-LEVEL `manifest.themes` (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884), never through `contributes.themes`, and the shapes differ ({id,label,path} here vs ThemeSchema there)." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.themes`. Prior verdict kept below as provenance: the census verdict that commit records, re-verified there independently rather than inherited from the filer: the non-spec `themes` hits all reach the registry through TOP-LEVEL `manifest.themes` (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884), never through `contributes.themes`, and the shapes differ ({id,label,path} here vs ThemeSchema there)." }, "translations": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.translations`. Prior verdict kept below as provenance: #10627 verdict — and documented as working: content/docs/protocol/kernel/i18n-standard.mdx carries an `os:check`-marked example stating translation files \"are registered under `contributes.translations`\". The live surface is the `translation` metadata type / stack `translations` collection (governed, packages/spec/liveness/translation.json)." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.translations`. Prior verdict kept below as provenance: the census verdict that commit records — and documented as working: content/docs/protocol/kernel/i18n-standard.mdx carries an `os:check`-marked example stating translation files \"are registered under `contributes.translations`\". The live surface is the `translation` metadata type / stack `translations` collection (governed, packages/spec/liveness/translation.json)." }, "actions": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.actions`. Prior verdict kept below as provenance: #10627 verdict. The live sibling is the stack `actions` collection (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884) plus `engine.registerAction`." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.actions`. Prior verdict kept below as provenance: the census verdict that commit records. The live sibling is the stack `actions` collection (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884) plus `engine.registerAction`." }, "drivers": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.drivers`. Prior verdict kept below as provenance: #10627 verdict. Its only in-repo author — packages/drivers/driver-memory/objectstack.config.ts:250 — is registered by the SERVICE path (packages/objectql/src/plugin.ts:592, `registerDriver` for services named `driver.*`), not by its declaration: the second decorative declaration in the tree, alongside `events`." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.drivers`. Prior verdict kept below as provenance: the census verdict that commit records. Its only in-repo author — packages/drivers/driver-memory/objectstack.config.ts:250 — is registered by the SERVICE path (packages/objectql/src/plugin.ts:592, `registerDriver` for services named `driver.*`), not by its declaration: the second decorative declaration in the tree, alongside `events`." }, "fieldTypes": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.fieldTypes`. Prior verdict kept below as provenance: #10627 verdict, and the cleanest of the set: there is no `registerFieldType` seam anywhere — zero hits monorepo-wide — so this declares an extension point that does not exist rather than one that is merely unread." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.fieldTypes`. Prior verdict kept below as provenance: the census verdict that commit records, and the cleanest of the set: there is no `registerFieldType` seam anywhere — zero hits monorepo-wide — so this declares an extension point that does not exist rather than one that is merely unread." }, "functions": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.functions`. Prior verdict kept below as provenance: #10627 verdict. The live spelling is `defineStack({ functions })` → packages/objectql/src/hook-binder.ts:128-143. ⚠️ docs/adr/0088-metadata-kind-admission-and-retirement.md credits `contributes.functions` as a DELIVERED form of the `function` kind; that row is wrong and its correction rides #10724." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.functions`. Prior verdict kept below as provenance: the census verdict that commit records. The live spelling is `defineStack({ functions })` → packages/objectql/src/hook-binder.ts:128-143. ⚠️ docs/adr/0088-metadata-kind-admission-and-retirement.md credited `contributes.functions` as a DELIVERED form of the `function` kind; that row was wrong, and commit 0b048393f corrected it." }, "routes": { "status": "dead", "verifiedAt": "2026-08-26", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10726, ADR-0049; maintainer-ruled Option B 2026-08-22, cloud precondition discharged by #10812), ADR-0087 D3 entry `plugin-manifest-contributes-routes-retired`, retired-key entry `kernel/Manifest:contributes.routes`. Prior verdict kept as provenance: #10627 verdict — zero readers, same as its siblings — but a DIFFERENT disposition, which is why it was the one member split onto its own card: four published surfaces presented it as working machinery, one a customer-published skill, so an author following the shipped skill got a clean parse and served nothing. Per the ruling's own sequencing the author-facing corrections landed FIRST (PR #11327: the skill's decision table, dispatcher.zod.ts protocol doc, ADR-0088:40, app.mdx), and the two remaining teaching sites (#11328: the plugin-rest-api.zod.ts worked manifest example, metadata-plugin.zod.ts `router` delivered-form comments) were redirected in the removal PR itself. The capability survives in its working forms: the imperative `http.server` mount (plugin-hono-server registers the service; examples/app-showcase recalc-endpoint mounts on kernel:ready) and declarative `defineStack({ apis })` for pipeline projections." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit bc56e1881, ADR-0049; maintainer-ruled Option B 2026-08-22, cloud precondition discharged by the 2026-08-24 reading), ADR-0087 D3 entry `plugin-manifest-contributes-routes-retired`, retired-key entry `kernel/Manifest:contributes.routes`. Prior verdict kept as provenance: the census verdict recorded in commit be21955ba — zero readers, same as its siblings — but a DIFFERENT disposition, which is why it was the one member split onto its own card: four published surfaces presented it as working machinery, one a customer-published skill, so an author following the shipped skill got a clean parse and served nothing. Per the ruling's own sequencing the author-facing corrections landed FIRST (PR #11327: the skill's decision table, dispatcher.zod.ts protocol doc, ADR-0088:40, app.mdx), and the two remaining teaching sites (the plugin-rest-api.zod.ts worked manifest example, metadata-plugin.zod.ts `router` delivered-form comments) were redirected in the removal PR itself. The capability survives in its working forms: the imperative `http.server` mount (plugin-hono-server registers the service; examples/app-showcase recalc-endpoint mounts on kernel:ready) and declarative `defineStack({ apis })` for pipeline projections." }, "commands": { "status": "dead", "verifiedAt": "2026-08-25", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#10724, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.commands`. Prior verdict kept below as provenance: #10627 verdict, and already known-superseded in-tree: packages/spec/src/kernel/cli-extension.zod.ts:58-63 states \"The previous plugin model required `contributes.commands` … The `objectstack.config.ts` plugins array no longer determines CLI commands.\" Yet manifest.zod.ts:429-462 still documents Commander.js runtime resolution as current behaviour — two spec files contradicting each other, with the stale one on the authoring surface." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit be21955ba, ADR-0049), ADR-0087 D3 entry `plugin-manifest-contributes-dead-members-retired`, retired-key entry `kernel/Manifest:contributes.commands`. Prior verdict kept below as provenance: the census verdict that commit records, and already known-superseded in-tree: packages/spec/src/kernel/cli-extension.zod.ts:58-63 states \"The previous plugin model required `contributes.commands` … The `objectstack.config.ts` plugins array no longer determines CLI commands.\" Yet manifest.zod.ts:429-462 still documents Commander.js runtime resolution as current behaviour — two spec files contradicting each other, with the stale one on the authoring surface." } } }, @@ -168,19 +168,19 @@ "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`. 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." + "note": "Deprecated in favour of `defineStack({ data })` but still read, so `live` rather than `dead`. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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": { "status": "dead", "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#11332, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:capabilities`. The container (PluginCapabilityManifestSchema) had ZERO reads in objectstack, objectui and cloud (#12400, cloud clean at 15f55df with positive controls), which settled all five children at once — the per-child rows this row used to carry (implements / provides / requires / extensionPoints / extensions, each dead-by-container, verified 2026-08-23) leave the ledger with the children, because the tombstone removes the drilled shape beneath the key. Provenance worth keeping: the bare `.capabilities` hits a re-measurement will find belong to other surfaces (driver loader contracts at packages/metadata/src/metadata-manager.ts, the QuickJS sandbox argument set at packages/runtime/src/sandbox/quickjs-runner.ts, REST discovery at packages/rest/src/rest-server.ts, the ADR-0066 stack-level `capabilities` collection); real dependency resolution runs off top-level `manifest.dependencies` (packages/metadata-protocol/src/protocol.ts), never off `capabilities.requires`. `PluginCapabilityManifestSchema` stays published — the plugin-registry surface still declares it." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit dce5cd4f0, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:capabilities`. The container (PluginCapabilityManifestSchema) had ZERO reads in objectstack, objectui and cloud (#12400, cloud clean at 15f55df with positive controls), which settled all five children at once — the per-child rows this row used to carry (implements / provides / requires / extensionPoints / extensions, each dead-by-container, verified 2026-08-23) leave the ledger with the children, because the tombstone removes the drilled shape beneath the key. Provenance worth keeping: the bare `.capabilities` hits a re-measurement will find belong to other surfaces (driver loader contracts at packages/metadata/src/metadata-manager.ts, the QuickJS sandbox argument set at packages/runtime/src/sandbox/quickjs-runner.ts, REST discovery at packages/rest/src/rest-server.ts, the ADR-0066 stack-level `capabilities` collection); real dependency resolution runs off top-level `manifest.dependencies` (packages/metadata-protocol/src/protocol.ts), never off `capabilities.requires`. `PluginCapabilityManifestSchema` stays published — the plugin-registry surface still declares it." }, "extensions": { "status": "dead", "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (#11332, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:extensions`. Prior verdict kept as provenance: `z.record(z.string(), z.unknown())` — an untyped escape hatch with zero reads in all three repos; because the value type was `unknown`, this key is where anything the platform does not yet model would get parked, so its emptiness was evidence that authors were not parking things here either — an untyped catch-all with no users was the cheapest removal in the family. The enforced extension channels are `contributes.kinds`, `navigationContributions` (ADR-0029 D7), and plugin code itself." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit dce5cd4f0, ADR-0049), ADR-0087 D3 entry `plugin-manifest-dead-containers-retired`, retired-key entry `kernel/Manifest:extensions`. Prior verdict kept as provenance: `z.record(z.string(), z.unknown())` — an untyped escape hatch with zero reads in all three repos; because the value type was `unknown`, this key is where anything the platform does not yet model would get parked, so its emptiness was evidence that authors were not parking things here either — an untyped catch-all with no users was the cheapest removal in the family. The enforced extension channels are `contributes.kinds`, `navigationContributions` (ADR-0029 D7), and plugin code itself." }, "navigationContributions": { "children": { @@ -189,28 +189,28 @@ "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. 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." + "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 (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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": "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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." } } }, @@ -227,7 +227,7 @@ "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, 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." + "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 (commit 9ee2dcfbd) 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." } } }, @@ -238,14 +238,14 @@ "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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#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(). 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." + "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 (commit 9ee2dcfbd) 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." } } }, @@ -254,14 +254,14 @@ "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", "evidence": "cloud: packages/service-cloud/src/plugin-permission-audit.ts#auditPluginPermissions @15f55df + packages/service-cloud/src/routes/package-publish.ts:530 — the marketplace publish gate: an unverified publisher requesting the node tier is hard-rejected with HTTP 422 and forced to manual review (#12400 reading of 2026-08-29). Load-side dispatch here is dead by measurement; the note carries that half.", - "note": "ENFORCED AT THE CLOUD PUBLISH GATE, NOT ENFORCED AT LOAD — and since #13483 the split IS the verdict: `live-elsewhere` says dead here by measurement, genuinely enforced in a sibling repo, and unlike the `dead` this row carried until then (with this note's qualifying sentence as the only guard — prose, which no check reads), it cannot be read as a licence to delete and the gate holds its criteria (foreign pointer, cross-repo scope, dated attestation with a 180d expiry — scripts/liveness/elsewhere.mts). LOCAL HALF, measured 2026-08-23 against b9e9227e3 with per-key controls: the only reads of `manifest.runtime` in objectstack are two CLI progress lines that echo the value — packages/cli/src/commands/plugin/build.ts:127 and packages/cli/src/commands/plugin/publish.ts:94, both `runtime: ${manifest.runtime ?? 'unset'}`. Nothing dispatches on the tier: there is no `runtime === 'sandbox'` branch anywhere, and the QuickJS runner (packages/runtime/src/sandbox/) is the hook/action SCRIPT-BODY sandbox, reached from body-runner factories, never from a plugin's declared tier. objectui: zero reads. So the declared capability — \"in-process full PluginContext vs QuickJS-WASM capability-gated vs out-of-process\" (ADR-0025 §3.6) — is not delivered at load, and echoing a string into a build log is not isolation. FOREIGN HALF (the evidence pointer): #12400 measured cloud origin/main @15f55df on 2026-08-29, controls held (15 manifest-property reads findable there) — `auditPluginPermissions` treats `runtime === 'node'` from an unverified publisher as a hard block, the caller turns it into HTTP 422 with forced manual review, and the tier is persisted to the version row (plugin-artifact.ts:179, package-publish.ts:531). `verifiedAt` on this row is THAT reading's date; re-attestation means someone with cloud access re-reads the enforcer and re-pins the commit — never a bare re-stamp (access is seat-dependent: `add_repo` on cloud is denied from some seats, including the #13483 seat on 2026-09-01, while the #10812 and #12400 readings prove cloud-capable seats exist). WHY THIS WAS SECURITY-SHAPED RATHER THAN COSMETIC: the tombstone this ledger records one key above used to tell every upgrading author, verbatim, \"If you were relying on [loading.sandboxing] for isolation, you had none — use the plugin trust tier (`manifest.runtime`) and the permission declarations, which are enforced\" — a redirection at a tier this repo does not enforce at load, i.e. ADR-0049 false compliance with a shipped migration message attached. Maintainer ruling 2026-08-30 (#11330) took option B — correct the text, do not retire the key — and explicitly ruled OUT retirement, because deleting the key would tear out the marketplace's trust-gate input; the tombstone, both `runtime` describes and the ADR-0087 D3 entry `plugin-manifest-loading-retired` state the publish-gate/load-side split. Adding load-side enforcement is the open half, tracked as a v18 direction, not as a removal — if it lands, the verdict here flips to `live` with local evidence." + "note": "ENFORCED AT THE CLOUD PUBLISH GATE, NOT ENFORCED AT LOAD — and since #13483 the split IS the verdict: `live-elsewhere` says dead here by measurement, genuinely enforced in a sibling repo, and unlike the `dead` this row carried until then (with this note's qualifying sentence as the only guard — prose, which no check reads), it cannot be read as a licence to delete and the gate holds its criteria (foreign pointer, cross-repo scope, dated attestation with a 180d expiry — scripts/liveness/elsewhere.mts). LOCAL HALF, measured 2026-08-23 against b9e9227e3 with per-key controls: the only reads of `manifest.runtime` in objectstack are two CLI progress lines that echo the value — packages/cli/src/commands/plugin/build.ts:127 and packages/cli/src/commands/plugin/publish.ts:94, both `runtime: ${manifest.runtime ?? 'unset'}`. Nothing dispatches on the tier: there is no `runtime === 'sandbox'` branch anywhere, and the QuickJS runner (packages/runtime/src/sandbox/) is the hook/action SCRIPT-BODY sandbox, reached from body-runner factories, never from a plugin's declared tier. objectui: zero reads. So the declared capability — \"in-process full PluginContext vs QuickJS-WASM capability-gated vs out-of-process\" (ADR-0025 §3.6) — is not delivered at load, and echoing a string into a build log is not isolation. FOREIGN HALF (the evidence pointer): #12400 measured cloud origin/main @15f55df on 2026-08-29, controls held (15 manifest-property reads findable there) — `auditPluginPermissions` treats `runtime === 'node'` from an unverified publisher as a hard block, the caller turns it into HTTP 422 with forced manual review, and the tier is persisted to the version row (plugin-artifact.ts:179, package-publish.ts:531). `verifiedAt` on this row is THAT reading's date; re-attestation means someone with cloud access re-reads the enforcer and re-pins the commit — never a bare re-stamp (access is seat-dependent: `add_repo` on cloud is denied from some seats, including the #13483 seat on 2026-09-01, while the 2026-08-24 reading (commit be21955ba) and the #12400 one prove cloud-capable seats exist). WHY THIS WAS SECURITY-SHAPED RATHER THAN COSMETIC: the tombstone this ledger records one key above used to tell every upgrading author, verbatim, \"If you were relying on [loading.sandboxing] for isolation, you had none — use the plugin trust tier (`manifest.runtime`) and the permission declarations, which are enforced\" — a redirection at a tier this repo does not enforce at load, i.e. ADR-0049 false compliance with a shipped migration message attached. Maintainer ruling 2026-08-30 (executed by commit a9ee98992) took option B — correct the text, do not retire the key — and explicitly ruled OUT retirement, because deleting the key would tear out the marketplace's trust-gate input; the tombstone, both `runtime` describes and the ADR-0087 D3 entry `plugin-manifest-loading-retired` state the publish-gate/load-side split. Adding load-side enforcement is the open half, tracked as a v18 direction, not as a removal — if it lands, the verdict here flips to `live` with local evidence." }, "packaging": { "status": "live", "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. 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." + "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 (commit 9ee2dcfbd) — 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." }, "main": { "status": "live", @@ -274,7 +274,7 @@ "status": "dead", "verifiedAt": "2026-08-30", "evidenceScope": "cross-repo", - "note": "Per-file content digests of the packaged artifact. Status left `dead` per the #13464 mandate: this row is the enforce-or-remove worklist entry for the RULED leg — re-verification at install/load-time unpack (ADR-0025 §3.5 step 5) — which still has zero implementation in this repo and is owned by the future runtime loader (ADR-0025 §3.5 steps 4–7), NOT by the cloud control plane, tracked on #11331 (cloud leg unmeasured, see `_note`); #13464 does NOT discharge it. What #13464 DID land, correcting this note's earlier census: the map is computed at build (packages/cli/src/commands/plugin/build.ts#PluginBuild via computeIntegrity) and is now read once in-repo — the `os plugin publish` preflight self-checks the artifact bytes against the manifest's own declared digests and refuses the publish on digest mismatch, missing declared file, or extra undeclared file (packages/cli/src/commands/plugin/publish.ts#PluginPublish, calling packages/core/src/security/plugin-artifact-integrity.ts#verifyIntegrity; absent map = permissive pass — the field is `.optional()`). So the false-compliance shape is narrowed, not closed: a publisher can no longer upload an artifact whose bytes contradict its own map, but nothing at unpack re-checks what a marketplace consumer actually installs. Adjacent machinery that DOES exist and is not this: packages/core/src/security/plugin-artifact-signature.ts verifies an artifact SIGNATURE (and returns `verified=false` rather than throwing when absent) — a different mechanism on a different field. If ledger semantics require the new non-test reader to flip this row to `live`, that flip belongs to the #11331 resolution / review chain, not to a rider here." + "note": "Per-file content digests of the packaged artifact. Status left `dead` per the #13464 mandate: this row is the enforce-or-remove worklist entry for the RULED leg — re-verification at install/load-time unpack (ADR-0025 §3.5 step 5) — which still has zero implementation in this repo and is owned by the future runtime loader (ADR-0025 §3.5 steps 4–7), NOT by the cloud control plane, and still unbuilt (cloud leg unmeasured, see `_note`); #13464 does NOT discharge it. What #13464 DID land, correcting this note's earlier census: the map is computed at build (packages/cli/src/commands/plugin/build.ts#PluginBuild via computeIntegrity) and is now read once in-repo — the `os plugin publish` preflight self-checks the artifact bytes against the manifest's own declared digests and refuses the publish on digest mismatch, missing declared file, or extra undeclared file (packages/cli/src/commands/plugin/publish.ts#PluginPublish, calling packages/core/src/security/plugin-artifact-integrity.ts#verifyIntegrity; absent map = permissive pass — the field is `.optional()`). So the false-compliance shape is narrowed, not closed: a publisher can no longer upload an artifact whose bytes contradict its own map, but nothing at unpack re-checks what a marketplace consumer actually installs. Adjacent machinery that DOES exist and is not this: packages/core/src/security/plugin-artifact-signature.ts verifies an artifact SIGNATURE (and returns `verified=false` rather than throwing when absent) — a different mechanism on a different field. If ledger semantics require the new non-test reader to flip this row to `live`, that flip belongs to whatever builds that load-time re-verification, not to a rider here." } } } diff --git a/packages/spec/liveness/permission.json b/packages/spec/liveness/permission.json index 7907b5126da..969b43f678e 100644 --- a/packages/spec/liveness/permission.json +++ b/packages/spec/liveness/permission.json @@ -51,40 +51,40 @@ "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#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." + "note": "Proof-bound 2026-08-23 (commit de6e2bfcb, adopting the adjudicated proposal of commit 976b687ab): 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 (commit 9ee2dcfbd) 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-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#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." + "note": "Proof-bound 2026-08-23 (commit de6e2bfcb) — 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 (commit 9ee2dcfbd) 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-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#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." + "note": "Proof-bound 2026-08-23 (commit de6e2bfcb) — 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 (commit 9ee2dcfbd) 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-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#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." + "note": "Proof-bound 2026-08-23 (commit de6e2bfcb) — 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 (commit 9ee2dcfbd) 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-09-25", "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/core/src/security/effective-object-permissions.ts#annotateEffectiveApiOperations (`const exportBit = acc.allowExport ?? wildExport` — the per-object bit and the `'*'` wildcard, for the /me/permissions projection the frontend renders, composed by `buildEffectiveObjectPermissions`)", - "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. 2026-09-25: REPOINTED again (#18783) — `annotateEffectiveApiOperations` moved out of plugin-hono-server's current-user-endpoints.ts into @objectstack/core's security/effective-object-permissions.ts, where `buildEffectiveObjectPermissions` composes it for both /auth/me/permissions and ISecurityService.getEffectiveObjectPermissions; plugin-hono-server re-exports the name, so the old file no longer names `allowExport` at all (0 mentions, 7 in the new file) and the key-mention signal reported it UNANCHORED. Re-closed by hand: the annotate leg still reads the per-object bit before the `'*'` fallback, `enforceExportPermission` still answers 403 EXPORT_NOT_PERMITTED off `security.canExport`, and `canExport` still asks `checkObjectPermission('export', …)`." + "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 line bound (commit 905019b1b) could not see it; the key-mention signal is what found it. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — 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 line bound (commit 905019b1b) could not see it\"). Re-closed by hand against c459da6bc. 2026-09-25: REPOINTED again (#18783) — `annotateEffectiveApiOperations` moved out of plugin-hono-server's current-user-endpoints.ts into @objectstack/core's security/effective-object-permissions.ts, where `buildEffectiveObjectPermissions` composes it for both /auth/me/permissions and ISecurityService.getEffectiveObjectPermissions; plugin-hono-server re-exports the name, so the old file no longer names `allowExport` at all (0 mentions, 7 in the new file) and the key-mention signal reported it UNANCHORED. Re-closed by hand: the annotate leg still reads the per-object bit before the `'*'` fallback, `enforceExportPermission` still answers 403 EXPORT_NOT_PERMITTED off `security.canExport`, and `canExport` still asks `checkObjectPermission('export', …)`." }, "allowTransfer": { "status": "live", "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." + "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 (commit 9ee2dcfbd) 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", @@ -102,28 +102,28 @@ "status": "live", "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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-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 (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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — 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-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 (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." + "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 (commit 9ee2dcfbd) — path-only citations upgraded to anchors. Re-closed by hand against c459da6bc." }, "writeScope": { "status": "live", "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#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." + "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 (commit 9ee2dcfbd) 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." } } }, @@ -134,13 +134,13 @@ "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#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." + "note": "FLS read-mask. Proof-bound 2026-08-23 (commit de6e2bfcb, adopting the adjudicated proposal of commit 976b687ab): 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 (commit 9ee2dcfbd) 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-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." + "note": "FLS write-deny. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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." } } }, @@ -149,14 +149,14 @@ "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#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." + "note": "Re-verified 2026-08-23 (commit de6e2bfcb) 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 (commit 9ee2dcfbd) 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-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#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." + "note": "Re-verified 2026-08-23 (commit 905019b1b) 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 (commit de6e2bfcb, PR #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 commit 905019b1b'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 (commit 9ee2dcfbd) 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": { @@ -164,7 +164,7 @@ "status": "live", "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) 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", @@ -182,39 +182,39 @@ "status": "live", "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "operation": { "status": "live", "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." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "using": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/plugins/plugin-security/src/rls-compiler.ts#compileFilter (`policy.using` is the predicate on the read pass, and on a `check` pass for a policy that declares no `check`); packages/plugins/plugin-security/src/security-plugin.ts#writeCheckPolicies (hands USING-only policies to that `check` pass only when no applicable policy for the write declares `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. 2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc. 2026-09-27: the evidence now also names `writeCheckPolicies`, which decides when a `using` stands in as the check (#19967); re-read against 0d3ec47137." + "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 (commit 9ee2dcfbd) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc. 2026-09-27: the evidence now also names `writeCheckPolicies`, which decides when a `using` stands in as the check (#19967); re-read against 0d3ec47137." }, "check": { "status": "live", "verifiedAt": "2026-09-27", "proof": "packages/qa/dogfood/test/showcase-d3-d4-capabilities.dogfood.test.ts#showcase-d3-d4-capabilities", "evidence": "packages/plugins/plugin-security/src/security-plugin.ts#writeCheckPolicies (the policies that declare `check` when any applicable policy for the write does, otherwise each applicable policy's `using`); packages/plugins/plugin-security/src/rls-compiler.ts#compileFilter (`policyDeclaresClause(policy, 'check') ? check : policy.using` on the `check` pass, OR-combined)", - "note": "2026-08-28: RE-ANCHORED (#13003) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc. 2026-09-27: RE-ANCHORED (#19967) — the cited `check ?? policy.using` expression left compileFilter when the per-operation selector `writeCheckPolicies` took over the choice (#19952); both halves re-read against 0d3ec47137." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — path-only citation upgraded to an anchor. Re-closed by hand against c459da6bc. 2026-09-27: RE-ANCHORED (#19967) — the cited `check ?? policy.using` expression left compileFilter when the per-operation selector `writeCheckPolicies` took over the choice (#19952); both halves re-read against 0d3ec47137." }, "positions": { "status": "live", "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." + "note": "flat match — no subordinate rollup (ADR-0090 D3 rename). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — the citation was still ACCURATE; grammar migration only. Re-closed by hand against c459da6bc." }, "enabled": { "status": "live", "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." + "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 (commit 9ee2dcfbd) — 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", From f64ad2156e449e02a089e100863ad4ac7d25d342 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 04:36:35 +0000 Subject: [PATCH 2/3] docs(changeset): the liveness re-anchor note states what holds for every rewritten note The release note said each rewritten note names the deciding commit; one note (the manifest integrity row) states its decision in words instead. The sentence now says each note either names the commit or says what was decided. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- .changeset/20234-liveness-ledger-provenance-anchors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/20234-liveness-ledger-provenance-anchors.md b/.changeset/20234-liveness-ledger-provenance-anchors.md index b5814d3b9c8..7bff40e9d41 100644 --- a/.changeset/20234-liveness-ledger-provenance-anchors.md +++ b/.changeset/20234-liveness-ledger-provenance-anchors.md @@ -7,8 +7,8 @@ The `manifest`, `dataset` and `permission` liveness ledgers cite the commit that Clause-②: no Notes in these three ledgers named GitHub issues that no longer exist, so a reader could -not tell why a row carries its verdict. Each such note now names the commit that made the -decision and, where the number alone carried the meaning, says what was decided. The +not tell why a row carries its verdict. Each such note now either names the commit that +made the decision or, where the number alone carried the meaning, says what was decided. The `liveness/` ledgers ship in this package's tarball, which is why this is a release note at all. Note text only: no row's status, evidence, proof or date changes, and no schema, export or runtime behaviour changes. From 7db6d77c68513dca88d72fc9bcbde17bc4649580 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 04:39:05 +0000 Subject: [PATCH 3/3] docs(changeset): the liveness re-anchor headline states what holds for every rewritten note The headline said the ledgers cite the deciding commit for each note; one note (the manifest integrity row) states its decision in words instead. The headline now names both forms. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- .changeset/20234-liveness-ledger-provenance-anchors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/20234-liveness-ledger-provenance-anchors.md b/.changeset/20234-liveness-ledger-provenance-anchors.md index 7bff40e9d41..749843e5c02 100644 --- a/.changeset/20234-liveness-ledger-provenance-anchors.md +++ b/.changeset/20234-liveness-ledger-provenance-anchors.md @@ -2,7 +2,7 @@ '@objectstack/spec': patch --- -The `manifest`, `dataset` and `permission` liveness ledgers cite the commit that decided each note instead of a tracker number that no longer resolves +The `manifest`, `dataset` and `permission` liveness ledgers cite the commit that decided each note, or say the decision in words, instead of a tracker number that no longer resolves Clause-②: no