Skip to content

Stop committing the generated explorer; keep the evidence manifest sorted - #96

Merged
seathatflowsinourveins merged 4 commits into
mainfrom
claude/generated-explorer-sorted-manifest
Sep 23, 2026
Merged

seathatflowsinourveins merged 4 commits into
mainfrom
claude/generated-explorer-sorted-manifest

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Owner

This removes the two generated-file conflicts that forced repeated re-merges of catalog main: #91 re-merged three times in an hour. The coordinating session (agent-lab-e9) approved items 1 and 2 on evidence and rejected item 3. The decision record is docs/decisions/2026-09-23-generated-explorer-sorted-manifest.md.

1. The generated explorer is no longer committed

  • docs/ecosystem/index.html (12 MB, one minified line) is removed from git and manifests/evidence.json, and added to .gitignore. Every doc change used to rewrite it, and it made history scans report about 2,322 SHA-like strings, which CI avoided only through the --max-target-megabytes 2 coverage hole.
  • build_ecosystem.py --write still generates it locally. --check now builds twice: once in-process, once in a separate interpreter with a different PYTHONHASHSEED. It requires identical bytes and reports the input and output sha256. All the builder's input validation is kept.
  • publish-catalog.yml builds the explorer, scans it for private content, attests it with the existing SHA-pinned attest step and uploads it as a workflow artifact. It runs on workflow_dispatch and on v* tags, and the docs state the 7-day retention. Permissions and pins are unchanged, with no new secrets or triggers.
  • Every reader-facing reference is updated to "generate with build_ecosystem.py --write or download the workflow artifact". Dated historical records are left as point-in-time evidence.
  • GitHub Pages is not enabled (GET /pages is 404), so no live site depends on the file.

2. manifests/evidence.json files[] stays sorted

  • scripts/evidence_manifest.py --write|--check sorts files[] by path and preserves every entry.
  • scripts/validate.py rejects an unsorted or duplicate files[] and names the fix command.
  • host_receipts.py registration inserts in sorted order (bisect), so record and review keep the order. Tests cover this.

Not adopted

  • GitHub merge queue: GitHub offers it only for organization-owned repositories, and gh api users/seathatflowsinourveins returns type User. Overturn condition: the repository moves to an organization.
  • Also considered: sharded manifests, and committing the explorer only on release tags. Both are recorded with the reasons.

Review

The Opus evidence review found 4 majors, all fixed:

  • a stale base;
  • no privacy scan of the published explorer;
  • a stale explorer README;
  • "release artifact" wording where the artifact expires after 7 days.

The coordinator then added the cross-process determinism check.

Verification

  • python3 -m unittest: 2,494 OK (329 environment skips).
  • These all pass: validate.py, evidence_manifest.py --check, build_ecosystem.py --check (three identical digests, including under PYTHONHASHSEED=7), host_receipts.py validate, component_matrix.py --check, landscape.py, build_verdicts.py --check and gap_crosswalk.py build --check.
  • zizmor, offline, on publish-catalog.yml finds nothing new.
  • A guarded gitleaks scan of the branch commits finds no leaks.

Limits

  • The new publish steps have not run on a hosted runner yet; the first tag or dispatch will show it.
  • Old history still contains the 12 MB file, so a full-history gitleaks git scan keeps its recorded size-skip for those commits.

🤖 Generated with Claude Code

… sorted

docs/ecosystem/index.html (12+ MB, rewritten on every doc change) caused
repeated main-branch merge conflicts and made every PR's gitleaks history
scan report ~2,322 pre-existing SHA-like strings, avoided only via a
--max-target-megabytes 2 coverage hole. GitHub Pages is not enabled, so no
live site depended on the committed file. It is now generated locally with
`python3 scripts/build_ecosystem.py --write`, gitignored, and published as
its own attested release artifact by publish-catalog.yml.
scripts/build_ecosystem.py --check no longer compares against a committed
file; it builds twice into temporary directories, asserts byte-identical
output, and reports input/output digests. Every reader-facing reference to
the file was updated to say how to get it now; dated historical records that
quote its past committed state were left untouched.

manifests/evidence.json's files[] now stays sorted by path for
conflict-friendly parallel inserts: a new scripts/evidence_manifest.py
--write|--check normalizer/checker, a matching scripts/validate.py rule, and
scripts/host_receipts.py's register_file() now inserting at the sorted
bisect position instead of always appending.

See docs/decisions/2026-09-23-generated-explorer-sorted-manifest.md for the
full evidence, alternatives (sharded manifests; release-tag-only commits;
GitHub merge queue -- rejected, this repository is a personal-account User,
not an organization) and overturn conditions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and artifact-retention wording

Fix round on the generated-explorer-sorted-manifest branch, resolving four
findings from an independent review:

1. The branch was one commit behind origin/main (base a10de9f; main is now
   168a3a8, #91), and #91 touched both files this branch rewrites
   (docs/ecosystem/index.html, manifests/evidence.json). Rebased onto
   168a3a8: the index.html modify/delete conflict resolved to the deletion,
   and evidence.json resolved to #91's current entries (including its 5 new
   leverage entries) minus the removed index.html entry, then rehashed with
   tools/rehash_evidence.py and re-sorted with
   scripts/evidence_manifest.py --write. The decision record's base-commit
   note now reflects 168a3a8.

2. The published explorer never went through the private-content scan:
   validate.py's scan_publication() only walks git-tracked/listed paths, so
   a freshly built, gitignored artifact was invisible to it. Added
   scripts/validate.py --scan-file PATH, a standalone mode running the same
   PRIVATE_CONTENT patterns directly against a file's bytes, and wired
   publish-catalog.yml to run it on the built explorer immediately before
   attesting it.

3. docs/ecosystem/README.md still described the old committed-file model
   (dead index.html link, "download the file from GitHub", stale --check
   semantics). Rewrote it to match the generate-locally-or-download-the-
   artifact model. tests/test_adoption_contract.py asserted every
   adoption/manifest.json sources[] entry is a committed file, which would
   now fail on a clean checkout for the generated ecosystem_explorer entry;
   it now explicitly skips that one key (pinning its expected value so the
   exclusion cannot hide a different broken reference).

4. Every reader-facing reference calling the workflow artifact a "release
   artifact" overstated its persistence: it is a 7-day-retention artifact
   from workflow_dispatch/v*-tag runs only, with no GitHub Release. Reworded
   every occurrence to name the actual retention and trigger scope, and
   added an overturn condition for when that limitation stops being
   acceptable.

Regression tests: tests/test_validate.py (scan_file_for_private_content unit
tests and --scan-file CLI tests), tests/test_adoption_contract.py (pins the
ecosystem_explorer exclusion to its exact expected path).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ferent hash seed; refresh the record's digests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e in the input digest

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins merged commit 796f759 into main Sep 23, 2026
8 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/generated-explorer-sorted-manifest branch September 23, 2026 03:41
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
…stall-20260923

origin/main advanced to af2c299 (through #96, which stopped committing the
generated explorer and started keeping manifests/evidence.json sorted, and
#99, an unrelated MLX smoke lock update) while round 2's fixes were in
progress. Per the coordinator's merge instructions:

- .gitignore: kept both sides' additions (macos-example.json's own
  exception, and main's new /docs/ecosystem/index.html ignore rule).
- docs/ecosystem/index.html: modify/delete conflict (main stopped tracking
  it; this branch's earlier round had rebuilt it) -- `git rm` per
  instructions; it is a build artifact now, not committed.
- manifests/evidence.json: took origin/main's version, then re-registered
  every file this branch's own commits changed (17 files across both fix
  rounds) with scripts/host_receipts.py's register_file, then normalized
  with `python3 scripts/evidence_manifest.py --write`.

python3 scripts/validate.py and python3 scripts/build_ecosystem.py --check
both pass after this merge; the full python3 -m unittest (2546 tests) is
OK.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins pushed a commit that referenced this pull request Sep 23, 2026
Merging origin/main (796f759, #96: docs/ecosystem/index.html is no
longer committed or tracked) left RebuildExplorerSubprocessTests'
setUpClass asserting the file WAS tracked in its scratch copy, which no
longer holds once the copied .gitignore excludes it. Split the class:
RebuildExplorerSubprocessTests now builds its scratch copy the plain way
(matching main's real, untracked-by-default state) and asserts
rehashed_explorer is False; a new TrackedExplorerSubprocessTests
force-adds a locally built index.html to keep exercising
rebuild_explorer()'s --write/register/--check loop for forward
compatibility, explicitly labeled as exercising a path that is currently
dead code on main.

This edit was made after the prior merge commit but not staged into it;
committing it separately here rather than amending, since the merge
commit already has other reviewers' context.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins pushed a commit that referenced this pull request Sep 23, 2026
docs/github-automation.md's propose-job walkthrough now says plainly
that step 4 (rebuild/rehash docs/ecosystem/index.html) is inert today,
since #96 already made the file .gitignore'd and uncommitted; it is kept
for the H1 regression it guards against if a future change ever tracks
the file again, and TrackedExplorerSubprocessTests keeps exercising it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
…okenizer, exact hostname) (#104)

* Fix CodeQL first-analysis alerts: href scheme allowlist, case-insensitive tag scan, exact hostname check

Resolves the 5 real defects from the repository's first CodeQL default-setup
analysis (commit 168a3a8, alerts 1/3/4/5/8), re-located at base 796f759 since
PR #96 changed the generated explorer between the two:

- js/xss-through-dom (docs/ecosystem/template.html:145): link() now builds
  href through a safeHref() helper that only allows http:/https: URLs,
  blocking a javascript:-URI href from catalog data.
- py/bad-tag-filter (scripts/build_ecosystem.py:704,
  tests/test_ecosystem_manifest.py:231, tests/test_claude_repository_evidence.py:147):
  add re.IGNORECASE so an injected uppercase <SCRIPT> tag is still counted
  into the page's inline-script CSP hash instead of silently bypassing the
  single-script precondition.
- py/incomplete-url-substring-sanitization (tests/test_lifecycle_capture.py:103):
  replace the "sec.gov" in url substring check with an exact
  urlparse(url).hostname comparison.

The remaining 5 alerts (2, 6, 7, 9, 10) are false positives / test-only
synthetic-secret fixtures; their justification is recorded for the
coordinator to apply via the GitHub API in
docs/decisions/2026-09-22-codeql-first-analysis.md and the sibling
codeql-dismissals.json, not dismissed here.

manifests/evidence.json's sha256/bytes entries for the five touched files
are refreshed so scripts/validate.py (run by validate.yml) still passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Correct alert 7/9 location prose and add an executed safeHref check (review fixes)

Resolves the independent reviewer's three medium findings on 65f73e2:
alerts #7 and #9's location descriptions and dismissal comments pointed at
the wrong code after the 796f759 re-location (both now cite the actual
CodeQL sink); alert #1's "node -e smoke check" claim named no runnable
command, so it is replaced with an executed unittest that runs the
committed safeHref helper (extracted verbatim from template.html) under
Node and asserts javascript:/data:/vbscript:/file:/mailto: are rejected
while http(s) and relative URLs pass through, and alert #1 is relabeled
defense-in-depth given build_ecosystem.py's public_url() is the primary
control. Refreshes manifests/evidence.json for the two touched files so
validate.py stays green. Re-running the full suite three times confirms
the reviewer's flagged skip-count (338) is deterministic, not a flake.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Replace the <script> regexes with stdlib HTML tokenizers (CodeQL py/bad-tag-filter)

re.I alone would likely leave py/bad-tag-filter open (it also flags missed
end-tag variants such as </script >). The build script and both tests now use
html.parser, which tokenizes like a browser; on the real generated pages the
parsers return the identical single body the regexes returned. Corrects the
record's alert #1 control description (loopback_url also admits http loopback
links) and the skip-count claim.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fail loudly where html.parser and a browser disagree on <script>

Review follow-up: a self-closing <script/> or a script after <!--> (Python 3.12)
was invisible to InlineScripts. render_from_data now requires no self-closing
script and requires the parser's script-start count to equal the raw
"<script" count; the CSP test asserts the same count. The record's loopback_url
and browser-equivalence wording is corrected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
)

* Let catalog-freshness open a reviewable, off-by-default evidence PR

The freshness job now diffs against the newest published
catalogs/sota-convergence/manifest-*.json (sorted by name) instead of a
hard-coded dated filename, exposes a `drift` job output, and reports the
test suite's skipped-test count in the job summary.

