# Stack Test Model

## Purpose

This document fixes the current PingCAP downstream stack test model in one
place:

- how upstream and downstream are split
- what artifact is actually tested
- how the independent local deployment scripts and remote deploy consume the downstream stack
- when fixes belong upstream vs downstream

## Repository Topology

This stack is a sibling-repo model, not an embedded-repo model.

- upstream repo:
  - `NexusCRM`
  - owns platform runtime and control plane
- official app-pack repo:
  - `nexuscrm-app-packs`
  - owns official standard pack authority
- downstream repo:
  - `nexuscrm-pingcap-pack`
  - owns PingCAP-specific inputs, final pack assembly, and deploy orchestration

Typical local layout:

```text
.../NexusCRM
.../nexuscrm-app-packs
.../nexuscrm-pingcap-pack
```

The optional `temp/nexuscrm-pingcap-pack` path used in the NexusCRM workspace
is only a symlink to the downstream repo for local collaboration convenience.
It does not change the authority split.

## Canonical Test Artifact

The tested artifact is not raw upstream `salesforce-crm-standard` by itself.

The tested artifact is:

1. upstream `NexusCRM` at the selected ref, defaulting to `main`
2. official `salesforce-crm-standard` at the selected ref, defaulting to `main`
3. plus downstream checked-in `packs/Salesforce-PingCAP-L2`
4. resulting in the final downstream pack:
   - `packs/NOT-EDIT-final-merged-packs`

Hard rule:

- `packs/Salesforce-PingCAP-L2` is an intermediate downstream source format
- `packs/NOT-EDIT-final-merged-packs` is the only installable test artifact
- bootstrap / deploy / reuse fingerprinting must key off the final artifact, not the intermediate source tree

Current authority files:

```text
stack.lock.json
scripts/build_pingcap_salesforce_sample_pack.sh
scripts/local_app_install.py
```

Current build lineage files:

```text
packs/Salesforce-PingCAP-L2/.build-lineage.json
packs/NOT-EDIT-final-merged-packs/.build-lineage.json
```

`stack.lock.json`, the checked-in L2 source, the generated final pack, and its
`.build-lineage.json` form the final-pack build contract. The lineage records
the exact NexusCRM and official app-pack commits used for assembly; it is build
provenance, not release selection. Local development remains non-release.
The former automatic shared EXP deployment workflow has been removed.
Full serviceified D3 deployments to Test, Staging, and Prod consume one exact
signed resolved BOM, which independently selects L0, L1, and L2. Bounded
private-Test process diagnostics do not become release deployments. The retired
Classic release-class workflows and their Candidate contracts are not
executable release authority.

Every full remote D3 initialization or stopped upgrade converges the app-shell
release configuration before Catalog publication. Configuration bytes and
their hash come from the BOM-selected exact L2 checkout; executable
orchestration comes from the reviewed deployment controller. Test, Staging,
and Prod therefore share one fail-closed callsite. Bounded Test-only process
image replacements do not mutate or republish app-shell metadata.

Formal D3 staging-to-prod promotion uses the successful Staging D3 run as its
complete admission boundary. `emit-staging-d3-release-tag.yml` binds that run's
workflow identity, exported immutable OCI BOM reference, exact BOM L2 commit,
and immutable Staging artifact ID/name/digest into an annotated
`staging-d3-release-*` Tag. It does not requalify the run's terminal receipt or
process counts. The Git Tag target equals that exact BOM L2 commit.
`deploy-multi-prod-d3-environment.yml` accepts only that Tag contract, verifies
the signed BOM again, requires its L2 commit to equal the Tag target, and
re-downloads the exact Staging artifact to prove its dispatch authorized the
Tag. Prod D3 retains the `prod-release-tidb-boundary` concurrency group name
while it reconciles release authority through closeout. The first D3 release
validates the one-way handoff from any still-live Classic runtime under that
lock. D3 then persists the selected cyclic target in
`multi_prod/d3/in-progress.json` and converges Catalog while the previous
runtime remains live. It changes nginx only after complete target readiness.
After public readback it stops and verifies the pending-authority-bound
predecessor, re-proves the public target, then publishes the D3 current marker
and clears pending authority. Retirement failure preserves the exact target for
retry. Stopped containers and release files remain for separate retention
cleanup. The Classic workflow and Tag emitter are absent.
This lane has no
Candidate, Business Acceptance field, release phase ledger, or S3 release
directory. [ADR 0007](adr/0007-serviceified-d3-four-slot-prod-promotion.md) is
the D3 promotion authority. ADR 0003 is retained only as historical design
context for the removed Classic lane.

