Files
clean-architecture-frontend…/docs/superpowers/plans/2026-08-02-cicd-frontend-assurance-delivery.md
T

37 KiB

CI/CD Frontend Assurance and Delivery Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make cicd-platform the sole owner of frontend workflow orchestration, remote projection of test-assurance plans, deterministic candidate production, supply-chain/provider evidence, immutable publication, and exact-candidate promotion.

Architecture: cicdctl compiles typed stages and consumes the digest-pinned test-assurance v3 external contract without interpreting test reports. One source revision produces one verified candidate; artifact-bound testing and every supply-chain stage consume that candidate by SHA-256. release-control is the only mutating publication/promotion boundary. The centrally installed required workflow remains a four-step pinned bootstrap and is never copied into a product repository.

Tech Stack: Go, JSON Schema 2020-12, Gitea Actions, Node.js/pnpm runners, canonical SHA-256, Ed25519, content-addressed static publication.

Global Constraints

  • Repository: /home/donghyeon/workspace/desktop-server-git/cicd-platform; every command runs from its isolated worktree root.
  • Start only from the immutable Test Assurance Task 10 commit, its v3 distribution digest, and its v3 schema-bundle digest. No current v2 binary may satisfy this dependency.
  • Resolve Go 1.26.5 from toolchains/toolchain-lock.yaml into .platform/toolchains/go-1.26.5/bin/go, verify the distribution SHA-256 before extraction, and set CICD_GO_BIN to that absolute path. The host currently has no go; PATH fallback is forbidden.
  • CI may choose runner placement and parallelism but may not change test selection, timeout, retry, artifact requirements, result status, or obligation satisfaction.
  • Test raw reports remain opaque to CI; only testctl v3 plans, normalized results, evidence, assessments, IDs, digests, attempts, and exit codes are consumed.
  • The platform path is the only candidate producer in shadow. Legacy product release/promotion commands may only compare bytes and evidence read-only.
  • The first deterministic build is the candidate. The isolated comparison build is destroyed and cannot be promoted.
  • Exact new IDs are ci-test-assurance, ci-dependency-vulnerability, ci-artifact-signing, ci-static-artifact-supply-chain, and ci-static-site-publish.
  • Existing ci-sbom and ci-provenance gain static-archive subjects without weakening container subjects.
  • All new capabilities remain P1/shadow until named Gitea, runner, scanner, signer, and provider evidence supports P2.
  • Each P1 transition is atomic: canonical ID, descriptor/policy/acceptance/runbook, provider registration, readiness registry row, fixture, immutable evidence, docs/decisions/readiness/<capability>-P1.yaml, and Makefile capability run land in the same commit. A P0 capability may not have an active provider.
  • Rollback selects a previous signed platform catalog and immutable subject through platform control; it never restores a copied workflow or a legacy writer.

Task 1: Define typed stage, artifact, and release-identity contracts

  • Materialize the locked Go toolchain before writing tests. Run from the isolated CICD worktree (network download requires the normal escalation approval):
mkdir -p .platform/downloads .platform/toolchains/go-1.26.5
curl --fail --location --proto '=https' --tlsv1.3 https://go.dev/dl/go1.26.5.linux-amd64.tar.gz --output .platform/downloads/go1.26.5.linux-amd64.tar.gz
printf '%s  %s\n' '5c2c3b16caefa1d968a94c1daca04a7ca301a496d9b086e17ad77bb81393f053' '.platform/downloads/go1.26.5.linux-amd64.tar.gz' | sha256sum --check
tar -xzf .platform/downloads/go1.26.5.linux-amd64.tar.gz --strip-components=1 -C .platform/toolchains/go-1.26.5
export CICD_GO_BIN="$PWD/.platform/toolchains/go-1.26.5/bin/go"
"$CICD_GO_BIN" version

Expected: exact go version go1.26.5 linux/amd64. Stop on download/digest mismatch; never use /usr/bin/go or another PATH binary.

