You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 60bcfe7
Browse filesBrowse the repository at this point in the historyBrowse files
Part of #15204
Clause-②: no (narrowing)
Stage **7-pre** of the ADR-0131 cutover (claim amendment 6105462924;
stage 0 item U1 of 6095755866). #15204 stays open: this is one stage of
its plan, and the later stages (6b-2, 7a, 7b, 8) are not addressed here.
## U1, measured on `main` `a18c514965` (re-read after merging
`490cb6d9fa`): the action retires
**1. What an "overlay" is now.** An active, environment-wide
`sys_metadata` row (`organization_id` null) of type `permission` or the
legacy `permissions`, stored under the name of a permission set a code
package ships (`classifyPackagedPermissionSet` verdict `packaged`). It
is not a `sys_permission_set` row. The removed module found it with
`findActiveOverlayRows` and gated it on that classifier.
**2. Can any caller-reachable path still create one? No.** Every door
was measured:
- **Metadata door.** `saveMetaItem` asks `refusePackagedBaseOverride`,
which refuses an in-place write over a packaged item of a type with no
overlay channel (`isSealedManagedItem`). `permission` is registered
`allowOrgOverride: false`. ADR-0131 D6: the `OS_METADATA_WRITABLE` hatch
"is not consulted" for managed content, on every topology.
`SysMetadataRepository.assertAllowed` repeats the refusal at the store
(`packages/metadata-protocol/src/protocol.ts`, `packagedBaseRefusal` and
its header).
- **Data door on `sys_metadata`.** The object is `apiMethods: ['get',
'list']` (`packages/metadata-core/src/objects/sys-metadata.object.ts`),
so it is read-only over the data API.
- **Data door on `sys_permission_set` and Setup.**
`packaged-permission-set-lock.ts` refuses a save over a packaged set
with 403 `NOT_OVERRIDABLE`, naming Clone ("No silent overlay row is ever
minted again"). The metadata-door half of that lock is
`packaged-permission-set-lock-gate.ts`.
- **Package install.** A package that declares a name the environment
catalog holds is refused with 422 `NAMESPACE_CONFLICT`, on a hot install
and on a cold boot (`refuseSecurityCatalogNameConflicts`;
`BUILT_IN_SECURITY_CATALOG_NAMES.permission` is empty, so no
permission-set name is exempt).
- **Rows stored before those refusals.** The cold boot refuses the
deployment right after `sys_metadata` hydrates:
`ObjectQLPlugin.refuseEnvironmentHeldSecurityCatalogNames` reads the
registry's `environmentHeldSecurityCatalogConflicts` (ADR-0048 N.3,
registered as `security-catalog-environment-overlay-refused`; pinned by
`packages/runtime/src/standalone-stack-security-catalog-one-holder.test.ts`,
"artifact boot over one database …"). So a server that could answer the
discard route never holds an overlay. The remedy that exists for those
rows is the offline `os migrate security-catalog-overlays [--apply]`
(maintainer ruling letter B on #22371).
**3. What the discard did, and whether the resolver reads it.** It
deleted the overlay row(s) under `SYSTEM_CTX`, then re-projected the
`sys_permission_set` row through `projectPermissionMutation`. An overlay
row would be read: hydration puts it in the registry's bare slot, which
the security catalog reader serves. But no running server holds one
(point 2). The re-projected row is not read for grants: since stage 1
the resolver reads the security catalog, and since stage 2a/2b the
services readers do too.
**Decision rule → RETIRE.** No path creates an overlay. The operator
workflow for existing databases is already served by the offline step,
so no product choice is left open.
**Four axes** (`.claude/skills/pm-dispatch/SKILL.md`, 升级与决策):
- **Real business need (measured):** none at runtime. On this tree the
action's target state cannot exist on a running server. The pull it
served (field-reported forks) is served by `os migrate
security-catalog-overlays`, which runs where the action cannot: before
boot.
- **Long-term soundness (leads):** one remedy, offline, ahead of the
boot that refuses, instead of a second in-server path that can no longer
fire. A name-keyed rewrite would build a transition piece for a state
the cutover removes (#22601 → B).
- **AI-error-proofing:** a declared route, Setup action and contract
member that can never succeed invite callers to rely on them. Removing
them leaves one prescription, named by the cold-boot refusal itself.
- **Startup focus:** retire now, with no deprecation window; about 1.5k
lines leave the tree.
## What changed
- **Deleted:** `plugin-security/src/permission-set-overlay-discard.ts`
and its test, and the dogfood eligibility test.
- **plugin-security:** the service wiring and the five exports go. The
**Discard Overlay** action on `sys_permission_set` goes (translations
regenerated). The `drift_status` field description, the drift
diagnostic's `overlay_shadow` detail and its comments lose their
reference to the action (deletions in a 6b-2 module). The boot reading
`reportPackagedPermissionSetOverlays`
(`packaged-permission-set-overlay-detection.ts`) now names the offline
step instead of the action. Comment references in the lock, the
projection and `write-refusals.ts` are trimmed.
- **rest:** the route and its route-ledger row go, and a RETIRED note
takes the row's place. Its rows in two rest ledger tests go.
- **spec:**
- `ISecurityService.discardPermissionSetOverlay` and
`PermissionSetOverlayDiscardResult` are removed, with their contract
test.
- `ERROR_CODE_LEDGER['@objectstack/plugin-security']` drops
`INVALID_STATE` and `NOT_FOUND`: the removed action was their only
producer in that package. Both stay under `@objectstack/rest`, so the
union is unchanged. The `NOT_FOUND` waiver reason and the ledger test
follow.
- New ADR-0087 semantic entry
`security-permission-set-overlay-discard-retired`.
- `api-surface/` and `export-origins/` are regenerated.
- **Kept, per the stage plan:** `packaged-permission-set-lock` and its
gate. `packaged-permission-set-overlay-detection` keeps its consumer:
the boot reading in `security-plugin.ts` calls it, and the discard never
imported it.
- **Docs:** `permission-sets.mdx` (the "Overlay shadow" remedy),
`system-context.mdx` (row 14 removed; census counts regenerated),
`metadata-lifecycle.mdx` and `environment-variables.mdx` (the
permission-set exception), and `tenant-audit-census.mdx` with its counts
ledger (one write site fewer). Nothing under `content/docs/releases/`.
- **Gate ledgers:** `engine-double-contract.pinned.json` (regenerated; 2
rows of the deleted test) and `objectql-double-limit.baseline.json` (its
entry, shrink-only).
- **Changeset:** `spec`, `plugin-security` and `rest` are `minor`,
BREAKING (the epic's launch-window convention), with a removed → instead
table and the ADR-0087 `registered` marker.
## Acceptance notes
- **Contract review owed.** This touches non-test `packages/spec/src/**`
(the contract, the error-code ledger and the migration entry), so a
same-head contract-tier PASS is owed before `pr_ready`.
- **Two pending changesets name the action** (not edited here;
`check-empty-changeset` refuses an edit to another PR's note, and
correcting a pending release note is the seat's call):
- `.changeset/22307-cold-boot-catalog-refusal.md` tells an operator to
use Discard Overlay "on the release you run now", before upgrading. That
stays true of the release they run, where the action exists.
- `.changeset/15206-reads-environment-only.md:26` says a packaged-set
fork "keeps its own ruling (detection reading and Discard Overlay) and
is still served". After this PR the "Discard Overlay" half is false. The
suggested correction is "(the detection reading)".
- `.changeset/22719-rest-write-hook-refusal-sentence.md:16` (landed on
`main` while this PR was open) lists `POST
/security/permission-sets/:id/discard-overlay` among the routes it
repaired. Once this PR lands, that route no longer exists. The suggested
correction is to drop it from that row.
- **objectui pin: NOT MEASURED.** No checkout of `../objectui` is
available here. The removed surface is a server route, a declarative
action and a contract type. An objectui file importing
`PermissionSetOverlayDiscardResult` or naming `discard-overlay` would be
the Console Pin Gate's red.
- Row 14 of `system-context.mdx` leaves a gap in its numbering (13 →
15). Renumbering would move every later row's key that other text cites.
## Verification (head `8f992cde3d`: the change plus one no-rebase merge
of `origin/main` `490cb6d9fa`)
- **Package suites:**
- `plugin-security`: test exit 0 (4003 passed, 45 skipped); typecheck
exit 0.
- `rest`: test exit 0 (5235 passed, 326 skipped); typecheck exit 0.
- `spec`: test exit 0 (19220 passed); typecheck exit 0.
- `client`: test exit 0 (653 passed); typecheck exit 0.
Each was run as `pnpm --filter PKG test` and `typecheck` behind
`os-verify-lock`, after `pnpm install --frozen-lockfile` and a closure
build.
- **Targeted:**
- The runtime cold-boot refusal pin
`standalone-stack-security-catalog-one-holder.test.ts`: 6/6.
- Dogfood `route-ledger-live-mount-parity`,
`permission-set-lock-row-provenance` and
`permission-set-clone-boot-unowned-warning`: 25/25.
- The full `runtime` and `dogfood` suites are left to CI. This is a
declared narrowing: neither package's source is in the diff, and the one
dogfood test file in the diff is deleted.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran` reports 131 derived, 131 run, 0 NOT
MEASURED, 0 UNRUN, and every family exits 0. That includes
`check:generated` for spec, `check:i18n`, `check:i18n-stale-fill`, the
system-context and tenant-audit censuses, `check:adr-0087-registration`,
`check:empty-changeset`, `check:engine-double-contract` and
`check:objectql-double-limit`. `check-route-ledger-census` also exits 0.
- **Census edits, from the measurement:**
- `execctx-consumer-census.test.ts`: 63 → 62 sites, 78 → 77 mentions, 47
→ 46 bare.
- The hook-refusal ledger: the write-row floor goes 45 → 44, and the
repaired rows 20 → 19.
---
_Generated by [Claude
Code](https://claude.ai/code/session_019TtY6pnoZSemcQRzbRUm5e)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(plugin-security,rest,spec)!: the permission-set "Discard Overlay" action, its REST route and its contract member are retired; no door creates the overlay it discarded, and a database holding one is refused at cold boot (ADR-0131 cutover, ADR-0048)
**BREAKING** — a narrowing of the public surface: one HTTP route, one Setup action and one optional contract member are removed. Shipped as `minor` under the launch-window convention for breaking changes.
14
+
15
+
The action deleted the environment-wide `sys_metadata` row that shadowed a package-declared permission set, then re-projected the set's `sys_permission_set` row. Neither half has anything left to do:
16
+
17
+
- No door creates such an overlay. The metadata door refuses a save over a packaged permission set on every topology, whatever `OS_METADATA_WRITABLE` says. The data door refuses an edit of one (clone it instead). The data API cannot write `sys_metadata`. A package that declares a name the environment holds is refused at install.
18
+
- An overlay stored before those refusals makes the cold boot refuse the deployment with `422 NAMESPACE_CONFLICT`, so a server that could answer the route never holds one.
19
+
- The authorization resolver reads the security catalog, not `sys_permission_set` rows, so the re-projected row granted nothing.
20
+
21
+
### What was removed
22
+
23
+
| removed | what to use instead |
24
+
| --- | --- |
25
+
|`POST /api/v1/security/permission-sets/:id/discard-overlay`|`os migrate security-catalog-overlays` with the flags and environment the deployment boots with lists every overlay row with no server running; `--apply` deletes them (a history tombstone per row). |
26
+
| the **Discard Overlay** action on `sys_permission_set` in Setup | the same offline step. To keep a customization of a packaged set, clone it under a new name (the **Clone** action) and assign the clone. |
27
+
|`ISecurityService.discardPermissionSetOverlay` and `PermissionSetOverlayDiscardResult` (`@objectstack/spec/contracts`) | nothing: delete the call and the import. `tsc` names each one. |
28
+
|`discardPermissionSetOverlay`, `PermissionSetNotFoundError`, `PermissionSetOverlayStateError` and the `PermissionSetOverlayDiscardDeps` / `PermissionSetOverlayDiscardResult` types (`@objectstack/plugin-security`) | nothing: delete the import. |
29
+
30
+
**The one-line fix:** stop calling the route or the member, and clear any overlay offline with `os migrate security-catalog-overlays --apply` before the first boot on this release.
31
+
32
+
`ERROR_CODE_LEDGER['@objectstack/plugin-security']` no longer lists `INVALID_STATE` or `NOT_FOUND`: the removed action was their only producer in this package. Both codes stay registered under `@objectstack/rest`, and the union of codes is unchanged.
33
+
34
+
The permission-set drift diagnostic no longer names the action in its `overlay_shadow` detail, and the boot reading of shadowed packaged sets names the offline step instead.
Copy file name to clipboardExpand all lines: content/docs/concepts/metadata-lifecycle.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -144,7 +144,7 @@ A permission set and a position also have a switch, outside the refusal: `POST /
144
144
145
145
The switches are operator-only where one install serves several organizations. The refusal names the same path whichever layer answers it: `403 NOT_OVERRIDABLE` for a write that names no base, and `403 ITEM_LOCKED` for one that names the read-only package. Which writes are refused, and with which code, is unchanged.
146
146
147
-
An environment row stored over a managed item of a type sealed against overlays (a flow, action, hook, object or datasource, for example — any type whose registry entry admits no overlay) before the seal — written through the `OS_METADATA_WRITABLE` hatch — is **not served** (ADR-0131 D6): every read, and boot, takes the package's definition, and the row stays at rest, untouched. Boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies: re-express the change as a new item under a new name, or delete the stored row. A stored fork of a code-declared permission set is the one exception: it keeps the detection reading and the operator's Discard Overlay action it already had.
147
+
An environment row stored over a managed item of a type sealed against overlays (a flow, action, hook, object or datasource, for example — any type whose registry entry admits no overlay) before the seal — written through the `OS_METADATA_WRITABLE` hatch — is **not served** (ADR-0131 D6): every read, and boot, takes the package's definition, and the row stays at rest, untouched. Boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies: re-express the change as a new item under a new name, or delete the stored row. A stored fork of a code-declared permission set is the one exception: the deployment does not boot while it holds one (ADR-0048), and `os migrate security-catalog-overlays` lists and, with `--apply`, deletes it.
Copy file name to clipboardExpand all lines: content/docs/deployment/environment-variables.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -306,7 +306,7 @@ that bypassed write hooks, `rebuildSearchCompanion` (from
306
306
|`OS_MARKETPLACE_CACHE`| enum |`on`|`off` disables the in-memory marketplace listing cache. |
307
307
|`OS_MARKETPLACE_PUBLIC_BASE_URL`| url | — | Public base URL of the marketplace registry (proxied from this runtime when set). |
308
308
| `OS_ALLOW_UNMASKED_OBJECT_METADATA` | boolean | `false` | Escape hatch for the metadata-plane field-level security mask ([ADR-0106](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0106-metadata-plane-fls-object-schema-masking.md) D8). By default every object schema served by `/meta` and `/metadata` is projected onto the fields the **calling user** may read, so a field they cannot read does not appear at all — not its name, label, type, picklist options, formula, `visibleWhen` predicate, `defaultValue`, or the `requiredPermissions` capability guarding it. Set to `1` to serve the full schema to every authenticated caller, as releases before this one did. This changes **disclosure only**: the data plane still masks values and refuses forbidden writes either way, and the console reads field affordances from `/auth/me/permissions`, so toggling it never changes UI correctness. The REST layer also honours a per-server `metadata.maskObjectFields: false`; this variable is the deployment-wide knob and covers the runtime `/metadata` dispatcher, which has no REST config to read. |
309
-
| `OS_METADATA_WRITABLE` | csv | — (none) | Comma-separated metadata type names (e.g. `hook,job`) granted a runtime escape hatch that treats them as `allowOrgOverride: true` for items **no managed package ships**: it opens runtime creation of a type whose registry entry allows none. It never opens an organization-scoped write: since [ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6 the metadata protocol refuses every organization-scoped write with `403 NOT_OVERRIDABLE`. It **never opens an item a managed package ships**: managed content is sealed ([ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6), so an overlay or a removal of a shipped flow, object, field, permission set, position or any other type without an environment overlay is refused with `403 NOT_OVERRIDABLE` whether the hatch is set or not. Customize managed content through its type's own route instead: an environment overlay for `view`, `dashboard`, `report`, `translation` and `email_template`; disable, or clone under a new name, for a flow; clone for a permission set. An overlay row this hatch wrote before over a managed item is kept at rest but **not served**: every read takes the package's definition, and boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies — re-express the change as a new item under a new name, or delete the row. A stored fork of a packaged permission set is the one exception: it is still served, with its detection reading and Discard Overlay. Removing that permission-set row restores the package's definition. A stored row over a managed item of the other types that merge overlays at read (`position`, `page`, `app`, `dataset`, `book`, `tool`, `skill`) can still be removed, but removing it changes nothing that is served: no read serves it, so the package's definition is already served. See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). |
309
+
| `OS_METADATA_WRITABLE` | csv | — (none) | Comma-separated metadata type names (e.g. `hook,job`) granted a runtime escape hatch that treats them as `allowOrgOverride: true` for items **no managed package ships**: it opens runtime creation of a type whose registry entry allows none. It never opens an organization-scoped write: since [ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6 the metadata protocol refuses every organization-scoped write with `403 NOT_OVERRIDABLE`. It **never opens an item a managed package ships**: managed content is sealed ([ADR-0131](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0131-total-organization-ownership-no-null-organization-id.md) D6), so an overlay or a removal of a shipped flow, object, field, permission set, position or any other type without an environment overlay is refused with `403 NOT_OVERRIDABLE` whether the hatch is set or not. Customize managed content through its type's own route instead: an environment overlay for `view`, `dashboard`, `report`, `translation` and `email_template`; disable, or clone under a new name, for a flow; clone for a permission set. An overlay row this hatch wrote before over a managed item is kept at rest but **not served**: every read takes the package's definition, and boot names each such row, per type (`[metadata_sealed_overlay_unserved]`), with the two remedies — re-express the change as a new item under a new name, or delete the row. A stored fork of a packaged permission set is the one exception: the deployment does not boot while it holds one (ADR-0048); `os migrate security-catalog-overlays` lists such rows and, with `--apply`, deletes them, which restores the package's definition. A stored row over a managed item of the other types that merge overlays at read (`position`, `page`, `app`, `dataset`, `book`, `tool`, `skill`) can still be removed, but removing it changes nothing that is served: no read serves it, so the package's definition is already served. See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). |
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.mdx
+9-10Lines changed: 9 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ the seed loader replaying package fixtures, a plugin's boot reconciler, a
10
10
service self-write, a migration.
11
11
12
12
This page is **the authority** for what that flag actually does. It exists
13
-
because the flag is not one concept: it is a single boolean read at **123
13
+
because the flag is not one concept: it is a single boolean read at **122
14
14
distinct sites across 20 packages**, and knowing three of those behaviours gives
15
15
no hint that the other hundred-and-four exist. Every documented app-side bug
16
16
traced to `isSystem` had the same shape — the metadata was complete and correct,
@@ -112,7 +112,6 @@ that silently does not happen.
112
112
| 11b | Position write-through skipped — a `sys_position` write is not mirrored into the environment ledger | plugin-security | Get: the seeders and the package door write rows for definitions that already have their home, untouched. Lose: a system-written row has no environment definition, so the security catalog read does not resolve it until a data-door edit, or the one-time row-only position backfill, gives it one |`packages/plugins/plugin-security/src/position-write-through.ts#createPositionWriteThrough`|
| 13 | Per-request performance timings disclosed | observability | Get: timing headers a normal caller cannot pull |`packages/observability/src/perf-timing.ts#isPerfDisclosurePrincipal`|
115
-
| 14 | Permission-set **overlay discard** skips the tenant-admin assertion | plugin-security | Get: an overlay can be discarded with no authenticated tenant administrator |`packages/plugins/plugin-security/src/permission-set-overlay-discard.ts#assertTenantAdmin`|
116
115
| 15 | MCP stdio bridge skips the object API-exposure gate | mcp | Get: the bridge reaches objects whose `apiEnabled` / `apiMethods` would refuse an external caller |`packages/mcp/src/stdio-data-bridge.ts#enforceApiExposure`|
117
116
| 16 |**Read-audit rows are not written**| plugin-audit | Lose: the "a person opened this record" trail. `sudo()` keeps the caller's `userId`, so this flag is the only thing separating a human read from a platform one |`packages/plugins/plugin-audit/src/read-audit.ts#installReadAuditWriter`|
118
117
| 17 | Approval snapshot payload redaction skipped, and so is the snapshot query guard | plugin-approvals | Get: the whole snapshot on `find` / `findOne` — the audit/replay channel — and a filter, sort or grouping by it on any read. Lose: field-visibility redaction over approval payloads, and the refusal of a query over the snapshot for a reader withheld a field of the objects it can reach |`packages/plugins/plugin-approvals/src/payload-redaction-middleware.ts#bindSnapshotRedactionMiddleware`, `packages/plugins/plugin-approvals/src/payload-predicate-guard.ts#bindSnapshotPredicateGuard`|
@@ -143,7 +142,7 @@ that silently does not happen.
143
142
144
143
### 3. Sharing (`plugin-sharing`)
145
144
146
-
The largest single consumer — **17 of the 123 sites**.
145
+
The largest single consumer — **17 of the 122 sites**.
147
146
148
147
| # | Behaviour when `isSystem`| What you get / what you lose | Anchor |
149
148
|:--|:---|:---|:---|
@@ -286,7 +285,7 @@ Ownership injection, `readonly` bypass and sharing materialisation are
286
285
independent decisions, and a seed loader plausibly wants the first two but not
287
286
the third. The concept is nevertheless **staying as one boolean**:
288
287
289
-
-**Shipped semantics.**`isSystem` is a published contract with 123 read sites
288
+
-**Shipped semantics.**`isSystem` is a published contract with 122 read sites
290
289
in 20 packages. Splitting it is a breaking contract change across all of them.
291
290
(The ruling was taken when the census read 80 sites in 18 packages; the count
292
291
has grown, which strengthens rather than weakens the argument.)
@@ -360,16 +359,16 @@ still holds equal to the census on every pull request:
360
359
| Appearances of the bare identifier `isSystem` in non-test sources | 813 | — |
361
360
| — parsed as a declaration | 28 | ✅ |
362
361
| — parsed as an object-literal / type key (producers and option objects) | 310 | — |
363
-
| — parsed as a property **read**|129| ✅ |
362
+
| — parsed as a property **read**|128| ✅ |
364
363
| — parsed in some other syntactic position (a local, a cast, a conditional) | 9 | ✅ |
365
364
| — the remainder: text inside comments and string literals | 358 | — |
366
365
| Of those reads: reads of one of the unrelated metadata fields | 6 | ✅ |
367
-
| Of those reads: reads of `ExecutionContext.isSystem`|**123**| ✅ |
368
-
| — behaviour-bearing (rows 1–61 above) |120| ✅ |
366
+
| Of those reads: reads of `ExecutionContext.isSystem`|**122**| ✅ |
367
+
| — behaviour-bearing (rows 1–61 above) |119| ✅ |
369
368
| — carry the flag onward only (rows 62–64 above) | 3 | ✅ |
370
369
| Packages containing at least one elevation read |**20**| ✅ |
371
-
| Files containing at least one elevation read |58| ✅ |
372
-
| — the distinct symbols those reads live in — what this page anchors |105| ✅ |
370
+
| Files containing at least one elevation read |57| ✅ |
371
+
| — the distinct symbols those reads live in — what this page anchors |104| ✅ |
373
372
| — of those files, the ones holding more than one read in one symbol | 8 | ✅ |
374
373
375
374
The six rows marked — are a **dated decomposition, not a live claim**: they were
@@ -433,7 +432,7 @@ same resolver, and the same registration shape, that holds `docs/adr/**`.
433
432
Renaming a symbol is now a loud red instead of a silent misdirection.
434
433
435
434
⚠️ **The precision that costs, priced here rather than buried.** A symbol anchor
436
-
cannot say WHICH read inside a function it means, and **8** of the **58**
435
+
cannot say WHICH read inside a function it means, and **8** of the **57**
437
436
anchored files hold more than one read inside a single symbol. So the population
438
437
check runs per file at symbol granularity: every file the census finds a read in
439
438
must be anchored, and the set of symbols this page cites into that file must
0 commit comments