Classic `staging-release-*` Tags and Candidate artifacts are historical only
[Release Candidate Contract](release-candidate-contract.md) records the retired
Classic v3 design; it is not a deployment requirement or operator surface.

Serviceified D3 delivery consumes a signed BOM that independently selects an
L0 Engine release, a signed L1 AppRelease, and one exact L2 metadata tree. BOM
publication verifies those bytes and their AppRelease provenance before
signing; deployment repeats the immutable artifact verification without making
L1/L2 constrain an L0 source commit. The pack builder reproduces metadata from
validated final-pack lineage and is not a release or deployment authority.

Current pack layering:

```text
packs/Salesforce-PingCAP-L2
  + official nexuscrm-app-packs/salesforce-crm-standard
  => packs/NOT-EDIT-final-merged-packs
```

Additional official app-pack dependencies can be listed in `stack.lock.json`
as `official_repo:<pack-name>`. These packs are installed from
`nexuscrm-app-packs` and are not copied into this downstream repository.
Downstream-owned packs remain `local_path` entries.

## Execution Model

Local functional verification:

Follow [Independent local deployment](local-independent-deployment.md): initialize
L0, install the App, activate L0, then deploy L1. The three entrypoints accept
local images without deployment version/provenance comparisons and manage L0
and L1 processes independently. Use case-local business fixtures. The installer
covers first installation, not metadata upgrades, rollback or partial recovery.

Test and Staging support production-simulation verification through their
[environment controllers](../deployments/README.md). Select the topology and
operation needed to prove the claim, reuse the relevant acceptance cases, and
record exact inputs and results. Local success does not certify the published
candidate. Full release/promotion proof uses the signed BOM and formal Staging
run; independent Test component changes prove only their own scope.

Remote Test serviceification:

The explicitly opted-in host-only Test3 development mode
(`run_serviceification_d3_test.py --local-unsigned`) is separate from the remote
signed release path below. Its immutable local-image manifest preserves exact
source and L2/AppRelease checks but makes no signature, provenance or SBOM
claims. It keeps the stopped-upgrade, Catalog, ownership and readiness flow,
preserves the database, and is rejected by Actions and other environments.
See `deploy/offline/test/README.md` for preparation and resume inputs.

1. an authorized manual dispatch selects the exact controller ref, and the
   resulting `github.sha` owns full deployment, production-to-Test database
   refresh, or SFCRM image replacement for that run
2. for full deployment, the operator selects one already-signed resolved BOM by
   immutable digest
3. the workflow verifies the BOM and every referenced release artifact before
   mutating the selected fixed slot
4. the shared serviceification runner preserves the Test database and converges
   the complete D3 topology
5. the workflow verifies the public serviceified readiness contract before
   reporting success

A branch product candidate must first cross its owning repository's publication
boundary and be selected as an immutable published artifact. The controller ref
is an independent exact input selected by workflow dispatch. Test1 also has a standalone deployment model outside full D3: L0,
AppRelease/L2 metadata, and L1 AppService/UI each have one independent owner
workflow. Test2 uses those boundaries plus independent Integration management; its runbook is
[`test2-standalone-deployment.md`](test2-standalone-deployment.md). The Test2
standalone controllers refuse existing full-D3 ownership; the old full-D3 CI
no longer accepts Test2. They use the existing slot concurrency group.
Test2 completed its one-time standalone transition; the transition controller
is retired. Integration has a separate stop/deploy owner consuming an exact
published Integration digest and canonical environment/Runtime configuration.
Its generated configuration replaces inherited D3 mounts; no other Test2
controller may replace Integration. See the Test2 runbook for its inputs and
startup prerequisites.
The integrated Test1 operator path is
[`test1-standalone-deployment.md`](test1-standalone-deployment.md); its component
contracts are linked from that runbook. These workflows build and publish
nothing, preserve the database, and never mutate a sibling component. The L0
controller resolves the requested source identity only to select its existing
published Engine Release; AppRelease and L1 Application consume exact artifact
digests. Test deployment and diagnostics remain separate from release promotion
evidence and functional Env Inspection.