Files:

  • Modify: packages/contracts/models.go
  • Modify: packages/contracts/schema.go
  • Preserve unchanged: contracts/schemas/execution-plan.schema.json and contracts/schemas/release-manifest.schema.json v1 contracts
  • Create: contracts/schemas/execution-plan-v2.schema.json
  • Create: contracts/schemas/release-manifest-v2.schema.json
  • Create: contracts/schemas/promotion-subject.schema.json
  • Modify: contracts/schemas/delivery-platform.schema.json
  • Create: contracts/schemas/artifact-reference.schema.json
  • Create: contracts/schemas/candidate-bundle.schema.json
  • Create: contracts/schemas/test-assurance-result.schema.json
  • Create: contracts/schemas/supply-chain-evidence.schema.json
  • Create: contracts/schemas/publication-record.schema.json
  • Sync generated copies under: packages/contracts/schemas/
  • Modify: packages/contracts/schema_test.go
  • Modify: packages/canonicalid/id.go
  • Modify: packages/canonicalid/id_test.go
  • Create: contracts/examples/valid/execution-plan-v2.json
  • Create: contracts/examples/valid/release-manifest-v2.json
  • Create: contracts/examples/valid/promotion-subject.json
  • Create: contracts/examples/invalid/13-v2-stage-with-shell-payload.json
  • Create: contracts/examples/invalid/14-release-manifest-with-promotion-state.json
  • Modify: contracts/snapshots/ only by adding a new v2 snapshot set; do not rewrite contracts/snapshots/v1/
  • Modify: contracts/readiness-cards.yaml
  • Create P0 directories: capabilities/ci-test-assurance/, capabilities/ci-dependency-vulnerability/, capabilities/ci-artifact-signing/, capabilities/ci-static-artifact-supply-chain/, capabilities/ci-static-site-publish/

Interfaces:

type StageInvocation struct {
    Kind       string // internal | platform-adapter | external-contract
    Engine     string // cicdctl | release-control | test-assurance | provider
    AdapterID  string
    Operation  string
    Payload    json.RawMessage // validated by the adapter/operation-specific schema
    WorkItemID string
    Inputs     []ArtifactReference
    Outputs    []ArtifactDeclaration
}

type TestAssuranceReference struct {
    APIVersion       string
    ManifestDigest   string
    PolicyDigest     string
    ExecutionPhase  string
    PlanDigest       string
    EvidenceDigest   string
    AssessmentDigest string
    InputArtifacts   []ArtifactReference
}

type PromotionSubject struct {
    ReleaseManifestDigest string
    TargetEnvironment string
    ApprovalID string
    ApprovalPolicyDigest string
    PublicationRecordDigest string
    ExpectedGitRevision string
    ExpectedTreeDigest string
}

ReleaseManifestV2 is immutable and carries separate source and artifact TestAssuranceReference values plus source revision, candidate archive/member-manifest digests, dependency-vulnerability/SBOM/provenance/signature digests, and platform policy/catalog/toolchain digests. Target environment, approval, publication, and Git CAS belong only to PromotionSubject; promotion history never rewrites the signed release manifest. The combined signed release identity is ReleaseManifestV2 + PromotionSubject.

Canonical kinds are fixed: ci-test-assurance is KindComposite; ci-dependency-vulnerability, ci-artifact-signing, and ci-static-site-publish are KindArtifact; ci-static-artifact-supply-chain is KindComposite.

  • Add RED tests rejecting free-form shell payload, missing invocation on a planned v2 stage, mutable artifact reference, duplicate output ID, test-assurance v2 reference, source/artifact reference aliasing, wrong candidate digest, release manifest missing either assessment, promotion fields inside the immutable manifest, and target/approval missing from PromotionSubject.
  • Run:
"$CICD_GO_BIN" test ./packages/contracts ./packages/canonicalid
make GO="$CICD_GO_BIN" contracts

Expected: RED because the typed contracts do not exist.

  • Implement v2 contracts and document-version mappings while retaining v1 dual-read behavior. StageInvocation.Payload is a discriminated typed payload validated by (AdapterID, Operation); the adapter passes its internal argv directly through exec.CommandContext and never invokes a shell.
  • Run:
make GO="$CICD_GO_BIN" contracts-sync
make GO="$CICD_GO_BIN" contracts
"$CICD_GO_BIN" test ./packages/contracts ./packages/canonicalid

Expected: PASS.

  • Commit:
git add packages/contracts packages/canonicalid contracts capabilities/ci-test-assurance capabilities/ci-dependency-vulnerability capabilities/ci-artifact-signing capabilities/ci-static-artifact-supply-chain capabilities/ci-static-site-publish
git commit -m "feat(contracts): define frontend delivery identities"

Task 2: Execute typed stages with verified artifact fan-out

Files:

  • Create: apps/cicdctl/internal/execution/adapter.go
  • Create: apps/cicdctl/internal/execution/engine.go
  • Create: apps/cicdctl/internal/execution/artifact_store.go
  • Create: apps/cicdctl/internal/execution/local_artifact_store.go
  • Create: apps/cicdctl/internal/execution/engine_test.go
  • Create: apps/cicdctl/internal/execution/artifact_store_test.go
  • Modify: apps/cicdctl/internal/execution/scheduler.go
  • Modify: apps/cicdctl/internal/execution/completeness.go
  • Modify: apps/cicdctl/internal/app/run_command.go

