diff --git a/.github/sync-manifest.yml b/.github/sync-manifest.yml index ee7e8674d..572974d1b 100644 --- a/.github/sync-manifest.yml +++ b/.github/sync-manifest.yml @@ -33,8 +33,8 @@ version: 1 workflows: # Core CI/CD - source: .github/workflows/pr-00-gate.yml - description: "Gate workflow - runs tests, lint, type checking before merge. Bootstrap note: the expanded template Gate is not yet fresh-consumer deployable; see issue #2158 before seeding it into new repos." - sync_mode: create_only # Don't overwrite existing consumer Gate files; fresh repos can still receive this file, so follow #2158 before first seeding. + description: "Gate workflow - runs tests, lint, type checking before merge. It remains create-only and not yet fresh-consumer deployable under the documented workaround (resolved: #2158) because consumers own their coverage and runtime policy." + sync_mode: create_only # Do not overwrite consumer-specific Gate coverage, runtime, or policy. overwrite_repos: - stranske/Template skip_repos: @@ -63,7 +63,7 @@ workflows: description: "Gate followups hub - consolidates keepalive and autofix followups" - source: .github/workflows/agents-keepalive-sweep.yml - description: "Keepalive sweep (#2267) - periodic level-based resync; dispatches the loop for open agent PRs so silent zero-commit stalls resurface" + description: "Keepalive sweep - periodic level-based resync; dispatches the loop for open agent PRs so silent zero-commit stalls resurface" # Agent system - execution - source: .github/workflows/agents-keepalive-loop-reporter.yml @@ -558,7 +558,7 @@ scripts: # Agent helper scripts and copy-delivered LangChain workflow support # - # delivery: channel annotation (issue #2347) + # delivery: channel annotation (resolved: issue #2347) # copy = physically copy-synced into the consumer tree by maint-68 and # read from the consumer's own working directory at runtime. # runtime = NOT copy-synced; the consuming consumer-template workflow does a @@ -658,7 +658,7 @@ scripts: description: "PR verifier - validates PR changes against acceptance criteria" delivery: copy - # NOTE (issue #2347): scripts/langchain/followup_issue_generator.py was MOVED + # NOTE (resolved: issue #2347): scripts/langchain/followup_issue_generator.py was MOVED # out of this copy-sync set into the `runtime_fetched:` section below. It is # delivered to consumers ONLY via runtime sparse-checkout (see that section). @@ -886,7 +886,7 @@ docs: - source: docs/SETUP_CHECKLIST.md target: docs/SETUP_CHECKLIST.md - description: "Consumer repo setup checklist. Includes the default_workflow_permissions=write step in section 3.3.1 that prevents a Gate startup_failure on fresh consumers (see #2157). Synced so it no longer drifts across the fleet." + description: "Consumer repo setup checklist. Includes the default_workflow_permissions=write step in section 3.3.1 that prevents a Gate startup_failure on fresh consumers (resolved: #2157). Synced so it no longer drifts across the fleet." # ---- Shared design system (fleet UX consistency) ---- # CSS tokens/components + a Streamlit adapter kit + the presentation-patterns @@ -933,7 +933,7 @@ git_config: description: "CodeRabbit auto-review config - skips maintenance-bot PRs (renovate/dependabot/github-actions/keepalive) and generated sync/draft PRs, while keeping substantial agent-authored PR reviews enabled. Advisory/non-gating per fleet rule." -# Workflows-owned runtime-fetched scripts (issue #2347) +# Workflows-owned runtime-fetched scripts (resolved: issue #2347) # # These files are intentionally NOT copy-synced into consumer repos. The # consumer-template workflows that use them perform a runtime sparse-checkout of diff --git a/.github/workflows/pr-00-gate.yml b/.github/workflows/pr-00-gate.yml index d36d1e9b4..c04342380 100644 --- a/.github/workflows/pr-00-gate.yml +++ b/.github/workflows/pr-00-gate.yml @@ -387,6 +387,12 @@ jobs: --head HEAD env: PR_BODY: ${{ github.event.pull_request.body || '' }} + - name: Verify open sync-manifest issue citations + if: ${{ hashFiles('tests/scripts/test_sync_manifest_docs.py') != '' }} + env: + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + run: python -m pytest tests/scripts/test_sync_manifest_docs.py -q - name: Apply runtime acceptance-criteria label if: ${{ steps.deliberate_break.outputs.has_marker == 'true' }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 diff --git a/config/template-drift-allowlist.txt b/config/template-drift-allowlist.txt index 296fa5c08..6f42d8d9c 100644 --- a/config/template-drift-allowlist.txt +++ b/config/template-drift-allowlist.txt @@ -45,137 +45,177 @@ main = .github/workflows/agents-63-issue-intake.yml template = templates/consumer-repo/.github/workflows/agents-issue-intake.yml main_sha256 = 200757cbb8d1801434ac3acb9e517a90ea37aa06e57ce925e8ac4480d829fc7f template_sha256 = 9176b7cffc68dba50fa7ff9c6a2386383c237433ac5a656053eccdd202628c6d -reason = Intentional divergence re-reviewed 2026-08-16: root and consumer intake surfaces both remove the operator draft toggle and always hand off ready-for-review automation PRs; root retains its richer failure summary while the consumer remains a pinned, minimal bridge contract. +divergence = Intentional divergence re-reviewed 2026-08-16: root and consumer intake surfaces both remove the operator draft toggle and always hand off ready-for-review automation PRs; root retains its richer failure summary while the consumer remains a pinned, minimal bridge contract. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.2] main = .github/workflows/agents-71-codex-belt-dispatcher.yml template = templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml main_sha256 = 0e9a4c7e7b120985fd9684a9e4f89049601b2bbc0b9da9bee5510d3ec1c3c175 template_sha256 = 6a7b6e203f6ac2837bc09dc2dc6620faa37b72cb78068af1bb77779e246cb1dd -reason = Intentional divergence re-baselined 2026-08-16: workflow_dispatch inputs (force_issue, agent_key) are now passed to the github-script step via step-level env: and read through process.env instead of being interpolated into the script body, removing a script-injection surface that caused GitHub to block the workflow as possibly malicious in consumer repos. Applied identically to both surfaces; consumer action pinning and Codex-specific wording preserved. +divergence = Intentional divergence re-baselined 2026-08-16: workflow_dispatch inputs (force_issue, agent_key) are now passed to the github-script step via step-level env: and read through process.env instead of being interpolated into the script body, removing a script-injection surface that caused GitHub to block the workflow as possibly malicious in consumer repos. Applied identically to both surfaces; consumer action pinning and Codex-specific wording preserved. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.3] main = .github/workflows/agents-72-codex-belt-worker.yml template = templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml main_sha256 = 905e1a6487c44f0705b4c83294fa5cc9e2693cfdf2927994f6dc28912516b758 template_sha256 = f8ce0688837030094e20af4aee01d25274dbffcb3eeec3fa5b34da16318f3993 -reason = Existing reviewed baseline drift re-baselined 2026-06-20: exported Orchestrator skill inputs were added to both root and consumer worker workflow_call surfaces while preserving consumer action pinning and guarded merge wording. +divergence = Intentional divergence re-reviewed 2026-06-20: exported Orchestrator skill inputs were added to both root and consumer worker workflow_call surfaces while preserving consumer action pinning and guarded merge wording. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.4] main = .github/workflows/agents-73-codex-belt-conveyor.yml template = templates/consumer-repo/.github/workflows/agents-73-codex-belt-conveyor.yml main_sha256 = c0f900cf6fd9d88b6f34538644b52e172fac31a599a2b1f95e650589c9f92012 template_sha256 = a1531951216890c2e7f766e53aaaa53ad6da1e2d94dd8a2db3889311672a13ec -reason = Intentional divergence re-reviewed 2026-08-10: root conveyor labels Gate and changed-file checks as authoritative; the consumer adds explicit best-effort rationale for only post-merge cleanup calls while preserving its action pins and Codex-specific behavior. +divergence = Intentional divergence re-reviewed 2026-08-10: root conveyor labels Gate and changed-file checks as authoritative; the consumer adds explicit best-effort rationale for only post-merge cleanup calls while preserving its action pins and Codex-specific behavior. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.5] main = .github/workflows/agents-auto-label.yml template = templates/consumer-repo/.github/workflows/agents-auto-label.yml main_sha256 = 84edb688a3f3629e206432bc63a891f7b7b6e8737d53044ddecce50f97f64231 template_sha256 = 7ca1550aa40b09a0c3f42d2ce3450551008aec62ed8dc94b6dc59a98bff16c78 -reason = Intentional divergence updated 2026-08-05: root and consumer workflows both isolate the eligibility sparse checkout under eligibility-source/ while retaining consumer SHA pins and auth plumbing. Do not align wholesale: that would strip the consumer security contract. +divergence = Intentional divergence updated 2026-08-05: root and consumer workflows both isolate the eligibility sparse checkout under eligibility-source/ while retaining consumer SHA pins and auth plumbing. Do not align wholesale: that would strip the consumer security contract. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.6] main = .github/workflows/agents-autofix-dispatcher.yml template = templates/consumer-repo/.github/workflows/agents-autofix-dispatcher.yml main_sha256 = d50546b43254e3374d53a8efb27b3f47efbddce20ae4511579a103bd6ca1e27c template_sha256 = 533b0f2241708e39e97de00bbae0795b7f9d95f32d9337e0ddcf51ed4fa4c76b -reason = Intentional divergence re-reviewed 2026-08-23: the Workflows-local dispatcher resolves and invokes the in-repository reusable autofix surface, while the consumer wrapper uses the published Workflows workflow with pinned actions and consumer secret wiring. Do not align wholesale because that would remove the consumer deployment contract. +divergence = Intentional divergence re-reviewed 2026-08-23: the Workflows-local dispatcher resolves and invokes the in-repository reusable autofix surface, while the consumer wrapper uses the published Workflows workflow with pinned actions and consumer secret wiring. Do not align wholesale because that would remove the consumer deployment contract. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.7] main = .github/workflows/agents-capability-check.yml template = templates/consumer-repo/.github/workflows/agents-capability-check.yml main_sha256 = 6a31f201710befd79283a59228524d279345f3b3351aa1e5bdf373da770590a3 template_sha256 = 92a2ab44ada9e1e5238f8117071df1b86f4c5b300a086b3c970bda623076b9cd -reason = Intentional divergence re-baselined 2026-08-22 after both surfaces migrated from the retired check_capability alias to classify_capabilities. The consumer template retains SHA-pinned actions and LangSmith tracing while the root workflow retains in-repo concurrency and sparse-checkout plumbing; do not align wholesale because that would strip the consumer security contract. +divergence = Intentional divergence re-baselined 2026-08-22 after both surfaces migrated from the retired check_capability alias to classify_capabilities. The consumer template retains SHA-pinned actions and LangSmith tracing while the root workflow retains in-repo concurrency and sparse-checkout plumbing; do not align wholesale because that would strip the consumer security contract. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.8] main = .github/workflows/agents-decompose.yml template = templates/consumer-repo/.github/workflows/agents-decompose.yml main_sha256 = 03bf9ec09953e0b1298783b1d14f030fefd40f00a61caf404cf0b562297a1e82 template_sha256 = 8964f9a4cdb6ffd724cba533befd09ba30336b4d9d2b3047351d045a39ed5949 -reason = Intentional divergence re-baselined 2026-08-09: root-only warning-only REST triage marker added; do not align wholesale because consumer pins and retry plumbing are contractually distinct. +divergence = Intentional divergence re-baselined 2026-08-09: root-only warning-only REST triage marker added; do not align wholesale because consumer pins and retry plumbing are contractually distinct. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.9] main = .github/workflows/agents-dedup.yml template = templates/consumer-repo/.github/workflows/agents-dedup.yml main_sha256 = 95d9d1c0acd6fbc143b1eb56e0c0741be370b2ab8853ed660ac1249821ea41dd template_sha256 = aa6c161b640eb303df8d47bc4d85535210f9cc7a49e4f165059432d6f0106bd2 -reason = Intentional divergence (re-baselined 2026-08-23): both surfaces now pass the eight named setup-api-client secret inputs instead of `secrets: ${{ toJSON(secrets) }}`, applied identically to root and consumer so the whole-secrets-context handoff is gone from both. Fingerprints refreshed for that change. The remaining divergence is unchanged and still intentional: consumer template SHA-pins third-party actions per the fleet action-pin contract (docs/HISTORY.md, PR #1925) and sets LangSmith tracing env; root uses floating major tags with repo-internal concurrency + sparse-checkout (docs/fixes/sparse-checkout-audit-2026-02-03.csv). Do not align: would strip consumer action pins. +divergence = Intentional divergence (re-baselined 2026-08-23): both surfaces now pass the eight named setup-api-client secret inputs instead of `secrets: ${{ toJSON(secrets) }}`, applied identically to root and consumer so the whole-secrets-context handoff is gone from both. Fingerprints refreshed for that change. The remaining divergence is unchanged and still intentional: consumer template SHA-pins third-party actions per the fleet action-pin contract (docs/HISTORY.md, PR #1925) and sets LangSmith tracing env; root uses floating major tags with repo-internal concurrency + sparse-checkout (docs/fixes/sparse-checkout-audit-2026-02-03.csv). Do not align: would strip consumer action pins. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.10] main = .github/workflows/agents-guard.yml template = templates/consumer-repo/.github/workflows/agents-guard.yml main_sha256 = eb5a10b5246ab1aa94afa481ed40e6a330a44be935f581827f6a49527b73e19f template_sha256 = a8bc6224681c3fbb3314a393135188bca1b1362439cfd509b81eceb1acc0e8cd -reason = Intentional divergence re-baselined 2026-06-30: root and consumer guard workflows differ for pinned consumer actions/App-token setup; stranske/Workflows digest pins were refreshed together in root and consumer guard surfaces after Renovate moved the Workflows digest to ebef44a. +divergence = Intentional divergence re-baselined 2026-06-30: root and consumer guard workflows differ for pinned consumer actions/App-token setup; stranske/Workflows digest pins were refreshed together in root and consumer guard surfaces after Renovate moved the Workflows digest to ebef44a. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.11] main = .github/workflows/agents-issue-optimizer.yml template = templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml main_sha256 = 6f27295c98bd7d94f1768b7036718c7331bbea281fd67faf4c110fe32da52696 template_sha256 = 1754c7ce1a227199af13bb746d80e506442d0e2572879f2fdfba284a88845238 -reason = Fingerprints refreshed 2026-08-23: both surfaces now read the issue number from the step-level EVENT_ISSUE_NUMBER env var instead of interpolating ${{ github.event.issue.number }} into the run: body, removing a template-injection surface of the same shape that caused agents-71-codex-belt-dispatcher to be blocked as possibly malicious in consumer repos (#3127, 2026-08-16). Applied identically to root and consumer, so it introduces no new divergence. Underlying divergence unchanged: Intentional divergence re-baselined 2026-08-11: root remains in-tree (scripts/langchain + .github/scripts/issue_format.py); consumer vendors those via Workflows sparse-checkout under workflows-scripts/. Shared behavioral contract includes live format eligibility checks, format-lease release when those checks skip work, checkout-aware path validation, explicit guard retry dispatch, and quoted lowercase identifier acceptance. Do not align wholesale — that would strip consumer action pins/token setup. +divergence = Fingerprints refreshed 2026-08-23: both surfaces now read the issue number from the step-level EVENT_ISSUE_NUMBER env var instead of interpolating ${{ github.event.issue.number }} into the run: body, removing a template-injection surface of the same shape that caused agents-71-codex-belt-dispatcher to be blocked as possibly malicious in consumer repos (#3127, 2026-08-16). Applied identically to root and consumer, so it introduces no new divergence. Underlying divergence unchanged: Intentional divergence re-baselined 2026-08-11: root remains in-tree (scripts/langchain + .github/scripts/issue_format.py); consumer vendors those via Workflows sparse-checkout under workflows-scripts/. Shared behavioral contract includes live format eligibility checks, format-lease release when those checks skip work, checkout-aware path validation, explicit guard retry dispatch, and quoted lowercase identifier acceptance. Do not align wholesale — that would strip consumer action pins/token setup. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.12] main = .github/workflows/agents-keepalive-loop-reporter.yml template = templates/consumer-repo/.github/workflows/agents-keepalive-loop-reporter.yml main_sha256 = 68ed7a2824a3536bf5e57699f0065646840335a5d36747758cb1ed2d1568288b template_sha256 = 93bfd23d1fecd7769aff11c80cfc78e7e50465d8f8172b6f4c63911b63addebc -reason = Intentional divergence re-reviewed 2026-08-15: both reporters mint the dedicated KEEPALIVE_APP token with WORKFLOWS_APP fallback, fail closed before trusted summary writes, and record the selected App writer. The root keeps its in-repo setup helpers while the consumer retains state fingerprinting and SHA-pinned actions; do not align wholesale. +divergence = Intentional divergence re-reviewed 2026-08-15: both reporters mint the dedicated KEEPALIVE_APP token with WORKFLOWS_APP fallback, fail closed before trusted summary writes, and record the selected App writer. The root keeps its in-repo setup helpers while the consumer retains state fingerprinting and SHA-pinned actions; do not align wholesale. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.13] main = .github/workflows/agents-keepalive-sweep.yml template = templates/consumer-repo/.github/workflows/agents-keepalive-sweep.yml main_sha256 = d298749854d5a646fae5197fd242ef656487b7a57d6f2e690cd52e838d2fb9e1 template_sha256 = 147d4da9048717e1cf86bcacfd51b4080cc3e228ff7851c3acf2d8fefc2a54ac -reason = Intentional divergence re-reviewed 2026-08-13: root and consumer sweeps share HMAC-signed due-authority claims bound to repository, PR, fingerprint, nonce, and exact sweep run; missing signing material fails closed to ordinary non-forced rechecks. Both bypass state debounce for ordinary sweep recovery, retain completed-runner debounce for ordinary wakeups, and bypass runner debounce only for a verified signed due claim, while the consumer retains its consolidated gate-followup dispatch path, inverted mode guard, and pinned action contract. +divergence = Intentional divergence re-reviewed 2026-08-13: root and consumer sweeps share HMAC-signed due-authority claims bound to repository, PR, fingerprint, nonce, and exact sweep run; missing signing material fails closed to ordinary non-forced rechecks. Both bypass state debounce for ordinary sweep recovery, retain completed-runner debounce for ordinary wakeups, and bypass runner debounce only for a verified signed due claim, while the consumer retains its consolidated gate-followup dispatch path, inverted mode guard, and pinned action contract. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.14] main = .github/workflows/agents-verifier.yml template = templates/consumer-repo/.github/workflows/agents-verifier.yml main_sha256 = 5e1cb04f45d27ccd30395eb804203677db130c7f1bcabf8054d80cb22c189e97 template_sha256 = 9c803d40ce8b26f4450a60d6f821eb2cadbd59c42b47259d3394ad1d932fee0f -reason = Intentional divergence re-reviewed 2026-08-23: the Workflows verifier retains its local corpus and provider plumbing, while the consumer verifier calls the distributed surface with consumer-specific action pins and token setup. Do not align wholesale because those execution environments are intentionally different. +divergence = Intentional divergence re-reviewed 2026-08-23: the Workflows verifier retains its local corpus and provider plumbing, while the consumer verifier calls the distributed surface with consumer-specific action pins and token setup. Do not align wholesale because those execution environments are intentionally different. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.15] main = .github/workflows/agents-weekly-metrics.yml template = templates/consumer-repo/.github/workflows/agents-weekly-metrics.yml main_sha256 = 6e48b36caaa2865c66fe978b75e775687b96b69eae74c5adbc1355346819f271 template_sha256 = 865fe609fbc6fde8f0f9faf338d8ba9e9e573448e8dc454fb759df003bd9b437 -reason = Intentional divergence (re-baselined 2026-07-14): consumer template SHA-pins third-party actions per the fleet action-pin contract (docs/HISTORY.md, PR #1925) and sets LangSmith tracing env; root uses floating major tags with repo-internal concurrency + sparse-checkout (docs/fixes/sparse-checkout-audit-2026-02-03.csv). Fingerprints refreshed after the durable-label guard was added to the template. Do not align: would strip consumer action pins. +divergence = Intentional divergence (re-baselined 2026-07-14): consumer template SHA-pins third-party actions per the fleet action-pin contract (docs/HISTORY.md, PR #1925) and sets LangSmith tracing env; root uses floating major tags with repo-internal concurrency + sparse-checkout (docs/fixes/sparse-checkout-audit-2026-02-03.csv). Fingerprints refreshed after the durable-label guard was added to the template. Do not align: would strip consumer action pins. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.16] main = .github/workflows/agents-auto-pilot.yml template = templates/consumer-repo/.github/workflows/agents-auto-pilot.yml main_sha256 = 68e132fb70a176cbfab05714eb828f3debcde9326d5fcf1cf5a667d0a2675e0d template_sha256 = 843461352cf105f1e6bc0f50c60403f08fe4100fc529b1b301579306343d80a9 -reason = Fingerprints refreshed 2026-08-23: both surfaces now pass the named setup-api-client secret inputs instead of the whole-secrets-context blob, applied identically to root and consumer so it introduces no new divergence. That removal is what cleared GitHub's suspicious-workflow hold on agents-dedup (#3185), which then ran at run_attempt 1 with nothing approved after 22 days held. Underlying divergence unchanged: Intentional divergence reviewed 2026-08-21: the Workflows-local auto-pilot retains Workflows-only PR-meta and keepalive fallbacks, while the consumer template dispatches only the consolidated Agents 80 and Agents 81 entry points. The sync manifest delivers the consumer-specific template so retired Workflows-local targets cannot be restored in consumer repositories. +divergence = Fingerprints refreshed 2026-08-23: both surfaces now pass the named setup-api-client secret inputs instead of the whole-secrets-context blob, applied identically to root and consumer so it introduces no new divergence. That removal is what cleared GitHub's suspicious-workflow hold on agents-dedup (#3185), which then ran at run_attempt 1 with nothing approved after 22 days held. Underlying divergence unchanged: Intentional divergence reviewed 2026-08-21: the Workflows-local auto-pilot retains Workflows-only PR-meta and keepalive fallbacks, while the consumer template dispatches only the consolidated Agents 80 and Agents 81 entry points. The sync manifest delivers the consumer-specific template so retired Workflows-local targets cannot be restored in consumer repositories. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.17] main = .github/workflows/autofix.yml template = templates/consumer-repo/.github/workflows/autofix.yml main_sha256 = 81f2268c29cc8b5da388a44eab46a8613df153d9e4e83449ab3a5070c03558a7 template_sha256 = 88cf48708d972137ef8e9fb3efc2323ee93c8cab16eaba0fc22c8c11ea6695c4 -reason = Intentional divergence reviewed 2026-08-23: the Workflows source runs its local reusable autofix workflow and uses the repository's CI concurrency/context contract; the consumer template calls the published reusable workflow with SHA-pinned actions, SERVICE_BOT_PAT, and workflow-run PR context recovery. Do not align wholesale because the consumer is a deployable wrapper rather than the source implementation. +divergence = Intentional divergence reviewed 2026-08-23: the Workflows source runs its local reusable autofix workflow and uses the repository's CI concurrency/context contract; the consumer template calls the published reusable workflow with SHA-pinned actions, SERVICE_BOT_PAT, and workflow-run PR context recovery. Do not align wholesale because the consumer is a deployable wrapper rather than the source implementation. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.18] main = .github/workflows/maint-coverage-guard.yml template = templates/consumer-repo/.github/workflows/maint-coverage-guard.yml main_sha256 = 3b08ecd7c98a8c0f9281136be6013f3602faecf07adc90ee38687f990fc7ffa6 template_sha256 = e7badb31c3a806d89cbf4a3d87368e0f98785683f901cf0130f64e1c000fdb1a -reason = Intentional divergence reviewed 2026-08-23: Workflows uses its local artifact-cache action and helper sparse checkout, while consumers mint an App token and download coverage artifacts through pinned public actions. The artifact retrieval routes are deliberately different because consumers cannot rely on Workflows-local actions. +divergence = Intentional divergence reviewed 2026-08-23: Workflows uses its local artifact-cache action and helper sparse checkout, while consumers mint an App token and download coverage artifacts through pinned public actions. The artifact retrieval routes are deliberately different because consumers cannot rely on Workflows-local actions. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.19] main = .github/workflows/pr-00-gate.yml template = templates/consumer-repo/.github/workflows/pr-00-gate.yml -main_sha256 = b10972cc49cce6879f6ccd70af19343ab40330572c5c0003a7140350c87c9a4e +main_sha256 = 03e408f560326e9692ede373803f753224c0f4ac68bd9661a85923017b5c906c template_sha256 = ae8266ef5429f5ef5f0982292dd8fd3572358352e285976fe006011d5926f2c5 -reason = Intentional divergence reviewed 2026-08-23: the source Gate runs Workflows-only package, ledger, and diff-quality jobs with local reusable workflows; the consumer Gate uses published reusable workflows, pinned actions, and skips unavailable Workflows-local deliberate-break helpers. The consumer template must remain a bootstrap-safe deployment surface. +divergence = Intentional divergence reviewed 2026-08-23: the source Gate runs Workflows-only package, ledger, diff-quality, and live sync-manifest issue-state checks with GH_TOKEN and GITHUB_TOKEN exported for the pytest guard; the consumer Gate uses published reusable workflows, pinned actions, and skips unavailable Workflows-local deliberate-break helpers. The consumer template must remain a bootstrap-safe deployment surface. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 [pair.20] main = .github/workflows/reusable-pr-context.yml template = templates/consumer-repo/.github/workflows/reusable-pr-context.yml main_sha256 = 34f19d0bc75d0b3dbf4d897d38a45ccc20a453a3467c930cb749b6881cfe724f template_sha256 = d4ac61af664ce7330a019ce09b2db451e234ebc9b720827320f1c2db32e35eb5 -reason = Intentional divergence reviewed 2026-08-23: Workflows keeps local setup-api-client, cancellation, and full multi-agent label detection; the consumer uses pinned actions, App-token setup, and only the consumer-supported routing labels. These execution contexts are intentionally not byte-identical. +divergence = Intentional divergence reviewed 2026-08-23: Workflows keeps local setup-api-client, cancellation, and full multi-agent label detection; the consumer uses pinned actions, App-token setup, and only the consumer-supported routing labels. These execution contexts are intentionally not byte-identical. +divergence_reviewed = 2026-08-23 +fingerprint_refreshed = 2026-08-23 diff --git a/scripts/check_template_drift.py b/scripts/check_template_drift.py index bb0bdb180..fb96c72d5 100644 --- a/scripts/check_template_drift.py +++ b/scripts/check_template_drift.py @@ -178,7 +178,11 @@ def read_allowlist(path: Path) -> TemplateDriftAllowlist: template_path=parser.get(section, "template"), main_sha256=parser.get(section, "main_sha256"), template_sha256=parser.get(section, "template_sha256"), - reason=parser.get(section, "reason", fallback=""), + reason=parser.get( + section, + "divergence", + fallback=parser.get(section, "reason", fallback=""), + ), ) ) return TemplateDriftAllowlist(tuple(entries)) diff --git a/scripts/cleanup_labels.py b/scripts/cleanup_labels.py index 84777c791..20fc41ec0 100755 --- a/scripts/cleanup_labels.py +++ b/scripts/cleanup_labels.py @@ -20,8 +20,15 @@ import os import sys from collections.abc import Iterable +from pathlib import Path from typing import NamedTuple +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from scripts.list_registered_consumer_repos import extract_repos # noqa: E402 + # Try to import github, fall back to instructions try: from github import Github @@ -225,15 +232,9 @@ def normalize_label_name(label_name: str) -> str: NORMALIZED_BLOAT_LABELS = {normalize_label_name(label) for label in BLOAT_LABELS} # Consumer repos to audit -CONSUMER_REPOS = [ - "stranske/Manager-Database", - "stranske/Template", - "stranske/trip-planner", - "stranske/Travel-Plan-Permission", - "stranske/Portable-Alpha-Extension-Model", - "stranske/Trend_Model_Project", - "stranske/Collab-Admin", -] +CONSUMER_REPOS = extract_repos( + Path(__file__).resolve().parents[1] / ".github/workflows/maint-68-sync-consumer-repos.yml" +) def get_github_client() -> Github: diff --git a/scripts/langsmith_fleet.py b/scripts/langsmith_fleet.py index ee54671ae..9ea07803f 100644 --- a/scripts/langsmith_fleet.py +++ b/scripts/langsmith_fleet.py @@ -14,6 +14,12 @@ from jsonschema import Draft202012Validator +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from scripts.list_registered_consumer_repos import extract_repos # noqa: E402 + SCHEMA_VERSION = "langsmith-fleet/v1" SCHEMA_PATH = Path("docs/contracts/schemas/langsmith-fleet-v1.schema.json") REGISTRY_SCHEMA_VERSION = "langsmith-fleet-registry/v1" @@ -29,22 +35,11 @@ "resume_condition", "review_by", ) -MANAGED_CONSUMER_REPOS = { - "stranske/Travel-Plan-Permission", - "stranske/Template", - "stranske/Counter_Risk", - "stranske/Pension-Data", - "stranske/Inv-Man-Intake", - "stranske/Ready", - "stranske/trip-planner", - "stranske/Manager-Database", - "stranske/Portable-Alpha-Extension-Model", - "stranske/Trend_Model_Project", - "stranske/Collab-Admin", - "stranske/learning-management-system", - "stranske/Fine-Art-Archive", - "stranske/Orchestrator", -} +MANAGED_CONSUMER_REPOS = set( + extract_repos( + Path(__file__).resolve().parents[1] / ".github/workflows/maint-68-sync-consumer-repos.yml" + ) +) REQUIRED_ACTIVE_REPO_ISSUES = { "stranske/trip-planner": 1208, "stranske/Pension-Data": 445, diff --git a/scripts/validate_template_completeness.py b/scripts/validate_template_completeness.py index 8407d7dbd..b0b1c53b5 100755 --- a/scripts/validate_template_completeness.py +++ b/scripts/validate_template_completeness.py @@ -26,12 +26,10 @@ # Maintenance workflows specific to Workflows repo "maint-52-sync-dev-versions.yml", "maint-68-sync-consumer-repos.yml", - "maint-post-ci.yml", # Health checks specific to Workflows repo "health-68-consumer-sync-drift.yml", "health-70-validate-sync-manifest.yml", "health-71-sync-health-check.yml", - "health-72-template-lint.yml", "health-75-api-rate-diagnostic.yml", "health-76-codex-cli-freshness.yml", # Debug/testing workflows @@ -41,12 +39,11 @@ "agents-keepalive-branch-sync.yml", "agents-keepalive-dispatch-handler.yml", # Workflows repo specific features - "agents-weekly-metrics.yml", "agents-moderate-connector.yml", # Older versions superseded in consumer repos "agents-63-issue-intake.yml", # consumers have agents-issue-intake.yml "agents-64-verify-agent-assignment.yml", # verification is different - "agents-70-orchestrator.yml", # consumers have agents-orchestrator.yml + "agents-70-orchestrator.yml", # retired from consumers; agents-80/81 replace it "agents-pr-meta-v4.yml", # consumers have agents-80-pr-event-hub.yml # First-party workflows retained in Workflows; consumer templates use # agents-80-pr-event-hub.yml and agents-81-gate-followups.yml instead. @@ -59,7 +56,6 @@ "reusable-codex-run.yml", "reusable-10-ci-python.yml", "reusable-18-autofix.yml", - "reusable-pr-context.yml", } diff --git a/tests/scripts/test_fleet_list_single_source.py b/tests/scripts/test_fleet_list_single_source.py new file mode 100644 index 000000000..5eec620a5 --- /dev/null +++ b/tests/scripts/test_fleet_list_single_source.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import ast +from pathlib import Path + +from scripts import cleanup_labels, langsmith_fleet +from scripts.list_registered_consumer_repos import extract_repos + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / ".github/workflows/maint-68-sync-consumer-repos.yml" + + +def _assignment_uses_extract_repos(path: Path, target_name: str) -> bool: + tree = ast.parse(path.read_text(encoding="utf-8")) + for node in tree.body: + value: ast.expr | None = None + if ( + isinstance(node, ast.Assign) + and any( + isinstance(target, ast.Name) and target.id == target_name for target in node.targets + ) + ) or ( + isinstance(node, ast.AnnAssign) + and isinstance(node.target, ast.Name) + and node.target.id == target_name + ): + value = node.value + if value is not None and any( + isinstance(child, ast.Call) + and isinstance(child.func, ast.Name) + and child.func.id == "extract_repos" + for child in ast.walk(value) + ): + return True + return False + + +def test_no_second_consumer_repo_literal() -> None: + registered = extract_repos(MANIFEST) + + assert MANIFEST.name == "maint-68-sync-consumer-repos.yml" + assert len(registered) >= 10 + assert registered == cleanup_labels.CONSUMER_REPOS + assert set(registered) == langsmith_fleet.MANAGED_CONSUMER_REPOS + assert _assignment_uses_extract_repos(ROOT / "scripts/cleanup_labels.py", "CONSUMER_REPOS") + assert _assignment_uses_extract_repos( + ROOT / "scripts/langsmith_fleet.py", "MANAGED_CONSUMER_REPOS" + ) diff --git a/tests/scripts/test_sync_manifest_docs.py b/tests/scripts/test_sync_manifest_docs.py new file mode 100644 index 000000000..1f80f88f2 --- /dev/null +++ b/tests/scripts/test_sync_manifest_docs.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +import os +import re +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / ".github" / "sync-manifest.yml" +ISSUE_REFERENCE = re.compile(r"#(?P\d+)") +ISSUE_STATE_PAIR = re.compile( + r"\b(?Popen|resolved):\s*(?:issue\s*)?#(?P\d+)", + re.IGNORECASE, +) + + +def _issue_state_pairs(line: str) -> list[tuple[str, str]]: + return [ + (match.group("state").lower(), match.group("number")) + for match in ISSUE_STATE_PAIR.finditer(line) + ] + + +def _unpaired_issue_references(line: str) -> list[str]: + residue = ISSUE_STATE_PAIR.sub("", line) + return [match.group(0) for match in ISSUE_REFERENCE.finditer(residue)] + + +def test_manifest_issue_citations_are_explicitly_stateful() -> None: + """Manifest citations name whether the referenced issue is open or resolved.""" + offenders = [ + line.strip() + for line in MANIFEST.read_text(encoding="utf-8").splitlines() + if _unpaired_issue_references(line) + ] + assert offenders == [] + + +def test_issue_state_parser_associates_each_citation() -> None: + line = "open: #2158; resolved: issue #2157" + + assert _issue_state_pairs(line) == [("open", "2158"), ("resolved", "2157")] + assert _unpaired_issue_references(line) == [] + assert _unpaired_issue_references("open: #2158; also #2157") == ["#2157"] + + +def test_manifest_issue_references_are_open() -> None: + """Live guard for manifest citations explicitly marked as open.""" + if not (os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")): + pytest.skip("GH_TOKEN or GITHUB_TOKEN is required to verify manifest issue references") + + for line in MANIFEST.read_text(encoding="utf-8").splitlines(): + for state, issue_number in _issue_state_pairs(line): + if state != "open": + continue + try: + result = subprocess.run( + [ + "gh", + "issue", + "view", + issue_number, + "--repo", + "stranske/Workflows", + "--json", + "state", + "--jq", + ".state", + ], + check=True, + capture_output=True, + text=True, + timeout=15, + ) + except subprocess.TimeoutExpired: + pytest.fail( + f"timed out checking manifest issue #{issue_number}: {line.strip()}", + pytrace=False, + ) + assert ( + result.stdout.strip() == "OPEN" + ), f"manifest references closed issue #{issue_number}: {line.strip()}" diff --git a/tests/scripts/test_template_drift_allowlist.py b/tests/scripts/test_template_drift_allowlist.py new file mode 100644 index 000000000..7f9b6ede3 --- /dev/null +++ b/tests/scripts/test_template_drift_allowlist.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import configparser +from pathlib import Path + +from scripts.check_template_drift import read_allowlist + +ROOT = Path(__file__).resolve().parents[2] +ALLOWLIST = ROOT / "config/template-drift-allowlist.txt" + + +def test_every_pair_states_its_divergence() -> None: + parser = configparser.ConfigParser(interpolation=None) + parser.read(ALLOWLIST, encoding="utf-8") + + assert ALLOWLIST.name == "template-drift-allowlist.txt" + assert len(parser.sections()) >= 1 + + for section in parser.sections(): + divergence = parser.get(section, "divergence", fallback="").strip() + reviewed = parser.get(section, "divergence_reviewed", fallback="").strip() + refreshed = parser.get(section, "fingerprint_refreshed", fallback="").strip() + assert divergence and "Existing reviewed baseline drift" not in divergence + assert reviewed + assert refreshed + + +def test_read_allowlist_prefers_divergence_and_supports_legacy_reason(tmp_path: Path) -> None: + allowlist_path = tmp_path / "allowlist.txt" + allowlist_path.write_text( + """ +[pair.current] +main = root.yml +template = template.yml +main_sha256 = main-current +template_sha256 = template-current +divergence = current rationale +reason = superseded legacy rationale + +[pair.legacy] +main = legacy-root.yml +template = legacy-template.yml +main_sha256 = main-legacy +template_sha256 = template-legacy +reason = legacy rationale +""".strip() + "\n", + encoding="utf-8", + ) + + entries = read_allowlist(allowlist_path).entries + + assert [entry.reason for entry in entries] == ["current rationale", "legacy rationale"] diff --git a/tests/scripts/test_validate_template_completeness.py b/tests/scripts/test_validate_template_completeness.py index 37d8af5bb..3a311b72d 100644 --- a/tests/scripts/test_validate_template_completeness.py +++ b/tests/scripts/test_validate_template_completeness.py @@ -24,6 +24,17 @@ def test_get_workflows_returns_top_level_yml_files_only(tmp_path: Path) -> None: assert validator.get_workflows(tmp_path / "missing") == set() +def test_workflows_only_is_live_and_consistent() -> None: + root = Path(__file__).resolve().parents[2] + root_workflows = validator.get_workflows(root / ".github/workflows") + template_workflows = validator.get_workflows(root / "templates/consumer-repo/.github/workflows") + manifest_workflows = validator.get_manifest_workflows(root / ".github/sync-manifest.yml") + + assert root_workflows >= validator.WORKFLOWS_ONLY + assert not validator.WORKFLOWS_ONLY & template_workflows + assert not validator.WORKFLOWS_ONLY & manifest_workflows + + def test_get_manifest_workflows_extracts_consumer_workflow_targets(tmp_path: Path) -> None: manifest_path = tmp_path / "sync-manifest.yml" manifest_path.write_text(