A new `propose` job (needs: freshness, off by default; runs only on a
manual open_pr:true dispatch or a scheduled run with the repository
variable CATALOG_FRESHNESS_PROPOSE=true) turns a detected drift into a
force-created automation/catalog-freshness branch and PR: it copies the
run's drift.md/manifest artifact into evidence/artifacts/, writes a
scripts/validate.py-shaped upstream_provenance receipt under
evidence/receipts/, registers both via scripts/host_receipts.py's
register_file (matched by path/id, never list position), conditionally
rebuilds/rehashes docs/ecosystem/index.html only while it stays a
tracked file, then commits and pushes with the job's own GITHUB_TOKEN
via GIT_CONFIG_COUNT/KEY/VALUE (never persisted to disk) and dispatches
validate.yml/token-report.yml on the branch, since a GITHUB_TOKEN push
does not trigger pull_request-event runs. It never touches
catalogs/sota-convergence/*, catalogs/landscape/*.json,
manifests/stack.json, or layer-verdicts* -- those stay owned by the
separate SOTA-convergence lane review.

The receipt/registration logic is factored into scripts/freshness_propose.py
(new) so it is unit-testable independent of the workflow YAML;
tests/test_catalog_freshness_propose.py covers drift-table parsing,
component-id selection with its stack-component fallback, an end-to-end
fixture that runs scripts.validate.validate() against apply()'s output,
list-order independence, and text-level checks of the committed workflow.
docs/decisions/2026-09-23-bot-pr-dispatch.md records the evidence
(GitHub's GITHUB_TOKEN and Actions-settings documentation), the
alternatives (GitHub App token, PAT, stay report-only) and the overturn
condition. docs/github-automation.md gets a matching section covering the
one-time "Allow GitHub Actions to create and approve pull requests"
setting, the CATALOG_FRESHNESS_PROPOSE variable, and why auto-merge stays
off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix round: stdout leak, fetch-depth, required-check design, race guards

Independent Opus review (H1/H2/M1/L1-L6/T1-T3) and Codex cross-family
review (P1) on the prior commit's catalog-freshness propose job and
scripts/freshness_propose.py:

- H1: rebuild_explorer() no longer lets build_ecosystem.py's own stdout
  leak into main()'s single-JSON-document stdout contract (capture_output
  on both the --write and --check subprocess calls); verified with a real
  subprocess integration test against a throwaway git-tracked repo copy.
- H2: propose's checkout now uses fetch-depth: 0, matching validate.yml,
  so host_receipts.py validate can resolve every existing receipt's
  pinned catalog_revision commit instead of failing on a shallow clone.
- M1: a rebuilt row with upstream.latest=None (a bounded --max-repos run
  or an API error, not an observed change) is excluded from the drift
  count and reported separately as "unfetched"; propose additionally
  refuses to run unless this run's own fetch was unbounded and recorded
  zero upstream errors.
- P1 (Codex, refuting the prior design): a workflow_dispatch run's checks
  do not satisfy a required status check on a pull request at all
  (GitHub's troubleshooting docs), so dispatching validate.yml/
  token-report.yml after the push was a green-looking, not-actually-
  required substitute. Removed. propose now relies on the PR's own
  pull_request-triggered runs, which GitHub puts into an approval-required
  state for a GITHUB_TOKEN-created PR (GITHUB_TOKEN docs); the job prints
  the PR URL and instructs a write-access reviewer to approve them. The
  REST approve-a-run endpoint is documented only for fork PRs, so this is
  left as a manual UI step rather than an unverified automatic call.
  docs/decisions/2026-09-23-bot-pr-dispatch.md records the corrected claim,
  the quotes, and the overturn condition.
- Race guards (Codex P2): a single `${{ github.workflow }}` concurrency
  group (no event_name, cancel-in-progress: false) serializes every run
  regardless of trigger; the evidence-branch push uses
  --force-with-lease against the remote tip `git ls-remote` just
  observed, not a plain --force.
- L1: drift.md/PR-body table cells are rendered through a new md_cell()
  helper (backtick-wrapped, `|`-escaped) so an upstream release tag
  fetched from an external API can't break the Markdown table.
- L2: no fallback to an unrelated fixed component set when no drifted id
  matches a stack component; raises instead, so the job fails loudly
  rather than opening a PR with a misleading component_ids.
- L4: the basic-auth header is masked (::add-mask::) before use.
- L6: doc fixes (explorer is rewritten, not just rehashed; `gh variable
  set` instead of a PATCH that fails on a new variable).
- Codex low: refuse to write through an existing symlink at any
  destination this module creates (receipt, drift/manifest copies).
- T3: the diff logic moved into scripts/freshness_propose.py's
  build_drift_report()/compute_drift()/render_drift_markdown(), imported
  by catalog-freshness.yml's diff step instead of duplicated there, so
  the drift-table header and the drift-vs-unfetched rule can't drift out
  of sync between the workflow and the module.

tests/test_catalog_freshness_propose.py rewritten: 52 tests covering all
of the above, including a full normalized `if:` expression assertion (T2)
and a real subprocess suite against a throwaway git-tracked repository
copy for the stdout contract and explorer rehash paths (T1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Adapt the explorer-rehash test fixtures to the now-untracked default

Merging origin/main (796f759, #96: docs/ecosystem/index.html is no
longer committed or tracked) left RebuildExplorerSubprocessTests'
setUpClass asserting the file WAS tracked in its scratch copy, which no
longer holds once the copied .gitignore excludes it. Split the class:
RebuildExplorerSubprocessTests now builds its scratch copy the plain way
(matching main's real, untracked-by-default state) and asserts
rehashed_explorer is False; a new TrackedExplorerSubprocessTests
force-adds a locally built index.html to keep exercising
rebuild_explorer()'s --write/register/--check loop for forward
compatibility, explicitly labeled as exercising a path that is currently
dead code on main.

This edit was made after the prior merge commit but not staged into it;
committing it separately here rather than amending, since the merge
commit already has other reviewers' context.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Note that the explorer-rehash branch is currently dead code on main

docs/github-automation.md's propose-job walkthrough now says plainly
that step 4 (rebuild/rehash docs/ecosystem/index.html) is inert today,
since #96 already made the file .gitignore'd and uncommitted; it is kept
for the H1 regression it guards against if a future change ever tracks
the file again, and TrackedExplorerSubprocessTests keeps exercising it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix a stale module docstring after the explorer-fixture split

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Second fix round: pin changes no longer hidden, partial errors gated

Independent Opus pass-with-findings review (N1/N2/N2b/N3/N4) and a
Codex cross-family re-review (reproducing N1/N2, plus P2-3):

- N1/N2 (compute_drift bug): the prior code skipped a row entirely
  whenever the *rebuilt* row's upstream.latest was None, before ever
  comparing pin. This hid real pin changes on any no-release repository
  (7 already exist in the published manifest: tavily-cli, skills-ref,
  poppler, ...) and, separately, treated a releases-endpoint failure
  papered over by a tags-endpoint fallback (partial_errors) as ordinary
  clean data. compute_drift() now returns three buckets -- drifted,
  unfetched, no_release -- comparing pin unconditionally and excluding a
  row as unfetched only when it lacks fetch evidence (upstream.pushed_at
  is None) or its raw github-freshness.json record shows a fetch problem
  (error or partial_errors, matched by URL or normalized GitHub slug).
  Regression tests reproduce both Opus's and Codex's exact scenarios.
- N2 (job-level gate): freshness now also emits a partial_errors output;
  propose's if: requires it to be '0' alongside upstream_errors.
- N2b: a missing/malformed github-freshness.json now fails closed
  (raises) instead of silently reading as zero errors.
- N3: corrected the approval instructions (the "Awaiting approval"
  button near the PR's merge box opens the merge status panel, which
  holds "Approve workflows to run" -- not the Actions tab) and the
  GITHUB_TOKEN quote (current wording: "...will not create a new
  workflow run, with the following exceptions: ..."), and noted the
  30-day auto-deletion of unapproved runs.
- N4: render_drift_markdown() now receives only rebuilt_path.name, never
  the absolute $RUNNER_TEMP path that used to land in committed evidence.
- P2-3 (Codex): the workflow-level concurrency group's default
  queue:single let a plain scheduled run silently replace a pending
  manual open_pr:true request. queue:max is the documented fix but is
  rejected by this repository's pinned actionlint 1.7.12 (checked
  directly). Moved concurrency to a job-scoped group on `propose` only,
  keyed on manual vs. scheduled, so the two categories can never replace
  each other's pending slot; --force-with-lease remains the actual
  data-safety guard for the resulting rare concurrent-execution case.
- Also documented that force-creating automation/catalog-freshness from
  main on every run discards any commit a human pushed to it directly.

docs/decisions/2026-09-23-bot-pr-dispatch.md records the corrected
quotes/wording as corrections, not silent edits, plus the queue:max
rejection and the chosen alternative with its overturn condition.
tests/test_catalog_freshness_propose.py: 76 tests, including exact N1
(skills-ref 0.1.0->0.1.1) and N2 (503+tag-fallback) reproductions at
both the compute_drift() and build_drift_report() levels, N2b fail-closed
coverage, N4's relative-path assertion, and the job-scoped/keyed
concurrency text checks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Correct the lease comment, approval-banner wording and pin-comparison claim (Opus verification)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Report pin changes on every row; guard the PR description by the branch head (Codex verification)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Make the evidence PR description run-independent (Codex verification of 52d136a)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Recover from the PR-create race, refresh the dated title, fix receipt prose (Codex verification of 250adae)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: seathatflowsinourveins <folera06@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com>
seathatflowsinourveins added a commit that referenced this pull request Sep 23, 2026
…immutable releases, target ruleset (#108)

* Close the catalog's GitHub automation: OSV/zizmor-online scans, gates, immutable releases, target ruleset

Add security-scan.yml: an osv-scanner job (OSV-Scanner 2.6.0, checksum-verified)
over every tracked lockfile in .github/osv-scanner-lockfiles.json with
--no-resolve, failing on unignored vulnerabilities and uploading SARIF off PRs,
plus a zizmor-online SARIF job. A unittest fails when a tracked lockfile is
missing from the inventory or an ignore lacks a <=90-day expiry.

Gate dependency review at high (warn-only removed) and grype at --fail-on high
with a reviewed, empty .grype.yaml; upload Scorecard SARIF with a job-scoped
security-events write; add a 7-day Dependabot cooldown. publish-catalog.yml
gains a tag-only release job (contents: write only) that re-checks the attested
digests and creates the immutable release with both files attached at creation.

.github/main-ruleset.json becomes the target (dependency-review and osv-scanner
required, strict checks, signatures, CodeQL code_scanning, squash only); the
tag rulesets match live 23829417 and 23859358. Record the evidence, the
CodeQL-vs-zizmor comparison on 168a3a8 and the superseded decisions in
docs/decisions/2026-09-22-github-automation-closure.md; update SECURITY.md,
automation.json, the docs, the regenerated verdicts/explorer and evidence hashes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Drop required_signatures from the target ruleset; gate .grype.yaml changes; fix stale gate docs

A measured agent-lab run (2026-09-23) blocked PRs with unsigned branch
commits under required_signatures even though GitHub signs the squash merge,
so the target main ruleset leaves the rule out as keep-but-compare until every
writer signs. supply-chain.yml now runs its grype gate when .grype.yaml
changes, with a test. Scorecard, dependency-review and grype docs no longer
call the new gates report-only, and the CodeQL default-setup row cites the
re-read settings GET.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Resolve the catalog-automation review findings after rebasing on main

- Rebase onto origin/main 8faca90 (#96, #99-#104, #106): take main's removal of
  docs/ecosystem/index.html, merge main's explorer build/attest/upload steps
  with the release job's digest outputs, and re-register manifest hashes
  with host_receipts.register_file.
- OSV inventory: add a reasoned "excluded" list for the #101 grype
  positive-control fixture; the coverage test accepts only listed,
  fixture-scoped exclusions with an evidence path and still fails on any
  unlisted tracked lockfile. osv-scanner 2.6.0 exits 0 on the 37 listed files.
- Target main ruleset: strict_required_status_checks_policy false (auto-merge
  without a merge queue would stall every open PR when main moves);
  required_signatures stays out; regression tests assert both.
- automation.json: gating lanes move to security_gate_lanes with boolean
  required_check/target_required_check; fresh CodeQL default-setup GET cited.
- foundation.json: restore ci-supply-chain lane gap text, replace stale
  automation.json line citations with JSON-path anchors at 92bb279;
  regenerate verdicts and the handbook section with build_verdicts.
- Handbook automation summary and closure record updated (mlx branch dropped
  after #99, fixture alerts 7-15 dismissed and #105 closed, security updates
  kept on, strict decision, gap-ledger mapping).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Align merge-queue and osv-scanner catalog text with the strict-off target; record #104 CodeQL triage

- automation.json merge-queue non-adoption no longer cites strict checks;
  it points at the strict-off decision (closure record section 10) with its
  overturn condition.
- osv-scanner selection says "required in the target ruleset once applied"
  and names the 37 listed lockfiles, 2 covered manifests and 1 fixture
  exclusion.
- Closure record section 2: the 10 first-analysis CodeQL alerts were
  resolved in #104, not left for owners.
- Hashes re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep Dependabot security PRs off the grype positive-control fixture

ignore: urllib3 on a pip entry scoped to the fixture directory (ignore applies to
security updates; exclude-paths does not). Clarify that osv-scanner becomes a
required check only after the target ruleset is applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Refresh evidence hashes after rebasing onto #95

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix the OSV SARIF path under bash -e; keep the zizmor write token away from the analyzer

shell: bash implies -e, so a findings exit ended the OSV step before the SARIF run
(reproduced; found by an independent-implementation comparison and the Codex
cross-family review). zizmor now runs read-only and a tool-free upload job holds
security-events: write. Tests cover both, plus OSV PackageOverrides and grype
review-by dates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant