diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8b1d366..e503fe04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Install dependencies @@ -150,7 +150,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} @@ -246,7 +246,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" @@ -287,7 +287,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" # Deliberately NOT `pip install -e .`: this job must exercise the built wheel, not diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index 44810841..8ead72b8 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -18,7 +18,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" diff --git a/.github/workflows/demo-gif.yml b/.github/workflows/demo-gif.yml index a5db739e..2b1665f1 100644 --- a/.github/workflows/demo-gif.yml +++ b/.github/workflows/demo-gif.yml @@ -36,7 +36,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" @@ -50,7 +50,7 @@ jobs: path: docs/assets/demo.tape - name: Upload GIF - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: demo-gif path: docs/assets/demo.gif @@ -66,7 +66,7 @@ jobs: with: persist-credentials: true - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" diff --git a/.github/workflows/figures.yml b/.github/workflows/figures.yml index 9f6f3332..d8837773 100644 --- a/.github/workflows/figures.yml +++ b/.github/workflows/figures.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Install dependencies diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index ac068ac2..5f389c6d 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -27,7 +27,7 @@ jobs: with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Install atheris and the package diff --git a/.github/workflows/quickstart.yml b/.github/workflows/quickstart.yml index 8bcec127..38848b7a 100644 --- a/.github/workflows/quickstart.yml +++ b/.github/workflows/quickstart.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00391c0e..4ed27cc4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Install build tools @@ -81,7 +81,7 @@ jobs: with: subject-path: dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 with: packages-dir: dist/ @@ -102,7 +102,7 @@ jobs: with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Install dependencies @@ -117,7 +117,7 @@ jobs: mv dist/chock "dist/chock-${{ matrix.os }}" fi - name: Upload binary artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: chock-${{ matrix.os }} path: dist/chock-${{ matrix.os }}* @@ -132,7 +132,7 @@ jobs: with: persist-credentials: false - name: Download all binary artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist/ pattern: chock-* diff --git a/.github/workflows/schemas.yml b/.github/workflows/schemas.yml index 61e4eb77..c12d67ba 100644 --- a/.github/workflows/schemas.yml +++ b/.github/workflows/schemas.yml @@ -93,4 +93,4 @@ jobs: with: path: _site - id: deploy - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index c4f557ee..7e9ec335 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -68,7 +68,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - name: Semgrep with the project rules diff --git a/.github/workflows/windows-paths.yml b/.github/workflows/windows-paths.yml index 085d8d85..c64b635e 100644 --- a/.github/workflows/windows-paths.yml +++ b/.github/workflows/windows-paths.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} diff --git a/src/chock/hooks/in_agent_generic.py b/src/chock/hooks/in_agent_generic.py index 3074f4ee..9033ef58 100644 --- a/src/chock/hooks/in_agent_generic.py +++ b/src/chock/hooks/in_agent_generic.py @@ -61,7 +61,7 @@ def _strip_ours(node: dict, marker: str) -> None: del node[key] elif isinstance(value, dict) and value: _strip_ours(value, marker) - if not value: + if not node[key]: del node[key] diff --git a/src/chock/incident.py b/src/chock/incident.py index 295780c4..87f90d35 100644 --- a/src/chock/incident.py +++ b/src/chock/incident.py @@ -17,20 +17,15 @@ from chock.gate.runner import journal from chock.gatelog_chain import Chain, chains, walk from chock.gatelog_read import HEARTBEAT_EVENT, ROUTES -from chock.resources import package_data_dir +from chock.incident_render import ALLOWED, RENDER from chock.witness_export import LOCAL_ONLY, redact -DATA: dict[str, str] = json.loads( - package_data_dir("chock", "data").joinpath("incident.json").read_text(encoding="utf-8") -) FORMATS = ("text", "md", "json") _STAMP = "%Y-%m-%dT%H:%M:%SZ" _STAMPED = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$") _RELATIVE = re.compile(r"^(\d+)([mhd])$") _UNITS = {"m": "minutes", "h": "hours", "d": "days"} STOP_EVENTS = ("repo_stop", "repo_resume") -#: A plain allowed tool call: counted per session, never listed or put to a policy (GAP-2b). -ALLOWED = "allowed" #: The fields an entry shows, in this order; every other field stays in the log. SHOWN = ( "route", @@ -256,7 +251,5 @@ def main(argv: list[str] | None = None) -> int: if args.until is not None and args.until <= args.since: parser.error(f"--until {args.until} is not after --since {args.since}") timeline = build(Path(args.repo).resolve(), args.since, args.until, route=args.route, share=args.share) - from chock.incident_render import RENDER # noqa: PLC0415 -- it imports this module's Timeline - sys.stdout.write(RENDER[args.format](timeline)) return 0 diff --git a/src/chock/incident_render.py b/src/chock/incident_render.py index 26412ff0..5700dee3 100644 --- a/src/chock/incident_render.py +++ b/src/chock/incident_render.py @@ -7,7 +7,13 @@ from chock.gate.runner.report import _cell as md_cell from chock.gate.runner.report import inert -from chock.incident import ALLOWED, DATA, Timeline +from chock.resources import package_data_dir + +DATA: dict[str, str] = json.loads( + package_data_dir("chock", "data").joinpath("incident.json").read_text(encoding="utf-8") +) +#: A plain allowed tool call: counted per session, never listed or put to a policy (GAP-2b). +ALLOWED = "allowed" def chain_line(found: dict[str, Any]) -> str: @@ -32,13 +38,13 @@ def _fields(entry: dict[str, Any]) -> list[tuple[str, object]]: return [(k, v) for k, v in entry.items() if k not in ("ts", "kind", "at")] -def _header(timeline: Timeline) -> list[str]: +def _header(timeline: Any) -> list[str]: window = f"{timeline.since} to {timeline.until or 'now'} (UTC; the start is included, the end is not)" privacy = DATA["share_notice"] if timeline.share else DATA["local_notice"] return [window, DATA["notice"], privacy, "Logs read: " + (", ".join(timeline.logs) or "no decision log found")] -def render_text(timeline: Timeline) -> str: +def render_text(timeline: Any) -> str: """Plain text; every value from the log is made inert before it reaches a terminal.""" lines = ["chock incident: " + inert(_header(timeline)[0]), *map(inert, _header(timeline)[1:]), ""] lines.append(f"Timeline ({len(timeline.entries)} entries)") @@ -56,7 +62,7 @@ def render_text(timeline: Timeline) -> str: return "\n".join(lines) + "\n" -def render_md(timeline: Timeline) -> str: +def render_md(timeline: Any) -> str: """Markdown; every value from the log stays inside its cell or bullet, inert.""" head = _header(timeline) lines = [ @@ -84,7 +90,7 @@ def render_md(timeline: Timeline) -> str: return "\n".join(lines) + "\n" -def render_json(timeline: Timeline) -> str: +def render_json(timeline: Any) -> str: """JSON (ASCII only: a control character is written as an escape, never raw).""" doc = { "format": DATA["format"], diff --git a/src/chock/validation/__init__.py b/src/chock/validation/__init__.py index 8ad394b7..22a6691f 100644 --- a/src/chock/validation/__init__.py +++ b/src/chock/validation/__init__.py @@ -85,7 +85,6 @@ validate_yaml_against_schema, ) from chock.validation.patterns import ( # noqa: F401 - _DETERMINISTIC_HEURISTIC_PATTERNS, _INT3_GRANDFATHERED_IDS, _VERB_PREFIXES, AGENT_SPECIFIC_KEYWORDS, diff --git a/src/chock/validation/checks_determinism.py b/src/chock/validation/checks_determinism.py index 81040eaa..6bf07c44 100644 --- a/src/chock/validation/checks_determinism.py +++ b/src/chock/validation/checks_determinism.py @@ -4,6 +4,7 @@ import hashlib import json +import re from pathlib import Path from typing import Any @@ -12,12 +13,17 @@ from chock.registry.core import is_shipped_file from chock.validation.checks_security import _is_script_file from chock.validation.patterns import ( - _DETERMINISTIC_HEURISTIC_PATTERNS, _INT3_GRANDFATHERED_IDS, _VERB_PREFIXES, ) from chock.validation.report import Finding, Report +_DETERMINISTIC_HEURISTIC_PATTERNS = [ + re.compile(r"\\b\(\?:|\(\?\w|\\[dws]\b|\\b\b|\[\^?"), + re.compile(r"\b(grep|awk|sed|find|jq|python -c|bash -c|sh -c|perl -e|ruby -e)\b"), + re.compile(r"\b(?:re\.search|re\.match|re\.compile|re\.findall|regex)\b"), +] + def check_verb_first_naming(artifact_dir: Path, manifest: dict[str, Any], artifact_type: str, report: Report) -> None: """Check INT-3: warn when a draft/sandbox executing artifact does not start with a verb.""" diff --git a/src/chock/validation/patterns.py b/src/chock/validation/patterns.py index d44fbb89..d28922bc 100644 --- a/src/chock/validation/patterns.py +++ b/src/chock/validation/patterns.py @@ -118,9 +118,3 @@ "pre-generated-scripts", } ) - -_DETERMINISTIC_HEURISTIC_PATTERNS = [ - re.compile(r"\\b\(\?:|\(\?\w|\\[dws]\b|\\b\b|\[\^?"), - re.compile(r"\b(grep|awk|sed|find|jq|python -c|bash -c|sh -c|perl -e|ruby -e)\b"), - re.compile(r"\b(?:re\.search|re\.match|re\.compile|re\.findall|regex)\b"), -] diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 42847ccd..832f339c 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -9,8 +9,7 @@ from agentseam import recordings from runtime_support import write_runtime -import chock -from chock import vendors +from chock import __version__, vendors from chock.doctor import checks, cli from chock.doctor.targets import Target, plugin_targets, sync_targets from chock.gate import runtime_bundle @@ -45,7 +44,7 @@ def _sync_repo(tmp_path: Path, agent: str = AGENT) -> Path: return repo -def _plugin(home: Path, *, version: str = chock.__version__) -> Path: +def _plugin(home: Path, *, version: str = __version__) -> Path: folder = home / ".chock" / "marketplace" / CLIENT / "claude" / "obs-bundle" (folder / "scripts").mkdir(parents=True) write_runtime(folder / "scripts" / f"{AGENT}.py", AGENT) @@ -128,7 +127,7 @@ def test_a_plugin_recorded_by_another_engine_version_warns_with_the_rebuild_fix( def test_an_install_marker_stamps_the_building_engine(): block = package.marker({"client": CLIENT}, {})["marker"] - assert block["engine"] == {"version": chock.__version__, "commit": engine_commit()} + assert block["engine"] == {"version": __version__, "commit": engine_commit()} def test_agents_without_a_session_start_event_list_no_heartbeat_available(tmp_path, home):