From 6292cc43874f1b90b1be466f09261cc530124b5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:05:33 +0900 Subject: [PATCH 1/8] fix(ci): skip docs-only changes for ci, reviewer-ci, patch-validator-image Org-wide CI audit found these workflows lack paths-ignore, triggering full CI on every docs-only push/PR and contributing to org Actions queue backlog. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/patch-validator-image.yml | 3 +++ .github/workflows/reviewer-ci.yml | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d83efcc04..4e05addb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,15 @@ name: ci on: pull_request: + paths-ignore: + - "docs/**" + - "*.md" push: branches: - main + paths-ignore: + - "docs/**" + - "*.md" concurrency: group: noema-ci-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/patch-validator-image.yml b/.github/workflows/patch-validator-image.yml index 89ed4139b..bc270452e 100644 --- a/.github/workflows/patch-validator-image.yml +++ b/.github/workflows/patch-validator-image.yml @@ -2,6 +2,9 @@ name: patch-validator-image on: pull_request: + paths-ignore: + - "docs/**" + - "*.md" workflow_dispatch: concurrency: diff --git a/.github/workflows/reviewer-ci.yml b/.github/workflows/reviewer-ci.yml index f5212251a..13aa6b169 100644 --- a/.github/workflows/reviewer-ci.yml +++ b/.github/workflows/reviewer-ci.yml @@ -2,9 +2,15 @@ name: reviewer-ci on: pull_request: + paths-ignore: + - "docs/**" + - "*.md" push: branches: - main + paths-ignore: + - "docs/**" + - "*.md" concurrency: group: noema-reviewer-ci-${{ github.event.pull_request.number || github.ref }} From fa4bb2bfc714d88a189a0bc37840c488c604ce7b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:31:10 +0900 Subject: [PATCH 2/8] test(ci): reject docs-only verification suppression --- test/ci-exact-head-contract.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/ci-exact-head-contract.test.ts b/test/ci-exact-head-contract.test.ts index 7112b158d..82a00e724 100644 --- a/test/ci-exact-head-contract.test.ts +++ b/test/ci-exact-head-contract.test.ts @@ -6,8 +6,13 @@ const workflowPaths = [ ".github/workflows/reviewer-ci.yml", ] as const; +const requiredVerificationWorkflowPaths = [ + ...workflowPaths, + ".github/workflows/patch-validator-image.yml", +] as const; + /** Read one authoritative pull-request verification workflow as plain text. */ -function readWorkflow(path: (typeof workflowPaths)[number]): string { +function readWorkflow(path: string): string { return readFileSync(path, "utf8"); } @@ -107,4 +112,11 @@ describe("pull-request verification exact-head checkout contract", () => { "- name: install (hash-pinned dependencies)", ); }); + + it("does not suppress required exact-head evidence for documentation-only changes", () => { + for (const path of requiredVerificationWorkflowPaths) { + const workflow = readWorkflow(path); + expect(workflow).not.toContain("paths-ignore:"); + } + }); }); From def52aadc50cd3feb779982070173209410dc1b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:31:55 +0900 Subject: [PATCH 3/8] fix(ci): keep application verification mandatory for docs-only changes --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e05addb1..d83efcc04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,15 +2,9 @@ name: ci on: pull_request: - paths-ignore: - - "docs/**" - - "*.md" push: branches: - main - paths-ignore: - - "docs/**" - - "*.md" concurrency: group: noema-ci-${{ github.event.pull_request.number || github.ref }} From 6bc6e590c4f0ba4d951fb157ab1a6270085e967f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:32:20 +0900 Subject: [PATCH 4/8] fix(ci): keep reviewer verification mandatory for docs-only changes --- .github/workflows/reviewer-ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/reviewer-ci.yml b/.github/workflows/reviewer-ci.yml index 13aa6b169..f5212251a 100644 --- a/.github/workflows/reviewer-ci.yml +++ b/.github/workflows/reviewer-ci.yml @@ -2,15 +2,9 @@ name: reviewer-ci on: pull_request: - paths-ignore: - - "docs/**" - - "*.md" push: branches: - main - paths-ignore: - - "docs/**" - - "*.md" concurrency: group: noema-reviewer-ci-${{ github.event.pull_request.number || github.ref }} From 60b83339cd53c639c28d4f1881d1ef4501e38534 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:33:56 +0900 Subject: [PATCH 5/8] fix(ci): keep image verification mandatory for docs-only changes --- .github/workflows/patch-validator-image.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/patch-validator-image.yml b/.github/workflows/patch-validator-image.yml index bc270452e..89ed4139b 100644 --- a/.github/workflows/patch-validator-image.yml +++ b/.github/workflows/patch-validator-image.yml @@ -2,9 +2,6 @@ name: patch-validator-image on: pull_request: - paths-ignore: - - "docs/**" - - "*.md" workflow_dispatch: concurrency: From f69b3a88b539fd1dd350d95b4524a0cbe620edc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 08:55:38 +0000 Subject: [PATCH 6/8] docs: fix stale "active PR #80" references in automation-threat-model.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #80 (fix/atomic-product-publisher-lease) closed unmerged on 2026-08-15, but three references here still described its proposed T-A07/T-A08 controls as active/current and said doctoring should be integrated "after that PR lands" — it never will, since it's closed. Corrected the tense/status in place; the underlying threat/control content is otherwise unchanged, since no current successor implements these specific controls yet. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX --- docs/automation-threat-model.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/automation-threat-model.md b/docs/automation-threat-model.md index 653693ee4..74c841906 100644 --- a/docs/automation-threat-model.md +++ b/docs/automation-threat-model.md @@ -128,13 +128,13 @@ The security objective is to prevent a lower-trust domain from converting its ou **Threat:** publisher creates a PR but loses the response, then broad cleanup closes/deletes another actor's resource. -**Controls proposed by PR #80:** unique cryptographic publication marker, exact branch/head/base match, numeric PR identity, unique recovery only, conditional branch cleanup. +**Controls proposed by closed, unmerged PR #80** (`fix/atomic-product-publisher-lease`, closed 2026-08-15 without merging; not implemented on protected `main`): unique cryptographic publication marker, exact branch/head/base match, numeric PR identity, unique recovery only, conditional branch cleanup. If reimplemented, do so from a fresh branch on current protected `main` rather than reviving this stale lineage — see #80's own closing comment for the convergence hazards that made a direct retarget unsafe. ### T-A08 Proposal branch race **Threat:** another actor creates same remote branch between inventory read and push, or advances it before cleanup. -**Controls proposed by PR #80:** expected-absence branch creation lease and exact-created-head deletion lease; no check-then-unguarded-push or unconditional delete. +**Controls proposed by closed, unmerged PR #80** (same status as T-A07 above; not implemented on protected `main`): expected-absence branch creation lease and exact-created-head deletion lease; no check-then-unguarded-push or unconditional delete. ### T-A09 Queue race after generation @@ -233,4 +233,4 @@ These remain external evidence and must not be closed with documentation-only ch ## 9. Rationale and references -Primary-source rationale and APA 7 references for GitHub OIDC, SLSA source identity, NIST SSDF, Cloudflare capability/state semantics are maintained in `docs/doctoring/architecture-trust-boundaries.md`. Git conditional ref-update and publisher-specific rationale is maintained in the active PR #80 doctoring and should be integrated without duplicating mutable implementation claims after that PR lands. +Primary-source rationale and APA 7 references for GitHub OIDC, SLSA source identity, NIST SSDF, Cloudflare capability/state semantics are maintained in `docs/doctoring/architecture-trust-boundaries.md`. Git conditional ref-update and publisher-specific rationale is maintained in closed, unmerged PR #80's own doctoring (it never landed); if these controls are reimplemented from a fresh branch, integrate that rationale then, without duplicating mutable implementation claims here in the meantime. From 2814e5eef9deb622b69398c0b57ac6e2e1950d63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:21:25 +0900 Subject: [PATCH 7/8] test(docs): bind publisher threat controls to protected implementation --- ...ocumentation-architecture-contract.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/documentation-architecture-contract.test.ts b/test/documentation-architecture-contract.test.ts index 4a7222c3d..9baf45415 100644 --- a/test/documentation-architecture-contract.test.ts +++ b/test/documentation-architecture-contract.test.ts @@ -123,6 +123,25 @@ describe("authoritative Noema documentation graph", () => { expect(automationOwnership).not.toContain("stacked target branch does not trigger"); }); + it("keeps protected publisher race controls code-current in the threat model", () => { + const threatModel = document("docs/automation-threat-model.md"); + const publisher = readFileSync( + ".github/workflows/hourly-product-development.yml", + "utf8", + ); + + expect(publisher).toContain( + 'git push --force-with-lease="refs/heads/${branch}:" origin "HEAD:refs/heads/${branch}"', + ); + expect(publisher).toContain( + 'git push --force-with-lease="refs/heads/${branch}:${proposal_head}" origin ":refs/heads/${branch}"', + ); + expect(publisher).toContain("recover_created_pr_number"); + expect(publisher).toContain("publication_marker"); + expect(threatModel).toContain("**Controls implemented on protected `main`:**"); + expect(threatModel).not.toContain("not implemented on protected `main`"); + }); + it("keeps immutable workflow-source trust separate from revision-local canonical-byte hardening", () => { const architecture = document("ARCHITECTURE.md"); const traceability = document("docs/TRACEABILITY.md"); From 107a973ff4e8ea081e4db843f2de05b530c74f3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:22:41 +0900 Subject: [PATCH 8/8] docs: reflect protected atomic publisher controls --- docs/automation-threat-model.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/automation-threat-model.md b/docs/automation-threat-model.md index 74c841906..adc1b5d9b 100644 --- a/docs/automation-threat-model.md +++ b/docs/automation-threat-model.md @@ -128,13 +128,13 @@ The security objective is to prevent a lower-trust domain from converting its ou **Threat:** publisher creates a PR but loses the response, then broad cleanup closes/deletes another actor's resource. -**Controls proposed by closed, unmerged PR #80** (`fix/atomic-product-publisher-lease`, closed 2026-08-15 without merging; not implemented on protected `main`): unique cryptographic publication marker, exact branch/head/base match, numeric PR identity, unique recovery only, conditional branch cleanup. If reimplemented, do so from a fresh branch on current protected `main` rather than reviving this stale lineage — see #80's own closing comment for the convergence hazards that made a direct retarget unsafe. +**Controls implemented on protected `main`:** the non-executing publisher uses a cryptographic publication marker, requires exact proposal head and expected base identity, accepts only a positive numeric pull-request identity, and recovers a lost/malformed create response only when a fully paginated head-scoped search yields exactly one PR whose head, base, and marker all match the current publication. Cleanup re-runs that unique recovery before closing a PR and couples remote-branch cleanup to the exact proposal head. Closed, unmerged PR #80 is historical lineage only; it is not the current implementation owner or evidence authority. ### T-A08 Proposal branch race **Threat:** another actor creates same remote branch between inventory read and push, or advances it before cleanup. -**Controls proposed by closed, unmerged PR #80** (same status as T-A07 above; not implemented on protected `main`): expected-absence branch creation lease and exact-created-head deletion lease; no check-then-unguarded-push or unconditional delete. +**Controls implemented on protected `main`:** branch creation uses Git's explicit expected-absence lease (`--force-with-lease=:`), and remote cleanup uses an exact-created-head deletion lease (`--force-with-lease=:`). There is no check-then-unguarded push or unconditional branch deletion. Closed, unmerged PR #80 is retained only as historical provenance. ### T-A09 Queue race after generation @@ -233,4 +233,4 @@ These remain external evidence and must not be closed with documentation-only ch ## 9. Rationale and references -Primary-source rationale and APA 7 references for GitHub OIDC, SLSA source identity, NIST SSDF, Cloudflare capability/state semantics are maintained in `docs/doctoring/architecture-trust-boundaries.md`. Git conditional ref-update and publisher-specific rationale is maintained in closed, unmerged PR #80's own doctoring (it never landed); if these controls are reimplemented from a fresh branch, integrate that rationale then, without duplicating mutable implementation claims here in the meantime. +Primary-source rationale and APA 7 references for GitHub OIDC, SLSA source identity, NIST SSDF, Cloudflare capability/state semantics are maintained in `docs/doctoring/architecture-trust-boundaries.md`. Git conditional ref-update and publisher-specific rationale for the protected implementation are maintained in `docs/doctoring/atomic-product-publisher-lease.md`. Closed, unmerged PR #80 is historical development lineage only and does not define current control status or implementation authority.