Commit Graph

21 Commits

Author SHA1 Message Date
rcourtman fe82cb29a3 Record browser receipts per commit so frontend PRs stop conflicting
Every commit touching user-visible frontend source rewrote the single
frontend-modern/browser-verification.json, so any two open frontend pull
requests conflicted as soon as either merged. A merge could not repair
the loser: concluding it staged main's frontend files as if the author
had changed them, and the guard then demanded a receipt for files the
author never ran. The only honest recovery was a rebuild on the new main
and a fresh browser pass, as #2415 -> #2418 -> #2433 showed on 2-3
October.

Each commit now adds its own receipt under
frontend-modern/browser-verification/, named by the fingerprint of the
content it verified. Receipts are only ever added, so git merges them
unaided. The guard keeps every binding it had: the commit's own parent,
the exact changed paths and their content digests, a desktop and a
narrow viewport, non-empty routes, states and interactions, and the
range check that each changed file's final content matches a receipt
from a non-merge commit in the range.

Concluding a merge no longer counts content a merged parent already
carries as the commit's own change. Merged content that neither parent
holds is flagged and still needs a follow-up receipt in range mode, and
a merge commit may not record one.

The shared file is retired: no commit may write it again. A commit that
modified it while its own parent still carried it stays valid evidence
in range mode, so a pull request that still rewrites it is repaired by
merging main and taking main's copy rather than by a rebuild. The file
is deleted in a change of its own, because deleting it here would
conflict with every pull request that still rewrites it.

--write-template writes the skeleton at the path the staged content
requires. --prune deletes receipts already on main. A change that
records a receipt may not delete one: git pairs the two as a rename,
which conflicts with any other change deleting the same receipt.
2026-10-03 11:11:25 +01:00
pulse-triage[bot] 6f3c436121 Stop retrying unchanged credential blocks
A committed release with an unchanged operator-owned containment failure cannot converge through unattended retries. Classify that evidence without weakening the block, and rearm only when the relevant private inputs or public controls change.

Change-source: pulse-maintainer
2026-09-04 00:14:40 +01:00
rcourtman d28666c396 Guard shipped docs mirror sync at commit time
Docs under frontend-modern/public/docs are byte-for-byte copies of repo
docs, but the only guard was a CI vitest the git hooks never run. On
2026-09-01 two commits (f4886c2dfb, f313882a7b) each edited a mirrored
doc without its copy, passed the hooks, and broke main's Frontend job.

Add scripts/check_docs_mirror.py with an index-blob --staged mode wired
into the pre-commit hook: a commit that stages either side of an
out-of-sync pair (or an orphan shipped copy) fails with the exact sync
command, while pre-existing drift from other commits only warns. The
worktree mode runs as a named step in the public-docs workflow, with
unit tests in scripts/tests picked up by the existing runner. The
docsLinks vitest stays as the CI backstop.

Build-time generation of public/docs was considered and rejected: the
shipped set is a curated subset (61 of 421 docs), so generation still
needs a hand-maintained manifest while adding build, dev-server, and
test-order coupling.
2026-09-01 11:16:05 +01:00
rcourtman 3de30d75fd Pin the pre-commit golangci-lint step to the go.mod toolchain
GOTOOLCHAIN=auto never downgrades, so once the system Go moved to
go1.27.0 the go command wrote version-4 export data that golangci-lint
v1.64.8 (built with go1.26, older vendored x/tools) cannot decode.
Every Go commit then failed with "export data version 4 is greater
than maximum supported version 2" and a cascade of bogus "type X has
no field" typecheck errors. Rebuilding the linter with go1.27 does not
help; its vendored x/tools is still too old for the new export data.

Derive GOTOOLCHAIN from the root go.mod (toolchain directive, falling
back to the go directive, else auto) and export it for both the root
and nested-module lint invocations, so lint always runs under the
toolchain the module actually ships with. The single root pin also
covers nested modules, whose go directives must stay at or below it.
GOLANGCI_LINT_GOTOOLCHAIN overrides the derivation.
2026-08-22 07:33:29 +01:00
rcourtman 2fd184315d Simplify and harden verified Patrol receipts
Contract-Neutral: Patrol compact receipt payload does not change agent lifecycle or storage recovery contracts
2026-08-15 10:25:04 +01:00
rcourtman 26b0227dcd build(hooks): raise pre-commit timeout ceilings for contended machines
Three commits failed today on timeouts that had nothing to do with their
diffs. golangci-lint hit its 5m default while loading packages, and
TestStatusJSONLaneEvidenceReferencesAreStructured hit Go's 10m default after
9m11s with its goroutine parked in a syscall. Both ran at load average 185,
with four pre-commit hooks and two dozen eslint and vitest processes from
other agent sessions competing for the same box. Each failure cost a full
re-run to diagnose, and the same commits passed unchanged once the machine
went quiet.

The repoctl guardrail tests are I/O bound rather than CPU bound: they os.Stat
every evidence path status.json references, across repos, so they degrade
badly under contention while doing nothing wrong. golangci-lint already
exposed GOLANGCI_LINT_TIMEOUT; the go test line had no -timeout at all and
silently inherited the 10m default.

This raises the ceilings to 20m and 30m and adds PULSE_HOOK_GO_TEST_TIMEOUT
for the latter. No check is skipped and no rule is relaxed: on an idle
machine these stages finish in seconds to minutes and the change is inert.
The cost is that a genuinely hung test now takes longer to surface, which is
the better trade against false failures that read as real ones.
2026-08-07 20:07:18 +01:00
rcourtman 024aa16d92 Enforce browser verification for frontend changes 2026-08-02 20:22:36 +01:00
rcourtman d89e3e3163 Add staged-file prettier formatting to pre-commit
New scripts/release_control/format_staged_frontend.py mirrors the staged
Go formatter: formats staged frontend-modern/src {ts,tsx,css,json} blobs
through prettier --stdin-filepath, writes results back to the index
directly (no broad restaging), syncs the worktree only when it matches
the previously staged content, and iterates to a fixed point to absorb
prettier's occasional non-idempotence. Skips gracefully when prettier is
not installed (fresh clones, linked worktrees without node_modules).
Wired into .husky/pre-commit after the Go formatter, with unit tests in
the governance battery, a README note, and a .gitignore allowlist entry.
With the one-time sweep in the previous commits, prettier drift can no
longer re-accumulate and make format stays clean on a clean tree.
2026-07-20 10:44:36 +01:00
rcourtman eb5a477dc9 fix(governance): make commit-time guard verdicts survive to CI
Canonical Governance failed on nearly every push today (6 of the last 8
commits) because the local pre-commit hook and the CI workflow disagreed
about the canonical completion guard in two ways.

Frontend-only commits skipped the guard entirely: the hook gated ALL
governance checks behind governance-path detection as a perf
optimization, but subsystem contracts name canonical frontend files, so
those commits landed locally and failed the same guard in CI. The hook
now always runs the (cheap) completion guard; only the multi-minute Go
test and audit battery stays path-gated.

Contract-neutral bypasses did not travel: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT
authorized the commit in the local shell and vanished, so CI re-ran the
guard without it and went red. A new prepare-commit-msg hook records the
reason as a Contract-Neutral trailer in the commit message, and the
workflow now evaluates the guard per commit (each commit's file list
against its own parent), honoring the trailer exactly as the commit-time
hook honored the env var. Per-commit evaluation also stops a compliant
commit from being blamed for range-mates.

The new hook is registered as a worktree-sensitive governance file in
governance_stage_guard.py with a matching unit test assert.
2026-07-17 14:35:34 +01:00
rcourtman 2d47c12e6d Normalize Pulse Intelligence release gates 2026-07-12 14:42:07 +01:00
rcourtman 1c11d0945d Skip deleted directories when deriving golangci-lint targets in pre-commit 2026-07-09 23:52:22 +01:00
rcourtman de109b65d8 Lint nested Go modules from their own module root in pre-commit
golangci-lint was invoked from the repo root for every staged Go
package, so files in nested modules like
tests/integration/mock-github-server failed typechecking with 'main
module does not contain package' and could never be committed. Group
staged package dirs by their nearest enclosing go.mod and lint nested
modules from their own root.
2026-07-08 01:04:36 +01:00
rcourtman 2e8bcad909 Gate heavy pre-commit governance checks on staged paths
The husky pre-commit hook unconditionally ran the full release-control
governance audit chain (`governance_stage_guard.py`, `control_plane_audit.py`,
`status_audit.py`, `registry_audit.py`, `contract_audit.py`, the
`go test ./internal/repoctl` package test, the readiness assertion guard,
and 15 Python helper unit tests) on every single commit, regardless of
what was actually staged. For a one-file frontend TS change, that meant
~15-20 minutes of audits the change couldn't possibly affect.

That cost is amplified by Pulse being driven by multiple agent harnesses
in parallel (Codex lanes, Claude Code interactive sessions, Anti-Gravity).
Each agent's commit serialized behind the same unrelated suite, even
when none of their staged files touched governance.

The hook already had `staged_files_match` and was using it to gate the
golangci-lint and `npm run lint` blocks. Apply the same pattern to the
governance block: run it only when the staged set actually touches
governance-relevant paths (docs/release-control/, scripts/release_control/,
internal/repoctl/, any .go file, go.{mod,sum,work,work.sum}, VERSION,
or the husky hooks themselves so hook changes still validate).

Frontend-only commits now finish in roughly the time the sensitivity
check, gitleaks scan, and `npm run lint` need (tens of seconds), rather
than tens of minutes. Governance-touching commits are unaffected; the
full audit chain still runs whenever any path in the gating regex is
staged.

Smoke-tested the regex against 13 representative paths (frontend TS,
README, internal docs JSON, release_control py, repoctl go, generic go,
go.mod/go.sum, VERSION, .husky/pre-commit, .husky/pre-push). All
classifications matched the intended skip/run behavior. Bash syntax of
the modified hook validated with `bash -n`.
2026-05-20 10:13:04 +01:00
rcourtman 407024fdc7 Filter pre-commit golangci-lint to new issues only
The previous hook scoped lint to staged packages but still surfaced
all pre-existing violations in those packages on every commit, which
turned long-standing technical debt (errcheck on test mock helpers,
dupl warnings on poll providers) into a per-commit blocker.

Add --new-from-rev=HEAD so golangci-lint only reports issues
introduced by the staged diff. Pre-existing violations on untouched
lines are filtered out; new violations on changed lines still fail
the commit. Override with GOLANGCI_LINT_NEW_FROM_REV (set to "" to
lint the whole package, or to a base revision such as HEAD~5 to
broaden the comparison window).
2026-05-08 16:45:14 +01:00
rcourtman 6c426d9f1a Scope pre-commit golangci-lint to staged packages
Whole-repo golangci-lint run ./... was OOMing the host (an in-flight
process held 18 GB resident under sustained load) and the macOS
runtime kept SIGTERM-ing it before completion, blocking commits.

Replace ./... with packages derived from staged Go files (falling
back to ./... when none) and add a 5 minute deadline overridable via
GOLANGCI_LINT_TIMEOUT. Behavior on substantial Go changes still hits
the same lint coverage; trivial-touch commits no longer scan the
whole tree.
2026-05-08 16:34:06 +01:00
rcourtman cf7381e818 Make Go lint pre-commit conditional 2026-05-01 10:14:32 +01:00
rcourtman 778a2577b6 feat: Pulse v6 release 2026-03-18 16:06:30 +00:00
rcourtman 07afa94d19 feat(security): add gitleaks secret scanning to pre-commit hook and CI
Add three layers of secret leak prevention:

1. .gitleaks.toml — config extending the default ruleset (~150 rules for
   AWS, GCP, Stripe, OpenAI, private keys, JWTs, etc.) with allowlists
   tuned to suppress false positives from test fixtures and docs.

2. .husky/pre-commit — enhanced with gitleaks protect --staged (graceful
   skip if not installed), sensitive file type blocking (.pem, .key, .enc,
   id_rsa, etc.), and broadened fallback patterns covering AWS, OpenAI,
   GCP, and private key headers alongside existing Stripe checks.

3. .github/workflows/build-and-test.yml — new secret-scan CI job using
   gitleaks-action that runs in parallel with build on every push/PR,
   serving as the last gate if someone bypasses local hooks.
2026-02-04 09:52:54 +00:00
rcourtman a9f342c09e fix(pre-commit): only re-stage files that were already staged
Prevents accidentally staging unrelated work-in-progress files when
the formatter modifies files during pre-commit hook.
2026-01-17 14:41:32 +00:00
rcourtman 6de1c660b1 chore: Improve pre-commit data validation and ignore patterns 2026-01-08 20:04:02 +00:00
rcourtman 3fdf753a5b Enhance devcontainer and CI workflows
- Add persistent volume mounts for Go/npm caches (faster rebuilds)
- Add shell config with helpful aliases and custom prompt
- Add comprehensive devcontainer documentation
- Add pre-commit hooks for Go formatting and linting
- Use go-version-file in CI workflows instead of hardcoded versions
- Simplify docker compose commands with --wait flag
- Add gitignore entries for devcontainer auth files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:29:15 +00:00