From 1971304d89382db713b8c0adc372b2e8c1a6f405 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:02:22 +0900 Subject: [PATCH 01/45] docs(adr): define immutable release supply-chain boundary --- ...immutable-release-supply-chain-boundary.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 docs/adr/0358-immutable-release-supply-chain-boundary.md diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md new file mode 100644 index 000000000..37a770d2f --- /dev/null +++ b/docs/adr/0358-immutable-release-supply-chain-boundary.md @@ -0,0 +1,167 @@ +# ADR 0358 — Immutable release supply-chain boundary + +**Decision status:** Proposed +**Date:** 2026-09-03 +**Related:** ContextualWisdomLab/.github#1782, LineageWeave #911 + +## Context + +LineageWeave does not currently publish an immutable GitHub release from a +product-local release workflow. The protected product line is versioned as a +Python package, but release evidence must identify one exact protected source +commit, the exact wheel and source distribution built from that commit, their +software bills of materials, and the immutable publication that buyers can +verify later. + +The organization already owns the reusable trust boundary for exact-artifact +SBOM attestation in `ContextualWisdomLab/.github`. LineageWeave must consume +that boundary rather than copy signing, OIDC, attestation-verification, or +provider policy into this repository. + +A fresh integration attempt exposed a canonical-owner prerequisite at +`ContextualWisdomLab/.github#1782`. The current reusable workflow requires the +GitHub Actions artifact digest as an input and also requires +`source-identity.json` *inside the same artifact* to contain that digest. The +artifact digest cannot be known until the artifact has been uploaded, while +changing the identity file changes the bytes whose digest GitHub computes. +That circular dependency makes the current handoff impossible to construct by +a deterministic caller without weakening the verifier. This ADR therefore +keeps the release integration Proposed until the canonical owner publishes an +acyclic exact-SHA contract. + +LineageWeave also cannot publish a commercial release from protected `main` +while the reachable synchronous PostgreSQL tooling path still contains the +LGPL-family `psycopg2-binary` dependency. PR #911 owns its replacement and the +reproducible lockfile migration. Release work must consume that merged, +license-clean protected result; it must not waive or suppress the inventory. + +## Decision + +1. LineageWeave owns the product-local release caller: release readiness, + package build, exact artifact preparation, release notes, tag creation, + immutable GitHub Release publication, reproducibility checks, and rollback + instructions. +2. `ContextualWisdomLab/.github` owns the reusable credentialed SBOM + attestation and verification boundary. The LineageWeave caller must invoke + a reviewed exact commit SHA of that reusable workflow. It must not vendor or + fork the trusted verifier to make a local release pass. +3. Release initiation is allowed only from the exact protected LineageWeave + `main` commit being released. A version is valid only when package metadata, + changelog/release notes, tag, distribution metadata, source identity and + GitHub Release all name the same canonical `MAJOR.MINOR.PATCH` version and + source SHA. +4. The unprivileged build stage receives `contents: read` only. It repeats the + repository's complete release-relevant test, documentation, lockfile, + security-contract and package checks before building a wheel and source + distribution. Pull-request-controlled source never receives OIDC, + attestation, release or package-publication credentials. +5. The build stage prepares one sealed evidence handoff containing exactly the + wheel, source distribution, one CycloneDX 1.7 SBOM bound to each exact + distribution, `source-identity.json`, and `checksums.sha256`. The inner + source identity binds repository, exact source SHA, predicate/schema, + distribution filenames and distribution/SBOM SHA-256 values. The canonical + owner decides the final acyclic representation after `.github#1782`. +6. GitHub's uploaded artifact ID/name/digest is an outer immutable transport + receipt. After `.github#1782` is repaired, the caller passes the returned + receipt and exact inner identities to the canonical reusable workflow. The + reusable workflow must independently revalidate the same-run receipt and + inert handoff before any OIDC token or attestation permission becomes + available. +7. Immutable publication occurs only after the exact artifact set has passed + canonical attestation verification. The release job creates the tag against + the already-verified protected source SHA and creates a non-draft, + non-prerelease GitHub Release without overwriting an existing tag, asset or + version. A failed or partial publication is an incident, not permission to + mutate previously published bytes under the same identity. +8. Reproducibility is tested by rebuilding the wheel and source distribution + from the same protected source under the reviewed toolchain and comparing + the release contract's declared deterministic subjects. Any known + nondeterministic field must be removed or normalized by source/tooling + repair; it is not excluded from comparison merely to obtain GREEN. +9. Rollback restores a previously reviewed workflow revision and produces new + artifacts from a new protected commit/version. It does not move an existing + release tag or reuse an old attestation for different bytes. +10. Package-registry publication is not inferred from a GitHub Release. If a + registry such as PyPI is adopted, its protected environment, trusted + publishing identity, independent review policy and immutable-version + behavior require a separate accepted decision before credentials or + publishing steps are added. + +## RED / GREEN acceptance + +The current RED is structural and owner-bound: LineageWeave has no product +release workflow, protected `main` is not yet license-clean, and the canonical +exact-artifact reusable cannot accept a deterministic first-party caller +because of `.github#1782`. + +GREEN requires all of the following on one unchanged protected source SHA: + +- #911 or a verified successor has removed the reachable disallowed dependency + and committed a reproducible lock that passes the frozen dependency gate; +- `.github#1782` is fixed on protected `.github/main` and LineageWeave pins the + repaired reusable workflow by exact commit SHA; +- a product-local release workflow builds wheel/sdist plus the exact six-file + evidence handoff without credentialed execution of pull-request source; +- the canonical reusable verifies and attests the exact returned artifact + receipt and exact wheel/sdist subjects; +- a clean rebuild proves the declared reproducibility contract; +- release notes, version, protected source SHA, tag, distributions, SBOMs, + attestations and immutable GitHub Release are mutually consistent; and +- rollback/incident instructions are exercised against synthetic release + fixtures without deleting or rewriting valid published evidence. + +Until every condition is evidenced, this ADR remains Proposed and no +LineageWeave release-readiness claim may cite this design as delivered. + +## Alternatives considered + +### Copy the central attestation workflow into LineageWeave + +Rejected. It would create a second signing-policy authority, duplicate security +fixes and let a product repository bypass a defect in the canonical owner. + +### Drop the GitHub artifact digest from verification locally + +Rejected. The outer receipt protects the exact same-run transport handoff. +The circularity is an owner-contract modeling defect; weakening digest binding +in a consumer is not a causal repair. + +### Publish a GitHub Release first and attach evidence later + +Rejected. Buyers would observe a release identity before its exact artifact, +SBOM and provenance evidence was complete. Partial evidence cannot be promoted +as immutable release readiness. + +### Wait for a package registry before creating any release boundary + +Rejected. GitHub Release immutability, exact source/artifact identity, SBOM, +provenance and rollback are independently valuable buyer controls. Registry +publication can be added later behind its own protected decision. + +## Risks and follow-up + +- The central reusable contract can change while `.github#1782` is repaired. + LineageWeave must inspect the protected implementation and pin its exact SHA; + no branch-name or mutable `main` reference is acceptable in release code. +- Reproducible Python distributions may expose timestamps, archive ordering or + backend metadata that require causal build-system repair. A mismatch remains + RED until explained and removed at the source. +- Current organization Actions queue saturation can delay evidence, but queue + latency is not a reason to bypass release gates or transfer predecessor-head + results. + +## References + +Bray, T. (2017). *The JavaScript Object Notation (JSON) data interchange +format* (RFC 8259). Internet Engineering Task Force. +https://doi.org/10.17487/RFC8259 + +CycloneDX Core Working Group. (2025). *CycloneDX specification 1.7*. +OWASP Foundation. https://cyclonedx.org/specification/overview/ + +GitHub. (2026). *Using artifact attestations to establish provenance for +builds*. GitHub Docs. +https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations + +Open Source Security Foundation. (2025). *SLSA specification version 1.2*. +https://slsa.dev/spec/v1.2/ From d0e82b64e17d1ee2d0f2c2e60b1d6904811181be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:03:04 +0900 Subject: [PATCH 02/45] docs(release): record immutable release gates --- docs/release.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/release.md diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..4c7eec1f7 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,93 @@ +# LineageWeave release contract + +This document is the operator projection of Proposed ADR 0358. It describes +what must be true before LineageWeave publishes an immutable release; it is not +evidence that a release already exists. + +## Current delivery state + +As of 2026-09-03, protected `main` has no product-local release workflow and no +GitHub Release has been published. Two prerequisites are intentionally outside +this document's implementation scope: + +- LineageWeave PR #911 owns removal of the reachable `psycopg2-binary` + commercial-license intake finding and the corresponding reproducible + `uv.lock` migration. +- `ContextualWisdomLab/.github#1782` owns the circular artifact-digest defect in + the canonical exact-artifact SBOM attestation reusable. LineageWeave will + consume the repaired protected-owner workflow by exact commit SHA; it will + not copy or weaken that trust boundary locally. + +A queued workflow, a predecessor-head success, a locally built wheel, a tag +without exact evidence, or a draft release does not satisfy this contract. + +## Release sequence + +1. Resolve the candidate from protected `main` and record its full 40-character + source SHA. Refuse another ref, detached historical branch, or a source SHA + that moves during the release decision. +2. Validate one canonical three-part version across `pyproject.toml`, package + metadata and release notes. Refuse a tag/version that already exists. +3. Reproduce the committed dependency lock and install from the reviewed frozen + inputs. The release candidate must be commercial-license clean under current + organization policy; do not suppress an inventory finding to continue. +4. Repeat the release-relevant repository contract on that exact SHA: complete + backend/frontend tests, documentation-as-contract checks, package checks and + the then-required security/governance gates. Required asynchronous GitHub + checks must be terminal-success on the unchanged candidate before publish. +5. Build a wheel and source distribution in an unprivileged job. The build job + receives `contents: read` only and does not receive OIDC, attestation, + release, package-publication or repository-write credentials. +6. Rebuild from the same exact source/toolchain and compare the deterministic + release subjects. Any unexplained byte difference is RED; do not omit the + differing subject merely to make the comparison pass. +7. Generate one CycloneDX 1.7 SBOM for each exact distribution and bind its root + component to the distribution filename and SHA-256 digest. Prepare the + six-file handoff defined by ADR 0358: wheel, wheel SBOM, source distribution, + source-distribution SBOM, `source-identity.json`, and + `checksums.sha256`. +8. Upload that handoff once and retain GitHub's returned artifact ID, name and + digest as the immutable outer transport receipt. +9. Invoke the repaired `ContextualWisdomLab/.github` exact-artifact reusable at + an immutable reviewed commit SHA. The central verifier must independently + bind the same run, source SHA, outer receipt, inner checksums and exact + wheel/sdist subjects before its credentialed attestation job runs. +10. Only after trusted verification succeeds, create the annotated release tag + against the verified source SHA and publish a non-draft, non-prerelease + GitHub Release with the verified distributions, SBOM/provenance evidence, + checksum material and release notes. Never move an existing release tag or + overwrite published bytes under an existing version. +11. Fetch the published release back through GitHub's API, verify tag/source + identity and asset digests against the sealed evidence, and retain this + post-publication receipt as release evidence. + +## Failure and rollback + +A failure before publication leaves no release identity to repair in place. +Preserve the run ID, exact source SHA, logs and any sealed evidence needed for +RCA, fix source/configuration through a normal protected PR, and start again +from a new exact candidate. + +If an already-published artifact or attestation is found invalid, preserve the +forensic evidence and identify affected subjects before any revocation or +removal. Correct the source or workflow through normal governance, publish new +artifacts under a new version/source SHA, and tell consumers which subjects are +invalid and which replacements they should verify. Rollback never means moving +a tag, replacing an asset under the same name/version, or reusing an old +attestation for new bytes. + +## Evidence that does not transfer + +Checks, reviews, package builds, SBOMs, attestations, browser evidence and +release receipts belong to the exact head/artifact they evaluated. A source +commit, dependency lock, release workflow, reusable-workflow pin or artifact +byte change invalidates predecessor evidence and requires fresh verification. + +## Owner boundaries + +LineageWeave owns release orchestration for its own package and buyer-visible +release receipt. `ContextualWisdomLab/.github` owns the credentialed reusable +attestation policy. Provider/model execution remains owned by +`contextual-orchestrator`; statistical/psychometric engines and their release +truth remain with their canonical owners. No release step copies those owners' +source or treats a mutable sibling branch as a production dependency. From 42c10c1b7c03f6d5246080c9bba5d5235ce6143c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:04:43 +0900 Subject: [PATCH 03/45] docs(adr): map release operator contract to ADR 0358 --- docs/adr/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/README.md b/docs/adr/README.md index 8979111c1..659e6460f 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -11,6 +11,7 @@ decision from them. |---|---| | [`product-requirements.md`](../product-requirements.md) | Product requirements projection across the ADR set; ADRs remain normative, including [0252](0252-temporal-primary-voice-history.md) | | [`product-technical-gap-baseline.md`](../product-technical-gap-baseline.md) | Product/technical traceability projection across the ADR set; ADRs remain normative | +| [`release.md`](../release.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md), [0238](0238-source-conversation-turn-import-contract.md) | | [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0251](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md) | From 72d99853d10588d20b21dfebc7737ff5ee09be1a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:06:03 +0900 Subject: [PATCH 04/45] docs(release): add authoritative supply-chain references --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md new file mode 100644 index 000000000..ab274b29a --- /dev/null +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -0,0 +1,76 @@ +# Release supply-chain references + +**Supporting evidence for:** ADR 0358 +**Reviewed:** 2026-09-03 +**Status:** Non-normative doctoring evidence. ADR 0358 remains the decision authority. + +This note records the authoritative external standards and platform contracts +used while defining LineageWeave's immutable release boundary. It does not +promote an unimplemented workflow or queued check to release evidence. + +## Current authoritative baseline + +### CycloneDX 1.7 + +The CycloneDX specification overview identifies **1.7** as the current +specification version and gives its release date as 2025-10-21. ADR 0358 uses +CycloneDX 1.7 only for the per-distribution SBOM representation; LineageWeave +does not claim that a generic repository-directory SBOM is an attestation of +one exact wheel or source distribution. + +CycloneDX Core Working Group. (2025). *CycloneDX specification 1.7*. OWASP +Foundation. https://cyclonedx.org/specification/overview/ + +### SLSA 1.2 + +The SLSA project announced Version **1.2** as the approved release on +2025-11-24. ADR 0358 uses SLSA as supply-chain threat/provenance grounding; it +does not claim a SLSA level merely because a workflow uses provenance or a +reusable workflow. + +Open Source Security Foundation. (2025, November 24). *Announcing SLSA v1.2*. +https://slsa.dev/blog/2025/11/announce-slsa-v1.2 + +### GitHub artifact attestations + +GitHub's current documentation requires explicit attestation/OIDC permissions +for credentialed provenance generation and documents verification of artifact +attestations. ADR 0358 therefore keeps pull-request-controlled build work in an +unprivileged job and delegates credentialed attestation to the canonical +organization reusable only after inert exact-artifact verification. + +GitHub. (n.d.). *Using artifact attestations to establish provenance for +builds*. GitHub Docs. Retrieved September 3, 2026, from +https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations + +GitHub. (n.d.). *Using artifact attestations*. GitHub Docs. Retrieved September +3, 2026, from +https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations + +### JSON strictness + +The canonical `.github` verifier rejects duplicate JSON properties, non-finite +numbers, invalid UTF-8 and unexpected evidence members before trusting the +handoff. RFC 8259 is the interoperability baseline for the JSON representation; +repository-specific stricter validation remains a security profile rather than +a claim that RFC 8259 itself mandates every fail-closed rule used by the +verifier. + +Bray, T. (2017). *The JavaScript Object Notation (JSON) data interchange +format* (RFC 8259). Internet Engineering Task Force. +https://doi.org/10.17487/RFC8259 + +## Traceability to ADR 0358 + +- exact wheel/sdist SBOM representation → CycloneDX 1.7; +- source/build provenance threat model → SLSA 1.2; +- credential separation and artifact-attestation verification → GitHub artifact + attestation documentation; +- strict machine-readable evidence intake → RFC 8259 plus the stricter + canonical `.github` verifier contract; +- current circular transport-receipt defect → `ContextualWisdomLab/.github#1782`. + +External standards do not override the current canonical owner implementation. +When `.github#1782` is repaired, LineageWeave must re-read protected +`ContextualWisdomLab/.github` and pin the reviewed exact reusable-workflow SHA +that implements the accepted handoff. From 0eea51577d8b2e90c93937d14b89bbde0fe7bf84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:06:44 +0900 Subject: [PATCH 05/45] docs(adr): trace release standards to ADR 0358 --- docs/adr/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/README.md b/docs/adr/README.md index 659e6460f..20426390d 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -12,6 +12,7 @@ decision from them. | [`product-requirements.md`](../product-requirements.md) | Product requirements projection across the ADR set; ADRs remain normative, including [0252](0252-temporal-primary-voice-history.md) | | [`product-technical-gap-baseline.md`](../product-technical-gap-baseline.md) | Product/technical traceability projection across the ADR set; ADRs remain normative | | [`release.md`](../release.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | +| [`RELEASE_SUPPLY_CHAIN_REFERENCES.md`](../doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md), [0238](0238-source-conversation-turn-import-contract.md) | | [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0251](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md) | From 98af84bc22b18b716b7a6a8f13cbff736c3ac2a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:22:10 +0900 Subject: [PATCH 06/45] test(adr): reject duplicate supporting-document mappings --- tests/test_adr_supporting_document_map.py | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/test_adr_supporting_document_map.py diff --git a/tests/test_adr_supporting_document_map.py b/tests/test_adr_supporting_document_map.py new file mode 100644 index 000000000..4ff2280cc --- /dev/null +++ b/tests/test_adr_supporting_document_map.py @@ -0,0 +1,29 @@ +"""Regression tests for the ADR supporting-document authority map.""" + +from collections import Counter +from pathlib import Path +import re + + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +_ADR_INDEX = _REPOSITORY_ROOT / "docs" / "adr" / "README.md" +_SUPPORTING_DOCUMENT_ROW = re.compile( + r"^\| \[`[^`]+`\]\((?P[^)]+)\) \|" +) + + +def test_supporting_document_map_has_unique_document_targets() -> None: + """Keep one normative ADR mapping row per supporting document target.""" + targets = [ + match.group("target") + for line in _ADR_INDEX.read_text(encoding="utf-8").splitlines() + if (match := _SUPPORTING_DOCUMENT_ROW.match(line)) is not None + ] + duplicate_targets = sorted( + target for target, count in Counter(targets).items() if count > 1 + ) + + assert duplicate_targets == [], ( + "docs/adr/README.md must map each supporting document exactly once; " + f"duplicate targets: {duplicate_targets}" + ) From 4552ac0aef7af60927d737771ebdcf946c48a7d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:22:57 +0900 Subject: [PATCH 07/45] fix(adr): deduplicate supporting-document authority map --- docs/adr/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index 20426390d..7a81463dd 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -15,7 +15,6 @@ decision from them. | [`RELEASE_SUPPLY_CHAIN_REFERENCES.md`](../doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md), [0238](0238-source-conversation-turn-import-contract.md) | | [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0251](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) | -| [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md) | | [`PROV_O_IMPLEMENTATION.md`](../PROV_O_IMPLEMENTATION.md) | [0065](0065-prov-o-provenance-boundary.md) | | [`PROV_O_IMPLEMENTATION_MATRIX.md`](../PROV_O_IMPLEMENTATION_MATRIX.md) | [0065](0065-prov-o-provenance-boundary.md) | | [`ONTOLOGY_NAMESPACE_INVENTORY.md`](../doctoring/ONTOLOGY_NAMESPACE_INVENTORY.md) | [0207](0207-repository-case-ontology-namespace-canonical.md), [0157](0157-public-ontology-namespace-identity.md) | @@ -55,4 +54,4 @@ decision from them. Files under `docs/doctoring/` remain non-normative supporting evidence even when this map links them to an ADR. Runtime-evidence files record observed -results for already-decided behavior. +results for already-decided behavior. \ No newline at end of file From f25677f8dcfe79ea18c1b732c2c18fb48774f7e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:24:27 +0900 Subject: [PATCH 08/45] test(release): require immutable-release admission gate --- tests/test_release_supply_chain_docs.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/test_release_supply_chain_docs.py diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py new file mode 100644 index 000000000..922207fa0 --- /dev/null +++ b/tests/test_release_supply_chain_docs.py @@ -0,0 +1,18 @@ +"""Contract tests for release supply-chain documentation.""" + +from pathlib import Path + + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +_ADR = _REPOSITORY_ROOT / "docs" / "adr" / "0358-immutable-release-supply-chain-boundary.md" +_RELEASE_GUIDE = _REPOSITORY_ROOT / "docs" / "release.md" +_IMMUTABILITY_ENDPOINT = "GET /repos/{owner}/{repo}/immutable-releases" + + +def test_release_publication_requires_enabled_github_release_immutability() -> None: + """Fail closed before publication unless GitHub release immutability is enabled.""" + for path in (_ADR, _RELEASE_GUIDE): + text = path.read_text(encoding="utf-8") + assert _IMMUTABILITY_ENDPOINT in text, path + assert "fail closed" in text.lower(), path + assert "before tag" in text.lower(), path From 2e2a6cf355fbff2dd3d9a9440b47413afff9714e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:25:29 +0900 Subject: [PATCH 09/45] fix(release): fail closed on mutable GitHub release policy --- ...immutable-release-supply-chain-boundary.md | 71 +++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md index 37a770d2f..c3cc33832 100644 --- a/docs/adr/0358-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0358-immutable-release-supply-chain-boundary.md @@ -35,6 +35,16 @@ LGPL-family `psycopg2-binary` dependency. PR #911 owns its replacement and the reproducible lockfile migration. Release work must consume that merged, license-clean protected result; it must not waive or suppress the inventory. +GitHub's immutable-release setting is a separate repository/organization +control from artifact attestation. GitHub documents that a published immutable +release locks the associated tag and assets, and automatically creates a +release attestation. It also exposes an authenticated repository endpoint, +`GET /repos/{owner}/{repo}/immutable-releases`, that returns success only when +release immutability is enabled. The release caller therefore needs an +administrative read-only preflight credential isolated from pull-request and +build execution; absence of that credential or an unsuccessful preflight is a +release-admission failure, not a reason to publish a mutable release. + ## Decision 1. LineageWeave owns the product-local release caller: release readiness, @@ -68,20 +78,32 @@ license-clean protected result; it must not waive or suppress the inventory. inert handoff before any OIDC token or attestation permission becomes available. 7. Immutable publication occurs only after the exact artifact set has passed - canonical attestation verification. The release job creates the tag against - the already-verified protected source SHA and creates a non-draft, - non-prerelease GitHub Release without overwriting an existing tag, asset or - version. A failed or partial publication is an incident, not permission to - mutate previously published bytes under the same identity. -8. Reproducibility is tested by rebuilding the wheel and source distribution + canonical attestation verification and repository release immutability has + been independently admitted. Before tag creation or Release publication, a + trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` with + the minimum GitHub Administration (read) permission. Only an authenticated + success response that confirms `enabled: true` is admissible. A 404, + permission failure, transport/API failure, malformed response, or any result + that does not confirm `enabled: true` must fail closed before tag creation. + The preflight credential is unavailable to pull-request and unprivileged + build jobs. +8. After that preflight, the release job creates the release-specific tag + against the already-verified protected source SHA, creates a draft GitHub + Release, attaches the complete verified asset set, and publishes the draft + as the immutable non-prerelease release. This follows GitHub's documented + immutable-release publication sequence so all assets are present before + publication locks the release. It must not overwrite an existing tag, asset + or version. A failed or partial publication is an incident, not permission + to mutate previously published bytes under the same identity. +9. Reproducibility is tested by rebuilding the wheel and source distribution from the same protected source under the reviewed toolchain and comparing the release contract's declared deterministic subjects. Any known nondeterministic field must be removed or normalized by source/tooling repair; it is not excluded from comparison merely to obtain GREEN. -9. Rollback restores a previously reviewed workflow revision and produces new - artifacts from a new protected commit/version. It does not move an existing - release tag or reuse an old attestation for different bytes. -10. Package-registry publication is not inferred from a GitHub Release. If a +10. Rollback restores a previously reviewed workflow revision and produces new + artifacts from a new protected commit/version. It does not move an existing + release tag or reuse an old attestation for different bytes. +11. Package-registry publication is not inferred from a GitHub Release. If a registry such as PyPI is adopted, its protected environment, trusted publishing identity, independent review policy and immutable-version behavior require a separate accepted decision before credentials or @@ -104,7 +126,12 @@ GREEN requires all of the following on one unchanged protected source SHA: evidence handoff without credentialed execution of pull-request source; - the canonical reusable verifies and attests the exact returned artifact receipt and exact wheel/sdist subjects; +- a trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` + before tag creation and confirms `enabled: true`; missing administrative-read + capability or any non-confirming result must fail closed; - a clean rebuild proves the declared reproducibility contract; +- the complete verified asset set is attached to a draft GitHub Release before + that draft is published as the immutable release; - release notes, version, protected source SHA, tag, distributions, SBOMs, attestations and immutable GitHub Release are mutually consistent; and - rollback/incident instructions are exercised against synthetic release @@ -126,11 +153,19 @@ Rejected. The outer receipt protects the exact same-run transport handoff. The circularity is an owner-contract modeling defect; weakening digest binding in a consumer is not a causal repair. +### Publish without proving GitHub release immutability is enabled + +Rejected. A release whose tag or assets remain mutable does not meet this +ADR's buyer-visible integrity claim. Failure to read the setting is also not +proof that the setting is enabled, so the publication path fails closed rather +than assuming repository configuration. + ### Publish a GitHub Release first and attach evidence later Rejected. Buyers would observe a release identity before its exact artifact, -SBOM and provenance evidence was complete. Partial evidence cannot be promoted -as immutable release readiness. +SBOM and provenance evidence was complete. GitHub's immutable-release guidance +also recommends attaching assets to a draft and publishing only after the +asset set is complete. ### Wait for a package registry before creating any release boundary @@ -143,6 +178,11 @@ publication can be added later behind its own protected decision. - The central reusable contract can change while `.github#1782` is repaired. LineageWeave must inspect the protected implementation and pin its exact SHA; no branch-name or mutable `main` reference is acceptable in release code. +- The immutable-release status endpoint requires administrative read access. + That capability must be provisioned to the trusted release admission step + only; it must not expand permissions for tests, builds, pull requests or the + canonical attestation reusable. If it cannot be provisioned, publication + remains RED. - Reproducible Python distributions may expose timestamps, archive ordering or backend metadata that require causal build-system repair. A mismatch remains RED until explained and removed at the source. @@ -159,6 +199,13 @@ https://doi.org/10.17487/RFC8259 CycloneDX Core Working Group. (2025). *CycloneDX specification 1.7*. OWASP Foundation. https://cyclonedx.org/specification/overview/ +GitHub. (2026). *Immutable releases*. GitHub Docs. +https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases + +GitHub. (2026). *REST API endpoints for repositories: Check if immutable +releases are enabled for a repository*. GitHub Docs. +https://docs.github.com/en/rest/repos/repos + GitHub. (2026). *Using artifact attestations to establish provenance for builds*. GitHub Docs. https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations From 28fd4bcb7b44a2c2c3e7ae3d498dfc4d8188742e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:26:01 +0900 Subject: [PATCH 10/45] docs(release): gate publication on immutable-release policy --- docs/release.md | 54 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/release.md b/docs/release.md index 4c7eec1f7..5cdf65c1b 100644 --- a/docs/release.md +++ b/docs/release.md @@ -19,7 +19,8 @@ this document's implementation scope: not copy or weaken that trust boundary locally. A queued workflow, a predecessor-head success, a locally built wheel, a tag -without exact evidence, or a draft release does not satisfy this contract. +without exact evidence, a mutable GitHub release, or an unpublished draft +release does not satisfy this contract. ## Release sequence @@ -52,21 +53,32 @@ without exact evidence, or a draft release does not satisfy this contract. an immutable reviewed commit SHA. The central verifier must independently bind the same run, source SHA, outer receipt, inner checksums and exact wheel/sdist subjects before its credentialed attestation job runs. -10. Only after trusted verification succeeds, create the annotated release tag - against the verified source SHA and publish a non-draft, non-prerelease - GitHub Release with the verified distributions, SBOM/provenance evidence, - checksum material and release notes. Never move an existing release tag or - overwrite published bytes under an existing version. -11. Fetch the published release back through GitHub's API, verify tag/source - identity and asset digests against the sealed evidence, and retain this - post-publication receipt as release evidence. +10. Before tag creation or Release publication, use a trusted admission step to + call `GET /repos/{owner}/{repo}/immutable-releases` with only the GitHub + Administration (read) capability required by that endpoint. Continue only + when the authenticated response confirms `enabled: true`. A 404, + permission error, transport/API error, malformed response, or any other + non-confirming result must fail closed. This credential is not exposed to + pull-request or unprivileged build execution. +11. After trusted verification and the immutability preflight both succeed, + create the annotated release-specific tag against the verified source SHA, + create a draft GitHub Release, and attach the complete verified + distributions, SBOM/provenance evidence, checksum material and release + notes. Do not publish an incomplete asset set. +12. Publish that fully populated draft as the non-prerelease immutable GitHub + Release. Never move an existing release tag or overwrite published bytes + under an existing version. +13. Fetch the published release back through GitHub's API, verify tag/source + identity, immutable-release state and asset digests against the sealed + evidence, and retain this post-publication receipt as release evidence. ## Failure and rollback -A failure before publication leaves no release identity to repair in place. -Preserve the run ID, exact source SHA, logs and any sealed evidence needed for -RCA, fix source/configuration through a normal protected PR, and start again -from a new exact candidate. +A failure before publication leaves no valid release identity to repair in +place. In particular, an immutability preflight failure stops before tag +creation. Preserve the run ID, exact source SHA, logs and any sealed evidence +needed for RCA, fix source/configuration through a normal protected PR, and +start again from a new exact candidate. If an already-published artifact or attestation is found invalid, preserve the forensic evidence and identify affected subjects before any revocation or @@ -80,14 +92,18 @@ attestation for new bytes. Checks, reviews, package builds, SBOMs, attestations, browser evidence and release receipts belong to the exact head/artifact they evaluated. A source -commit, dependency lock, release workflow, reusable-workflow pin or artifact -byte change invalidates predecessor evidence and requires fresh verification. +commit, dependency lock, release workflow, reusable-workflow pin, repository +immutability setting or artifact byte change invalidates predecessor evidence +and requires fresh verification. ## Owner boundaries LineageWeave owns release orchestration for its own package and buyer-visible release receipt. `ContextualWisdomLab/.github` owns the credentialed reusable -attestation policy. Provider/model execution remains owned by -`contextual-orchestrator`; statistical/psychometric engines and their release -truth remain with their canonical owners. No release step copies those owners' -source or treats a mutable sibling branch as a production dependency. +attestation policy. The trusted release-admission step may receive only the +administrative read capability needed to prove repository release immutability; +that capability is not a build, pull-request, attestation-policy or provider +credential. Provider/model execution remains owned by `contextual-orchestrator`; +statistical/psychometric engines and their release truth remain with their +canonical owners. No release step copies those owners' source or treats a +mutable sibling branch as a production dependency. From 52f43c907d272fc9b2f2d3a0a28473d777e79c76 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:26:26 +0900 Subject: [PATCH 11/45] docs(release): trace immutable-release admission sources --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index ab274b29a..11a1f1c38 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -47,6 +47,35 @@ GitHub. (n.d.). *Using artifact attestations*. GitHub Docs. Retrieved September 3, 2026, from https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations +### GitHub immutable releases + +GitHub documents immutable releases as a repository/organization control that +locks a published release's associated tag and assets. Publication also creates +a release attestation. GitHub recommends creating a draft release, attaching +all assets, and publishing the populated draft so immutability does not leave a +partially populated release. + +The current repository REST API exposes +`GET /repos/{owner}/{repo}/immutable-releases` to check whether the control is +enabled. GitHub documents an authenticated `200` response when enabled and a +`404` when it is not enabled, and requires repository Administration (read) +permission for the check. ADR 0358 therefore treats an authenticated +`enabled: true` result as release admission and fails closed when the status +cannot be established. That administrative read capability belongs only to the +trusted admission step; it is not granted to pull-request or build execution. + +GitHub. (n.d.). *Immutable releases*. GitHub Docs. Retrieved September 3, 2026, +from +https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases + +GitHub. (n.d.). *Preventing changes to your releases*. GitHub Docs. Retrieved +September 3, 2026, from +https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes + +GitHub. (n.d.). *REST API endpoints for repositories: Check if immutable +releases are enabled for a repository*. GitHub Docs. Retrieved September 3, +2026, from https://docs.github.com/en/rest/repos/repos + ### JSON strictness The canonical `.github` verifier rejects duplicate JSON properties, non-finite @@ -66,6 +95,8 @@ https://doi.org/10.17487/RFC8259 - source/build provenance threat model → SLSA 1.2; - credential separation and artifact-attestation verification → GitHub artifact attestation documentation; +- immutable tag/asset admission and draft-first publication → GitHub immutable + release documentation and repository REST API; - strict machine-readable evidence intake → RFC 8259 plus the stricter canonical `.github` verifier contract; - current circular transport-receipt defect → `ContextualWisdomLab/.github#1782`. From 01743821d468f423b946b04fea9900c4cff6f505 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:48:55 +0900 Subject: [PATCH 12/45] test(release): require publish-boundary immutability recheck --- tests/test_release_supply_chain_docs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 922207fa0..197c24241 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -16,3 +16,12 @@ def test_release_publication_requires_enabled_github_release_immutability() -> N assert _IMMUTABILITY_ENDPOINT in text, path assert "fail closed" in text.lower(), path assert "before tag" in text.lower(), path + + +def test_release_publication_rechecks_immutability_and_tag_identity_at_publish_boundary() -> None: + """Close the preflight-to-publish TOCTOU window for immutable release identity.""" + for path in (_ADR, _RELEASE_GUIDE): + text = path.read_text(encoding="utf-8").lower() + assert "immediately before publish" in text, path + assert "recheck" in text, path + assert "tag" in text and "source sha" in text, path From 374a490013d47affb7c31e44ab2b0e03cf0f0867 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:49:45 +0900 Subject: [PATCH 13/45] fix(release): revalidate immutable tag at publish boundary --- docs/release.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/release.md b/docs/release.md index 5cdf65c1b..40aed2abd 100644 --- a/docs/release.md +++ b/docs/release.md @@ -65,20 +65,28 @@ release does not satisfy this contract. create a draft GitHub Release, and attach the complete verified distributions, SBOM/provenance evidence, checksum material and release notes. Do not publish an incomplete asset set. -12. Publish that fully populated draft as the non-prerelease immutable GitHub +12. Immediately before publish, recheck repository release immutability through + `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the release + tag. The response must still confirm `enabled: true`, and the tag must still + resolve to the exact protected source SHA admitted in step 1. Any setting + change, lookup failure, malformed response, missing tag, or tag/source SHA + mismatch must fail closed without publishing the draft. This second check + closes the time-of-check/time-of-use window between admission and publish. +13. Publish that fully populated draft as the non-prerelease immutable GitHub Release. Never move an existing release tag or overwrite published bytes under an existing version. -13. Fetch the published release back through GitHub's API, verify tag/source +14. Fetch the published release back through GitHub's API, verify tag/source identity, immutable-release state and asset digests against the sealed evidence, and retain this post-publication receipt as release evidence. ## Failure and rollback A failure before publication leaves no valid release identity to repair in -place. In particular, an immutability preflight failure stops before tag -creation. Preserve the run ID, exact source SHA, logs and any sealed evidence -needed for RCA, fix source/configuration through a normal protected PR, and -start again from a new exact candidate. +place. In particular, either immutability admission check or the publish-boundary +tag/source identity recheck failing leaves the draft unpublished. Preserve the +run ID, exact source SHA, logs and any sealed evidence needed for RCA, fix +source/configuration through a normal protected PR, and start again from a new +exact candidate. If an already-published artifact or attestation is found invalid, preserve the forensic evidence and identify affected subjects before any revocation or From adc564ddd5d23868fe2fbae83a5e64a1591b5e5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:50:35 +0900 Subject: [PATCH 14/45] fix(release): close preflight-to-publish TOCTOU window --- ...immutable-release-supply-chain-boundary.md | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md index c3cc33832..750ffa4b6 100644 --- a/docs/adr/0358-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0358-immutable-release-supply-chain-boundary.md @@ -89,12 +89,19 @@ release-admission failure, not a reason to publish a mutable release. build jobs. 8. After that preflight, the release job creates the release-specific tag against the already-verified protected source SHA, creates a draft GitHub - Release, attaches the complete verified asset set, and publishes the draft - as the immutable non-prerelease release. This follows GitHub's documented - immutable-release publication sequence so all assets are present before - publication locks the release. It must not overwrite an existing tag, asset - or version. A failed or partial publication is an incident, not permission - to mutate previously published bytes under the same identity. + Release, and attaches the complete verified asset set. Immediately before + publish, the trusted release boundary must recheck + `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve that tag. The + repository must still report `enabled: true`, and the tag must still resolve + to the exact protected source SHA admitted for the release. Any setting + change, lookup/API failure, malformed response, missing tag, or tag/source + SHA mismatch must fail closed while the draft remains unpublished. Only + after this second admission may the draft be published as the immutable + non-prerelease release. This closes the time-of-check/time-of-use interval + between the initial immutability admission and publication. It must not + overwrite an existing tag, asset or version. A failed or partial publication + is an incident, not permission to mutate previously published bytes under + the same identity. 9. Reproducibility is tested by rebuilding the wheel and source distribution from the same protected source under the reviewed toolchain and comparing the release contract's declared deterministic subjects. Any known @@ -130,8 +137,11 @@ GREEN requires all of the following on one unchanged protected source SHA: before tag creation and confirms `enabled: true`; missing administrative-read capability or any non-confirming result must fail closed; - a clean rebuild proves the declared reproducibility contract; -- the complete verified asset set is attached to a draft GitHub Release before - that draft is published as the immutable release; +- the complete verified asset set is attached to a draft GitHub Release; +- immediately before publish, the trusted boundary must recheck repository + immutability and prove the release tag still resolves to the same protected + source SHA; any non-confirming result must fail closed with the draft + unpublished; - release notes, version, protected source SHA, tag, distributions, SBOMs, attestations and immutable GitHub Release are mutually consistent; and - rollback/incident instructions are exercised against synthetic release @@ -183,6 +193,10 @@ publication can be added later behind its own protected decision. only; it must not expand permissions for tests, builds, pull requests or the canonical attestation reusable. If it cannot be provisioned, publication remains RED. +- The repository immutability setting and release tag are mutable until the + immutable release is published. A single early preflight therefore has a + time-of-check/time-of-use window; both are revalidated immediately before + publish and any drift leaves the draft unpublished. - Reproducible Python distributions may expose timestamps, archive ordering or backend metadata that require causal build-system repair. A mismatch remains RED until explained and removed at the source. @@ -208,7 +222,7 @@ https://docs.github.com/en/rest/repos/repos GitHub. (2026). *Using artifact attestations to establish provenance for builds*. GitHub Docs. -https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations +https://docs.github.com/en/actions/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes Open Source Security Foundation. (2025). *SLSA specification version 1.2*. https://slsa.dev/spec/v1.2/ From a15fd0412959099318d2fad8779d9c62f856e191 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:51:25 +0900 Subject: [PATCH 15/45] docs(release): correct artifact-attestation reference --- docs/adr/0358-immutable-release-supply-chain-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md index 750ffa4b6..3bff651e2 100644 --- a/docs/adr/0358-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0358-immutable-release-supply-chain-boundary.md @@ -222,7 +222,7 @@ https://docs.github.com/en/rest/repos/repos GitHub. (2026). *Using artifact attestations to establish provenance for builds*. GitHub Docs. -https://docs.github.com/en/actions/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes +https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations Open Source Security Foundation. (2025). *SLSA specification version 1.2*. https://slsa.dev/spec/v1.2/ From a7c57533300aaf09517e0118b62b317ef32c71e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:51:58 +0900 Subject: [PATCH 16/45] docs(release): trace publish-boundary revalidation --- docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 11a1f1c38..0d4edf6f2 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -63,6 +63,11 @@ permission for the check. ADR 0358 therefore treats an authenticated `enabled: true` result as release admission and fails closed when the status cannot be established. That administrative read capability belongs only to the trusted admission step; it is not granted to pull-request or build execution. +Because the setting and a newly created release tag remain mutable until the +release is actually published, the release contract performs the status check +before tag creation and rechecks both repository immutability and exact tag to +source-SHA identity immediately before publish rather than treating an earlier +preflight as durable evidence. GitHub. (n.d.). *Immutable releases*. GitHub Docs. Retrieved September 3, 2026, from @@ -95,8 +100,8 @@ https://doi.org/10.17487/RFC8259 - source/build provenance threat model → SLSA 1.2; - credential separation and artifact-attestation verification → GitHub artifact attestation documentation; -- immutable tag/asset admission and draft-first publication → GitHub immutable - release documentation and repository REST API; +- immutable tag/asset admission, publish-boundary revalidation, and draft-first + publication → GitHub immutable release documentation and repository REST API; - strict machine-readable evidence intake → RFC 8259 plus the stricter canonical `.github` verifier contract; - current circular transport-receipt defect → `ContextualWisdomLab/.github#1782`. From f0ec13f2b59fb2c147f326653254a0b40e8a3249 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:50:18 +0900 Subject: [PATCH 17/45] test(release): require annotated-tag commit peeling --- tests/test_release_supply_chain_docs.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 197c24241..5bf350c25 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -25,3 +25,14 @@ def test_release_publication_rechecks_immutability_and_tag_identity_at_publish_b assert "immediately before publish" in text, path assert "recheck" in text, path assert "tag" in text and "source sha" in text, path + + +def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: + """Do not compare an annotated tag-object SHA directly with the source commit SHA.""" + for path in (_ADR, _RELEASE_GUIDE): + text = path.read_text(encoding="utf-8").lower() + assert "annotated tag" in text, path + assert "tag object" in text, path + assert "peel" in text, path + assert "type `commit`" in text, path + assert "exact protected source sha" in text, path From f94ebb0263ae1e701253f21dc00ee39187836eef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:51:00 +0900 Subject: [PATCH 18/45] docs(release): peel annotated tags to source commits --- docs/release.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/release.md b/docs/release.md index 40aed2abd..894a4a647 100644 --- a/docs/release.md +++ b/docs/release.md @@ -61,23 +61,33 @@ release does not satisfy this contract. non-confirming result must fail closed. This credential is not exposed to pull-request or unprivileged build execution. 11. After trusted verification and the immutability preflight both succeed, - create the annotated release-specific tag against the verified source SHA, - create a draft GitHub Release, and attach the complete verified - distributions, SBOM/provenance evidence, checksum material and release - notes. Do not publish an incomplete asset set. + create an annotated release-specific tag object whose target has + `type `commit`` and whose target SHA is the exact protected source SHA from + step 1, then create `refs/tags/` pointing to that tag object. + Refuse lightweight tags, tree/blob targets, an existing ref, or any tag + object whose target differs from the admitted source. Create a draft GitHub + Release and attach the complete verified distributions, SBOM/provenance + evidence, checksum material and release notes. Do not publish an incomplete + asset set. 12. Immediately before publish, recheck repository release immutability through `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the release - tag. The response must still confirm `enabled: true`, and the tag must still - resolve to the exact protected source SHA admitted in step 1. Any setting - change, lookup failure, malformed response, missing tag, or tag/source SHA - mismatch must fail closed without publishing the draft. This second check - closes the time-of-check/time-of-use window between admission and publish. + tag. Because an annotated tag ref points to the Git tag object SHA rather + than directly to its source commit, fetch that tag object and peel it to its + target. The target must have `type `commit`` and its SHA must equal the + exact protected source SHA admitted in step 1. Never compare the tag-object + SHA itself with the source commit SHA. Any setting change, lookup failure, + malformed response, missing ref/tag object, unexpected object type, or + peeled commit/source SHA mismatch must fail closed without publishing the + draft. This second check closes the time-of-check/time-of-use window between + admission and publish. 13. Publish that fully populated draft as the non-prerelease immutable GitHub Release. Never move an existing release tag or overwrite published bytes under an existing version. -14. Fetch the published release back through GitHub's API, verify tag/source - identity, immutable-release state and asset digests against the sealed - evidence, and retain this post-publication receipt as release evidence. +14. Fetch the published release back through GitHub's API, verify immutable + state and repeat the same annotated-tag peel to prove the release tag still + reaches the exact protected source commit; verify asset digests against the + sealed evidence and retain this post-publication receipt as release + evidence. ## Failure and rollback From a92012ea5eacf7738c4a006be18debc92b99c00f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:52:08 +0900 Subject: [PATCH 19/45] docs(adr): define annotated-tag commit identity --- ...immutable-release-supply-chain-boundary.md | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md index 3bff651e2..b9dd75788 100644 --- a/docs/adr/0358-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0358-immutable-release-supply-chain-boundary.md @@ -45,6 +45,15 @@ administrative read-only preflight credential isolated from pull-request and build execution; absence of that credential or an unsuccessful preflight is a release-admission failure, not a reason to publish a mutable release. +GitHub's Git data model also distinguishes an annotated tag reference from the +commit that the tag names. `refs/tags/` points to a Git tag object; +that tag object in turn names its target object and target type. A release +contract that compares the ref's tag-object SHA directly with the protected +source commit SHA would therefore reject a valid annotated tag or, worse, +encode the wrong identity rule. The release boundary must peel the annotated +tag object and admit only a target with type `commit` and the exact protected +source SHA. + ## Decision 1. LineageWeave owns the product-local release caller: release readiness, @@ -87,21 +96,27 @@ release-admission failure, not a reason to publish a mutable release. that does not confirm `enabled: true` must fail closed before tag creation. The preflight credential is unavailable to pull-request and unprivileged build jobs. -8. After that preflight, the release job creates the release-specific tag - against the already-verified protected source SHA, creates a draft GitHub - Release, and attaches the complete verified asset set. Immediately before - publish, the trusted release boundary must recheck - `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve that tag. The - repository must still report `enabled: true`, and the tag must still resolve - to the exact protected source SHA admitted for the release. Any setting - change, lookup/API failure, malformed response, missing tag, or tag/source - SHA mismatch must fail closed while the draft remains unpublished. Only - after this second admission may the draft be published as the immutable - non-prerelease release. This closes the time-of-check/time-of-use interval - between the initial immutability admission and publication. It must not - overwrite an existing tag, asset or version. A failed or partial publication - is an incident, not permission to mutate previously published bytes under - the same identity. +8. After that preflight, the release job creates an annotated tag object whose + target type is `commit` and whose target SHA is the exact protected source + SHA admitted for the release, then creates `refs/tags/` pointing to + that tag object. Lightweight tags, tree/blob targets, existing refs, or a + tag object targeting any other commit are inadmissible. The job then creates + a draft GitHub Release and attaches the complete verified asset set. + Immediately before publish, the trusted release boundary must recheck + `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the tag. For + the annotated tag, the ref's object SHA is the tag-object SHA, not the source + commit SHA; the boundary must fetch that tag object and peel its target, + require target type `commit`, and compare the peeled commit SHA with the + exact protected source SHA. It must never compare the tag-object SHA itself + with the source commit SHA. Any setting change, lookup/API failure, + malformed response, missing ref/tag object, unexpected target type, or + peeled commit/source SHA mismatch must fail closed while the draft remains + unpublished. Only after this second admission may the draft be published as + the immutable non-prerelease release. This closes the + time-of-check/time-of-use interval between the initial immutability admission + and publication. It must not overwrite an existing tag, asset or version. A + failed or partial publication is an incident, not permission to mutate + previously published bytes under the same identity. 9. Reproducibility is tested by rebuilding the wheel and source distribution from the same protected source under the reviewed toolchain and comparing the release contract's declared deterministic subjects. Any known @@ -136,12 +151,15 @@ GREEN requires all of the following on one unchanged protected source SHA: - a trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` before tag creation and confirms `enabled: true`; missing administrative-read capability or any non-confirming result must fail closed; +- the release-specific ref points to an annotated tag object whose target type + is `commit` and whose peeled target is the exact protected source SHA; direct + comparison of the tag-object SHA with the source commit SHA is forbidden; - a clean rebuild proves the declared reproducibility contract; - the complete verified asset set is attached to a draft GitHub Release; - immediately before publish, the trusted boundary must recheck repository - immutability and prove the release tag still resolves to the same protected - source SHA; any non-confirming result must fail closed with the draft - unpublished; + immutability and repeat the annotated-tag peel to prove the release tag still + reaches the same exact protected source SHA; any non-confirming result must + fail closed with the draft unpublished; - release notes, version, protected source SHA, tag, distributions, SBOMs, attestations and immutable GitHub Release are mutually consistent; and - rollback/incident instructions are exercised against synthetic release @@ -170,6 +188,12 @@ ADR's buyer-visible integrity claim. Failure to read the setting is also not proof that the setting is enabled, so the publication path fails closed rather than assuming repository configuration. +### Compare an annotated tag ref SHA directly with the source commit SHA + +Rejected. For an annotated tag, the ref names a Git tag object rather than the +source commit. The trusted boundary must peel the tag object, require target +type `commit`, and compare that target SHA with the admitted source SHA. + ### Publish a GitHub Release first and attach evidence later Rejected. Buyers would observe a release identity before its exact artifact, @@ -197,6 +221,10 @@ publication can be added later behind its own protected decision. immutable release is published. A single early preflight therefore has a time-of-check/time-of-use window; both are revalidated immediately before publish and any drift leaves the draft unpublished. +- An annotated tag ref exposes a tag-object SHA. Implementations that skip the + tag-object lookup can either produce a false mismatch or validate the wrong + identity. Current-head tests must exercise annotated-tag success plus missing, + non-commit-target and wrong-commit failures before release code can be GREEN. - Reproducible Python distributions may expose timestamps, archive ordering or backend metadata that require causal build-system repair. A mismatch remains RED until explained and removed at the source. @@ -220,6 +248,12 @@ GitHub. (2026). *REST API endpoints for repositories: Check if immutable releases are enabled for a repository*. GitHub Docs. https://docs.github.com/en/rest/repos/repos +GitHub. (2026). *REST API endpoints for Git references*. GitHub Docs. +https://docs.github.com/en/rest/git/refs + +GitHub. (2026). *REST API endpoints for Git tags*. GitHub Docs. +https://docs.github.com/en/rest/git/tags + GitHub. (2026). *Using artifact attestations to establish provenance for builds*. GitHub Docs. https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations From b4715a7708d7bc28e9d4f61257ae99f37218aae3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:52:30 +0900 Subject: [PATCH 20/45] docs(release): clarify annotated-tag target type --- docs/release.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/release.md b/docs/release.md index 894a4a647..9bf9a4234 100644 --- a/docs/release.md +++ b/docs/release.md @@ -61,21 +61,20 @@ release does not satisfy this contract. non-confirming result must fail closed. This credential is not exposed to pull-request or unprivileged build execution. 11. After trusted verification and the immutability preflight both succeed, - create an annotated release-specific tag object whose target has - `type `commit`` and whose target SHA is the exact protected source SHA from - step 1, then create `refs/tags/` pointing to that tag object. - Refuse lightweight tags, tree/blob targets, an existing ref, or any tag - object whose target differs from the admitted source. Create a draft GitHub - Release and attach the complete verified distributions, SBOM/provenance - evidence, checksum material and release notes. Do not publish an incomplete - asset set. + create an annotated release-specific tag object whose target type is + `commit` and whose target SHA is the exact protected source SHA from step 1, + then create `refs/tags/` pointing to that tag object. Refuse + lightweight tags, tree/blob targets, an existing ref, or any tag object + whose target differs from the admitted source. Create a draft GitHub Release + and attach the complete verified distributions, SBOM/provenance evidence, + checksum material and release notes. Do not publish an incomplete asset set. 12. Immediately before publish, recheck repository release immutability through `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the release tag. Because an annotated tag ref points to the Git tag object SHA rather than directly to its source commit, fetch that tag object and peel it to its - target. The target must have `type `commit`` and its SHA must equal the - exact protected source SHA admitted in step 1. Never compare the tag-object - SHA itself with the source commit SHA. Any setting change, lookup failure, + target. The target type must be `commit` and its SHA must equal the exact + protected source SHA admitted in step 1. Never compare the tag-object SHA + itself with the source commit SHA. Any setting change, lookup failure, malformed response, missing ref/tag object, unexpected object type, or peeled commit/source SHA mismatch must fail closed without publishing the draft. This second check closes the time-of-check/time-of-use window between From fa6eeb7cbe035ab5565bae985c6ca219ab1d59f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:52:52 +0900 Subject: [PATCH 21/45] docs(doctoring): trace annotated-tag identity --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 0d4edf6f2..1cfc62c4b 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -81,6 +81,26 @@ GitHub. (n.d.). *REST API endpoints for repositories: Check if immutable releases are enabled for a repository*. GitHub Docs. Retrieved September 3, 2026, from https://docs.github.com/en/rest/repos/repos +### GitHub annotated tag identity + +GitHub's Git data APIs distinguish the tag reference from an annotated tag +object. Creating an annotated tag requires creating a Git tag object and then a +`refs/tags/` reference that points to that object. The tag object separately +records its target object type and target SHA; GitHub documents `commit`, +`tree`, and `blob` as possible target types for tag-object creation. Therefore a +trusted release verifier cannot compare the tag ref's object SHA directly with +the protected source commit SHA. For ADR 0358 the release tag is valid only when +the ref resolves to the expected annotated tag object, that object has target +type `commit`, and the peeled target SHA equals the exact protected source SHA. +Missing refs/tag objects, non-commit targets, and mismatched target SHAs fail +closed before publication and again during post-publication verification. + +GitHub. (n.d.). *REST API endpoints for Git references*. GitHub Docs. Retrieved +September 3, 2026, from https://docs.github.com/en/rest/git/refs + +GitHub. (n.d.). *REST API endpoints for Git tags*. GitHub Docs. Retrieved +September 3, 2026, from https://docs.github.com/en/rest/git/tags + ### JSON strictness The canonical `.github` verifier rejects duplicate JSON properties, non-finite @@ -102,6 +122,8 @@ https://doi.org/10.17487/RFC8259 attestation documentation; - immutable tag/asset admission, publish-boundary revalidation, and draft-first publication → GitHub immutable release documentation and repository REST API; +- annotated-tag ref/object separation and exact source-commit peeling → GitHub + Git references and Git tags REST documentation; - strict machine-readable evidence intake → RFC 8259 plus the stricter canonical `.github` verifier contract; - current circular transport-receipt defect → `ContextualWisdomLab/.github#1782`. From b9b0e00c1fc32229ed03013934093ec401cffa40 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:50:24 +0900 Subject: [PATCH 22/45] fix(adr): move release boundary to unused ADR 0361 --- ...immutable-release-supply-chain-boundary.md | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 docs/adr/0361-immutable-release-supply-chain-boundary.md diff --git a/docs/adr/0361-immutable-release-supply-chain-boundary.md b/docs/adr/0361-immutable-release-supply-chain-boundary.md new file mode 100644 index 000000000..96cd36709 --- /dev/null +++ b/docs/adr/0361-immutable-release-supply-chain-boundary.md @@ -0,0 +1,262 @@ +# ADR 0361 — Immutable release supply-chain boundary + +**Decision status:** Proposed +**Date:** 2026-09-03 +**Related:** ContextualWisdomLab/.github#1782, LineageWeave #911 + +## Context + +LineageWeave does not currently publish an immutable GitHub release from a +product-local release workflow. The protected product line is versioned as a +Python package, but release evidence must identify one exact protected source +commit, the exact wheel and source distribution built from that commit, their +software bills of materials, and the immutable publication that buyers can +verify later. + +The organization already owns the reusable trust boundary for exact-artifact +SBOM attestation in `ContextualWisdomLab/.github`. LineageWeave must consume +that boundary rather than copy signing, OIDC, attestation-verification, or +provider policy into this repository. + +A fresh integration attempt exposed a canonical-owner prerequisite at +`ContextualWisdomLab/.github#1782`. The current reusable workflow requires the +GitHub Actions artifact digest as an input and also requires +`source-identity.json` *inside the same artifact* to contain that digest. The +artifact digest cannot be known until the artifact has been uploaded, while +changing the identity file changes the bytes whose digest GitHub computes. +That circular dependency makes the current handoff impossible to construct by +a deterministic caller without weakening the verifier. This ADR therefore +keeps the release integration Proposed until the canonical owner publishes an +acyclic exact-SHA contract. + +LineageWeave also cannot publish a commercial release from protected `main` +while the reachable synchronous PostgreSQL tooling path still contains the +LGPL-family `psycopg2-binary` dependency. PR #911 owns its replacement and the +reproducible lockfile migration. Release work must consume that merged, +license-clean protected result; it must not waive or suppress the inventory. + +GitHub's immutable-release setting is a separate repository/organization +control from artifact attestation. GitHub documents that a published immutable +release locks the associated tag and assets, and automatically creates a +release attestation. It also exposes an authenticated repository endpoint, +`GET /repos/{owner}/{repo}/immutable-releases`, that returns success only when +release immutability is enabled. The release caller therefore needs an +administrative read-only preflight credential isolated from pull-request and +build execution; absence of that credential or an unsuccessful preflight is a +release-admission failure, not a reason to publish a mutable release. + +GitHub's Git data model also distinguishes an annotated tag reference from the +commit that the tag names. `refs/tags/` points to a Git tag object; +that tag object in turn names its target object and target type. A release +contract that compares the ref's tag-object SHA directly with the protected +source commit SHA would therefore reject a valid annotated tag or, worse, +encode the wrong identity rule. The release boundary must peel the annotated +tag object and admit only a target with type `commit` and the exact protected +source SHA. + +## Decision + +1. LineageWeave owns the product-local release caller: release readiness, + package build, exact artifact preparation, release notes, tag creation, + immutable GitHub Release publication, reproducibility checks, and rollback + instructions. +2. `ContextualWisdomLab/.github` owns the reusable credentialed SBOM + attestation and verification boundary. The LineageWeave caller must invoke + a reviewed exact commit SHA of that reusable workflow. It must not vendor or + fork the trusted verifier to make a local release pass. +3. Release initiation is allowed only from the exact protected LineageWeave + `main` commit being released. A version is valid only when package metadata, + changelog/release notes, tag, distribution metadata, source identity and + GitHub Release all name the same canonical `MAJOR.MINOR.PATCH` version and + source SHA. +4. The unprivileged build stage receives `contents: read` only. It repeats the + repository's complete release-relevant test, documentation, lockfile, + security-contract and package checks before building a wheel and source + distribution. Pull-request-controlled source never receives OIDC, + attestation, release or package-publication credentials. +5. The build stage prepares one sealed evidence handoff containing exactly the + wheel, source distribution, one CycloneDX 1.7 SBOM bound to each exact + distribution, `source-identity.json`, and `checksums.sha256`. The inner + source identity binds repository, exact source SHA, predicate/schema, + distribution filenames and distribution/SBOM SHA-256 values. The canonical + owner decides the final acyclic representation after `.github#1782`. +6. GitHub's uploaded artifact ID/name/digest is an outer immutable transport + receipt. After `.github#1782` is repaired, the caller passes the returned + receipt and exact inner identities to the canonical reusable workflow. The + reusable workflow must independently revalidate the same-run receipt and + inert handoff before any OIDC token or attestation permission becomes + available. +7. Immutable publication occurs only after the exact artifact set has passed + canonical attestation verification and repository release immutability has + been independently admitted. Before tag creation or Release publication, a + trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` with + the minimum GitHub Administration (read) permission. Only an authenticated + success response that confirms `enabled: true` is admissible. A 404, + permission failure, transport/API failure, malformed response, or any result + that does not confirm `enabled: true` must fail closed before tag creation. + The preflight credential is unavailable to pull-request and unprivileged + build jobs. +8. After that preflight, the release job creates an annotated tag object whose + target type is `commit` and whose target SHA is the exact protected source + SHA admitted for the release, then creates `refs/tags/` pointing to + that tag object. Lightweight tags, tree/blob targets, existing refs, or a + tag object targeting any other commit are inadmissible. The job then creates + a draft GitHub Release and attaches the complete verified asset set. + Immediately before publish, the trusted release boundary must recheck + `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the tag. For + the annotated tag, the ref's object SHA is the tag-object SHA, not the source + commit SHA; the boundary must fetch that tag object and peel its target, + require target type `commit`, and compare the peeled commit SHA with the + exact protected source SHA. It must never compare the tag-object SHA itself + with the source commit SHA. Any setting change, lookup/API failure, + malformed response, missing ref/tag object, unexpected target type, or + peeled commit/source SHA mismatch must fail closed while the draft remains + unpublished. Only after this second admission may the draft be published as + the immutable non-prerelease release. This closes the + time-of-check/time-of-use interval between the initial immutability admission + and publication. It must not overwrite an existing tag, asset or version. A + failed or partial publication is an incident, not permission to mutate + previously published bytes under the same identity. +9. Reproducibility is tested by rebuilding the wheel and source distribution + from the same protected source under the reviewed toolchain and comparing + the release contract's declared deterministic subjects. Any known + nondeterministic field must be removed or normalized by source/tooling + repair; it is not excluded from comparison merely to obtain GREEN. +10. Rollback restores a previously reviewed workflow revision and produces new + artifacts from a new protected commit/version. It does not move an existing + release tag or reuse an old attestation for different bytes. +11. Package-registry publication is not inferred from a GitHub Release. If a + registry such as PyPI is adopted, its protected environment, trusted + publishing identity, independent review policy and immutable-version + behavior require a separate accepted decision before credentials or + publishing steps are added. + +## RED / GREEN acceptance + +The current RED is structural and owner-bound: LineageWeave has no product +release workflow, protected `main` is not yet license-clean, and the canonical +exact-artifact reusable cannot accept a deterministic first-party caller +because of `.github#1782`. + +GREEN requires all of the following on one unchanged protected source SHA: + +- #911 or a verified successor has removed the reachable disallowed dependency + and committed a reproducible lock that passes the frozen dependency gate; +- `.github#1782` is fixed on protected `.github/main` and LineageWeave pins the + repaired reusable workflow by exact commit SHA; +- a product-local release workflow builds wheel/sdist plus the exact six-file + evidence handoff without credentialed execution of pull-request source; +- the canonical reusable verifies and attests the exact returned artifact + receipt and exact wheel/sdist subjects; +- a trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` + before tag creation and confirms `enabled: true`; missing administrative-read + capability or any non-confirming result must fail closed; +- the release-specific ref points to an annotated tag object whose target type + is `commit` and whose peeled target is the exact protected source SHA; direct + comparison of the tag-object SHA with the source commit SHA is forbidden; +- a clean rebuild proves the declared reproducibility contract; +- the complete verified asset set is attached to a draft GitHub Release; +- immediately before publish, the trusted boundary must recheck repository + immutability and repeat the annotated-tag peel to prove the release tag still + reaches the same exact protected source SHA; any non-confirming result must + fail closed with the draft unpublished; +- release notes, version, protected source SHA, tag, distributions, SBOMs, + attestations and immutable GitHub Release are mutually consistent; and +- rollback/incident instructions are exercised against synthetic release + fixtures without deleting or rewriting valid published evidence. + +Until every condition is evidenced, this ADR remains Proposed and no +LineageWeave release-readiness claim may cite this design as delivered. + +## Alternatives considered + +### Copy the central attestation workflow into LineageWeave + +Rejected. It would create a second signing-policy authority, duplicate security +fixes and let a product repository bypass a defect in the canonical owner. + +### Drop the GitHub artifact digest from verification locally + +Rejected. The outer receipt protects the exact same-run transport handoff. +The circularity is an owner-contract modeling defect; weakening digest binding +in a consumer is not a causal repair. + +### Publish without proving GitHub release immutability is enabled + +Rejected. A release whose tag or assets remain mutable does not meet this +ADR's buyer-visible integrity claim. Failure to read the setting is also not +proof that the setting is enabled, so the publication path fails closed rather +than assuming repository configuration. + +### Compare an annotated tag ref SHA directly with the source commit SHA + +Rejected. For an annotated tag, the ref names a Git tag object rather than the +source commit. The trusted boundary must peel the tag object, require target +type `commit`, and compare that target SHA with the admitted source SHA. + +### Publish a GitHub Release first and attach evidence later + +Rejected. Buyers would observe a release identity before its exact artifact, +SBOM and provenance evidence was complete. GitHub's immutable-release guidance +also recommends attaching assets to a draft and publishing only after the +asset set is complete. + +### Wait for a package registry before creating any release boundary + +Rejected. GitHub Release immutability, exact source/artifact identity, SBOM, +provenance and rollback are independently valuable buyer controls. Registry +publication can be added later behind its own protected decision. + +## Risks and follow-up + +- The central reusable contract can change while `.github#1782` is repaired. + LineageWeave must inspect the protected implementation and pin its exact SHA; + no branch-name or mutable `main` reference is acceptable in release code. +- The immutable-release status endpoint requires administrative read access. + That capability must be provisioned to the trusted release admission step + only; it must not expand permissions for tests, builds, pull requests or the + canonical attestation reusable. If it cannot be provisioned, publication + remains RED. +- The repository immutability setting and release tag are mutable until the + immutable release is published. A single early preflight therefore has a + time-of-check/time-of-use window; both are revalidated immediately before + publish and any drift leaves the draft unpublished. +- An annotated tag ref exposes a tag-object SHA. Implementations that skip the + tag-object lookup can either produce a false mismatch or validate the wrong + identity. Current-head tests must exercise annotated-tag success plus missing, + non-commit-target and wrong-commit failures before release code can be GREEN. +- Reproducible Python distributions may expose timestamps, archive ordering or + backend metadata that require causal build-system repair. A mismatch remains + RED until explained and removed at the source. +- Current organization Actions queue saturation can delay evidence, but queue + latency is not a reason to bypass release gates or transfer predecessor-head + results. + +## References + +Bray, T. (2017). *The JavaScript Object Notation (JSON) data interchange +format* (RFC 8259). Internet Engineering Task Force. +https://doi.org/10.17487/RFC8259 + +CycloneDX Core Working Group. (2025). *CycloneDX specification 1.7*. +OWASP Foundation. https://cyclonedx.org/specification/overview/ + +GitHub. (2026). *Immutable releases*. GitHub Docs. +https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases + +GitHub. (2026). *REST API endpoints for repositories: Check if immutable +releases are enabled for a repository*. GitHub Docs. +https://docs.github.com/en/rest/repos/repos + +GitHub. (2026). *REST API endpoints for Git references*. GitHub Docs. +https://docs.github.com/en/rest/git/refs + +GitHub. (2026). *REST API endpoints for Git tags*. GitHub Docs. +https://docs.github.com/en/rest/git/tags + +GitHub. (2026). *Using artifact attestations to establish provenance for +builds*. GitHub Docs. +https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations + +Open Source Security Foundation. (2025). *SLSA specification version 1.2*. +https://slsa.dev/spec/v1.2/ \ No newline at end of file From bdb2d996cb8c222698b9576d3f1d23f13cdf5eed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:50:29 +0900 Subject: [PATCH 23/45] fix(adr): remove collided ADR 0358 release path --- ...immutable-release-supply-chain-boundary.md | 262 ------------------ 1 file changed, 262 deletions(-) delete mode 100644 docs/adr/0358-immutable-release-supply-chain-boundary.md diff --git a/docs/adr/0358-immutable-release-supply-chain-boundary.md b/docs/adr/0358-immutable-release-supply-chain-boundary.md deleted file mode 100644 index b9dd75788..000000000 --- a/docs/adr/0358-immutable-release-supply-chain-boundary.md +++ /dev/null @@ -1,262 +0,0 @@ -# ADR 0358 — Immutable release supply-chain boundary - -**Decision status:** Proposed -**Date:** 2026-09-03 -**Related:** ContextualWisdomLab/.github#1782, LineageWeave #911 - -## Context - -LineageWeave does not currently publish an immutable GitHub release from a -product-local release workflow. The protected product line is versioned as a -Python package, but release evidence must identify one exact protected source -commit, the exact wheel and source distribution built from that commit, their -software bills of materials, and the immutable publication that buyers can -verify later. - -The organization already owns the reusable trust boundary for exact-artifact -SBOM attestation in `ContextualWisdomLab/.github`. LineageWeave must consume -that boundary rather than copy signing, OIDC, attestation-verification, or -provider policy into this repository. - -A fresh integration attempt exposed a canonical-owner prerequisite at -`ContextualWisdomLab/.github#1782`. The current reusable workflow requires the -GitHub Actions artifact digest as an input and also requires -`source-identity.json` *inside the same artifact* to contain that digest. The -artifact digest cannot be known until the artifact has been uploaded, while -changing the identity file changes the bytes whose digest GitHub computes. -That circular dependency makes the current handoff impossible to construct by -a deterministic caller without weakening the verifier. This ADR therefore -keeps the release integration Proposed until the canonical owner publishes an -acyclic exact-SHA contract. - -LineageWeave also cannot publish a commercial release from protected `main` -while the reachable synchronous PostgreSQL tooling path still contains the -LGPL-family `psycopg2-binary` dependency. PR #911 owns its replacement and the -reproducible lockfile migration. Release work must consume that merged, -license-clean protected result; it must not waive or suppress the inventory. - -GitHub's immutable-release setting is a separate repository/organization -control from artifact attestation. GitHub documents that a published immutable -release locks the associated tag and assets, and automatically creates a -release attestation. It also exposes an authenticated repository endpoint, -`GET /repos/{owner}/{repo}/immutable-releases`, that returns success only when -release immutability is enabled. The release caller therefore needs an -administrative read-only preflight credential isolated from pull-request and -build execution; absence of that credential or an unsuccessful preflight is a -release-admission failure, not a reason to publish a mutable release. - -GitHub's Git data model also distinguishes an annotated tag reference from the -commit that the tag names. `refs/tags/` points to a Git tag object; -that tag object in turn names its target object and target type. A release -contract that compares the ref's tag-object SHA directly with the protected -source commit SHA would therefore reject a valid annotated tag or, worse, -encode the wrong identity rule. The release boundary must peel the annotated -tag object and admit only a target with type `commit` and the exact protected -source SHA. - -## Decision - -1. LineageWeave owns the product-local release caller: release readiness, - package build, exact artifact preparation, release notes, tag creation, - immutable GitHub Release publication, reproducibility checks, and rollback - instructions. -2. `ContextualWisdomLab/.github` owns the reusable credentialed SBOM - attestation and verification boundary. The LineageWeave caller must invoke - a reviewed exact commit SHA of that reusable workflow. It must not vendor or - fork the trusted verifier to make a local release pass. -3. Release initiation is allowed only from the exact protected LineageWeave - `main` commit being released. A version is valid only when package metadata, - changelog/release notes, tag, distribution metadata, source identity and - GitHub Release all name the same canonical `MAJOR.MINOR.PATCH` version and - source SHA. -4. The unprivileged build stage receives `contents: read` only. It repeats the - repository's complete release-relevant test, documentation, lockfile, - security-contract and package checks before building a wheel and source - distribution. Pull-request-controlled source never receives OIDC, - attestation, release or package-publication credentials. -5. The build stage prepares one sealed evidence handoff containing exactly the - wheel, source distribution, one CycloneDX 1.7 SBOM bound to each exact - distribution, `source-identity.json`, and `checksums.sha256`. The inner - source identity binds repository, exact source SHA, predicate/schema, - distribution filenames and distribution/SBOM SHA-256 values. The canonical - owner decides the final acyclic representation after `.github#1782`. -6. GitHub's uploaded artifact ID/name/digest is an outer immutable transport - receipt. After `.github#1782` is repaired, the caller passes the returned - receipt and exact inner identities to the canonical reusable workflow. The - reusable workflow must independently revalidate the same-run receipt and - inert handoff before any OIDC token or attestation permission becomes - available. -7. Immutable publication occurs only after the exact artifact set has passed - canonical attestation verification and repository release immutability has - been independently admitted. Before tag creation or Release publication, a - trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` with - the minimum GitHub Administration (read) permission. Only an authenticated - success response that confirms `enabled: true` is admissible. A 404, - permission failure, transport/API failure, malformed response, or any result - that does not confirm `enabled: true` must fail closed before tag creation. - The preflight credential is unavailable to pull-request and unprivileged - build jobs. -8. After that preflight, the release job creates an annotated tag object whose - target type is `commit` and whose target SHA is the exact protected source - SHA admitted for the release, then creates `refs/tags/` pointing to - that tag object. Lightweight tags, tree/blob targets, existing refs, or a - tag object targeting any other commit are inadmissible. The job then creates - a draft GitHub Release and attaches the complete verified asset set. - Immediately before publish, the trusted release boundary must recheck - `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the tag. For - the annotated tag, the ref's object SHA is the tag-object SHA, not the source - commit SHA; the boundary must fetch that tag object and peel its target, - require target type `commit`, and compare the peeled commit SHA with the - exact protected source SHA. It must never compare the tag-object SHA itself - with the source commit SHA. Any setting change, lookup/API failure, - malformed response, missing ref/tag object, unexpected target type, or - peeled commit/source SHA mismatch must fail closed while the draft remains - unpublished. Only after this second admission may the draft be published as - the immutable non-prerelease release. This closes the - time-of-check/time-of-use interval between the initial immutability admission - and publication. It must not overwrite an existing tag, asset or version. A - failed or partial publication is an incident, not permission to mutate - previously published bytes under the same identity. -9. Reproducibility is tested by rebuilding the wheel and source distribution - from the same protected source under the reviewed toolchain and comparing - the release contract's declared deterministic subjects. Any known - nondeterministic field must be removed or normalized by source/tooling - repair; it is not excluded from comparison merely to obtain GREEN. -10. Rollback restores a previously reviewed workflow revision and produces new - artifacts from a new protected commit/version. It does not move an existing - release tag or reuse an old attestation for different bytes. -11. Package-registry publication is not inferred from a GitHub Release. If a - registry such as PyPI is adopted, its protected environment, trusted - publishing identity, independent review policy and immutable-version - behavior require a separate accepted decision before credentials or - publishing steps are added. - -## RED / GREEN acceptance - -The current RED is structural and owner-bound: LineageWeave has no product -release workflow, protected `main` is not yet license-clean, and the canonical -exact-artifact reusable cannot accept a deterministic first-party caller -because of `.github#1782`. - -GREEN requires all of the following on one unchanged protected source SHA: - -- #911 or a verified successor has removed the reachable disallowed dependency - and committed a reproducible lock that passes the frozen dependency gate; -- `.github#1782` is fixed on protected `.github/main` and LineageWeave pins the - repaired reusable workflow by exact commit SHA; -- a product-local release workflow builds wheel/sdist plus the exact six-file - evidence handoff without credentialed execution of pull-request source; -- the canonical reusable verifies and attests the exact returned artifact - receipt and exact wheel/sdist subjects; -- a trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` - before tag creation and confirms `enabled: true`; missing administrative-read - capability or any non-confirming result must fail closed; -- the release-specific ref points to an annotated tag object whose target type - is `commit` and whose peeled target is the exact protected source SHA; direct - comparison of the tag-object SHA with the source commit SHA is forbidden; -- a clean rebuild proves the declared reproducibility contract; -- the complete verified asset set is attached to a draft GitHub Release; -- immediately before publish, the trusted boundary must recheck repository - immutability and repeat the annotated-tag peel to prove the release tag still - reaches the same exact protected source SHA; any non-confirming result must - fail closed with the draft unpublished; -- release notes, version, protected source SHA, tag, distributions, SBOMs, - attestations and immutable GitHub Release are mutually consistent; and -- rollback/incident instructions are exercised against synthetic release - fixtures without deleting or rewriting valid published evidence. - -Until every condition is evidenced, this ADR remains Proposed and no -LineageWeave release-readiness claim may cite this design as delivered. - -## Alternatives considered - -### Copy the central attestation workflow into LineageWeave - -Rejected. It would create a second signing-policy authority, duplicate security -fixes and let a product repository bypass a defect in the canonical owner. - -### Drop the GitHub artifact digest from verification locally - -Rejected. The outer receipt protects the exact same-run transport handoff. -The circularity is an owner-contract modeling defect; weakening digest binding -in a consumer is not a causal repair. - -### Publish without proving GitHub release immutability is enabled - -Rejected. A release whose tag or assets remain mutable does not meet this -ADR's buyer-visible integrity claim. Failure to read the setting is also not -proof that the setting is enabled, so the publication path fails closed rather -than assuming repository configuration. - -### Compare an annotated tag ref SHA directly with the source commit SHA - -Rejected. For an annotated tag, the ref names a Git tag object rather than the -source commit. The trusted boundary must peel the tag object, require target -type `commit`, and compare that target SHA with the admitted source SHA. - -### Publish a GitHub Release first and attach evidence later - -Rejected. Buyers would observe a release identity before its exact artifact, -SBOM and provenance evidence was complete. GitHub's immutable-release guidance -also recommends attaching assets to a draft and publishing only after the -asset set is complete. - -### Wait for a package registry before creating any release boundary - -Rejected. GitHub Release immutability, exact source/artifact identity, SBOM, -provenance and rollback are independently valuable buyer controls. Registry -publication can be added later behind its own protected decision. - -## Risks and follow-up - -- The central reusable contract can change while `.github#1782` is repaired. - LineageWeave must inspect the protected implementation and pin its exact SHA; - no branch-name or mutable `main` reference is acceptable in release code. -- The immutable-release status endpoint requires administrative read access. - That capability must be provisioned to the trusted release admission step - only; it must not expand permissions for tests, builds, pull requests or the - canonical attestation reusable. If it cannot be provisioned, publication - remains RED. -- The repository immutability setting and release tag are mutable until the - immutable release is published. A single early preflight therefore has a - time-of-check/time-of-use window; both are revalidated immediately before - publish and any drift leaves the draft unpublished. -- An annotated tag ref exposes a tag-object SHA. Implementations that skip the - tag-object lookup can either produce a false mismatch or validate the wrong - identity. Current-head tests must exercise annotated-tag success plus missing, - non-commit-target and wrong-commit failures before release code can be GREEN. -- Reproducible Python distributions may expose timestamps, archive ordering or - backend metadata that require causal build-system repair. A mismatch remains - RED until explained and removed at the source. -- Current organization Actions queue saturation can delay evidence, but queue - latency is not a reason to bypass release gates or transfer predecessor-head - results. - -## References - -Bray, T. (2017). *The JavaScript Object Notation (JSON) data interchange -format* (RFC 8259). Internet Engineering Task Force. -https://doi.org/10.17487/RFC8259 - -CycloneDX Core Working Group. (2025). *CycloneDX specification 1.7*. -OWASP Foundation. https://cyclonedx.org/specification/overview/ - -GitHub. (2026). *Immutable releases*. GitHub Docs. -https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases - -GitHub. (2026). *REST API endpoints for repositories: Check if immutable -releases are enabled for a repository*. GitHub Docs. -https://docs.github.com/en/rest/repos/repos - -GitHub. (2026). *REST API endpoints for Git references*. GitHub Docs. -https://docs.github.com/en/rest/git/refs - -GitHub. (2026). *REST API endpoints for Git tags*. GitHub Docs. -https://docs.github.com/en/rest/git/tags - -GitHub. (2026). *Using artifact attestations to establish provenance for -builds*. GitHub Docs. -https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations - -Open Source Security Foundation. (2025). *SLSA specification version 1.2*. -https://slsa.dev/spec/v1.2/ From 9dbf979fee0a6f40ea193901c3cfa8838819acd3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:50:56 +0900 Subject: [PATCH 24/45] fix(adr): point release supporting docs to ADR 0361 --- docs/adr/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index 7a81463dd..cfb80d99b 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -11,8 +11,8 @@ decision from them. |---|---| | [`product-requirements.md`](../product-requirements.md) | Product requirements projection across the ADR set; ADRs remain normative, including [0252](0252-temporal-primary-voice-history.md) | | [`product-technical-gap-baseline.md`](../product-technical-gap-baseline.md) | Product/technical traceability projection across the ADR set; ADRs remain normative | -| [`release.md`](../release.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | -| [`RELEASE_SUPPLY_CHAIN_REFERENCES.md`](../doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md) | [0358](0358-immutable-release-supply-chain-boundary.md) | +| [`release.md`](../release.md) | [0361](0361-immutable-release-supply-chain-boundary.md) | +| [`RELEASE_SUPPLY_CHAIN_REFERENCES.md`](../doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md) | [0361](0361-immutable-release-supply-chain-boundary.md) | | [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md), [0238](0238-source-conversation-turn-import-contract.md) | | [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0251](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) | | [`PROV_O_IMPLEMENTATION.md`](../PROV_O_IMPLEMENTATION.md) | [0065](0065-prov-o-provenance-boundary.md) | From a35f61c89051d2b499f691e01d7a055c63a4705a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:51:26 +0900 Subject: [PATCH 25/45] fix(adr): align release operator guide with ADR 0361 --- docs/release.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release.md b/docs/release.md index 9bf9a4234..740123621 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,6 +1,6 @@ # LineageWeave release contract -This document is the operator projection of Proposed ADR 0358. It describes +This document is the operator projection of Proposed ADR 0361. It describes what must be true before LineageWeave publishes an immutable release; it is not evidence that a release already exists. @@ -44,7 +44,7 @@ release does not satisfy this contract. differing subject merely to make the comparison pass. 7. Generate one CycloneDX 1.7 SBOM for each exact distribution and bind its root component to the distribution filename and SHA-256 digest. Prepare the - six-file handoff defined by ADR 0358: wheel, wheel SBOM, source distribution, + six-file handoff defined by ADR 0361: wheel, wheel SBOM, source distribution, source-distribution SBOM, `source-identity.json`, and `checksums.sha256`. 8. Upload that handoff once and retain GitHub's returned artifact ID, name and @@ -123,4 +123,4 @@ that capability is not a build, pull-request, attestation-policy or provider credential. Provider/model execution remains owned by `contextual-orchestrator`; statistical/psychometric engines and their release truth remain with their canonical owners. No release step copies those owners' source or treats a -mutable sibling branch as a production dependency. +mutable sibling branch as a production dependency. \ No newline at end of file From 0442ff7d88d48d3f26b5dde626e51dba740ba779 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:51:49 +0900 Subject: [PATCH 26/45] fix(adr): bind release traceability to ADR 0361 --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 1cfc62c4b..2662e9f3d 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -1,8 +1,8 @@ # Release supply-chain references -**Supporting evidence for:** ADR 0358 +**Supporting evidence for:** ADR 0361 **Reviewed:** 2026-09-03 -**Status:** Non-normative doctoring evidence. ADR 0358 remains the decision authority. +**Status:** Non-normative doctoring evidence. ADR 0361 remains the decision authority. This note records the authoritative external standards and platform contracts used while defining LineageWeave's immutable release boundary. It does not @@ -13,7 +13,7 @@ promote an unimplemented workflow or queued check to release evidence. ### CycloneDX 1.7 The CycloneDX specification overview identifies **1.7** as the current -specification version and gives its release date as 2025-10-21. ADR 0358 uses +specification version and gives its release date as 2025-10-21. ADR 0361 uses CycloneDX 1.7 only for the per-distribution SBOM representation; LineageWeave does not claim that a generic repository-directory SBOM is an attestation of one exact wheel or source distribution. @@ -24,7 +24,7 @@ Foundation. https://cyclonedx.org/specification/overview/ ### SLSA 1.2 The SLSA project announced Version **1.2** as the approved release on -2025-11-24. ADR 0358 uses SLSA as supply-chain threat/provenance grounding; it +2025-11-24. ADR 0361 uses SLSA as supply-chain threat/provenance grounding; it does not claim a SLSA level merely because a workflow uses provenance or a reusable workflow. @@ -35,7 +35,7 @@ https://slsa.dev/blog/2025/11/announce-slsa-v1.2 GitHub's current documentation requires explicit attestation/OIDC permissions for credentialed provenance generation and documents verification of artifact -attestations. ADR 0358 therefore keeps pull-request-controlled build work in an +attestations. ADR 0361 therefore keeps pull-request-controlled build work in an unprivileged job and delegates credentialed attestation to the canonical organization reusable only after inert exact-artifact verification. @@ -59,7 +59,7 @@ The current repository REST API exposes `GET /repos/{owner}/{repo}/immutable-releases` to check whether the control is enabled. GitHub documents an authenticated `200` response when enabled and a `404` when it is not enabled, and requires repository Administration (read) -permission for the check. ADR 0358 therefore treats an authenticated +permission for the check. ADR 0361 therefore treats an authenticated `enabled: true` result as release admission and fails closed when the status cannot be established. That administrative read capability belongs only to the trusted admission step; it is not granted to pull-request or build execution. @@ -89,7 +89,7 @@ object. Creating an annotated tag requires creating a Git tag object and then a records its target object type and target SHA; GitHub documents `commit`, `tree`, and `blob` as possible target types for tag-object creation. Therefore a trusted release verifier cannot compare the tag ref's object SHA directly with -the protected source commit SHA. For ADR 0358 the release tag is valid only when +the protected source commit SHA. For ADR 0361 the release tag is valid only when the ref resolves to the expected annotated tag object, that object has target type `commit`, and the peeled target SHA equals the exact protected source SHA. Missing refs/tag objects, non-commit targets, and mismatched target SHAs fail @@ -114,7 +114,7 @@ Bray, T. (2017). *The JavaScript Object Notation (JSON) data interchange format* (RFC 8259). Internet Engineering Task Force. https://doi.org/10.17487/RFC8259 -## Traceability to ADR 0358 +## Traceability to ADR 0361 - exact wheel/sdist SBOM representation → CycloneDX 1.7; - source/build provenance threat model → SLSA 1.2; @@ -131,4 +131,4 @@ https://doi.org/10.17487/RFC8259 External standards do not override the current canonical owner implementation. When `.github#1782` is repaired, LineageWeave must re-read protected `ContextualWisdomLab/.github` and pin the reviewed exact reusable-workflow SHA -that implements the accepted handoff. +that implements the accepted handoff. \ No newline at end of file From 0ba595744c55ca72dc4bf0500797da8f55fda7d5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:52:06 +0900 Subject: [PATCH 27/45] test(adr): follow release contract at ADR 0361 --- tests/test_release_supply_chain_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 5bf350c25..3f13c9934 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -4,7 +4,7 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -_ADR = _REPOSITORY_ROOT / "docs" / "adr" / "0358-immutable-release-supply-chain-boundary.md" +_ADR = _REPOSITORY_ROOT / "docs" / "adr" / "0361-immutable-release-supply-chain-boundary.md" _RELEASE_GUIDE = _REPOSITORY_ROOT / "docs" / "release.md" _IMMUTABILITY_ENDPOINT = "GET /repos/{owner}/{repo}/immutable-releases" From f7ed27ce39aed0e8ef4dc79b01f3e2eeabe84f27 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:15:52 +0900 Subject: [PATCH 28/45] test(release): require identity-safe prepublication abort --- tests/test_release_supply_chain_docs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 3f13c9934..ad2a9c0b3 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -36,3 +36,15 @@ def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: assert "peel" in text, path assert "type `commit`" in text, path assert "exact protected source sha" in text, path + + +def test_prepublication_abort_has_identity_safe_cleanup_before_same_version_retry() -> None: + """Do not orphan or unsafely reuse a draft release identity after an aborted publish.""" + for path in (_ADR, _RELEASE_GUIDE): + text = path.read_text(encoding="utf-8").lower() + assert "pre-publication abort" in text, path + assert "draft" in text and "unpublished" in text, path + assert "delete" in text and "candidate tag" in text, path + assert "re-resolve" in text and "absent" in text, path + assert "quarantine" in text and "version" in text, path + assert "never reuse" in text and "published immutable release" in text, path From b97c5b909b4dd7fa6a9558760a5f399cdf8fd956 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:16:36 +0900 Subject: [PATCH 29/45] docs(release): define identity-safe prepublication abort --- docs/release.md | 60 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/docs/release.md b/docs/release.md index 740123621..43e5c4fc2 100644 --- a/docs/release.md +++ b/docs/release.md @@ -63,11 +63,14 @@ release does not satisfy this contract. 11. After trusted verification and the immutability preflight both succeed, create an annotated release-specific tag object whose target type is `commit` and whose target SHA is the exact protected source SHA from step 1, - then create `refs/tags/` pointing to that tag object. Refuse - lightweight tags, tree/blob targets, an existing ref, or any tag object - whose target differs from the admitted source. Create a draft GitHub Release - and attach the complete verified distributions, SBOM/provenance evidence, - checksum material and release notes. Do not publish an incomplete asset set. + then create `refs/tags/` pointing to that tag object. Record the + newly created tag-object SHA/ref and the pre-create proof that the ref was + absent. Refuse lightweight tags, tree/blob targets, an existing ref, or any + tag object whose target differs from the admitted source. Create a draft + GitHub Release, retain its exact release ID and `draft: true` creation + receipt, and attach the complete verified distributions, SBOM/provenance + evidence, checksum material and release notes. Do not publish an incomplete + asset set. 12. Immediately before publish, recheck repository release immutability through `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the release tag. Because an annotated tag ref points to the Git tag object SHA rather @@ -78,7 +81,10 @@ release does not satisfy this contract. malformed response, missing ref/tag object, unexpected object type, or peeled commit/source SHA mismatch must fail closed without publishing the draft. This second check closes the time-of-check/time-of-use window between - admission and publish. + admission and publish. If it fails after step 11 created the candidate tag + and draft, execute the pre-publication abort procedure below before any + same-version retry; an unpublished draft and occupied tag ref are not + silently treated as if no release identity had been created. 13. Publish that fully populated draft as the non-prerelease immutable GitHub Release. Never move an existing release tag or overwrite published bytes under an existing version. @@ -90,12 +96,40 @@ release does not satisfy this contract. ## Failure and rollback -A failure before publication leaves no valid release identity to repair in -place. In particular, either immutability admission check or the publish-boundary -tag/source identity recheck failing leaves the draft unpublished. Preserve the -run ID, exact source SHA, logs and any sealed evidence needed for RCA, fix -source/configuration through a normal protected PR, and start again from a new -exact candidate. +A failure before step 11 creates no candidate release identity. Preserve the +run ID, exact source SHA, logs and any sealed evidence needed for RCA, fix the +source/configuration through a normal protected PR, and start again from a +fresh exact candidate. + +A failure after step 11 but before publication is a **pre-publication abort**, +not proof that no identity exists. The run may already own an unpublished draft +release and a candidate tag ref. Before retrying the same version, the trusted +release boundary must use the recorded creation receipts to prove all of the +following: the exact release ID created by this run still resolves as +`draft: true` and unpublished; its `tag_name` is the admitted version; the +candidate tag ref still points to the exact annotated tag object created by +this run; that tag object still peels to the admitted protected source commit; +and no published release resolves for the candidate tag. If any lookup, +identity, ownership, or publication-state proof is missing or ambiguous, do not +delete or retarget anything. Quarantine that version and require a new +version/source candidate. + +Only after those unpublished-only proofs succeed may the abort cleanup delete +the exact draft release ID created by this run. Re-resolve that release ID as +absent, then recheck that no published release resolves for the candidate tag +and that the ref still points to the recorded candidate tag object. Only then +may the cleanup delete that exact candidate tag ref. Re-resolve both the draft +release identity and candidate tag ref as absent before a same-version retry is +admissible. A cleanup failure remains RED and quarantines the version; it is +not permission to force, retarget, or reuse the identity. + +Never reuse a tag name that has been associated with a published immutable +release. GitHub's immutable-release contract locks the associated tag after +publication and reserves that tag name even if the immutable release is later +deleted. Once publication may have occurred, rollback is forward-only: preserve +the forensic evidence, identify affected subjects, correct source/workflow +through normal governance, and publish replacement artifacts under a new +version and protected source SHA. If an already-published artifact or attestation is found invalid, preserve the forensic evidence and identify affected subjects before any revocation or @@ -123,4 +157,4 @@ that capability is not a build, pull-request, attestation-policy or provider credential. Provider/model execution remains owned by `contextual-orchestrator`; statistical/psychometric engines and their release truth remain with their canonical owners. No release step copies those owners' source or treats a -mutable sibling branch as a production dependency. \ No newline at end of file +mutable sibling branch as a production dependency. From dc6b4e1a6c277b45d1686d8fedaf518ebefb06e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:18:01 +0900 Subject: [PATCH 30/45] docs(adr): fail closed on aborted release identity --- ...immutable-release-supply-chain-boundary.md | 101 +++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/docs/adr/0361-immutable-release-supply-chain-boundary.md b/docs/adr/0361-immutable-release-supply-chain-boundary.md index 96cd36709..dbd00bc05 100644 --- a/docs/adr/0361-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0361-immutable-release-supply-chain-boundary.md @@ -54,6 +54,15 @@ encode the wrong identity rule. The release boundary must peel the annotated tag object and admit only a target with type `commit` and the exact protected source SHA. +The immutable protections begin at publication, not at draft creation. GitHub +recommends creating a draft, attaching all assets, and only then publishing it. +That creates a deliberate pre-publication interval in which the release caller +has already created a candidate tag and draft release but publication may still +abort. Without an identity-safe abort contract, a failed final admission can +leave an occupied tag/draft that blocks a retry or encourages unsafe retargeting. +The abort path therefore needs the same exact-identity discipline as the +publication path. + ## Decision 1. LineageWeave owns the product-local release caller: release readiness, @@ -99,37 +108,56 @@ source SHA. 8. After that preflight, the release job creates an annotated tag object whose target type is `commit` and whose target SHA is the exact protected source SHA admitted for the release, then creates `refs/tags/` pointing to - that tag object. Lightweight tags, tree/blob targets, existing refs, or a - tag object targeting any other commit are inadmissible. The job then creates - a draft GitHub Release and attaches the complete verified asset set. - Immediately before publish, the trusted release boundary must recheck - `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the tag. For - the annotated tag, the ref's object SHA is the tag-object SHA, not the source - commit SHA; the boundary must fetch that tag object and peel its target, - require target type `commit`, and compare the peeled commit SHA with the - exact protected source SHA. It must never compare the tag-object SHA itself - with the source commit SHA. Any setting change, lookup/API failure, - malformed response, missing ref/tag object, unexpected target type, or - peeled commit/source SHA mismatch must fail closed while the draft remains - unpublished. Only after this second admission may the draft be published as - the immutable non-prerelease release. This closes the + that tag object. It records the pre-create absence proof, exact tag-object + SHA/ref, and the exact draft Release ID returned by GitHub. Lightweight tags, + tree/blob targets, existing refs, or a tag object targeting any other commit + are inadmissible. The job then creates a draft GitHub Release and attaches + the complete verified asset set. Immediately before publish, the trusted + release boundary must recheck `GET /repos/{owner}/{repo}/immutable-releases` + and re-resolve the tag. For the annotated tag, the ref's object SHA is the + tag-object SHA, not the source commit SHA; the boundary must fetch that tag + object and peel its target, require target type `commit`, and compare the + peeled commit SHA with the exact protected source SHA. It must never compare + the tag-object SHA itself with the source commit SHA. Any setting change, + lookup/API failure, malformed response, missing ref/tag object, unexpected + target type, or peeled commit/source SHA mismatch must fail closed while the + draft remains unpublished. Only after this second admission may the draft be + published as the immutable non-prerelease release. This closes the time-of-check/time-of-use interval between the initial immutability admission and publication. It must not overwrite an existing tag, asset or version. A failed or partial publication is an incident, not permission to mutate previously published bytes under the same identity. -9. Reproducibility is tested by rebuilding the wheel and source distribution +9. A failure after the candidate tag/draft are created but before publication is + a **pre-publication abort**. Same-version retry is admissible only after the + trusted release boundary proves from recorded receipts that the exact Release + ID created by this run is still `draft: true` and unpublished, its `tag_name` + matches the admitted version, the candidate tag ref still points to the exact + tag object created by this run, that tag object still peels to the admitted + protected source commit, and no published release resolves for that tag. If + any identity, ownership, or publication-state proof is missing or ambiguous, + no deletion or retarget is allowed: quarantine the version and require a new + version/source candidate. After all proofs succeed, delete only that exact + unpublished draft, re-resolve the draft as absent, recheck that no published + release resolves for the tag and that the ref still names the recorded tag + object, then delete only that exact candidate tag ref. Re-resolve both as + absent before same-version retry. Never reuse a tag name that has been + associated with a published immutable release. +10. Reproducibility is tested by rebuilding the wheel and source distribution from the same protected source under the reviewed toolchain and comparing the release contract's declared deterministic subjects. Any known nondeterministic field must be removed or normalized by source/tooling repair; it is not excluded from comparison merely to obtain GREEN. -10. Rollback restores a previously reviewed workflow revision and produces new - artifacts from a new protected commit/version. It does not move an existing - release tag or reuse an old attestation for different bytes. -11. Package-registry publication is not inferred from a GitHub Release. If a - registry such as PyPI is adopted, its protected environment, trusted - publishing identity, independent review policy and immutable-version - behavior require a separate accepted decision before credentials or - publishing steps are added. +11. Rollback restores a previously reviewed workflow revision and produces new + artifacts from a new protected commit/version. It does not move an existing + release tag or reuse an old attestation for different bytes. Once publication + may have happened, remediation is forward-only; a tag name associated with a + published immutable release is never reused even if that release is later + deleted. +12. Package-registry publication is not inferred from a GitHub Release. If a + registry such as PyPI is adopted, its protected environment, trusted + publishing identity, independent review policy and immutable-version + behavior require a separate accepted decision before credentials or + publishing steps are added. ## RED / GREEN acceptance @@ -160,6 +188,11 @@ GREEN requires all of the following on one unchanged protected source SHA: immutability and repeat the annotated-tag peel to prove the release tag still reaches the same exact protected source SHA; any non-confirming result must fail closed with the draft unpublished; +- synthetic release fixtures exercise the pre-publication abort path, including + exact draft/tag ownership proofs, safe deletion only while unpublished, + re-resolve-as-absent before same-version retry, quarantine on ambiguous + cleanup, and never reuse of a tag associated with a published immutable + release; - release notes, version, protected source SHA, tag, distributions, SBOMs, attestations and immutable GitHub Release are mutually consistent; and - rollback/incident instructions are exercised against synthetic release @@ -194,6 +227,14 @@ Rejected. For an annotated tag, the ref names a Git tag object rather than the source commit. The trusted boundary must peel the tag object, require target type `commit`, and compare that target SHA with the admitted source SHA. +### Leave an aborted draft/tag in place and retry around it + +Rejected. An unpublished draft and occupied tag are real candidate identities. +Ignoring them either blocks same-version retry or creates pressure to force or +retarget the tag. Cleanup is allowed only for the exact unpublished identities +created by the same run after their state and ownership are re-proved; otherwise +the version is quarantined and remediation moves forward. + ### Publish a GitHub Release first and attach evidence later Rejected. Buyers would observe a release identity before its exact artifact, @@ -221,6 +262,15 @@ publication can be added later behind its own protected decision. immutable release is published. A single early preflight therefore has a time-of-check/time-of-use window; both are revalidated immediately before publish and any drift leaves the draft unpublished. +- An unpublished draft/tag can survive a failed final admission. The release + workflow must retain exact creation receipts and must not assume cleanup is + safe from version text alone. If the draft is no longer provably unpublished + or the tag no longer exactly matches the recorded candidate object, cleanup + fails closed and the version is quarantined. +- GitHub documents that a tag name associated with a published immutable release + cannot be reused even if that immutable release is later deleted. Recovery + after possible publication therefore uses a new version rather than trying to + reclaim the old name. - An annotated tag ref exposes a tag-object SHA. Implementations that skip the tag-object lookup can either produce a false mismatch or validate the wrong identity. Current-head tests must exercise annotated-tag success plus missing, @@ -244,6 +294,9 @@ OWASP Foundation. https://cyclonedx.org/specification/overview/ GitHub. (2026). *Immutable releases*. GitHub Docs. https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases +GitHub. (2026). *REST API endpoints for releases*. GitHub Docs. +https://docs.github.com/en/rest/releases/releases + GitHub. (2026). *REST API endpoints for repositories: Check if immutable releases are enabled for a repository*. GitHub Docs. https://docs.github.com/en/rest/repos/repos @@ -259,4 +312,4 @@ builds*. GitHub Docs. https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations Open Source Security Foundation. (2025). *SLSA specification version 1.2*. -https://slsa.dev/spec/v1.2/ \ No newline at end of file +https://slsa.dev/spec/v1.2/ From 8a17e2aa4fc7ec345ea12fe0060c0098f333258d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:18:34 +0900 Subject: [PATCH 31/45] docs(doctoring): trace aborted release identity cleanup --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 2662e9f3d..7291919d5 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -69,6 +69,21 @@ before tag creation and rechecks both repository immutability and exact tag to source-SHA identity immediately before publish rather than treating an earlier preflight as durable evidence. +The same publication boundary creates an abort obligation. GitHub's immutable +protections apply after publication, while its release REST API separately +supports creating, retrieving, modifying, and deleting releases and exposes +drafts only to authorized callers. A final admission failure after draft/tag +creation can therefore leave a real unpublished candidate identity. ADR 0361 +does not infer that deletion is safe from a version string alone: the caller +must retain the exact draft Release ID and tag-object receipt, re-prove that the +release remains draft/unpublished and that the candidate ref still names the +recorded tag object, and fail closed if those proofs are ambiguous. Only that +exact unpublished draft and candidate tag may be removed before a same-version +retry. GitHub also states that once a tag has been associated with a published +immutable release, the tag name cannot be reused even after that immutable +release is deleted; possible publication therefore changes recovery to a new +version rather than cleanup/reuse. + GitHub. (n.d.). *Immutable releases*. GitHub Docs. Retrieved September 3, 2026, from https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases @@ -81,6 +96,9 @@ GitHub. (n.d.). *REST API endpoints for repositories: Check if immutable releases are enabled for a repository*. GitHub Docs. Retrieved September 3, 2026, from https://docs.github.com/en/rest/repos/repos +GitHub. (n.d.). *REST API endpoints for releases*. GitHub Docs. Retrieved +September 3, 2026, from https://docs.github.com/en/rest/releases/releases + ### GitHub annotated tag identity GitHub's Git data APIs distinguish the tag reference from an annotated tag @@ -120,8 +138,10 @@ https://doi.org/10.17487/RFC8259 - source/build provenance threat model → SLSA 1.2; - credential separation and artifact-attestation verification → GitHub artifact attestation documentation; -- immutable tag/asset admission, publish-boundary revalidation, and draft-first - publication → GitHub immutable release documentation and repository REST API; +- immutable tag/asset admission, publish-boundary revalidation, draft-first + publication, identity-safe pre-publication abort, and published-tag non-reuse + → GitHub immutable release and release REST documentation plus repository + immutability REST API; - annotated-tag ref/object separation and exact source-commit peeling → GitHub Git references and Git tags REST documentation; - strict machine-readable evidence intake → RFC 8259 plus the stricter @@ -131,4 +151,4 @@ https://doi.org/10.17487/RFC8259 External standards do not override the current canonical owner implementation. When `.github#1782` is repaired, LineageWeave must re-read protected `ContextualWisdomLab/.github` and pin the reviewed exact reusable-workflow SHA -that implements the accepted handoff. \ No newline at end of file +that implements the accepted handoff. From 9dfb79da481e37fe10e86e279f50b48179770dd1 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 4 Sep 2026 13:17:23 +0900 Subject: [PATCH 32/45] docs(release): align immutable identity contract Signed-off-by: Codex --- docs/release.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/release.md b/docs/release.md index 43e5c4fc2..fddee3ca6 100644 --- a/docs/release.md +++ b/docs/release.md @@ -61,8 +61,8 @@ release does not satisfy this contract. non-confirming result must fail closed. This credential is not exposed to pull-request or unprivileged build execution. 11. After trusted verification and the immutability preflight both succeed, - create an annotated release-specific tag object whose target type is - `commit` and whose target SHA is the exact protected source SHA from step 1, + create an annotated release-specific tag object with type `commit` and + target SHA equal to the exact protected source SHA from step 1, then create `refs/tags/` pointing to that tag object. Record the newly created tag-object SHA/ref and the pre-create proof that the ref was absent. Refuse lightweight tags, tree/blob targets, an existing ref, or any @@ -123,8 +123,8 @@ release identity and candidate tag ref as absent before a same-version retry is admissible. A cleanup failure remains RED and quarantines the version; it is not permission to force, retarget, or reuse the identity. -Never reuse a tag name that has been associated with a published immutable -release. GitHub's immutable-release contract locks the associated tag after +Never reuse a tag name that has been associated with a published immutable release. +GitHub's immutable-release contract locks the associated tag after publication and reserves that tag name even if the immutable release is later deleted. Once publication may have occurred, rollback is forward-only: preserve the forensic evidence, identify affected subjects, correct source/workflow From 29a443c1a47232719325f8ed41cd3e17a7ed1ccb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:20:55 +0900 Subject: [PATCH 33/45] test(release): pin repaired canonical attestation owner --- tests/test_release_supply_chain_docs.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index ad2a9c0b3..657dda99e 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -6,7 +6,9 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[1] _ADR = _REPOSITORY_ROOT / "docs" / "adr" / "0361-immutable-release-supply-chain-boundary.md" _RELEASE_GUIDE = _REPOSITORY_ROOT / "docs" / "release.md" +_DOCTORING = _REPOSITORY_ROOT / "docs" / "doctoring" / "RELEASE_SUPPLY_CHAIN_REFERENCES.md" _IMMUTABILITY_ENDPOINT = "GET /repos/{owner}/{repo}/immutable-releases" +_EXACT_ARTIFACT_OWNER_SHA = "bd866a21cca2a7e709f0b7a88150c310a9d98239" def test_release_publication_requires_enabled_github_release_immutability() -> None: @@ -29,7 +31,7 @@ def test_release_publication_rechecks_immutability_and_tag_identity_at_publish_b def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: """Do not compare an annotated tag-object SHA directly with the source commit SHA.""" - for path in (_ADR, _RELEASE_GUIDE): + for path in (_ADR, _REQUIRED_RELEASE_GUIDE := _RELEASE_GUIDE): text = path.read_text(encoding="utf-8").lower() assert "annotated tag" in text, path assert "tag object" in text, path @@ -39,7 +41,7 @@ def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: def test_prepublication_abort_has_identity_safe_cleanup_before_same_version_retry() -> None: - """Do not orphan or unsafely reuse a draft release identity after an aborted publish.""" + """Do not orphan or unsafe-reuse a draft release identity after an aborted publish.""" for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8").lower() assert "pre-publication abort" in text, path @@ -48,3 +50,18 @@ def test_prepublication_abort_has_identity_safe_cleanup_before_same_version_retr assert "re-resolve" in text and "absent" in text, path assert "quarantine" in text and "version" in text, path assert "never reuse" in text and "published immutable release" in text, path + + +def test_release_contract_pins_the_repaired_canonical_attestation_owner() -> None: + """Consume the merged acyclic handoff by immutable owner SHA, never mutable main or the old blocker.""" + for path in (_ADR, _RELEASE_GUIDE, _DOCTORING): + text = path.read_text(encoding="utf-8") + assert _EXACT_ARTIFACT_OWNER_SHA in text, path + assert ".github#1791" in text, path + assert ".github#1782 remains open" not in text, path + + release_text = _RELEASE_GUIDE.read_text(encoding="utf-8") + assert ( + "ContextualWisdomLab/.github/.github/workflows/" + "exact-artifact-sbom-attestation.yml@" + _EXACT_ARTIFACT_OWNER_SHA + ) in release_text From d222a702105a843d9d9ef09fb0cdb74eca4e3370 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:22:16 +0900 Subject: [PATCH 34/45] docs(release): consume repaired exact-artifact owner --- docs/release.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/release.md b/docs/release.md index fddee3ca6..69b20cd26 100644 --- a/docs/release.md +++ b/docs/release.md @@ -6,17 +6,23 @@ evidence that a release already exists. ## Current delivery state -As of 2026-09-03, protected `main` has no product-local release workflow and no -GitHub Release has been published. Two prerequisites are intentionally outside +As of 2026-09-04, protected `main` has no product-local release workflow and no +GitHub Release has been published. One product prerequisite remains outside this document's implementation scope: - LineageWeave PR #911 owns removal of the reachable `psycopg2-binary` commercial-license intake finding and the corresponding reproducible `uv.lock` migration. -- `ContextualWisdomLab/.github#1782` owns the circular artifact-digest defect in - the canonical exact-artifact SBOM attestation reusable. LineageWeave will - consume the repaired protected-owner workflow by exact commit SHA; it will - not copy or weaken that trust boundary locally. + +The former canonical-owner blocker `ContextualWisdomLab/.github#1782` is +resolved by merged `.github#1791`. LineageWeave consumes the repaired acyclic +exact-artifact reusable at immutable protected-owner commit +`bd866a21cca2a7e709f0b7a88150c310a9d98239`; it does not copy or weaken that +trust boundary locally. At that owner commit the sealed inner +`source-identity.json` is constructible before upload and excludes the +post-upload artifact digest, while GitHub's artifact ID/name/digest remains an +outer transport receipt that is independently rechecked before and inside the +credentialed signer boundary. A queued workflow, a predecessor-head success, a locally built wheel, a tag without exact evidence, a mutable GitHub release, or an unpublished draft @@ -49,10 +55,12 @@ release does not satisfy this contract. `checksums.sha256`. 8. Upload that handoff once and retain GitHub's returned artifact ID, name and digest as the immutable outer transport receipt. -9. Invoke the repaired `ContextualWisdomLab/.github` exact-artifact reusable at - an immutable reviewed commit SHA. The central verifier must independently - bind the same run, source SHA, outer receipt, inner checksums and exact - wheel/sdist subjects before its credentialed attestation job runs. +9. Invoke + `ContextualWisdomLab/.github/.github/workflows/exact-artifact-sbom-attestation.yml@bd866a21cca2a7e709f0b7a88150c310a9d98239`. + The canonical verifier must independently bind the same run, source SHA, + outer artifact ID/name/digest receipt, inner checksums and exact wheel/sdist + subjects before its credentialed attestation job runs. A later mutable + `.github/main` commit is not a substitute for this reviewed pin. 10. Before tag creation or Release publication, use a trusted admission step to call `GET /repos/{owner}/{repo}/immutable-releases` with only the GitHub Administration (read) capability required by that endpoint. Continue only From bca820058609936529e8afbbb3777b0d005ae4a7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:22:53 +0900 Subject: [PATCH 35/45] docs(adr): converge repaired attestation owner --- ...immutable-release-supply-chain-boundary.md | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/docs/adr/0361-immutable-release-supply-chain-boundary.md b/docs/adr/0361-immutable-release-supply-chain-boundary.md index dbd00bc05..27f270819 100644 --- a/docs/adr/0361-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0361-immutable-release-supply-chain-boundary.md @@ -2,7 +2,7 @@ **Decision status:** Proposed **Date:** 2026-09-03 -**Related:** ContextualWisdomLab/.github#1782, LineageWeave #911 +**Related:** ContextualWisdomLab/.github#1782, ContextualWisdomLab/.github#1791, LineageWeave #911 ## Context @@ -18,22 +18,24 @@ SBOM attestation in `ContextualWisdomLab/.github`. LineageWeave must consume that boundary rather than copy signing, OIDC, attestation-verification, or provider policy into this repository. -A fresh integration attempt exposed a canonical-owner prerequisite at -`ContextualWisdomLab/.github#1782`. The current reusable workflow requires the -GitHub Actions artifact digest as an input and also requires -`source-identity.json` *inside the same artifact* to contain that digest. The -artifact digest cannot be known until the artifact has been uploaded, while -changing the identity file changes the bytes whose digest GitHub computes. -That circular dependency makes the current handoff impossible to construct by -a deterministic caller without weakening the verifier. This ADR therefore -keeps the release integration Proposed until the canonical owner publishes an -acyclic exact-SHA contract. - -LineageWeave also cannot publish a commercial release from protected `main` -while the reachable synchronous PostgreSQL tooling path still contains the -LGPL-family `psycopg2-binary` dependency. PR #911 owns its replacement and the -reproducible lockfile migration. Release work must consume that merged, -license-clean protected result; it must not waive or suppress the inventory. +A prior integration attempt exposed the circular handoff defect tracked by +`ContextualWisdomLab/.github#1782`: the reusable required the post-upload GitHub +Actions artifact digest inside `source-identity.json`, even though that file was +part of the same upload whose bytes determine the digest. The canonical owner +repaired that model in merged `.github#1791`. Protected-owner commit +`bd866a21cca2a7e709f0b7a88150c310a9d98239` is the immutable consumer pin for +this decision. At that commit the inner source identity binds repository, +source SHA, predicate/schema and exact distribution/SBOM filenames and digests, +but not the outer GitHub artifact digest. The reusable independently verifies +the returned artifact ID/name/digest before downloading the sealed evidence and +again inside the credentialed signer boundary. This resolves the owner-side +cryptographic cycle without weakening transport-receipt verification. + +LineageWeave still cannot publish a commercial release from protected `main` +while the reachable synchronous PostgreSQL tooling path contains the LGPL-family +`psycopg2-binary` dependency. PR #911 owns its replacement and the reproducible +lockfile migration. Release work must consume that merged, license-clean +protected result; it must not waive or suppress the inventory. GitHub's immutable-release setting is a separate repository/organization control from artifact attestation. GitHub documents that a published immutable @@ -71,8 +73,9 @@ publication path. instructions. 2. `ContextualWisdomLab/.github` owns the reusable credentialed SBOM attestation and verification boundary. The LineageWeave caller must invoke - a reviewed exact commit SHA of that reusable workflow. It must not vendor or - fork the trusted verifier to make a local release pass. + `ContextualWisdomLab/.github/.github/workflows/exact-artifact-sbom-attestation.yml@bd866a21cca2a7e709f0b7a88150c310a9d98239`. + It must not vendor or fork the trusted verifier to make a local release pass, + and a later mutable `.github/main` SHA does not replace this reviewed pin. 3. Release initiation is allowed only from the exact protected LineageWeave `main` commit being released. A version is valid only when package metadata, changelog/release notes, tag, distribution metadata, source identity and @@ -87,14 +90,15 @@ publication path. wheel, source distribution, one CycloneDX 1.7 SBOM bound to each exact distribution, `source-identity.json`, and `checksums.sha256`. The inner source identity binds repository, exact source SHA, predicate/schema, - distribution filenames and distribution/SBOM SHA-256 values. The canonical - owner decides the final acyclic representation after `.github#1782`. + distribution filenames and distribution/SBOM SHA-256 values. It deliberately + excludes the post-upload GitHub Actions artifact digest; that value exists + only in the outer transport receipt returned after upload. 6. GitHub's uploaded artifact ID/name/digest is an outer immutable transport - receipt. After `.github#1782` is repaired, the caller passes the returned - receipt and exact inner identities to the canonical reusable workflow. The - reusable workflow must independently revalidate the same-run receipt and - inert handoff before any OIDC token or attestation permission becomes - available. + receipt. The caller passes that returned receipt and the exact inner + identities to the pinned canonical reusable. The reusable independently + revalidates same-run artifact ID/name/digest and the inert handoff before any + OIDC token or attestation permission becomes available, and repeats the + outer-receipt verification inside the credentialed signer boundary. 7. Immutable publication occurs only after the exact artifact set has passed canonical attestation verification and repository release immutability has been independently admitted. Before tag creation or Release publication, a @@ -161,17 +165,19 @@ publication path. ## RED / GREEN acceptance -The current RED is structural and owner-bound: LineageWeave has no product -release workflow, protected `main` is not yet license-clean, and the canonical -exact-artifact reusable cannot accept a deterministic first-party caller -because of `.github#1782`. +The current RED is product-local: LineageWeave has no release workflow and +protected `main` is not yet license-clean. The former canonical handoff blocker +`.github#1782` is resolved by `.github#1791` and the reviewed owner pin +`bd866a21cca2a7e709f0b7a88150c310a9d98239`; predecessor wording that treated +that owner defect as open is no longer an admissible release-state description. GREEN requires all of the following on one unchanged protected source SHA: - #911 or a verified successor has removed the reachable disallowed dependency and committed a reproducible lock that passes the frozen dependency gate; -- `.github#1782` is fixed on protected `.github/main` and LineageWeave pins the - repaired reusable workflow by exact commit SHA; +- the product-local workflow consumes + `exact-artifact-sbom-attestation.yml@bd866a21cca2a7e709f0b7a88150c310a9d98239` + rather than mutable `.github/main` or a copied verifier; - a product-local release workflow builds wheel/sdist plus the exact six-file evidence handoff without credentialed execution of pull-request source; - the canonical reusable verifies and attests the exact returned artifact @@ -211,8 +217,10 @@ fixes and let a product repository bypass a defect in the canonical owner. ### Drop the GitHub artifact digest from verification locally Rejected. The outer receipt protects the exact same-run transport handoff. -The circularity is an owner-contract modeling defect; weakening digest binding -in a consumer is not a causal repair. +The former circularity was an owner-contract modeling defect; the canonical +repair moved the digest out of the pre-upload inner identity while retaining +independent outer-receipt verification. A consumer must not weaken that digest +binding locally. ### Publish without proving GitHub release immutability is enabled @@ -250,9 +258,10 @@ publication can be added later behind its own protected decision. ## Risks and follow-up -- The central reusable contract can change while `.github#1782` is repaired. - LineageWeave must inspect the protected implementation and pin its exact SHA; - no branch-name or mutable `main` reference is acceptable in release code. +- The canonical reusable can evolve after the reviewed owner repair. This ADR + intentionally pins `bd866a21cca2a7e709f0b7a88150c310a9d98239`; a later + owner revision requires a normal review and fresh exact evidence before the + LineageWeave caller changes its pin. - The immutable-release status endpoint requires administrative read access. That capability must be provisioned to the trusted release admission step only; it must not expand permissions for tests, builds, pull requests or the From 8a97e52bb528f7181c4f09e508768cf14fa5c7e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:23:14 +0900 Subject: [PATCH 36/45] docs(doctoring): record acyclic owner handoff --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 7291919d5..48e7990ec 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -1,12 +1,13 @@ # Release supply-chain references **Supporting evidence for:** ADR 0361 -**Reviewed:** 2026-09-03 +**Reviewed:** 2026-09-04 **Status:** Non-normative doctoring evidence. ADR 0361 remains the decision authority. -This note records the authoritative external standards and platform contracts -used while defining LineageWeave's immutable release boundary. It does not -promote an unimplemented workflow or queued check to release evidence. +This note records the authoritative external standards, platform contracts and +canonical-owner implementation evidence used while defining LineageWeave's +immutable release boundary. It does not promote an unimplemented workflow or a +queued check to release evidence. ## Current authoritative baseline @@ -47,6 +48,34 @@ GitHub. (n.d.). *Using artifact attestations*. GitHub Docs. Retrieved September 3, 2026, from https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations +### Canonical exact-artifact handoff + +The circular transport-receipt defect originally tracked by +`ContextualWisdomLab/.github#1782` is resolved by merged `.github#1791`. The +reviewed immutable owner commit for LineageWeave is +`bd866a21cca2a7e709f0b7a88150c310a9d98239`, and the consumer surface is: + +`ContextualWisdomLab/.github/.github/workflows/exact-artifact-sbom-attestation.yml@bd866a21cca2a7e709f0b7a88150c310a9d98239` + +At that commit, `source-identity.json` contains the inner source/artifact +identity only: schema version, source repository/SHA, evidence artifact name, +predicate/schema and exact wheel/sdist plus SBOM filenames and SHA-256 values. +It does not contain the post-upload GitHub artifact digest. The reusable keeps +`evidence_artifact_id`, `evidence_artifact_name` and +`evidence_artifact_digest` as outer receipt inputs, independently queries the +same-run GitHub Actions artifact metadata, requires exact ID/name/digest/run and +non-expired state before download, and repeats that receipt verification inside +the credentialed signer boundary. The six-file cardinality, strict JSON, +checksums and exact-subject CycloneDX validation remain fail closed. + +The current protected `.github/main` descends from the repair commit, but ADR +0361 pins the reviewed repair commit itself rather than a moving default branch. +A later owner revision is a new dependency change requiring normal review and +fresh exact evidence. + +ContextualWisdomLab. (2026). *fix(release): make exact artifact handoff acyclic* +(PR #1791). GitHub. + ### GitHub immutable releases GitHub documents immutable releases as a repository/organization control that @@ -138,6 +167,9 @@ https://doi.org/10.17487/RFC8259 - source/build provenance threat model → SLSA 1.2; - credential separation and artifact-attestation verification → GitHub artifact attestation documentation; +- acyclic inner identity plus immutable outer GitHub Actions artifact receipt → + `ContextualWisdomLab/.github#1791` at + `bd866a21cca2a7e709f0b7a88150c310a9d98239`; - immutable tag/asset admission, publish-boundary revalidation, draft-first publication, identity-safe pre-publication abort, and published-tag non-reuse → GitHub immutable release and release REST documentation plus repository @@ -145,10 +177,9 @@ https://doi.org/10.17487/RFC8259 - annotated-tag ref/object separation and exact source-commit peeling → GitHub Git references and Git tags REST documentation; - strict machine-readable evidence intake → RFC 8259 plus the stricter - canonical `.github` verifier contract; -- current circular transport-receipt defect → `ContextualWisdomLab/.github#1782`. + canonical `.github` verifier contract. External standards do not override the current canonical owner implementation. -When `.github#1782` is repaired, LineageWeave must re-read protected -`ContextualWisdomLab/.github` and pin the reviewed exact reusable-workflow SHA -that implements the accepted handoff. +LineageWeave consumes only the reviewed immutable owner SHA above; moving to a +later `.github` revision requires a normal dependency review and regenerated +exact-head evidence. From c8774e920952a9ef2e5589d280e52cf08aac587c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:23:38 +0900 Subject: [PATCH 37/45] test(release): clean exact-owner contract fixture --- tests/test_release_supply_chain_docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 657dda99e..be4aab13b 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -31,7 +31,7 @@ def test_release_publication_rechecks_immutability_and_tag_identity_at_publish_b def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: """Do not compare an annotated tag-object SHA directly with the source commit SHA.""" - for path in (_ADR, _REQUIRED_RELEASE_GUIDE := _RELEASE_GUIDE): + for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8").lower() assert "annotated tag" in text, path assert "tag object" in text, path @@ -41,7 +41,7 @@ def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: def test_prepublication_abort_has_identity_safe_cleanup_before_same_version_retry() -> None: - """Do not orphan or unsafe-reuse a draft release identity after an aborted publish.""" + """Do not orphan or unsafely reuse a draft release identity after an aborted publish.""" for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8").lower() assert "pre-publication abort" in text, path From 099c28976e26cb27a86feaf5799d13b3c134d402 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:24:50 +0900 Subject: [PATCH 38/45] test(release): pin publish and cleanup race controls --- tests/test_release_supply_chain_docs.py | 63 +++++++++++++++---------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index be4aab13b..228f209c1 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -11,42 +11,57 @@ _EXACT_ARTIFACT_OWNER_SHA = "bd866a21cca2a7e709f0b7a88150c310a9d98239" -def test_release_publication_requires_enabled_github_release_immutability() -> None: - """Fail closed before publication unless GitHub release immutability is enabled.""" - for path in (_ADR, _RELEASE_GUIDE): - text = path.read_text(encoding="utf-8") - assert _IMMUTABILITY_ENDPOINT in text, path - assert "fail closed" in text.lower(), path - assert "before tag" in text.lower(), path +def _numbered_step(text: str, number: int, following: int) -> str: + """Return one numbered release/decision step so ordering assertions stay local.""" + start = text.index(f"\n{number}. ") + end = text.index(f"\n{following}. ", start) + return text[start:end].lower() -def test_release_publication_rechecks_immutability_and_tag_identity_at_publish_boundary() -> None: - """Close the preflight-to-publish TOCTOU window for immutable release identity.""" +def test_release_publication_requires_owner_enforced_github_release_immutability() -> None: + """Keep the immutability admission predicate in one ordered preflight step.""" for path in (_ADR, _RELEASE_GUIDE): - text = path.read_text(encoding="utf-8").lower() - assert "immediately before publish" in text, path - assert "recheck" in text, path - assert "tag" in text and "source sha" in text, path + text = path.read_text(encoding="utf-8") + preflight = _numbered_step(text, 10 if path == _RELEASE_GUIDE else 7, 11 if path == _RELEASE_GUIDE else 8) + assert _IMMUTABILITY_ENDPOINT.lower() in preflight, path + assert "enabled: true" in preflight, path + assert "enforced_by_owner: true" in preflight, path + assert "fail closed" in preflight, path -def test_annotated_release_tag_is_peeled_to_the_exact_source_commit() -> None: - """Do not compare an annotated tag-object SHA directly with the source commit SHA.""" +def test_release_publication_rechecks_exact_draft_tag_assets_and_immutability_at_boundary() -> None: + """Bind the final publish decision to one exact draft, tag, asset set and immutable policy.""" for path in (_ADR, _RELEASE_GUIDE): - text = path.read_text(encoding="utf-8").lower() - assert "annotated tag" in text, path - assert "tag object" in text, path - assert "peel" in text, path - assert "type `commit`" in text, path - assert "exact protected source sha" in text, path + text = path.read_text(encoding="utf-8") + publish_step = _numbered_step(text, 12 if path == _RELEASE_GUIDE else 8, 13 if path == _RELEASE_GUIDE else 9) + for required in ( + "immediately before publish", + "exact release id", + "draft: true", + "tag_name", + "prerelease: false", + "asset", + "digest", + "annotated tag", + "tag object", + "peel", + "type `commit`", + "exact protected source sha", + ): + assert required in publish_step, (path, required) + assert "enforced_by_owner: true" in publish_step, path + assert "trusted release writer" in publish_step, path + assert "fail closed" in publish_step, path -def test_prepublication_abort_has_identity_safe_cleanup_before_same_version_retry() -> None: - """Do not orphan or unsafely reuse a draft release identity after an aborted publish.""" +def test_prepublication_abort_has_conditional_tag_cleanup_before_same_version_retry() -> None: + """Never delete a candidate ref after a stale ownership check.""" for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8").lower() assert "pre-publication abort" in text, path assert "draft" in text and "unpublished" in text, path - assert "delete" in text and "candidate tag" in text, path + assert "compare-and-delete" in text, path + assert "trusted release writer" in text, path assert "re-resolve" in text and "absent" in text, path assert "quarantine" in text and "version" in text, path assert "never reuse" in text and "published immutable release" in text, path From 778d77649135a8218afe247e71ffcc6e69ad5d20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:25:29 +0900 Subject: [PATCH 39/45] docs(release): serialize immutable publication boundary --- docs/release.md | 142 ++++++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/docs/release.md b/docs/release.md index 69b20cd26..a7c268caf 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,6 +1,6 @@ # LineageWeave release contract -This document is the operator projection of Proposed ADR 0361. It describes +This document is the operator projection of Proposed ADR 0360. It describes what must be true before LineageWeave publishes an immutable release; it is not evidence that a release already exists. @@ -10,7 +10,7 @@ As of 2026-09-04, protected `main` has no product-local release workflow and no GitHub Release has been published. One product prerequisite remains outside this document's implementation scope: -- LineageWeave PR #911 owns removal of the reachable `psycopg2-binary` +- LineageWeave PR #911 owns removal of the reachable `psycopos-binary` commercial-license intake finding and the corresponding reproducible `uv.lock` migration. @@ -50,7 +50,7 @@ release does not satisfy this contract. differing subject merely to make the comparison pass. 7. Generate one CycloneDX 1.7 SBOM for each exact distribution and bind its root component to the distribution filename and SHA-256 digest. Prepare the - six-file handoff defined by ADR 0361: wheel, wheel SBOM, source distribution, + six-file handoff defined by ADR 0360: wheel, wheel SBOM, source distribution, source-distribution SBOM, `source-identity.json`, and `checksums.sha256`. 8. Upload that handoff once and retain GitHub's returned artifact ID, name and @@ -61,46 +61,61 @@ release does not satisfy this contract. outer artifact ID/name/digest receipt, inner checksums and exact wheel/sdist subjects before its credentialed attestation job runs. A later mutable `.github/main` commit is not a substitute for this reviewed pin. -10. Before tag creation or Release publication, use a trusted admission step to - call `GET /repos/{owner}/{repo}/immutable-releases` with only the GitHub - Administration (read) capability required by that endpoint. Continue only - when the authenticated response confirms `enabled: true`. A 404, - permission error, transport/API error, malformed response, or any other - non-confirming result must fail closed. This credential is not exposed to +10. Before tag creation or Release publication, enter one **trusted release writer** + that is exclusively serialized for the candidate release/tag namespace and + call `GET /repos/{owner}/{repo}/immutable-releases` with the minimum GitHub + Administration (read) capability. Continue only when the authenticated + response confirms both `enabled: true` and `enforced_by_owner: true`. + Repository-level enablement without owner enforcement is insufficient for + this high-assurance path because a repository administrator could disable + the setting between admission and publication. The release-tag namespace + must also be protected by a reviewed ruleset that rejects retarget/delete by + actors outside this trusted release writer. If owner enforcement, writer + serialization, or tag protection cannot be proved, fail closed before tag + creation. This credential and writer capability are not exposed to pull-request or unprivileged build execution. -11. After trusted verification and the immutability preflight both succeed, - create an annotated release-specific tag object with type `commit` and - target SHA equal to the exact protected source SHA from step 1, - then create `refs/tags/` pointing to that tag object. Record the - newly created tag-object SHA/ref and the pre-create proof that the ref was - absent. Refuse lightweight tags, tree/blob targets, an existing ref, or any - tag object whose target differs from the admitted source. Create a draft - GitHub Release, retain its exact release ID and `draft: true` creation - receipt, and attach the complete verified distributions, SBOM/provenance - evidence, checksum material and release notes. Do not publish an incomplete - asset set. -12. Immediately before publish, recheck repository release immutability through - `GET /repos/{owner}/{repo}/immutable-releases` and re-resolve the release - tag. Because an annotated tag ref points to the Git tag object SHA rather - than directly to its source commit, fetch that tag object and peel it to its - target. The target type must be `commit` and its SHA must equal the exact - protected source SHA admitted in step 1. Never compare the tag-object SHA - itself with the source commit SHA. Any setting change, lookup failure, - malformed response, missing ref/tag object, unexpected object type, or - peeled commit/source SHA mismatch must fail closed without publishing the - draft. This second check closes the time-of-check/time-of-use window between - admission and publish. If it fails after step 11 created the candidate tag - and draft, execute the pre-publication abort procedure below before any - same-version retry; an unpublished draft and occupied tag ref are not - silently treated as if no release identity had been created. +11. After trusted verification and the step-10 admission both succeed, create an + annotated release-specific tag object with type `commit` and target SHA + equal to the exact protected source SHA from step 1, then create + `refs/tags/` pointing to that tag object. Record the newly created + tag-object SHA/ref, the active protection/ruleset receipt, and the pre-create + proof that the ref was absent. Refuse lightweight tags, tree/blob targets, + an existing ref, or any tag object whose target differs from the admitted + source. Create a draft GitHub Release, retain its exact Release ID and + `draft: true` creation receipt, and attach the complete verified + distributions, SBOM/provenance evidence, checksum material and release + notes. Record the exact expected asset name/digest set from the sealed + evidence. Do not publish an incomplete asset set. +12. **Immediately before publish**, while the same trusted release writer still + owns the exclusive candidate namespace, recheck + `GET /repos/{owner}/{repo}/immutable-releases` and require `enabled: true` + plus `enforced_by_owner: true`; re-read the exact Release ID and require + `draft: true`, the exact admitted `tag_name`, `prerelease: false`, and an + asset set whose names and digests exactly equal the sealed evidence. Then + re-resolve the annotated tag ref, require it still points to the recorded + tag object, fetch that tag object, peel its target, require type `commit`, + and compare the peeled commit with the **exact protected source SHA** from + step 1. The protected tag ruleset plus the exclusively serialized trusted + release writer are the lease that prevents another admitted writer from + retargeting the ref between this validation and publication. GitHub exposes + no consumer-supplied compare-and-publish precondition for repository + immutability, so this contract does not pretend that two independent REST + calls are atomic: owner-enforced immutability and the protected/serialized + tag namespace are mandatory configuration locks. Any drift, missing lock, + Release ID/state/tag/prerelease mismatch, asset/digest mismatch, lookup + failure, malformed response, tag-object mismatch, unexpected object type, + or peeled commit/source mismatch must **fail closed** with the draft still + unpublished. If that happens after step 11, execute the pre-publication + abort procedure below before any same-version retry. 13. Publish that fully populated draft as the non-prerelease immutable GitHub Release. Never move an existing release tag or overwrite published bytes under an existing version. -14. Fetch the published release back through GitHub's API, verify immutable - state and repeat the same annotated-tag peel to prove the release tag still - reaches the exact protected source commit; verify asset digests against the - sealed evidence and retain this post-publication receipt as release - evidence. +14. Fetch the published release back through GitHub's API, require + `immutable: true`, verify the exact Release ID/tag and asset digests again, + and repeat the annotated-tag peel to prove the release tag still reaches + the exact protected source commit. Retain this post-publication receipt as + release evidence. A non-immutable result is a release incident even if the + preceding admission checks succeeded. ## Failure and rollback @@ -112,32 +127,39 @@ fresh exact candidate. A failure after step 11 but before publication is a **pre-publication abort**, not proof that no identity exists. The run may already own an unpublished draft release and a candidate tag ref. Before retrying the same version, the trusted -release boundary must use the recorded creation receipts to prove all of the -following: the exact release ID created by this run still resolves as -`draft: true` and unpublished; its `tag_name` is the admitted version; the -candidate tag ref still points to the exact annotated tag object created by +release writer must use the recorded creation receipts to prove all of the +following: the exact Release ID created by this run still resolves as +`draft: true` and unpublished; its `tag_name` is the admitted version and +`prerelease: false`; its asset set/digests still match the recorded candidate; +the candidate tag ref still points to the exact annotated tag object created by this run; that tag object still peels to the admitted protected source commit; and no published release resolves for the candidate tag. If any lookup, -identity, ownership, or publication-state proof is missing or ambiguous, do not -delete or retarget anything. Quarantine that version and require a new -version/source candidate. +identity, ownership, publication-state, ruleset, or exclusive-writer proof is +missing or ambiguous, do not delete or retarget anything. Quarantine that +version and require a new version/source candidate. Only after those unpublished-only proofs succeed may the abort cleanup delete -the exact draft release ID created by this run. Re-resolve that release ID as +the exact draft Release ID created by this run. Re-resolve that Release ID as absent, then recheck that no published release resolves for the candidate tag -and that the ref still points to the recorded candidate tag object. Only then -may the cleanup delete that exact candidate tag ref. Re-resolve both the draft -release identity and candidate tag ref as absent before a same-version retry is -admissible. A cleanup failure remains RED and quarantines the version; it is -not permission to force, retarget, or reuse the identity. +and that the ref still points to the recorded candidate tag object. Candidate +ref removal is a **compare-and-delete** operation under the same exclusively +serialized trusted release writer and protected tag namespace: immediately +before deletion, compare the live ref with the recorded tag-object SHA and +proceed only if the writer/ruleset guarantee prevents another admitted writer +from changing it before delete. If the platform/ruleset cannot provide that +serialization guarantee, do not delete the ref; quarantine the version. After +a permitted deletion, re-resolve both the draft release identity and candidate +tag ref as absent before a same-version retry is admissible. A cleanup failure +remains RED and quarantines the version; it is not permission to force, +retarget, or reuse the identity. Never reuse a tag name that has been associated with a published immutable release. -GitHub's immutable-release contract locks the associated tag after -publication and reserves that tag name even if the immutable release is later -deleted. Once publication may have occurred, rollback is forward-only: preserve -the forensic evidence, identify affected subjects, correct source/workflow -through normal governance, and publish replacement artifacts under a new -version and protected source SHA. +GitHub's immutable-release contract locks the associated tag after publication +and reserves that tag name even if the immutable release is later deleted. Once +publication may have occurred, rollback is forward-only: preserve the forensic +evidence, identify affected subjects, correct source/workflow through normal +governance, and publish replacement artifacts under a new version and protected +source SHA. If an already-published artifact or attestation is found invalid, preserve the forensic evidence and identify affected subjects before any revocation or @@ -152,8 +174,8 @@ attestation for new bytes. Checks, reviews, package builds, SBOMs, attestations, browser evidence and release receipts belong to the exact head/artifact they evaluated. A source commit, dependency lock, release workflow, reusable-workflow pin, repository -immutability setting or artifact byte change invalidates predecessor evidence -and requires fresh verification. +immutability setting, tag-protection ruleset or artifact byte change invalidates +predecessor evidence and requires fresh verification. ## Owner boundaries From c5d4a0db97e849979b883f7f530563c3dadf0b0c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:26:10 +0900 Subject: [PATCH 40/45] docs(adr): close release publication race boundaries --- ...immutable-release-supply-chain-boundary.md | 283 +++++++++--------- 1 file changed, 139 insertions(+), 144 deletions(-) diff --git a/docs/adr/0361-immutable-release-supply-chain-boundary.md b/docs/adr/0361-immutable-release-supply-chain-boundary.md index 27f270819..11956fa5e 100644 --- a/docs/adr/0361-immutable-release-supply-chain-boundary.md +++ b/docs/adr/0361-immutable-release-supply-chain-boundary.md @@ -39,31 +39,39 @@ protected result; it must not waive or suppress the inventory. GitHub's immutable-release setting is a separate repository/organization control from artifact attestation. GitHub documents that a published immutable -release locks the associated tag and assets, and automatically creates a -release attestation. It also exposes an authenticated repository endpoint, -`GET /repos/{owner}/{repo}/immutable-releases`, that returns success only when -release immutability is enabled. The release caller therefore needs an -administrative read-only preflight credential isolated from pull-request and -build execution; absence of that credential or an unsuccessful preflight is a -release-admission failure, not a reason to publish a mutable release. - -GitHub's Git data model also distinguishes an annotated tag reference from the +release locks the associated tag and assets and automatically creates a release +attestation. The repository API returns both `enabled` and `enforced_by_owner` +for immutable-release admission. For this high-assurance commercial release +path, repository-local enablement is not enough: the final publication boundary +requires owner-enforced immutability so a repository administrator cannot turn +the control off between admission and publish. The trusted release writer uses +only the administrative read capability needed to prove that state. + +The API does not expose a consumer-supplied compare-and-publish precondition +that atomically couples an earlier settings/tag read to Release publication. +The release design therefore cannot treat two REST reads as atomic. It closes +the controllable races with mandatory configuration locks: owner-enforced +release immutability, a reviewed protected ruleset for the candidate tag +namespace, and one exclusively serialized trusted release writer for all +candidate tag/ref and Release mutations. If those controls cannot be proved, +publication remains RED. + +GitHub's Git data model distinguishes an annotated tag reference from the commit that the tag names. `refs/tags/` points to a Git tag object; -that tag object in turn names its target object and target type. A release -contract that compares the ref's tag-object SHA directly with the protected -source commit SHA would therefore reject a valid annotated tag or, worse, -encode the wrong identity rule. The release boundary must peel the annotated -tag object and admit only a target with type `commit` and the exact protected -source SHA. +that tag object separately names its target object and target type. The final +publish decision must therefore re-read the exact tag object, peel it to a +`commit`, and compare that target with the exact protected source SHA while the +protected/serialized candidate namespace prevents an admitted writer from +retargeting the ref. The immutable protections begin at publication, not at draft creation. GitHub recommends creating a draft, attaching all assets, and only then publishing it. -That creates a deliberate pre-publication interval in which the release caller -has already created a candidate tag and draft release but publication may still -abort. Without an identity-safe abort contract, a failed final admission can -leave an occupied tag/draft that blocks a retry or encourages unsafe retargeting. -The abort path therefore needs the same exact-identity discipline as the -publication path. +That creates a pre-publication interval in which the candidate tag, draft and +assets are already real identities. The final admission must revalidate the +exact Release ID, `draft: true`, `tag_name`, `prerelease: false`, and the full +asset name/digest set. Abort cleanup must likewise bind deletion to the exact +candidate under the same writer/ruleset serialization; otherwise cleanup is +quarantined rather than risking deletion of a retargeted ref. ## Decision @@ -74,8 +82,8 @@ publication path. 2. `ContextualWisdomLab/.github` owns the reusable credentialed SBOM attestation and verification boundary. The LineageWeave caller must invoke `ContextualWisdomLab/.github/.github/workflows/exact-artifact-sbom-attestation.yml@bd866a21cca2a7e709f0b7a88150c310a9d98239`. - It must not vendor or fork the trusted verifier to make a local release pass, - and a later mutable `.github/main` SHA does not replace this reviewed pin. + It must not vendor or fork the trusted verifier, and a later mutable + `.github/main` SHA does not replace this reviewed pin. 3. Release initiation is allowed only from the exact protected LineageWeave `main` commit being released. A version is valid only when package metadata, changelog/release notes, tag, distribution metadata, source identity and @@ -99,53 +107,54 @@ publication path. revalidates same-run artifact ID/name/digest and the inert handoff before any OIDC token or attestation permission becomes available, and repeats the outer-receipt verification inside the credentialed signer boundary. -7. Immutable publication occurs only after the exact artifact set has passed - canonical attestation verification and repository release immutability has - been independently admitted. Before tag creation or Release publication, a - trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` with - the minimum GitHub Administration (read) permission. Only an authenticated - success response that confirms `enabled: true` is admissible. A 404, - permission failure, transport/API failure, malformed response, or any result - that does not confirm `enabled: true` must fail closed before tag creation. - The preflight credential is unavailable to pull-request and unprivileged - build jobs. -8. After that preflight, the release job creates an annotated tag object whose - target type is `commit` and whose target SHA is the exact protected source - SHA admitted for the release, then creates `refs/tags/` pointing to - that tag object. It records the pre-create absence proof, exact tag-object - SHA/ref, and the exact draft Release ID returned by GitHub. Lightweight tags, - tree/blob targets, existing refs, or a tag object targeting any other commit - are inadmissible. The job then creates a draft GitHub Release and attaches - the complete verified asset set. Immediately before publish, the trusted - release boundary must recheck `GET /repos/{owner}/{repo}/immutable-releases` - and re-resolve the tag. For the annotated tag, the ref's object SHA is the - tag-object SHA, not the source commit SHA; the boundary must fetch that tag - object and peel its target, require target type `commit`, and compare the - peeled commit SHA with the exact protected source SHA. It must never compare - the tag-object SHA itself with the source commit SHA. Any setting change, - lookup/API failure, malformed response, missing ref/tag object, unexpected - target type, or peeled commit/source SHA mismatch must fail closed while the - draft remains unpublished. Only after this second admission may the draft be - published as the immutable non-prerelease release. This closes the - time-of-check/time-of-use interval between the initial immutability admission - and publication. It must not overwrite an existing tag, asset or version. A - failed or partial publication is an incident, not permission to mutate - previously published bytes under the same identity. -9. A failure after the candidate tag/draft are created but before publication is - a **pre-publication abort**. Same-version retry is admissible only after the - trusted release boundary proves from recorded receipts that the exact Release - ID created by this run is still `draft: true` and unpublished, its `tag_name` - matches the admitted version, the candidate tag ref still points to the exact - tag object created by this run, that tag object still peels to the admitted - protected source commit, and no published release resolves for that tag. If - any identity, ownership, or publication-state proof is missing or ambiguous, - no deletion or retarget is allowed: quarantine the version and require a new - version/source candidate. After all proofs succeed, delete only that exact - unpublished draft, re-resolve the draft as absent, recheck that no published - release resolves for the tag and that the ref still names the recorded tag - object, then delete only that exact candidate tag ref. Re-resolve both as - absent before same-version retry. Never reuse a tag name that has been - associated with a published immutable release. +7. Before any release tag or draft is created, enter a single **trusted release + writer** serialized for the candidate version/tag namespace. Call + `GET /repos/{owner}/{repo}/immutable-releases` and require both + `enabled: true` and `enforced_by_owner: true`. Require a reviewed tag + ruleset/protection receipt that prevents actors outside the trusted release + writer from retargeting or deleting the candidate release-tag namespace. + Any missing administrative-read capability, non-confirming settings result, + absent protection receipt, or inability to prove writer serialization must + **fail closed** before tag creation. These privileged controls are not + available to pull-request or unprivileged build jobs. +8. After that admission, create an annotated tag object whose target type is + `commit` and whose target SHA is the exact protected source SHA, then create + `refs/tags/` pointing to that object. Record the pre-create absence + proof, exact tag-object SHA/ref and active protection receipt. Create a draft + Release, retain its **exact Release ID**, require the creation receipt to be + `draft: true` and `prerelease: false` with the exact admitted `tag_name`, and + attach the complete verified asset set while recording every expected asset + name and digest. **Immediately before publish**, while the same trusted + release writer still owns the serialized namespace, recheck + `GET /repos/{owner}/{repo}/immutable-releases` and again require + `enabled: true` and `enforced_by_owner: true`; re-read the exact Release ID + and require `draft: true`, exact `tag_name`, `prerelease: false`, and exact + asset names/digests; then re-resolve the annotated tag, require the ref still + names the recorded **tag object**, fetch it, **peel** its target, require + **type `commit`**, and compare the peeled target with the **exact protected + source SHA**. The owner-enforced setting, protected tag namespace and + exclusively serialized trusted release writer are mandatory configuration + locks for the interval that GitHub does not expose as one atomic API call. + Any state drift, missing lock, mismatch, lookup failure or malformed response + must **fail closed** with the draft unpublished. Only after all of those + exact predicates remain true may the trusted writer publish the draft. +9. A failure after candidate tag/draft creation but before publication is a + **pre-publication abort**. Same-version retry is admissible only after the + trusted release writer proves from recorded receipts that the exact Release + ID is still `draft: true`, unpublished, `prerelease: false`, names the exact + candidate `tag_name`, and retains the exact candidate asset/digest set; the + candidate ref still points to the recorded tag object; that tag object still + peels to the admitted protected source commit; and no published release + resolves for the tag. Delete only the exact draft ID after those proofs and + re-resolve it as absent. Candidate tag deletion is a **compare-and-delete** + under the same trusted release writer and protected namespace: immediately + before deletion compare the live ref with the recorded tag-object SHA, and + proceed only while serialization/protection guarantees that another admitted + writer cannot change it before deletion. If compare-and-delete semantics or + exclusive serialization cannot be guaranteed, do not delete or retarget the + ref; quarantine the version. After an allowed deletion, re-resolve both draft + and ref as absent before same-version retry. Never reuse a tag name that has + been associated with a published immutable release. 10. Reproducibility is tested by rebuilding the wheel and source distribution from the same protected source under the reviewed toolchain and comparing the release contract's declared deterministic subjects. Any known @@ -168,8 +177,7 @@ publication path. The current RED is product-local: LineageWeave has no release workflow and protected `main` is not yet license-clean. The former canonical handoff blocker `.github#1782` is resolved by `.github#1791` and the reviewed owner pin -`bd866a21cca2a7e709f0b7a88150c310a9d98239`; predecessor wording that treated -that owner defect as open is no longer an admissible release-state description. +`bd866a21cca2a7e709f0b7a88150c310a9d98239`. GREEN requires all of the following on one unchanged protected source SHA: @@ -182,27 +190,25 @@ GREEN requires all of the following on one unchanged protected source SHA: evidence handoff without credentialed execution of pull-request source; - the canonical reusable verifies and attests the exact returned artifact receipt and exact wheel/sdist subjects; -- a trusted preflight calls `GET /repos/{owner}/{repo}/immutable-releases` - before tag creation and confirms `enabled: true`; missing administrative-read - capability or any non-confirming result must fail closed; -- the release-specific ref points to an annotated tag object whose target type - is `commit` and whose peeled target is the exact protected source SHA; direct - comparison of the tag-object SHA with the source commit SHA is forbidden; +- one trusted release writer has exclusive candidate-version serialization and + an active protected tag namespace; the immutable-release preflight and final + publish-boundary check both require `enabled: true` and + `enforced_by_owner: true`; +- immediately before publish, the exact Release ID still has `draft: true`, the + exact `tag_name`, `prerelease: false`, and the exact sealed asset name/digest + set; the annotated tag ref still names the recorded tag object, which peels to + type `commit` and the exact protected source SHA; - a clean rebuild proves the declared reproducibility contract; -- the complete verified asset set is attached to a draft GitHub Release; -- immediately before publish, the trusted boundary must recheck repository - immutability and repeat the annotated-tag peel to prove the release tag still - reaches the same exact protected source SHA; any non-confirming result must - fail closed with the draft unpublished; -- synthetic release fixtures exercise the pre-publication abort path, including - exact draft/tag ownership proofs, safe deletion only while unpublished, - re-resolve-as-absent before same-version retry, quarantine on ambiguous - cleanup, and never reuse of a tag associated with a published immutable - release; +- post-publication verification observes the same release/tag/assets and + `immutable: true`; +- synthetic release fixtures exercise pre-publication abort, conditional + compare-and-delete, exact draft/tag ownership, quarantine when serialization + cannot be proved, re-resolve-as-absent before same-version retry, and never + reuse a tag associated with a published immutable release; - release notes, version, protected source SHA, tag, distributions, SBOMs, attestations and immutable GitHub Release are mutually consistent; and -- rollback/incident instructions are exercised against synthetic release - fixtures without deleting or rewriting valid published evidence. +- rollback/incident instructions are exercised without deleting or rewriting + valid published evidence. Until every condition is evidenced, this ADR remains Proposed and no LineageWeave release-readiness claim may cite this design as delivered. @@ -212,22 +218,30 @@ LineageWeave release-readiness claim may cite this design as delivered. ### Copy the central attestation workflow into LineageWeave Rejected. It would create a second signing-policy authority, duplicate security -fixes and let a product repository bypass a defect in the canonical owner. +fixes and let a product repository bypass the canonical owner. ### Drop the GitHub artifact digest from verification locally -Rejected. The outer receipt protects the exact same-run transport handoff. -The former circularity was an owner-contract modeling defect; the canonical -repair moved the digest out of the pre-upload inner identity while retaining -independent outer-receipt verification. A consumer must not weaken that digest -binding locally. +Rejected. The outer receipt protects the exact same-run transport handoff. The +former circularity was an owner-contract modeling defect; the canonical repair +moved the digest out of the pre-upload inner identity while retaining +independent outer-receipt verification. -### Publish without proving GitHub release immutability is enabled +### Accept repository-local immutable-release enablement -Rejected. A release whose tag or assets remain mutable does not meet this -ADR's buyer-visible integrity claim. Failure to read the setting is also not -proof that the setting is enabled, so the publication path fails closed rather -than assuming repository configuration. +Rejected for the high-assurance commercial path. GitHub exposes +`enforced_by_owner`; requiring that owner-level control materially narrows the +settings race that a repository administrator could otherwise create between +admission and publication. If owner enforcement is unavailable, publication is +RED rather than silently downgraded to a mutable-release risk. + +### Treat a final settings/tag read as an atomic publish precondition + +Rejected because the documented Release REST API does not expose a consumer +compare-and-publish condition that atomically binds those earlier reads. The +contract therefore requires owner-enforced immutability plus protected tag +namespace and trusted-writer serialization, and verifies `immutable: true` +after publication instead of claiming a nonexistent REST atomicity primitive. ### Compare an annotated tag ref SHA directly with the source commit SHA @@ -235,61 +249,42 @@ Rejected. For an annotated tag, the ref names a Git tag object rather than the source commit. The trusted boundary must peel the tag object, require target type `commit`, and compare that target SHA with the admitted source SHA. -### Leave an aborted draft/tag in place and retry around it +### Delete a candidate tag after a standalone read -Rejected. An unpublished draft and occupied tag are real candidate identities. -Ignoring them either blocks same-version retry or creates pressure to force or -retarget the tag. Cleanup is allowed only for the exact unpublished identities -created by the same run after their state and ownership are re-proved; otherwise -the version is quarantined and remediation moves forward. +Rejected. A read-then-delete sequence can delete a ref that was retargeted after +the read. Candidate deletion is allowed only as compare-and-delete under the +exclusive trusted writer/protected namespace; without that serialization proof, +the version is quarantined and the ref is left untouched. ### Publish a GitHub Release first and attach evidence later Rejected. Buyers would observe a release identity before its exact artifact, -SBOM and provenance evidence was complete. GitHub's immutable-release guidance -also recommends attaching assets to a draft and publishing only after the -asset set is complete. - -### Wait for a package registry before creating any release boundary - -Rejected. GitHub Release immutability, exact source/artifact identity, SBOM, -provenance and rollback are independently valuable buyer controls. Registry -publication can be added later behind its own protected decision. +SBOM and provenance evidence was complete. GitHub recommends attaching assets +to a draft and publishing only after the asset set is complete. ## Risks and follow-up - The canonical reusable can evolve after the reviewed owner repair. This ADR - intentionally pins `bd866a21cca2a7e709f0b7a88150c310a9d98239`; a later - owner revision requires a normal review and fresh exact evidence before the - LineageWeave caller changes its pin. -- The immutable-release status endpoint requires administrative read access. - That capability must be provisioned to the trusted release admission step - only; it must not expand permissions for tests, builds, pull requests or the - canonical attestation reusable. If it cannot be provisioned, publication - remains RED. -- The repository immutability setting and release tag are mutable until the - immutable release is published. A single early preflight therefore has a - time-of-check/time-of-use window; both are revalidated immediately before - publish and any drift leaves the draft unpublished. -- An unpublished draft/tag can survive a failed final admission. The release - workflow must retain exact creation receipts and must not assume cleanup is - safe from version text alone. If the draft is no longer provably unpublished - or the tag no longer exactly matches the recorded candidate object, cleanup - fails closed and the version is quarantined. -- GitHub documents that a tag name associated with a published immutable release - cannot be reused even if that immutable release is later deleted. Recovery - after possible publication therefore uses a new version rather than trying to - reclaim the old name. -- An annotated tag ref exposes a tag-object SHA. Implementations that skip the - tag-object lookup can either produce a false mismatch or validate the wrong - identity. Current-head tests must exercise annotated-tag success plus missing, - non-commit-target and wrong-commit failures before release code can be GREEN. + pins `bd866a21cca2a7e709f0b7a88150c310a9d98239`; changing that dependency + requires normal review and fresh exact evidence. +- `enforced_by_owner: true` depends on organization release-immutability policy. + If the organization cannot provide that configuration lock, this Proposed ADR + does not authorize release publication. +- The trusted release writer and candidate-tag ruleset must be implemented and + tested before Accepted status. A workflow-level concurrency group without a + tag ruleset is not sufficient against out-of-band tag mutation. +- The GitHub API does not provide a consumer-side atomic publish precondition + for all checked configuration. Post-publication `immutable: true` verification + is therefore mandatory and a failure is a release incident, not evidence that + the earlier checks were atomic. +- An unpublished draft/tag can survive a failed final admission. Ambiguous + ownership, missing serialization, or a changed ref prevents cleanup and + quarantines the version. - Reproducible Python distributions may expose timestamps, archive ordering or backend metadata that require causal build-system repair. A mismatch remains RED until explained and removed at the source. -- Current organization Actions queue saturation can delay evidence, but queue - latency is not a reason to bypass release gates or transfer predecessor-head - results. +- Actions queue saturation can delay evidence, but queue latency is not a reason + to bypass release gates or transfer predecessor-head results. ## References From a51ed489b4e5d3b32d9cc6f1051609738d249512 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:26:46 +0900 Subject: [PATCH 41/45] docs(release): correct release contract identities --- docs/release.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release.md b/docs/release.md index a7c268caf..160572a63 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,6 +1,6 @@ # LineageWeave release contract -This document is the operator projection of Proposed ADR 0360. It describes +This document is the operator projection of Proposed ADR 0361. It describes what must be true before LineageWeave publishes an immutable release; it is not evidence that a release already exists. @@ -10,7 +10,7 @@ As of 2026-09-04, protected `main` has no product-local release workflow and no GitHub Release has been published. One product prerequisite remains outside this document's implementation scope: -- LineageWeave PR #911 owns removal of the reachable `psycopos-binary` +- LineageWeave PR #911 owns removal of the reachable `psycopg2-binary` commercial-license intake finding and the corresponding reproducible `uv.lock` migration. @@ -50,7 +50,7 @@ release does not satisfy this contract. differing subject merely to make the comparison pass. 7. Generate one CycloneDX 1.7 SBOM for each exact distribution and bind its root component to the distribution filename and SHA-256 digest. Prepare the - six-file handoff defined by ADR 0360: wheel, wheel SBOM, source distribution, + six-file handoff defined by ADR 0361: wheel, wheel SBOM, source distribution, source-distribution SBOM, `source-identity.json`, and `checksums.sha256`. 8. Upload that handoff once and retain GitHub's returned artifact ID, name and From d0adba9b24adf349a071ee6c67640da32af841bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:27:17 +0900 Subject: [PATCH 42/45] docs(doctoring): trace release race controls --- .../RELEASE_SUPPLY_CHAIN_REFERENCES.md | 96 +++++++++++-------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md index 48e7990ec..67049b20a 100644 --- a/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md +++ b/docs/doctoring/RELEASE_SUPPLY_CHAIN_REFERENCES.md @@ -84,49 +84,60 @@ a release attestation. GitHub recommends creating a draft release, attaching all assets, and publishing the populated draft so immutability does not leave a partially populated release. -The current repository REST API exposes -`GET /repos/{owner}/{repo}/immutable-releases` to check whether the control is -enabled. GitHub documents an authenticated `200` response when enabled and a -`404` when it is not enabled, and requires repository Administration (read) -permission for the check. ADR 0361 therefore treats an authenticated -`enabled: true` result as release admission and fails closed when the status -cannot be established. That administrative read capability belongs only to the -trusted admission step; it is not granted to pull-request or build execution. -Because the setting and a newly created release tag remain mutable until the -release is actually published, the release contract performs the status check -before tag creation and rechecks both repository immutability and exact tag to -source-SHA identity immediately before publish rather than treating an earlier -preflight as durable evidence. - -The same publication boundary creates an abort obligation. GitHub's immutable -protections apply after publication, while its release REST API separately -supports creating, retrieving, modifying, and deleting releases and exposes -drafts only to authorized callers. A final admission failure after draft/tag -creation can therefore leave a real unpublished candidate identity. ADR 0361 -does not infer that deletion is safe from a version string alone: the caller -must retain the exact draft Release ID and tag-object receipt, re-prove that the -release remains draft/unpublished and that the candidate ref still names the -recorded tag object, and fail closed if those proofs are ambiguous. Only that -exact unpublished draft and candidate tag may be removed before a same-version -retry. GitHub also states that once a tag has been associated with a published -immutable release, the tag name cannot be reused even after that immutable -release is deleted; possible publication therefore changes recovery to a new -version rather than cleanup/reuse. - -GitHub. (n.d.). *Immutable releases*. GitHub Docs. Retrieved September 3, 2026, +The repository REST API exposes `GET /repos/{owner}/{repo}/immutable-releases` +and returns an object containing `enabled` and `enforced_by_owner`. For ADR 0361 +both must be true: `enabled: true` proves the feature is active and +`enforced_by_owner: true` proves repository administrators cannot locally turn +it off during the release interval. The administrative read capability used to +inspect that state belongs only to the trusted release boundary. + +The Release REST representation exposes the exact Release ID, `tag_name`, +`draft`, `prerelease`, asset list and asset digests, and published releases +report whether they are `immutable`. ADR 0361 therefore treats a final settings +read alone as insufficient. Immediately before publish it re-reads the exact +Release ID, requires `draft: true`, the admitted `tag_name`, +`prerelease: false`, and the exact sealed asset name/digest set, then repeats the +annotated-tag object/commit check. After publication it requires +`immutable: true` and verifies the same tag/assets again. + +GitHub documents no consumer-supplied compare-and-publish precondition that +atomically binds those prior REST reads to publication. The architecture does +not invent one. The controllable race is narrowed with three mandatory +configuration/serialization controls: owner-enforced release immutability, a +reviewed ruleset protecting the candidate release-tag namespace, and one +exclusively serialized **trusted release writer** for candidate tag/ref and +Release mutations. If those controls are absent or cannot be proved, release +publication stays RED. + +That same ownership rule applies to abort cleanup. Verifying a candidate ref +and deleting it in separate unsynchronized calls leaves a TOCTOU window. ADR +0361 therefore defines candidate ref removal as **compare-and-delete** under the +same trusted release writer and protected tag namespace: compare the live ref +with the recorded tag-object SHA immediately before deletion and allow deletion +only when writer/ruleset serialization prevents another admitted writer from +retargeting it before the delete. If the platform configuration cannot provide +that guarantee, the ref is not deleted; the version is quarantined. Exact draft +identity, unpublished state, asset set and post-delete absent-state checks are +still required. + +GitHub also states that once a tag has been associated with a published +immutable release, the tag name cannot be reused even after that release is +deleted. Possible publication therefore changes recovery to a new version +rather than cleanup/reuse. + +GitHub. (n.d.). *Immutable releases*. GitHub Docs. Retrieved September 4, 2026, from https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases GitHub. (n.d.). *Preventing changes to your releases*. GitHub Docs. Retrieved -September 3, 2026, from +September 4, 2026, from https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes -GitHub. (n.d.). *REST API endpoints for repositories: Check if immutable -releases are enabled for a repository*. GitHub Docs. Retrieved September 3, -2026, from https://docs.github.com/en/rest/repos/repos +GitHub. (n.d.). *REST API endpoints for repositories*. GitHub Docs. Retrieved +September 4, 2026, from https://docs.github.com/en/rest/repos/repos GitHub. (n.d.). *REST API endpoints for releases*. GitHub Docs. Retrieved -September 3, 2026, from https://docs.github.com/en/rest/releases/releases +September 4, 2026, from https://docs.github.com/en/rest/releases/releases ### GitHub annotated tag identity @@ -143,10 +154,10 @@ Missing refs/tag objects, non-commit targets, and mismatched target SHAs fail closed before publication and again during post-publication verification. GitHub. (n.d.). *REST API endpoints for Git references*. GitHub Docs. Retrieved -September 3, 2026, from https://docs.github.com/en/rest/git/refs +September 4, 2026, from https://docs.github.com/en/rest/git/refs GitHub. (n.d.). *REST API endpoints for Git tags*. GitHub Docs. Retrieved -September 3, 2026, from https://docs.github.com/en/rest/git/tags +September 4, 2026, from https://docs.github.com/en/rest/git/tags ### JSON strictness @@ -170,10 +181,13 @@ https://doi.org/10.17487/RFC8259 - acyclic inner identity plus immutable outer GitHub Actions artifact receipt → `ContextualWisdomLab/.github#1791` at `bd866a21cca2a7e709f0b7a88150c310a9d98239`; -- immutable tag/asset admission, publish-boundary revalidation, draft-first - publication, identity-safe pre-publication abort, and published-tag non-reuse - → GitHub immutable release and release REST documentation plus repository - immutability REST API; +- owner-enforced immutable-release admission, exact draft/asset verification, + protected tag namespace, trusted-writer serialization and post-publication + `immutable: true` verification → GitHub immutable release, repository and + Release REST documentation; +- conditional candidate-ref compare-and-delete and quarantine on missing + serialization → the CWE-367 recovery boundary derived from those GitHub API + semantics; no undocumented atomic REST primitive is assumed; - annotated-tag ref/object separation and exact source-commit peeling → GitHub Git references and Git tags REST documentation; - strict machine-readable evidence intake → RFC 8259 plus the stricter From 655ad58efa83f0e350f11cc7f141fdaf8e75baed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:32:45 +0900 Subject: [PATCH 43/45] test(release): order abort identity and cleanup contract --- tests/test_release_supply_chain_docs.py | 69 ++++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 228f209c1..73c41ea31 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -18,11 +18,22 @@ def _numbered_step(text: str, number: int, following: int) -> str: return text[start:end].lower() +def _between(text: str, start_marker: str, end_marker: str) -> str: + """Return one named procedure slice instead of accepting document-wide keywords.""" + start = text.lower().index(start_marker.lower()) + end = text.lower().index(end_marker.lower(), start) + return text[start:end].lower() + + def test_release_publication_requires_owner_enforced_github_release_immutability() -> None: """Keep the immutability admission predicate in one ordered preflight step.""" for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8") - preflight = _numbered_step(text, 10 if path == _RELEASE_GUIDE else 7, 11 if path == _RELEASE_GUIDE else 8) + preflight = _numbered_step( + text, + 10 if path == _RELEASE_GUIDE else 7, + 11 if path == _RELEASE_GUIDE else 8, + ) assert _IMMUTABILITY_ENDPOINT.lower() in preflight, path assert "enabled: true" in preflight, path assert "enforced_by_owner: true" in preflight, path @@ -33,7 +44,11 @@ def test_release_publication_rechecks_exact_draft_tag_assets_and_immutability_at """Bind the final publish decision to one exact draft, tag, asset set and immutable policy.""" for path in (_ADR, _RELEASE_GUIDE): text = path.read_text(encoding="utf-8") - publish_step = _numbered_step(text, 12 if path == _RELEASE_GUIDE else 8, 13 if path == _RELEASE_GUIDE else 9) + publish_step = _numbered_step( + text, + 12 if path == _RELEASE_GUIDE else 8, + 13 if path == _RELEASE_GUIDE else 9, + ) for required in ( "immediately before publish", "exact release id", @@ -54,17 +69,45 @@ def test_release_publication_rechecks_exact_draft_tag_assets_and_immutability_at assert "fail closed" in publish_step, path -def test_prepublication_abort_has_conditional_tag_cleanup_before_same_version_retry() -> None: - """Never delete a candidate ref after a stale ownership check.""" - for path in (_ADR, _RELEASE_GUIDE): - text = path.read_text(encoding="utf-8").lower() - assert "pre-publication abort" in text, path - assert "draft" in text and "unpublished" in text, path - assert "compare-and-delete" in text, path - assert "trusted release writer" in text, path - assert "re-resolve" in text and "absent" in text, path - assert "quarantine" in text and "version" in text, path - assert "never reuse" in text and "published immutable release" in text, path +def test_prepublication_abort_orders_identity_proof_before_conditional_tag_cleanup() -> None: + """Exact candidate ownership must be proved before compare-and-delete or quarantine.""" + adr_text = _ADR.read_text(encoding="utf-8") + release_text = _RELEASE_GUIDE.read_text(encoding="utf-8") + abort_procedures = ( + (_ADR, _numbered_step(adr_text, 9, 10)), + ( + _RELEASE_GUIDE, + _between( + release_text, + "A failure after step 11 but before publication is a **pre-publication abort**", + "Never reuse a tag name that has been associated with a published immutable release", + ), + ), + ) + + for path, procedure in abort_procedures: + proof_terms = ( + "exact release id", + "draft: true", + "prerelease: false", + "tag_name", + "asset", + "digest", + "recorded tag object" if path == _ADR else "recorded candidate tag object", + ) + proof_end = max(procedure.index(term) for term in proof_terms) + compare_delete = procedure.index("compare-and-delete") + quarantine = procedure.index("quarantine", compare_delete) + + assert proof_end < compare_delete < quarantine, path + cleanup_slice = procedure[compare_delete:quarantine] + assert "trusted release writer" in cleanup_slice, path + assert "protected" in cleanup_slice and "tag" in cleanup_slice, path + assert "recorded tag-object sha" in cleanup_slice, path + assert "serialization" in cleanup_slice, path + assert "do not delete" in procedure[compare_delete:], path + assert "re-resolve" in procedure, path + assert "absent" in procedure, path def test_release_contract_pins_the_repaired_canonical_attestation_owner() -> None: From 084649ecc8542fcb54c81d6f05b27845415e0ed6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:36:52 +0900 Subject: [PATCH 44/45] test(release): enforce cleanup control ordering --- tests/test_release_supply_chain_docs.py | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 73c41ea31..8684581af 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -74,7 +74,7 @@ def test_prepublication_abort_orders_identity_proof_before_conditional_tag_clean adr_text = _ADR.read_text(encoding="utf-8") release_text = _RELEASE_GUIDE.read_text(encoding="utf-8") abort_procedures = ( - (_ADR, _numbered_step(adr_text, 9, 10)), + (_ADR, _numbered_step(adr_text, 9, 10), "protected namespace"), ( _RELEASE_GUIDE, _between( @@ -82,10 +82,11 @@ def test_prepublication_abort_orders_identity_proof_before_conditional_tag_clean "A failure after step 11 but before publication is a **pre-publication abort**", "Never reuse a tag name that has been associated with a published immutable release", ), + "protected tag namespace", ), ) - for path, procedure in abort_procedures: + for path, procedure, protected_marker in abort_procedures: proof_terms = ( "exact release id", "draft: true", @@ -97,17 +98,24 @@ def test_prepublication_abort_orders_identity_proof_before_conditional_tag_clean ) proof_end = max(procedure.index(term) for term in proof_terms) compare_delete = procedure.index("compare-and-delete") - quarantine = procedure.index("quarantine", compare_delete) - - assert proof_end < compare_delete < quarantine, path - cleanup_slice = procedure[compare_delete:quarantine] - assert "trusted release writer" in cleanup_slice, path - assert "protected" in cleanup_slice and "tag" in cleanup_slice, path - assert "recorded tag-object sha" in cleanup_slice, path - assert "serialization" in cleanup_slice, path - assert "do not delete" in procedure[compare_delete:], path - assert "re-resolve" in procedure, path - assert "absent" in procedure, path + trusted_writer = procedure.index("trusted release writer", compare_delete) + protected_tag = procedure.index(protected_marker, trusted_writer) + recorded_tag_object = procedure.index("recorded tag-object sha", protected_tag) + serialization = procedure.index("serialization", recorded_tag_object) + quarantine = procedure.index("quarantine", serialization) + + assert ( + proof_end + < compare_delete + < trusted_writer + < protected_tag + < recorded_tag_object + < serialization + < quarantine + ), path + assert "do not delete" in procedure[serialization:quarantine], path + assert "re-resolve" in procedure[compare_delete:], path + assert "absent" in procedure[compare_delete:], path def test_release_contract_pins_the_repaired_canonical_attestation_owner() -> None: From 350152984fcb162c88188f1036da818362670ecd Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 4 Sep 2026 17:12:10 +0900 Subject: [PATCH 45/45] test(release): normalize wrapped contract steps --- tests/test_release_supply_chain_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_release_supply_chain_docs.py b/tests/test_release_supply_chain_docs.py index 8684581af..b85627d8e 100644 --- a/tests/test_release_supply_chain_docs.py +++ b/tests/test_release_supply_chain_docs.py @@ -15,7 +15,7 @@ def _numbered_step(text: str, number: int, following: int) -> str: """Return one numbered release/decision step so ordering assertions stay local.""" start = text.index(f"\n{number}. ") end = text.index(f"\n{following}. ", start) - return text[start:end].lower() + return " ".join(text[start:end].lower().split()) def _between(text: str, start_marker: str, end_marker: str) -> str: