# P7b: non-serving CatalogSnapshot and RuntimeMetadataView

> **Status:** implemented non-serving build and verification mechanism. This
> document does not approve serving, activation, or a Runtime cutover.
>
> **Current-tree evidence revision:** NexusCRM
> `ecb7a11b4d854ca32f284e561bec9866df317887`.
>
> **Machine-readable evidence:**
> [`runtime-metadata-snapshot-p7b.json`](runtime-metadata-snapshot-p7b.json).
> The P7a per-accessor inventory remains
> [`runtime-metadata-closure-p7a.json`](runtime-metadata-closure-p7a.json).

## 1. Decision and ownership

P7b adds one app-blind mechanism at
[`nexuscrm-metadata/catalogsnapshot`](../../../nexuscrm-metadata/catalogsnapshot):

```text
explicit Catalog-owned revision input
  -> deterministic complete CatalogSnapshot
  -> exact AppInstallation/capability projection
  -> least-privilege RuntimeMetadataView
  -> immutable SHA-256-addressed artifact store
```

This is not a second authority. The compiler accepts the Catalog revision and
an opaque effective `AppInstallationID` scope; it does not create, update,
activate, migrate, or validate AppInstallation lifecycle state. A1c remains the
sole owner of AppRelease, AppInstallation, HandlerBinding, their writer and
their projection publication. P7b contains no PackManifest reader or legacy App
adapter.

The input shape consumes, without reinterpreting, the P7b handoff frozen in
[`app-authority-contract.json`](../../design/app-authority-contract.json): an
opaque Catalog-authored AppInstallation identity, capability-bound effective
scope, Catalog revision/compatibility lineage, and the prohibition on an App
lifecycle writer or PackManifest fallback. A1c may replace the checked neutral
input adapter with its authoritative projection later; the compiler contract
does not wait for or compete with that implementation.

The compiler package lives below the independent Runtime composition and is not
imported by it. P7c/S1 owns the future request adapter, `ServingAdmission`,
active-revision selection, and the atomic switch away from the current sources.
A Catalog snapshot can be compiled with zero Runtime projections, so P7b does
not wait for A1c publication. Creating a view still requires an exact
Catalog-supplied installation/capability scope and fails closed while it is
absent.

## 2. Re-derived independent Runtime consumer map

The scope is the two independently deployable Runtime roles, not every metadata
consumer in the legacy monolith.

### 2.1 `runtime-api`

The executable command still invokes
`runtimeapi.AssembleNonServingBootstrap` and cannot serve
([`backend/cmd/runtime-api/main.go`](../../../backend/cmd/runtime-api/main.go)).
The candidate production graph remains `runtimeapi.AssembleProduction`, which
opens the Runtime database and builds `runtimeassembly.NewRecordsReadRuntime`
([`backend/internal/runtimeapi/process.go`](../../../backend/internal/runtimeapi/process.go),
[`backend/internal/runtimeassembly/records_read.go`](../../../backend/internal/runtimeassembly/records_read.go)).

The reachable metadata consumers in that candidate graph are finite:

| Consumer | Current source/fallback | Revision assumption | P7c/S1 action |
|---|---|---|---|
| Runtime handler receipts | Independent `ReceiptSource.Receipts` read; bootstrap receipts are zero-generation | Receipts are not coupled to schema/policy pointers | Bind receipts and view once in ServingAdmission |
| Records root schema | Process-global `MetadataService.GetSchemaOrError`; cache miss may read a raw repository schema | Root may be cached or raw, with no CatalogRevision | Use the request-bound view |
| Permissions CRUD/FLS/RLS metadata | Repeated MetadataService schema/relationship calls; sharing rules have a separate five-minute cache/repository path | Recursive calls may observe different global pointers | Use the same bound view for object, field, parent, target-record and rule facts |
| Analytics selected-root query | Carries the root schema but delegates RLS metadata to PermissionService | Root pointer is locally stable; recursive graph is not revision-bound | Use only the bound view and carried schema |
| Persistence/formula routing | Physical fields/base read source and request-dependent formula-view choice come from the carried schema | Inherits the root pointer's unstated generation | Derive all routing from the bound view/schema |

The precise accessors, caches, repository fallbacks and outside-closure facts
remain anchored in the P7a manifest. P7b rechecked those anchors at the evidence
revision and adds a shorter machine map of the consumers that must switch
together.

### 2.2 `runtime-worker`

`runtime-worker` remains a no-work skeleton with no capability, engine graph,
metadata repository, cache, or metadata reader
([`backend/cmd/runtime-worker/main.go`](../../../backend/cmd/runtime-worker/main.go)).
It therefore has no P7b projection consumer and no P7c switch in the current
tree. V2 must reopen this inventory when real durable work enters the role.

## 3. Artifact model

### 3.1 Distinct version axes

The Go model uses distinct named types and fields:

| Axis | Meaning |
|---|---|
| `CatalogRevision` | One immutable effective Catalog publication for an org |
| `SchemaGeneration` | Physical-schema compatibility floor |
| `EngineGeneration` | Runtime implementation compatibility |
| `DefinitionVersion` | Version of one executable definition |

`CatalogRevision`, `SchemaGeneration`, and `EngineGeneration` are mandatory and
non-zero on every snapshot. Every object, relationship, rule, definition and
projection compiler input carries source `CatalogRevision`; any missing or
different value rejects the complete build. Component revision labels are
compiler provenance checks and are not repeated as competing per-object
authorities in the canonical artifact.

`DefinitionVersion` appears only on an optional definition descriptor. The
current selected-root projection includes no executable definitions, so it does
not receive a synthetic DefinitionVersion.

### 3.2 Canonical serialization and digest

The two closed schemas are:

- `nexuscrm.catalog-snapshot/v1`;
- `nexuscrm.runtime-metadata-view/v1`.

Canonical JSON sorts compiler input collections and known unordered reference
sets case-insensitively with a deterministic case-sensitive tie break. Map keys
use Go's deterministic JSON order. Metadata lists whose evaluation order can
affect behavior remain ordered. Unknown fields, trailing content and
non-canonical encodings fail closed.

The content address is lowercase `sha256:<64 hex>` over the exact canonical
bytes. Opening an artifact recalculates the digest, rejects kind/schema
mismatches, validates the closed shape and policy/relationship closure, and
requires a byte-identical canonical round trip. The Runtime view additionally
records the exact source snapshot digest and lineage. `VerifySource` regenerates
the scoped projection from that snapshot and requires the digest to match; a
structurally valid but incomplete or widened view therefore fails closed as
well as a different org, revision, generation, or source digest.

Artifact compatibility is explicit and fail closed. V2 remains the historical
profile-bound non-mutation grammar. The short-lived v3 Catalog mutation grammar
is accepted only as a migration source: its retired component-scope index is
strictly decoded and validated but never used to select behavior, and projection
regenerates a v4 Runtime view from the complete effective closure without
carrying its ambiguous mutation definition selection forward. A persisted
v3 mutation Runtime view is rejected because its already-filtered bytes cannot
prove that omitted effective rules are recoverable; it must be regenerated from
the source Catalog snapshot. V4 is the current mutation grammar and does not
carry that index. Unknown versions are rejected rather than partially decoded.
For every opened view, local physical `SchemaGeneration` must be at least the
snapshot's required floor and `EngineGeneration` must match exactly;
`VerifyCompatibility` rejects either mismatch before the view is usable.

### 3.3 Immutable store

`DirectoryStore` exposes only:

```text
Put(exact artifact)
Get(exact kind, exact digest)
```

Publication durably creates and syncs every new directory entry, writes and
syncs a private staging file, then uses a same-directory hard link so an
existing digest path cannot be replaced. Every successful writer syncs the
publication directory, including a concurrent writer that observes an
identical existing target. Concurrent identical writes converge. Existing
different or corrupt bytes fail integrity checks. Every read verifies digest,
canonical bytes and schema again.

The store deliberately has no current/latest/list/watch/TTL/activation method.
TTL freshness can therefore never become activation authority accidentally.

## 4. Complete snapshot and least-privilege Runtime projection

`CatalogSnapshot` records the complete effective object/field metadata supplied
by Catalog, including the physical field names omitted from ordinary model JSON,
the effective relationship graph, sharing-rule definitions, definition
lineage, and every explicit Runtime projection scope for the revision.

`RuntimeMetadataView` does not copy the complete object model. For the current
independent read closure it projects only:

- object API identity and required read permission;
- the read-reachable access-policy expression closure and row policy;
- sharing model and hierarchy flag;
- effective fields required for scalar typing, exact decimal, physical column,
  formula/read routing, read protection and relationship semantics;
- validated physical base read source plus the field/formula facts used for
  request-dependent read-target routing;
- the minimal child-to-parent edge and relationship type used for
  sharing-parent read semantics, without relationship row identity,
  presentation or mutation behavior;
- sharing-rule policy expressions and the role/group reference IDs required to
  evaluate them for included objects, without rule row identity or display
  name.

Mutation hooks, validators, default child rows, write policy, layouts,
navigation, frontend facts, Integration payloads, secrets, record data, users,
sessions and effective grants are structurally absent. Role/group entities,
memberships and generated share rows are also absent: the sharing-rule
references above are metadata inputs to, not copies of, the independently owned
SecurityEpoch state.
Object/field labels, internal metadata row IDs, create/update flags, create
defaults, writable-reference targets and child-list relationship names are also
absent because the selected-root read closure does not consume them.

Projection is selected by exact `(AppInstallationID, capability)` from one
opened snapshot. It returns only the explicit object and definition scope.
Unrelated AppInstallation objects and definitions are excluded. Ordinary
lookup relationships do not pull target-object metadata into this root-only
view.

`targetRecordRead` is the explicit exception required by the current read
contract. PermissionService scans every schema to discover caller-readable
dynamic targets and then recursively builds each target's RLS. Catalog cannot
narrow that set without importing per-user SecurityEpoch state. Therefore, if
any explicitly projected object has `rowRead.kind=targetRecordRead`, the
explicit object list must equal the complete same-revision Catalog object set.
The compiler fails publication and names every missing object; it never widens
the list implicitly. Those schemas are policy-reachable dependencies, not
unrelated App payload, while definitions remain capability-scoped.

A projected sharing-parent child cannot omit its parent, and a read-policy
expression cannot reference an object outside the view; either case rejects the
entire view.

Readers receive deep copies. Mutating an object, field, relationship or rule
returned by a view cannot change later reads or the artifact digest.

## 5. Determinism, conflicts and parity

The compiler has stable conflict kinds for missing/mixed revisions, invalid
identity, duplicate object/field/relationship, ambiguous sharing parent,
invalid read source, relationship, sharing rule, definition, projection and
encoding failures.

The stricter publication results are intentional and remain visible for P7c:

| Case | Observed legacy behavior | P7b publication behavior |
|---|---|---|
| Duplicate case-folded object or field | Map/index construction can retain one value | Reject the revision |
| Missing/ambiguous sharing parent | Permission lookup can degrade to no parent | Reject the revision |
| Repository/decorator dependency failure | Some legacy paths can degrade to empty or undecorated output | The owner must not invoke the compiler without complete effective input |
| Invalid non-empty sharing-rule criteria | Can fail later while a request is evaluated | Always run the kernel-owned Runtime-equivalent criteria normalizer and formula validator before publication |

`CompareRuntimeView` is a repository-free parity oracle. At one controlled
state, a test-only adapter captures the current effective MetadataService output
after decorator and relationship assembly, compiles it, projects the view and
compares every projected object/field, physical/read-source, access/RLS,
relationship and sharing-rule behavior fact. Relationship identity,
presentation and mutation attributes plus sharing-rule identity/name are
structurally excluded; required role/group references remain policy inputs.
The adapter exists only in `_test.go`; no
production source can call it.

The selected-root parity fixture covers the scalar metadata family, exact
decimal precision/scale, read-protected fields, parent RLS, sharing rules,
physical aliases and post-decorator virtual fields. Existing selected-root
goldens remain the behavioral oracle for CRUD/FLS/RLS/outcome/cancellation;
P7b proves the metadata facts consumed by that path are byte-equivalent without
making the view selectable by the engine.

## 6. Executable non-serving proof

The boundary test
[`serviceification_runtime_metadata_snapshot_test.go`](../../../backend/internal/boundary/serviceification_runtime_metadata_snapshot_test.go)
enforces all of the following:

1. every current consumer entry remains anchored in the current source;
2. `go list -deps` for every production backend command (including
   `runtime-api` and `runtime-worker`) excludes
   `github.com/nexuscrm/metadata/catalogsnapshot` and every future subpackage;
3. the compiler package and future subpackages cannot depend on Runtime,
   Records, Permissions, Analytics, Persistence or repository ports;
4. no production Runtime command, assembly package, image or manifest names an
   artifact schema or imports the package;
5. no compiler/store file adds environment activation, a ticker, or a
   current/latest/watch/activate selector;
6. the existing P7a guard continues to require
   `AssembleNonServingBootstrap` in the executable command.

Tests may instantiate compiler/store/view directly. Production composition
cannot select it.

## 7. Exact P7c/S1 follow-up

After A1c and release-publication prerequisites exist, P7c/S1 must perform one
ownership flip:

```text
P6 SecurityAdmission installs TrustedCall
  -> ServingAdmission validates AppInstallation + SecurityEpoch
     + CatalogRevision + SchemaGeneration + EngineGeneration + digest
     + exact source projection through VerifySource
  -> bind one exact RuntimeMetadataView and receipts to the request
  -> switch Records, Permissions, Analytics and Persistence/formula routing
  -> delete or make unreachable every process-global/repository fallback
```

P7c must introduce the request-bound adapter; P7b deliberately does not make
the view implement or replace today's broad engine interfaces. Missing scope,
snapshot, digest, revision or compatibility must fail before record repository
access. Restart, stale replica, concurrent publication and mid-request
activation tests then prove one request cannot mix revisions.

Until that unit lands, P7b remains evidence and preparation only. It does not
complete P7c/S1, V1, a route cutover, or production serving.