Staging deployment, D3 Tag emission, historical-data maintenance, and
business-path inspection have separate workflow conclusions. A successful
Staging D3 deployment is Tag-eligible even when the independent Env Inspection
follow-up fails or has not run; Env Inspection remains business-behavior
evidence rather than D3 release admission.

1. `deploy-staging-blue-green-environment.yml` has one deploy operation and a
   `reset_branch` input. When true, its conditional `reset-staging-database`
   predecessor job selects the inferred standby, clones the current Prod
   parent, removes cloned credential/ingress/queue state and workflow-action
   SyncState errors, invalidates the old standby D3 generation, and leaves
   nginx unchanged. When false, GitHub skips that job. The reset writes no
   recoverable receipt; failure blocks the deploy job. Deploy also refuses a
   standby whose reset or queue-sanitation marker remains pending, and reset
   refuses to repeat while either marker is pending. An exact retained run that
   already completed its database boundary resumes without another clone. The
   workflow verifies the signed BOM and exact-L2 patch request before reset
   mutation, and holds the staging mutation boundary across validation, reset,
   and deploy.
2. The same workflow's `deploy-staging-blue-green` job always preserves the
   database presented by the skipped-or-successful reset job, publishes the selected signed-BOM
   D3 stack, optionally replays one reviewed idempotent SQL patch bundle from
   the exact BOM-selected L2 checkout, and verifies the live service contract.
   A separate trusted-main emitter may bind a completed/success run to a D3
   Tag. The Tag copies the nullable Patch Bundle path/digest from the successful
   Staging dispatch. Prod has no independent patch input: it replays that exact
   bundle from the Tag-selected L2 checkout before Catalog apply. The deploy
   does not repair or test the case-specific historical Order attachment
   fixture.
3. `salesforce-staging-order-file-projection-maintenance.yml` operates on the
   current live slot. It is manual-only, defaults to a read-only audit, and
   requires `apply_repairs=true` before it may write `_System_File` projection
   rows for either Order or Contract from raw Salesforce tables or a local
   snapshot. One object-neutral workflow owns both parent types and their
   distinct attachment kinds; each run selects exactly one target and a missing
   target-key binding fails closed. Before touching the
   database, it verifies the active slot's immutable downstream ref is on
   `origin/main` and checks out that exact deployed revision for its tooling.
4. Every successful Staging `deploy-staging-blue-green` job dispatches
   `staging-inspection.yml`, including a successful phase-aware resume that has
   no remaining release phase to replay. The follow-up runs all roles and cases
   from `main`, sends its report to Feishu, and remains independently
   dispatchable for focused user-path evidence. Env Inspection verifies the
   live environment's business behavior; it does not accept or validate a
   backend build SHA. For staging, it binds the installed Salesforce-compatible
   programmatic OAuth client and uses the active-slot encrypted database
   boundary to quiesce both HubSpot inbound secret values before execution and
   restore them after success, failure, or cancellation. The focused cases include
   `admin/orders-quotes/order-migrated-files-present-api` and
   `admin/orders-quotes/order-migrated-file-download-api`.

The staging and test maintenance entrypoints are thin manual wrappers around
`salesforce-order-file-projection-maintenance-core.yml`. Despite the retained
compatibility filename, the shared core is object-neutral and owns Order plus
Contract audit, repair, and readiness through one selected-target path. The shared core owns
the audit, raw repair, snapshot fallback, final audit, S3 readiness, summary,
and artifact steps. Environment adapters retain staging's active-slot and
`origin/main` ancestry checks and test's fixed-slot exact-commit checkout.