Interfaces:

type InvocationAdapter interface {
    AdapterID() string
    Execute(context.Context, InvocationRequest) (InvocationOutcome, error)
}

type ArtifactStore interface {
    Put(context.Context, ArtifactInput) (contracts.ArtifactReference, error)
    MaterializeVerified(context.Context, contracts.ArtifactReference, string) error
}
  • Add RED tests proving source checkout is read-only, independent stages do not share mutable output paths, mutation after Put fails, missing adapters are platform defects, failed dependencies block descendants, and a missing terminal result cannot pass.
  • Run:
"$CICD_GO_BIN" test ./apps/cicdctl/internal/execution ./apps/cicdctl/internal/app -count=1

Expected: RED because no invocation engine/artifact store exists.

  • Implement bounded scheduler waves over existing state-machine rules. Every downstream artifact is materialized to a private directory and rehashed before adapter invocation.
  • Add cicdctl run execute --plan --checkout --results --artifact-root with atomic result writes.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/execution ./apps/cicdctl/internal/app -count=1; expected PASS.
  • Commit:
git add apps/cicdctl/internal/execution apps/cicdctl/internal/app
git commit -m "feat(cicdctl): execute typed artifact-bound stages"

Task 3: Integrate the digest-pinned test-assurance v3 contract

Files:

  • Create: apps/cicdctl/internal/adapters/testassurance/contract.go
  • Create: apps/cicdctl/internal/adapters/testassurance/runner.go
  • Create: apps/cicdctl/internal/adapters/testassurance/projector.go
  • Create: apps/cicdctl/internal/adapters/testassurance/contract_test.go
  • Create: apps/cicdctl/internal/adapters/testassurance/runner_test.go
  • Create: apps/cicdctl/internal/adapters/testassurance/projector_test.go
  • Modify: apps/cicdctl/internal/planner/plan.go
  • Modify: apps/cicdctl/internal/capabilities/capabilities.go
  • Modify: apps/cicdctl/internal/app/run_command.go
  • Create imported immutable schema bundle: toolchains/external-contracts/test-assurance-v3/
  • Create: toolchains/external-contracts/test-assurance-v3/contract-lock.json
  • Create: contracts/schemas/external-contract-lock.schema.json
  • Create: packages/contracts/schemas/external-contract-lock.schema.json
  • Modify: toolchains/platform-release-catalog.yaml
  • Modify: contracts/schemas/platform-release-catalog.schema.json
  • Modify: packages/contracts/schemas/platform-release-catalog.schema.json
  • Modify: packages/contracts/schema.go
  • Modify: packages/contracts/schema_test.go
  • Modify: contracts/examples/valid/platform-release-catalog.json
  • Create/complete: capabilities/ci-test-assurance/descriptor.yaml, capabilities/ci-test-assurance/policy.yaml, capabilities/ci-test-assurance/acceptance.yaml, capabilities/ci-test-assurance/runbook-index.yaml
  • Create: docs/decisions/readiness/ci-test-assurance-P1.yaml
  • Create evidence under: docs/decisions/readiness/evidence/ci-test-assurance/
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

External calls:

testctl validate
testctl lock
testctl compile
testctl select
testctl plan
testctl execute-one
testctl normalize
testctl bundle
testctl assess

All calls use argv arrays and a distribution/schema digest from the signed platform catalog. contract-lock.json binds test-assurance source revision, v3 schema major, distribution digest, and schema-bundle digest. The catalog schema adds a typed test-assurance engine reference; without that engine, ci-test-assurance compilation fails.

projector.go preserves the external plan bytes/digest unchanged and creates a separate CI projection whose nodes reference only {externalPlanDigest, workItemId} plus runner placement and CI dependency edges. It never writes a modified testctl plan.

The source request is planned before the build with no input artifacts. After ci-frontend freezes the first candidate, CI creates an artifact request containing its exact {artifactId, mediaType, sha256}. Testctl, not CI, matches that request to repository ArtifactSuiteTemplate declarations and materializes executable v3 suites/work items. CI rejects any unresolved template, placeholder digest, or work item whose input tuple differs from the candidate reference.

  • Add RED tests for source-revision/schema/distribution digest mismatch, absent catalog engine, non-v3 output, altered external plan bytes, altered timeout/retry, missing work-item result, source/artifact plan mixing, opaque evidence preservation, and exact testctl exit-code mapping from the published external contract.
  • Run:
"$CICD_GO_BIN" test ./apps/cicdctl/internal/adapters/testassurance ./apps/cicdctl/internal/planner ./apps/cicdctl/internal/capabilities -count=1

