mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 07:52:03 +00:00
Stop 13 jobs firing on a README edit, and delete an SBOM check that never ran
Two GitHub-side changes, both about not wasting runs. PATH FILTERS. tests.yml (nine jobs across three OSes and three Pythons) and python-ci.yml (four) both triggered on every push and every pull request with no path restriction, so editing a markdown file spent thirteen jobs. They now trigger on the Python implementation and the files that pin its environment. ts-parity keeps triggering on src/** as well as js/**, deliberately: Python is the reference implementation the port is validated against, so a change there can break parity even when no TypeScript moved. THE SBOM STEP IS GONE. It invoked `@cyclonedx/cyclonedx-npm@latest --package-lock-only`, which is the NPM tool and reads package-lock.json — a file this pnpm workspace does not have and never will. It could not have worked on any run. It then swallowed the failure with `|| echo "non-fatal"` and uploaded with `if-no-files-found: warn`, two independent layers stopping it from going red, and its log said "No files were found with the provided path" every time. Removed rather than pinned. Pinning the version of a tool that cannot read this repository's lockfile fixes the wrong problem, and a security check that cannot fail is the same anti-pattern as the pwsh exit-code bug fixed earlier today — worse than absent, because it is trusted. `pnpm audit --audit-level moderate` stays and does fail on findings. A real bill of materials wants a pnpm-native generator, tracked in TODO.md. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
1 parent
864229bad1
commit
e9d49d10bc
6 files changed
+127
-33
No files matched your search
@@ -3,8 +3,32 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ "**" ]
|
||||
paths:
|
||||
# The Python implementation and everything that pins its environment.
|
||||
# It is the reference implementation the TypeScript port is validated
|
||||
# against, so it still has to be tested — but a docs edit or a change
|
||||
# under js/ has no business firing 4 jobs.
|
||||
- "src/**"
|
||||
- "scripts/**"
|
||||
- "pyproject.toml"
|
||||
- "poetry.lock"
|
||||
- "requirements.lock"
|
||||
- "conftest.py"
|
||||
- ".github/workflows/python-ci.yml"
|
||||
pull_request:
|
||||
branches: [ "**" ]
|
||||
paths:
|
||||
# The Python implementation and everything that pins its environment.
|
||||
# It is the reference implementation the TypeScript port is validated
|
||||
# against, so it still has to be tested — but a docs edit or a change
|
||||
# under js/ has no business firing 4 jobs.
|
||||
- "src/**"
|
||||
- "scripts/**"
|
||||
- "pyproject.toml"
|
||||
- "poetry.lock"
|
||||
- "requirements.lock"
|
||||
- "conftest.py"
|
||||
- ".github/workflows/python-ci.yml"
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
|
||||
@@ -3,8 +3,32 @@ name: Tests
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
paths:
|
||||
# The Python implementation and everything that pins its environment.
|
||||
# It is the reference implementation the TypeScript port is validated
|
||||
# against, so it still has to be tested — but a docs edit or a change
|
||||
# under js/ has no business firing 9 jobs.
|
||||
- "src/**"
|
||||
- "scripts/**"
|
||||
- "pyproject.toml"
|
||||
- "poetry.lock"
|
||||
- "requirements.lock"
|
||||
- "conftest.py"
|
||||
- ".github/workflows/tests.yml"
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
paths:
|
||||
# The Python implementation and everything that pins its environment.
|
||||
# It is the reference implementation the TypeScript port is validated
|
||||
# against, so it still has to be tested — but a docs edit or a change
|
||||
# under js/ has no business firing 9 jobs.
|
||||
- "src/**"
|
||||
- "scripts/**"
|
||||
- "pyproject.toml"
|
||||
- "poetry.lock"
|
||||
- "requirements.lock"
|
||||
- "conftest.py"
|
||||
- ".github/workflows/tests.yml"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
@@ -94,19 +94,19 @@ jobs:
|
||||
# The Python side has had a dependency audit for a while; the
|
||||
# JavaScript tree had none.
|
||||
run: pnpm audit --audit-level moderate
|
||||
- name: Generate a CycloneDX SBOM
|
||||
run: |
|
||||
npx --yes @cyclonedx/cyclonedx-npm@latest \
|
||||
--output-format JSON \
|
||||
--output-file "$RUNNER_TEMP/seedpass-js-sbom.json" \
|
||||
--package-lock-only || echo "SBOM generation failed (non-fatal)"
|
||||
- name: Upload SBOM
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: seedpass-js-sbom
|
||||
path: ${{ runner.temp }}/seedpass-js-sbom.json
|
||||
if-no-files-found: warn
|
||||
# An SBOM step used to sit here and it never worked. It invoked
|
||||
# `@cyclonedx/cyclonedx-npm@latest --package-lock-only`, which is the
|
||||
# NPM tool and reads package-lock.json — a file this pnpm workspace does
|
||||
# not have and never will. It then swallowed the failure with
|
||||
# `|| echo "non-fatal"` and uploaded with `if-no-files-found: warn`, so
|
||||
# two separate layers stopped it from ever going red. Its logs said
|
||||
# "No files were found with the provided path" on every run.
|
||||
#
|
||||
# Removed rather than pinned: pinning the version of a tool that cannot
|
||||
# read this repository's lockfile fixes the wrong problem. Generating a
|
||||
# real SBOM needs a pnpm-native generator, which is tracked in TODO.md.
|
||||
# `pnpm audit` above is the supply-chain check that actually runs and
|
||||
# actually fails.
|
||||
|
||||
# Fixtures prove the two implementations compute the same values; this job
|
||||
# proves they can read each other's on-disk profiles and backups.
|
||||
|
||||
@@ -342,6 +342,22 @@ everything still open, in the order it should be tackled.
|
||||
smoke step assert something that can fail. Do not leave it as a
|
||||
dependency nobody declared and tests nobody runs.
|
||||
(Found 2026-08-20 while fixing the first CI run of this branch.)
|
||||
- [ ] **The JavaScript SBOM needs a pnpm-native generator.** The old
|
||||
step invoked `@cyclonedx/cyclonedx-npm --package-lock-only`, which
|
||||
is the npm tool and reads `package-lock.json` — a file this pnpm
|
||||
workspace does not have. It then swallowed the failure twice over
|
||||
(`|| echo "non-fatal"` and `if-no-files-found: warn`), so it
|
||||
reported success on every run while its own log said "No files
|
||||
were found with the provided path". Removed 2026-08-20 rather than
|
||||
pinned, because pinning a tool that cannot read our lockfile fixes
|
||||
the wrong problem.
|
||||
|
||||
`pnpm audit --audit-level moderate` remains and does fail on
|
||||
findings, so supply-chain checking is not absent — only the bill
|
||||
of materials is. The shipped bundle has six production
|
||||
dependencies, so a generator over `pnpm list --json` is likely
|
||||
simpler and more auditable than another third-party tool, and
|
||||
fits the zero-dependency posture of the bundle itself.
|
||||
- [ ] **A nested export path answers 500 instead of a refusal.**
|
||||
`POST /api/v1/entry/:id/document/export` with
|
||||
`{"path": "exports/nested/secret.txt"}` fails with an opaque
|
||||
|
||||
@@ -3,3 +3,4 @@ packages:
|
||||
- apps/*
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
libxmljs2: set this to true or false
|
||||
+49
-20
@@ -55,7 +55,9 @@ failures: list[str] = []
|
||||
|
||||
|
||||
def ok(label: str, detail: str = "") -> None:
|
||||
print(f" {GREEN}ok{RESET} {label}" + (f" {DIM}{detail}{RESET}" if detail else ""))
|
||||
print(
|
||||
f" {GREEN}ok{RESET} {label}" + (f" {DIM}{detail}{RESET}" if detail else "")
|
||||
)
|
||||
|
||||
|
||||
def bad(label: str, detail: str, fix: str) -> None:
|
||||
@@ -101,14 +103,22 @@ def check_python_env() -> None:
|
||||
|
||||
locked = parse_lock_versions(REPO / "poetry.lock")
|
||||
code, out = run(
|
||||
["poetry", "run", "python", "-c",
|
||||
"import json;from importlib.metadata import distributions;"
|
||||
"print(json.dumps({d.metadata['Name'].lower(): d.version "
|
||||
"for d in distributions() if d.metadata['Name']}))"]
|
||||
[
|
||||
"poetry",
|
||||
"run",
|
||||
"python",
|
||||
"-c",
|
||||
"import json;from importlib.metadata import distributions;"
|
||||
"print(json.dumps({d.metadata['Name'].lower(): d.version "
|
||||
"for d in distributions() if d.metadata['Name']}))",
|
||||
]
|
||||
)
|
||||
if code != 0:
|
||||
bad("cannot inspect the poetry environment", out.splitlines()[-1] if out else "",
|
||||
"./scripts/dev_sync.sh")
|
||||
bad(
|
||||
"cannot inspect the poetry environment",
|
||||
out.splitlines()[-1] if out else "",
|
||||
"./scripts/dev_sync.sh",
|
||||
)
|
||||
return
|
||||
installed = json.loads(out.splitlines()[-1])
|
||||
|
||||
@@ -122,11 +132,16 @@ def check_python_env() -> None:
|
||||
elif have != want:
|
||||
drift.append(f"{tool} {have} != locked {want}")
|
||||
if drift:
|
||||
bad("installed tools disagree with poetry.lock", "; ".join(drift),
|
||||
"./scripts/dev_sync.sh")
|
||||
bad(
|
||||
"installed tools disagree with poetry.lock",
|
||||
"; ".join(drift),
|
||||
"./scripts/dev_sync.sh",
|
||||
)
|
||||
else:
|
||||
ok("black, pytest and coverage match poetry.lock",
|
||||
" ".join(f"{t}={locked.get(t,'?')}" for t in VERDICT_TOOLS))
|
||||
ok(
|
||||
"black, pytest and coverage match poetry.lock",
|
||||
" ".join(f"{t}={locked.get(t,'?')}" for t in VERDICT_TOOLS),
|
||||
)
|
||||
|
||||
|
||||
def check_js_env() -> None:
|
||||
@@ -136,19 +151,28 @@ def check_js_env() -> None:
|
||||
want_node = str(pkg.get("engines", {}).get("node", "")).lstrip("^>=~ ")
|
||||
have_node = sys.version and run(["node", "--version"])[1].lstrip("v")
|
||||
if not want_node:
|
||||
bad("js/package.json declares no engines.node", "nothing to check against",
|
||||
'add "engines": {"node": ">=22"} to js/package.json')
|
||||
bad(
|
||||
"js/package.json declares no engines.node",
|
||||
"nothing to check against",
|
||||
'add "engines": {"node": ">=22"} to js/package.json',
|
||||
)
|
||||
elif have_node.split(".")[0] != want_node.split(".")[0]:
|
||||
bad("node major differs from engines.node", f"have {have_node}, want {want_node}",
|
||||
f"install Node {want_node.split('.')[0]}")
|
||||
bad(
|
||||
"node major differs from engines.node",
|
||||
f"have {have_node}, want {want_node}",
|
||||
f"install Node {want_node.split('.')[0]}",
|
||||
)
|
||||
else:
|
||||
ok("node matches engines.node", f"v{have_node}")
|
||||
|
||||
want_pnpm = str(pkg.get("packageManager", "")).removeprefix("pnpm@")
|
||||
have_pnpm = run(["pnpm", "--version"])[1]
|
||||
if want_pnpm and have_pnpm != want_pnpm:
|
||||
bad("pnpm differs from packageManager", f"have {have_pnpm}, want {want_pnpm}",
|
||||
"corepack enable && corepack prepare --activate")
|
||||
bad(
|
||||
"pnpm differs from packageManager",
|
||||
f"have {have_pnpm}, want {want_pnpm}",
|
||||
"corepack enable && corepack prepare --activate",
|
||||
)
|
||||
else:
|
||||
ok("pnpm matches packageManager", have_pnpm)
|
||||
|
||||
@@ -157,8 +181,11 @@ def check_js_env() -> None:
|
||||
if code == 0:
|
||||
ok("node_modules satisfies pnpm-lock.yaml")
|
||||
else:
|
||||
bad("pnpm-lock.yaml not satisfied", out.splitlines()[-1] if out else "",
|
||||
"./scripts/dev_sync.sh")
|
||||
bad(
|
||||
"pnpm-lock.yaml not satisfied",
|
||||
out.splitlines()[-1] if out else "",
|
||||
"./scripts/dev_sync.sh",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -168,7 +195,9 @@ def main() -> int:
|
||||
|
||||
print()
|
||||
if failures:
|
||||
print(f"{RED}preflight failed{RESET} — a local gate run now could disagree with CI.\n")
|
||||
print(
|
||||
f"{RED}preflight failed{RESET} — a local gate run now could disagree with CI.\n"
|
||||
)
|
||||
for f in failures:
|
||||
print(f" - {f}")
|
||||
print("\nRepair with: ./scripts/dev_sync.sh")
|
||||
|
||||
Reference in new issue
Block a user