The staging maintenance and staging inspection workflows share the staging
deploy concurrency group so they cannot mutate the active database during a
deploy or cutover, but their status and artifacts remain independent. The
non-blocking dispatch adapter runs only after the deployment job succeeds; a
failed deployment dispatches neither Inspection nor the successful-deployment
Feishu notification. The automatically dispatched inspection is still a
separate workflow run. A dispatch transport failure is recorded without
rewriting an already completed deployment as failed, while a maintenance or
inspection failure reports data/readiness drift independently and does not
change that deployment's D3 Tag eligibility.

Test deployment is deliberately separate from release publication, data
maintenance, and user-path inspection:

1. Full D3 remains in `deploy-test-serviceification.yml` for Test1/Test3 only.
   Test2 has been removed from that workflow. Standalone Test1 and Test2 use
   three separate owner workflows for L0, AppRelease/L2 metadata, and L1
   AppService/UI, as specified by
   [`test1-standalone-deployment.md`](test1-standalone-deployment.md) and
   [`test2-standalone-deployment.md`](test2-standalone-deployment.md).
2. The `refresh-database-from-production` operation in
   `deploy-test-serviceification.yml` separately owns intentional Test1/Test3
   production-derived Test database refresh. Normal deploy always preserves the
   current database; a sealed `patch_dir` is optional migration input, not a
   reset or product-release override.
3. Runtime validation, focused inspection, Full Inspection, and maintenance
   workflows retain independent conclusions. They may inspect the live result
   but do not rewrite the deployment workflow's conclusion.

The deployment, validation, and maintenance workflows share the stable
`deploy-test-multi-slot-environment-<slot>` concurrency identity where needed
so they do not inspect or mutate a slot during replacement. Their results never
rewrite the deployment result.

The Classic release-candidate contract is a historical design record with no
operator surface. Serviceified D3 Staging uses the signed-BOM contract
described above.

## Fix Ownership

Send the fix upstream when the issue is about:

- platform runtime behavior
- control-plane behavior
- generic pack/runtime contract
- official standard pack content

Keep the fix downstream when the issue is about:

- PingCAP-specific metadata shape
- downstream pack assembly rules
- downstream deployment flow
- downstream environment conventions

If downstream adds a temporary compatibility shim because the current runtime cannot yet
install the raw PingCAP pack shape, record it in:

- `docs/downstream-pack-compatibility-notes.md`

### Upstream runtime changes

Generic runtime behavior belongs in `NexusCRM`. When the pack requires an
upstream runtime capability or bug fix, land that change upstream first and
consume it here by selecting the updated upstream ref.

Do not carry local patch files for upstream code in this repository. Build
local images from the owning repository; remote deployment consumes
published artifacts through its documented controller.

## Serviceification D3 Test Stack

The D3 stack is the current service topology:

```text
local test client
  -> independently published sfcrm AppService
  -> independently published serving Runtime API
  -> the test2 TiDB service
```

`release/serviceification-d3-bom.json` is the sole desired release-selection
input. The repository keeps one current desired grammar and one current resolved
grammar; historical D1/D2 parsers, evidence validators, and mixed-version
activation paths have been removed. The current AppRelease, L0 Engine release,
frontend, L2 metadata, worker roles, and execution topology are resolved into
one immutable candidate.

The frontend publication is bound to one canonical digest over all three input
revisions (NexusCRM, app-packs, and L2). Its publication commit is the separate
L2 publisher revision that performed the build; it must not impersonate the L2
metadata input commit. The PR-time BOM gate is record-only: it validates the v10 BOM shape and
deliberately holds no credentials, fetches no selected sources, and executes
nothing the BOM selects, because no publisher/trust-anchor rules yet define a
non-circular L1 provenance proof. Exact-source verification and the BOM freeze
run in the trusted deploy/publish workflows instead. The resolved document is
accepted only when its canonical projection reproduces the exact current
desired document and its `desired_bom_sha256`.

The Catalog reconcile image owns AppRelease import, AppInstallation and
binding reconciliation, Runtime security-projection publication, Catalog
snapshot/Runtime-view publication, and the resulting serving receipts. The L2
workflow supplies immutable inputs and operator secrets, then consumes the
owner's result. It must not query TiDB to reconstruct Catalog state, compile a
snapshot itself, or synthesize a serving revision.

Catalog remains the sole owner of snapshot, Runtime-view, Catalog-revision and
security-epoch facts. The Runtime render contains only the deployment selection
accepted by the current Runtime grammar: the Catalog store root, service release,
per-capability ownership epochs and compatibility receipts, generations and
capability identities. At startup Runtime resolves and binds one complete
multi-capability serving scope from Catalog;
L2 does not copy Catalog-owned facts into a competing configuration authority.
Changing files behind a stable store path still cannot preserve a valid serving
scope because Catalog's content-addressed store and compatibility receipt are
verified by the Runtime owner path.

Artifact production and test2 activation remain separate authorities. One fixed
manual caller invokes one local reusable candidate producer from the same
commit, which must be current main when preparation starts. It can write only
run-scoped `pending-*` candidates and is split into
frontend and resolved-BOM phases so frontend signing/verification
occurs before BOM projection. It has no OIDC/KMS permission, stable-tag path,
or deployment operation. Candidate receipts bind the
canonical reviewed caller/producer-file closure, so the signer does not
infer producer identity from a mutable workflow name. A1 owns the signing
policy/key, direct caller, independent final verification, and immutable
source-SHA discovery tags.

The D3 environment workflows consume one exact, KMS-signed resolved-BOM digest
and retain the existing workload identity, recovery, database migration, and
Runtime capability checks.

The signed resolved BOM is the L2 deployment authorization, not an unsigned
collection of image names. Before signing it, the candidate producer verifies
the Gateway, Runtime, Catalog, AppService and frontend owner publications
against each owner's immutable policy and key. The L2 signer then verifies the
candidate receipt and producer closure before signing the exact resolved-BOM
digest. Activation verifies that L2 signature and embedded binding once; it
does not create a second owner-verification path or re-author any service
release fact at deploy time.

The activation's mutable state is run-owned rather than host-owned. It creates
the scratch database name, bootstrap root, runtime environment, Docker config,
PID file, and process-owner marker before the first authenticated or bootstrap
operation. Source credentials are removed from the bootstrap environment and
Git remotes before the legacy reference backend starts; the Docker credential
exists only in the run-scoped config and is removed with the run root. The host
runtime environment remains the infrastructure-owned TiDB credential source;
no database password is copied into durable cleanup state. Before any run-owned
mutation, the workflow identity deterministically defines the cleanup scope.
Preparation atomically publishes an adjacent, protected recovery record
containing only the TiDB endpoint, exact database, run identity, and random
guard token. The ephemeral run root may later contain database credentials,
GitHub credentials, admin credentials, and generated keys. Cleanup first proves
that every run-owned container and backend process has stopped; if that proof
fails, it retains the database, run root, PID state, and recovery record for a
dependency-ordered retry. Once consumers are stopped, it scrubs the run root
even if the downstream database drop fails. The recovery record is deleted only
after every owned resource is released; otherwise it remains as the proof
needed to retry cleanup. Before a shell consumer starts, D1 removes source
credentials and rewrites every retained `runtime.env` value with shell quoting,
so secrets are read as data rather than syntax; the scratch parser reverses
that encoding without executing it. The legacy source `runtime.env` is only a
transient bootstrap input and is never cleanup authority. D1 creates the exact database itself,
installs the guard in `_D1_ScratchGuard` before bootstrap, and runs the legacy
helper with `RESET_DB=0`. Cleanup drops a database only after the durable record
matches the guard stored inside that database. Serving containers carry the
same run-owner label, which is checked before removal.

The workflow timeout is a hard safety cap, not a promise that GitHub will run
the final step after forceful termination. A retry may name one interrupted
workflow run ID and attempt. Before allocating its own marker, it reconstructs
only that prior marker's paths and invokes the same owner-checked cleanup. It
does not discover, prune, or infer ownership from ports, process names, or
database-name patterns. After recovery, the normal preflight must prove the D1
ports are free before the new run creates its recovery record.