Expected: RED.

  • Implement the adapter without importing JUnit, Playwright, coverage, HTTP, accessibility, or visual parsing code. Invoke v3 artifact work as testctl execute-one --plan <plan> --work-item-id <id> --artifact-map <map> --output <dir>.
  • Raise ci-test-assurance to P1/shadow only after a local v3 source→artifact conformance fixture completes.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/adapters/testassurance ./apps/cicdctl/internal/planner ./apps/cicdctl/internal/capabilities -count=1 and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Commit:
git add apps/cicdctl toolchains/external-contracts/test-assurance-v3 toolchains/platform-release-catalog.yaml contracts/schemas/platform-release-catalog.schema.json contracts/examples/valid/platform-release-catalog.json packages/contracts capabilities/ci-test-assurance contracts/readiness-cards.yaml docs/decisions/readiness/ci-test-assurance-P1.yaml docs/decisions/readiness/evidence/ci-test-assurance Makefile
git commit -m "feat(test-assurance): project external v3 test plans"

Task 4: Split Node ownership and freeze the first deterministic candidate

Files:

  • Modify: apps/cicdctl/internal/manifest/model.go
  • Modify: apps/cicdctl/internal/manifest/compiler.go
  • Modify: apps/cicdctl/internal/manifest/compiler_test.go
  • Modify: apps/cicdctl/internal/templates/nodetypescript/resolver.go
  • Modify: apps/cicdctl/internal/templates/nodetypescript/resolver_test.go
  • Modify: apps/cicdctl/internal/templates/frontend/resolver.go
  • Modify: apps/cicdctl/internal/templates/frontend/resolver_test.go
  • Create: apps/cicdctl/internal/artifacts/candidate/builder.go
  • Create: apps/cicdctl/internal/artifacts/candidate/archive.go
  • Create: apps/cicdctl/internal/artifacts/candidate/builder_test.go
  • Create: apps/cicdctl/internal/artifacts/candidate/archive_test.go
  • Modify: capabilities/ci-node-typescript/*
  • Modify: capabilities/ci-frontend/*
  • Create: docs/decisions/readiness/ci-frontend-P1.yaml
  • Create: docs/decisions/readiness/evidence/ci-frontend-P1.bundle.json
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Manifest config:

type NodeTypeScriptConfig struct {
    LintScript string
    TypecheckScript string
    TestOwner string // empty or ci-test-assurance
}

type FrontendConfig struct {
    BuildScript string
    OutputDirectory string
    SizeBudgetBytes int64
    ForbiddenEnvironment []string
}

Package manager/version/install mode are repository facts derived from package.json.packageManager and pnpm-lock.yaml; capability config cannot override them. The execution-plan compiler permits exactly one candidate-producer operation for a selected release output. A second producer, a legacy product candidate command, or a writer without platform writer identity/operation ID/idempotency key is a contract error.

  • Add RED tests proving capability config is decoded, pnpm is derived and uses frozen install, manifest package-manager override is rejected, lint/typecheck remain in CI, unit/coverage stages disappear when test owner is ci-test-assurance, source revision is built twice in isolated workspaces, only the first byte-identical candidate is retained, and a plan with zero/two candidate producers is rejected.
  • Add archive adversarial tests for empty output, traversal, symlink, host path, undeclared member, duplicate path, environment leak, size overflow, and changed tree digest.
  • Run "$CICD_GO_BIN" test ./apps/cicdctl/internal/manifest ./apps/cicdctl/internal/templates/nodetypescript ./apps/cicdctl/internal/templates/frontend ./apps/cicdctl/internal/artifacts/candidate -count=1; expected RED.
  • Implement strict config decoding and canonical archive/member manifest generation. Destroy the verification workspace before returning the candidate reference.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/manifest ./apps/cicdctl/internal/templates/nodetypescript ./apps/cicdctl/internal/templates/frontend ./apps/cicdctl/internal/artifacts/candidate -count=1 and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Commit:
git add apps/cicdctl/internal/manifest apps/cicdctl/internal/templates apps/cicdctl/internal/artifacts/candidate capabilities/ci-node-typescript capabilities/ci-frontend contracts/readiness-cards.yaml docs/decisions/readiness/ci-frontend-P1.yaml docs/decisions/readiness/evidence/ci-frontend-P1.bundle.json Makefile
git commit -m "feat(frontend): freeze one deterministic static candidate"

Task 5: Bind dependency-vulnerability evidence to source and candidate

Files:

  • Create: apps/cicdctl/internal/artifacts/vulnerability/contract.go
  • Create: apps/cicdctl/internal/artifacts/vulnerability/adapter.go
  • Create: apps/cicdctl/internal/artifacts/vulnerability/validator.go
  • Create: apps/cicdctl/internal/artifacts/vulnerability/adapter_test.go
  • Create: apps/cicdctl/internal/artifacts/vulnerability/validator_test.go
  • Modify: apps/cicdctl/internal/capabilities/capabilities.go
  • Create/complete: capabilities/ci-dependency-vulnerability/descriptor.yaml, capabilities/ci-dependency-vulnerability/policy.yaml, capabilities/ci-dependency-vulnerability/acceptance.yaml, capabilities/ci-dependency-vulnerability/runbook-index.yaml
  • Create: docs/decisions/readiness/ci-dependency-vulnerability-P1.yaml
  • Create evidence under: docs/decisions/readiness/evidence/ci-dependency-vulnerability/
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Evidence identity: source revision, lockfile digest, candidate subject digest, provider ID, scanner/tool digest, vulnerability DB snapshot digest/time, invocation digest, normalized finding set, and evidence signature.

The adapter executes a digest-pinned provider engine through a typed contract and validates its output. It contains no scanner HTTP client and receives no provider credential; network/credential handling stays inside the provider trust boundary.

  • Add RED cases for absent report, wrong lockfile/candidate, stale DB, provider crash, malformed report, invalid signature, and zero findings without valid invocation metadata.
  • Run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/vulnerability -count=1; expected RED.
  • Implement a typed provider profile selected by manifest ID and direct process execution of the pinned engine; arbitrary repository commands and in-process provider clients are forbidden.
  • Raise to P1/shadow with signed local fixtures; keep P2 blocked on a named real scanner/provider.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/vulnerability ./apps/cicdctl/internal/capabilities -count=1 and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Commit:
git add apps/cicdctl/internal/artifacts/vulnerability apps/cicdctl/internal/capabilities capabilities/ci-dependency-vulnerability contracts/readiness-cards.yaml docs/decisions/readiness/ci-dependency-vulnerability-P1.yaml docs/decisions/readiness/evidence/ci-dependency-vulnerability Makefile
git commit -m "feat(security): bind dependency findings to candidates"

Task 6: Generalize SBOM and provenance to static archives

Files:

  • Create: apps/cicdctl/internal/artifacts/subject/subject.go
  • Create: apps/cicdctl/internal/artifacts/subject/subject_test.go
  • Create: apps/cicdctl/internal/artifacts/sbom/contract.go
  • Create: apps/cicdctl/internal/artifacts/sbom/validator.go
  • Create: apps/cicdctl/internal/artifacts/sbom/sbom_test.go
  • Create: apps/cicdctl/internal/artifacts/provenance/contract.go
  • Create: apps/cicdctl/internal/artifacts/provenance/validator.go
  • Create: apps/cicdctl/internal/artifacts/provenance/provenance_test.go
  • Modify: capabilities/ci-sbom/*
  • Modify: capabilities/ci-provenance/*
  • Modify: apps/cicdctl/internal/capabilities/capabilities.go
  • Create: docs/decisions/readiness/ci-sbom-P1.yaml
  • Create: docs/decisions/readiness/ci-provenance-P1.yaml
  • Create: docs/decisions/readiness/evidence/ci-sbom-P1.bundle.json
  • Create: docs/decisions/readiness/evidence/ci-provenance-P1.bundle.json
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Interface: ImmutableSubject is a tagged union of container image or static archive. Both evidence types bind subject kind/digest; static provenance additionally binds source revision, build invocation, member-manifest digest, platform/toolchain digests, and determinism evidence. Digest-pinned external SBOM/provenance engines generate documents; cicdctl validates and binds returned evidence but implements no provider network client.

  • Add RED tests for empty/incomplete SBOM, duplicate package identity, wrong subject kind/digest, missing build invocation, changed source revision, changed member manifest, and fabricated provenance.
  • Run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/subject ./apps/cicdctl/internal/artifacts/sbom ./apps/cicdctl/internal/artifacts/provenance -count=1; expected RED.
  • Implement output-kind adapters while preserving every existing container test unchanged.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/subject ./apps/cicdctl/internal/artifacts/sbom ./apps/cicdctl/internal/artifacts/provenance ./apps/cicdctl/internal/capabilities -count=1, make GO="$CICD_GO_BIN" contracts, and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Commit:
git add apps/cicdctl/internal/artifacts/subject apps/cicdctl/internal/artifacts/sbom apps/cicdctl/internal/artifacts/provenance apps/cicdctl/internal/capabilities capabilities/ci-sbom capabilities/ci-provenance contracts/readiness-cards.yaml docs/decisions/readiness/ci-sbom-P1.yaml docs/decisions/readiness/ci-provenance-P1.yaml docs/decisions/readiness/evidence/ci-sbom-P1.bundle.json docs/decisions/readiness/evidence/ci-provenance-P1.bundle.json Makefile
git commit -m "feat(supply-chain): support static archive subjects"

Task 7: Sign the artifact and aggregate reference-only supply-chain evidence

Files:

  • Create: apps/cicdctl/internal/artifacts/signing/request.go
  • Create: apps/cicdctl/internal/artifacts/signing/verifier.go
  • Create: apps/cicdctl/internal/artifacts/signing/signing_test.go
  • Create: apps/cicdctl/internal/artifacts/supplychain/aggregate.go
  • Create: apps/cicdctl/internal/artifacts/supplychain/aggregate_test.go
  • Modify: apps/cicdctl/internal/capabilities/capabilities.go
  • Create/complete: capabilities/ci-artifact-signing/descriptor.yaml, capabilities/ci-artifact-signing/policy.yaml, capabilities/ci-artifact-signing/acceptance.yaml, capabilities/ci-artifact-signing/runbook-index.yaml
  • Create/complete: capabilities/ci-static-artifact-supply-chain/descriptor.yaml, capabilities/ci-static-artifact-supply-chain/policy.yaml, capabilities/ci-static-artifact-supply-chain/acceptance.yaml, capabilities/ci-static-artifact-supply-chain/runbook-index.yaml
  • Create: docs/decisions/readiness/ci-artifact-signing-P1.yaml
  • Create: docs/decisions/readiness/ci-static-artifact-supply-chain-P1.yaml
  • Create evidence under: docs/decisions/readiness/evidence/ci-artifact-signing/ and docs/decisions/readiness/evidence/ci-static-artifact-supply-chain/
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Interfaces:

type SigningRequest struct { SubjectDigest, KeyID, OperationID string }

func VerifyStaticSupplyChain(
    candidate contracts.CandidateBundle,
    dependencyVulnerability, sbom, provenance, signature contracts.EvidenceRef,
) (contracts.SupplyChainEvidenceSet, error)
  • Add RED tests proving the signer receives only identity data, not source/candidate bytes; reject wrong subject, expired key, missing/duplicate evidence kind, invalid signature, altered candidate, and copied/rewritten child evidence.
  • Generate ephemeral Ed25519 test keys only.
  • Run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/signing ./apps/cicdctl/internal/artifacts/supplychain -count=1; expected RED.
  • Implement signing on the isolated-signer trust partition and a composite containing immutable child references only.
  • Raise both to P1/shadow; keep P2 blocked on real signer evidence.
  • Re-run "$CICD_GO_BIN" test ./apps/cicdctl/internal/artifacts/signing ./apps/cicdctl/internal/artifacts/supplychain ./apps/cicdctl/internal/capabilities -count=1 and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Commit:
git add apps/cicdctl/internal/artifacts/signing apps/cicdctl/internal/artifacts/supplychain apps/cicdctl/internal/capabilities capabilities/ci-artifact-signing capabilities/ci-static-artifact-supply-chain contracts/readiness-cards.yaml docs/decisions/readiness/ci-artifact-signing-P1.yaml docs/decisions/readiness/ci-static-artifact-supply-chain-P1.yaml docs/decisions/readiness/evidence/ci-artifact-signing docs/decisions/readiness/evidence/ci-static-artifact-supply-chain Makefile
git commit -m "feat(supply-chain): sign static candidates and aggregate evidence"

Task 8: Publish static candidates without rebuilding

Files:

  • Create: apps/release-control/internal/publish/publisher.go
  • Create: apps/release-control/internal/publish/local.go
  • Create: apps/release-control/internal/publish/service.go
  • Create: apps/release-control/internal/publish/publisher_test.go
  • Create: apps/release-control/internal/publish/local_test.go
  • Modify: apps/release-control/internal/app/app.go
  • Create/complete: capabilities/ci-static-site-publish/descriptor.yaml, capabilities/ci-static-site-publish/policy.yaml, capabilities/ci-static-site-publish/acceptance.yaml, capabilities/ci-static-site-publish/runbook-index.yaml
  • Create: docs/decisions/readiness/ci-static-site-publish-P1.yaml
  • Create evidence under: docs/decisions/readiness/evidence/ci-static-site-publish/
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Interface:

type PublishRequest struct {
    OperationID, SubjectDigest, CandidateDigest, ReleaseManifestDigest string
}
type PublicationRecord struct {
    OperationID, ProviderID, ImmutableURI, SubjectDigest, ServedContentDigest, State string
}
  • Add RED tests for idempotent put, same digest already present, different digest collision, response loss after mutation, failed reconciliation, served-content mismatch, mutable URI, and any attempted build/repackage operation.
  • Run "$CICD_GO_BIN" test ./apps/release-control/internal/publish ./apps/release-control/internal/app -count=1; expected RED.
  • Implement a content-addressed local P1 publisher. Response loss returns INDETERMINATE; retry requires reconciliation by operation ID.
  • Require provider subject and served-content digests both equal the approved candidate.
  • Re-run "$CICD_GO_BIN" test ./apps/release-control/internal/publish ./apps/release-control/internal/app -count=1 and make GO="$CICD_GO_BIN" registry; expected PASS.
  • Raise to P1/shadow; keep real provider P2 blocked.
  • Commit:
git add apps/release-control/internal/publish apps/release-control/internal/app capabilities/ci-static-site-publish contracts/readiness-cards.yaml docs/decisions/readiness/ci-static-site-publish-P1.yaml docs/decisions/readiness/evidence/ci-static-site-publish Makefile
git commit -m "feat(release-control): publish immutable static candidates"

Task 9: Promote the exact signed static candidate

Files:

  • Modify: apps/release-control/internal/candidate/candidate.go
  • Modify: apps/release-control/internal/candidate/candidate_test.go
  • Modify: apps/release-control/internal/approval/approval.go
  • Modify: apps/release-control/internal/approval/approval_test.go
  • Modify: apps/release-control/internal/gitops/cas.go
  • Modify: apps/release-control/internal/gitops/cas_test.go
  • Create: apps/release-control/internal/promotion/service.go
  • Create: apps/release-control/internal/promotion/service_test.go
  • Modify: apps/release-control/internal/app/app.go
  • Modify: capabilities/delivery-release-control/*
  • Create: docs/decisions/readiness/delivery-release-control-P1.yaml
  • Create: docs/decisions/readiness/evidence/delivery-release-control-P1.bundle.json
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile

Promotion request: signed release manifest, confirmed publication, source and artifact test-assurance references, supply-chain evidence index, approval subject/expiry, expected Git revision/tree digest, and operation ID. No source path, build command, or mutable provider URL is accepted.

type PromotionRequest struct {
    OperationID string
    ReleaseManifest contracts.ReleaseManifestV2
    Subject contracts.PromotionSubject
    Publication contracts.PublicationRecord
    EvidenceIndexDigest string
}

The compiler selects exactly one environment adapter by output profile: static archives require the static-site desired-state adapter, while container images require the existing Kubernetes/GitOps adapter. Zero or multiple environment adapters is invalid.

  • Add RED tests rejecting failed/missing source or artifact assessment, changed candidate, unconfirmed publication, served-content mismatch, unsigned evidence, stale approval, stale Git base, direct mutable URL, rebuild/repackage request, and zero/multiple/wrong-kind environment adapters.
  • Preserve and run existing real local Git CAS/response-loss tests.
  • Implement static desired-state promotion without an unconditional Kubernetes dependency; container releases keep their environment adapter.
  • Raise delivery-release-control only to P1/shadow using local Git and local content-addressed publication.
  • Run "$CICD_GO_BIN" test ./apps/release-control/internal/candidate ./apps/release-control/internal/approval ./apps/release-control/internal/gitops ./apps/release-control/internal/publish ./apps/release-control/internal/promotion ./apps/release-control/internal/app -count=1; expected PASS.
  • Commit:
git add apps/release-control capabilities/delivery-release-control contracts/readiness-cards.yaml docs/decisions/readiness/delivery-release-control-P1.yaml docs/decisions/readiness/evidence/delivery-release-control-P1.bundle.json Makefile
git commit -m "feat(release-control): promote verified static subjects"

Task 10: Keep the centrally installed required workflow thin

Files:

  • Create: apps/cicdctl/cmd/sourcectl/main.go
  • Create: apps/cicdctl/cmd/platform-bootstrap/main.go
  • Create: apps/cicdctl/internal/bootstrap/catalog.go
  • Create: apps/cicdctl/internal/bootstrap/pipeline.go
  • Create: apps/cicdctl/internal/bootstrap/status.go
  • Create tests under: apps/cicdctl/internal/bootstrap/
  • Modify: .gitea/workflows/required-delivery-guard.yaml
  • Modify: tools/contractctl/internal/workflow/required_status.go
  • Modify: tools/contractctl/internal/workflow/required_status_test.go
  • Modify: Makefile
  • Create: images/platform-bootstrap/Dockerfile
  • Create: images/platform-bootstrap/entrypoint.sh
  • Create: images/platform-bootstrap/README.md
  • Modify: toolchains/platform-release-catalog.yaml

Workflow sequence: exact source checkout → signed catalog verification → pinned bootstrap execution → one terminal sentinel publication. All language/test/build/provider/promotion stages are compiled inside the platform plan, not written in YAML. The only required status name is exactly platform/delivery-pipeline.

  • Add RED tests for exactly one stable required status, no language/build logic, no floating action references, digest-pinned binaries, a sentinel on every exit path, and absence of any rule requiring a product-repository workflow copy.
  • Run:
"$CICD_GO_BIN" test ./tools/contractctl/internal/workflow ./apps/cicdctl/internal/bootstrap ./apps/cicdctl/internal/app -count=1

Expected: RED until bootstrap binaries and status finalization exist.

  • Implement the four-step bootstrap and atomic sentinel finalization.
  • Re-run "$CICD_GO_BIN" test ./tools/contractctl/internal/workflow ./apps/cicdctl/internal/bootstrap ./apps/cicdctl/internal/app -count=1; expected PASS.
  • Commit:
git add apps/cicdctl/cmd apps/cicdctl/internal/bootstrap .gitea/workflows/required-delivery-guard.yaml tools/contractctl/internal/workflow images/platform-bootstrap Makefile toolchains/platform-release-catalog.yaml
git commit -m "feat(workflow): run the pinned delivery platform"

Task 11: Prove the complete frontend vertical in shadow

Files:

  • Create: fixtures/frontend-delivery-vertical/ with a minimal pnpm frontend, both consumer manifests, v3 source/artifact testctl fixtures, deterministic build, adversarial reports, local signer, local static publisher, and local Git desired state
  • Create: apps/cicdctl/internal/reports/shadow_parity.go
  • Create: apps/cicdctl/internal/reports/shadow_parity_test.go
  • Create: contracts/schemas/shadow-parity.schema.json
  • Sync: packages/contracts/schemas/shadow-parity.schema.json
  • Modify: packages/contracts/schema.go
  • Modify: packages/contracts/schema_test.go
  • Create: contracts/snapshots/v2/shadow-parity.schema.json
  • Create: contracts/examples/valid/shadow-parity.json
  • Create: docs/migration/frontend-template.md
  • Create P1 evidence under: docs/decisions/readiness/evidence/
  • Modify: docs/decisions/blocked-tasks.md
  • Modify: contracts/readiness-cards.yaml
  • Modify: Makefile
  • Modify fixture registry files consumed by: tools/fixturectl/
  • Modify: README.md

End-to-end order: source test plan/assessment → one deterministic candidate → artifact test plan/assessment → vulnerability/SBOM/provenance/signature → static supply-chain composite → content-addressed publication → Git CAS promotion → evidence-index sentinel.

  • Add RED vertical tests plus faults for missing work item, changed candidate, wrong-subject provider result, missing signature, response loss, concurrent Git writer, expired approval, and missing sentinel.
  • Add parity comparison over source revision; selected suites/counts/outcomes; coverage universe; HTTP scenario IDs; three browser outcomes; candidate/member digests; provider/supply-chain digests; and promotion readiness. Exclude timestamps, durations, temp paths, and runner IDs.
  • Verify one-writer behavior: the platform fixture produces the only candidate; legacy probes receive read-only references and cannot publish/promote.
  • Run before evidence updates:
make GO="$CICD_GO_BIN" capabilities

Expected: RED because the P1 chain lacks complete evidence.

  • Add only locally observed P1/shadow evidence. Keep P2 blocked with named missing Gitea/runner/scanner/signer/provider prerequisites.
  • Run:
make GO="$CICD_GO_BIN" contracts
make GO="$CICD_GO_BIN" registry
make GO="$CICD_GO_BIN" capabilities
make GO="$CICD_GO_BIN" boundary
make GO="$CICD_GO_BIN" verify
git diff --check

Expected: PASS at P1/shadow; no P2/active claim.

  • Commit:
git add fixtures/frontend-delivery-vertical apps/cicdctl/internal/reports contracts packages/contracts Makefile docs README.md
git commit -m "test(vertical): prove frontend delivery in shadow"

Handoff to the frontend consumer

The consumer migration may begin from the immutable Task 11 platform release. The product manifest pins its signed catalog version and selects all required capabilities. Central workflow/status installation remains an environment/platform operation. Product files never copy the workflow, provider orchestration, test normalizers, or promotion engine.