The activation starts that backend as a dedicated process-group leader and may
stop only the PID/PGID that proves the same run marker. A port observation is
only an ownership assertion and is never permission to kill an arbitrary
listener. Scratch-database cleanup also requires the exact run-owned database
name and the TLS-backed TiDB contract.

Metadata authority closes through one chain: the signed resolved BOM selects
an exact clean L2 commit and final-pack tree; that commit's `stack.lock.json`
and validated final-pack lineage bind the app-pack build inputs. These metadata
build inputs are intentionally independent from the executable Runtime and
AppService image source commits selected by the same BOM. The reviewed
activation checkout owns the orchestration helpers that consume those exact
inputs; a historical metadata commit is data authority, not workflow-code
authority. The D1 activation verifier owns this deployment-time proof without
changing the canonical BOM producer grammar or its signed closure.

The first successful activation run proves the real digest-pinned walking
slice, parity baseline, cancellation, restart behavior, and absence of a
legacy fallback. It does not by itself close every D1 acceptance item; stale
security/metadata, metadata-activation, and broader fault gates remain explicit
D1 evidence until executable test2 scenarios prove them.

The D2A extension preserves that topology and adds the coherent synchronous
query family. Setup creates a bounded scratch-only Account data set through the
public CRM composite mutation API; the fixture never writes TiDB directly. It
then compares the existing monolith oracle with the independent stack for
`query`, `queryAll`, `queryMore`, and a history query. The first-page comparison
excludes the opaque locator bytes. The independent stack consumes both its own
locator, proving the normal `queryAll` to `queryMore` path, and the legacy-issued
locator, proving that an in-flight client can continue after the ownership
flip. Each opaque locator is consumed once; the legacy oracle uses an
independent cursor rather than assuming replayability. The evidence records
both facts plus aggregate RPC, admission, repository and SQL counters. It
records neither locator bytes, the SOQL text, record identifiers, tenant
identifiers nor response payloads.
Exact RPC deltas prove that each request crosses Identity and Records once; the
legacy call remains an oracle, never a fallback.
The v4 extension adds the bounded `ui-api/related-list-counts` family without
reinterpreting the signed v3 query activation. One HTTP batch contains two
position-stable items: a readable parent whose lookup value is derived from the
parent record identifier and a missing parent whose count is masked to zero.
The independent stack must match the legacy oracle byte for byte while using
exactly one Identity RPC and one Records RPC. Empty and one-over-50 batches must
be rejected before Identity, Runtime admission, repository, or SQL work. The
evidence contains only counts and boolean outcomes, never record identifiers or
payloads. That proof covers the query and related-list-count families only.

The exact v4 AppService image and L2 runner also activate the two notification
read surfaces: `/product/notifications` and the v59-v63 Connect compatibility
routes. They reuse the existing query capability; there is no notification RPC,
Catalog capability, or second identity channel. `Notification` is a
system-owned table and is not a public application fixture-write surface. The
case-local L2 test operator therefore inserts exactly two bounded rows directly
into the marker-owned scratch TiDB database: one current-principal Notification
and one isolation contrast row. This privileged fixture setup is outside the
application path, is TLS-enforced, and is removed when the scratch database is
dropped. It does not create a second serving write path or claim to test a
Notification producer. The read proof then checks
legacy response parity, recipient isolation, unread filtering, one Identity and
one Records RPC per request, local rejection before Runtime, and outage
recovery without fallback. Evidence v4 records only counts and boolean outcomes,
never notification payloads, principals, or record identifiers. These three
activated read families do not claim that the remaining launch-scope reads or
D2A itself are complete.

The v5 extension adds the bounded recycle-bin read family. The test operator
creates ordinary Account rows through the public composite API and deletes two
of them through the public CRM record route; the independent AppService never
receives direct database fixture authority. The legacy monolith is called once
as the explicit parity oracle, then the independent stack is called twice to
prove byte parity and deterministic ordering. Each new-stack request must use
exactly one Identity RPC and one Records RPC. Invalid scope and unauthenticated
requests must be rejected locally with zero Runtime admission, repository, or
SQL work. Runtime, AppService, and Gateway outage probes must fail closed and
recover without using the legacy route. Public evidence v5 records only counts
and boolean outcomes; it never records deleted record identifiers or response
payloads. This activates only recycle-bin read and does not move delete or
restore ownership.

The two phases have independent publisher commits. The resolved-BOM phase
accepts the exact source commit, digest and binding of the already signed
frontend, requires that source commit to be an ancestor of its own producer,
and verifies the frontend revision, signature, provenance and embedded source
tuple against that commit. It never substitutes its newer publisher commit for
the frontend's release identity.

Preparation runs in a read-only job that may execute exact upstream source.
Pending publication runs in a separate package-write job that downloads only
the prepared artifact and checks out only the reviewed L2 publisher. No
upstream checkout or npm lifecycle shares the write-authority job.

Registry fetching and owner semantic execution are separate authorities inside
resolved-BOM preparation. Reviewed L2 code fetches the private OCI evidence,
then removes the isolated Docker credential before invoking the already-built
Gateway verifier on local evidence. Gateway is verified last, so no later read
depends on a credential that owner code could observe.

The read-only verifier follows BuildKit's content-addressed SBOM chain from the
owner-signed OCI index to the selected platform manifest, its linked
attestation manifest and the SPDX in-toto blob. It checks descriptor hashes and
sizes, the in-toto subject, predicate type and required SPDX document fields.
Merely finding an `spdx` annotation is not accepted as supply-chain evidence.
For L2 subjects, the verifier also extracts the signed image's source-tuple or
resolved-BOM file and proves its canonical content and embedded label agree with
the signed binding.

The candidate path binds execution identity as well as checked-in bytes. Both
jobs require the runner-controlled caller workflow ref/SHA to equal the fixed
current-main caller. That caller is part of the closure and has exactly one
same-commit local reusable-producer call, from which the recorded producer
ref/SHA is derived. Another workflow therefore cannot invoke the reusable
producer while claiming the reviewed caller's closure.

Candidate builds disable BuildKit provenance. Their digest-bound receipts carry
the exact desired source facts and pinned B1 caller/producer closure; S1 alone
proves the successful producer commit is an ancestor of the immutable main
commit bound to workflow dispatch and turns that authorization evidence into
the canonical owner-signed SLSA v1 release provenance. The producer run owns
the release source identity, so an unrelated later main commit neither renames
nor invalidates the candidate. A
diverged or unreachable producer commit cannot be signed or promoted. The
signer uses that dispatch commit as the exact signing authority, requires its
policy and public key to be byte-identical to the candidate versions, and
requires the OIDC `sha` claim to match before exchange. A signer, key or policy
transition therefore invalidates an unsigned candidate instead of authorizing
it through a compatibility path. The resolved-BOM phase independently rechecks
the exact L2 metadata commit, path and tree digest instead of relying on a
future signer to provide that proof transitively.

The reusable S1 kernel accepts one B1 run and subject at a time. Its
non-OIDC gate derives the candidate digest and semantic binding from the
run-owned receipt, recomputes the recorded B1 closure from the exact producer
commit, and requires that commit to be an ancestor of the immutable
dispatch-authority commit. It separately requires the candidate signing policy
and public key to match that same authority snapshot.
Only then may its isolated signing job create the canonical provenance and KMS
signature for that exact digest. S1 has no direct trigger, stable-tag operation,
independent final-verification owner, deployment operation, or test2 authority.
The source-owned signing policy carries the reviewed producer-closure digest;
changing producer configuration requires a reviewed policy update but does not
change the reusable signer's OIDC identity.
Its provenance contains only B1 receipt/closure facts, never the S1 run or
attempt, so interruption retry cannot manufacture a second semantic provenance
statement. A1 routes an already complete subject through verify-only.
A1 invokes S1 separately for the frontend and, after frontend verification and
BOM projection, the resolved BOM. Before invoking S1 it classifies the
source-SHA discovery tag. A complete existing subject goes directly through
the read-only verifier, so reruns neither call KMS nor rewrite the tag.
