Skip to content

cleanup: close #134 Low findings in inventory, snapshots and worktree checks - #181

Merged
Dimkox merged 11 commits into
mainfrom
ccr-315b4684-r3j424
Oct 24, 2026
Merged

Dimkox merged 11 commits into
mainfrom
ccr-315b4684-r3j424

Conversation

@Dimkox

@Dimkox Dimkox commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Slice A of the Low batch in #134: the findings from the reviews of #174–#178 and #180.

What changed

  • Inventory (resource_inventory.py):
    • The inventory does not read a linked .getzilla or runtime folder. It reports itself incomplete instead.
    • One entry budget covers both the copy search and every copy size.
    • These make the report incomplete: an unreadable folder, a linked copy root, or a copy whose size is cut by the budget.
  • Review snapshots (review_snapshot.py):
    • The parent-folder refusal has its own message. It no longer says "symlink".
    • The module now states that a snapshot holds committed content only, so commit the candidate first.
  • getzilla status --help prints help instead of the status JSON.
  • Tests:
    • A linked .getzilla, runtime folder or copy root; an unreadable copy folder; the shared budget; a missing runtime folder; a .. part on its own.
    • Snapshot guards: annotated tag peel, marker removal after a failed worktree add, the main-worktree base from a task worktree, marker schema, 0700 mode on an existing folder, a marker next to an unregistered folder, option-shaped revisions.
    • Worktree checks: refs/worktree on its own (the reflog is expired first), a failed reflog read, assertIs(False) for the 2 s start margin, and the 2 s future tolerance at +1 s and +5 s.
    • The snapshot CLI test stops find_root at the test project.
    • Every launcher command answers --help with a usage line.

Mutation probes (private scratch copy)

  • 13 of 14 mutants are killed.
  • The survivor removes --end-of-options. Git rejects option-shaped revisions with or without it, so the mutant is equivalent; the flag stays as defense in depth.
  • One probe showed a redundant not linked term in the complete value; it is removed.

Not in this PR (stays in #134)

  • getzilla_protected_write.py session binding. A local grant conflict blocked the edit. It fails closed today.
  • The GETZILLA_SESSION_ID CLI test.
  • The slice B items (update/upgrade, runner, CAS, report, vulns).

Checks

  • Changed-module tests plus test_package_status, test_text_writes, test_structure and test_policy: 154 passed.
  • Ruff is clean.

Generated by Claude Code

claude added 10 commits October 24, 2026 13:58
Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
… to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
…tree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T18:22:17.827758Z 6d8b0b7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…ctly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR
@Dimkox
Dimkox merged commit 2b5756f into main Oct 24, 2026
8 checks passed
Dimkox pushed a commit that referenced this pull request Oct 24, 2026
Dimkox added a commit that referenced this pull request Oct 24, 2026
* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 25, 2026
…t margin (#188)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 25, 2026
… session (#189)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 25, 2026
* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(eu-ai-act): cover the deferred negative cases from #122

- accepted_by: missing, empty, short or non-string role; missing,
  empty, malformed or impossible date.
- Matrix sources: ftp, gopher, file and mailto schemes and a URL
  without a host are refused.
- Attestation identity: attestation_id, key_id and policy_digest
  missing, empty or of the wrong type are refused.
- The verifier refusal uses a working executable inside the project,
  so only the location guard can stop it.
- The first audit call uses the captured providers, never the engine
  loader.
- An archive install (getzilla.zip, no inherited PYTHONPATH) finds the
  scaffold templates through STACK_DIR.

Each test fails when its guard is removed (7 of 7 mutants killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 25, 2026
…#192)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(eu-ai-act): cover the deferred negative cases from #122

- accepted_by: missing, empty, short or non-string role; missing,
  empty, malformed or impossible date.
- Matrix sources: ftp, gopher, file and mailto schemes and a URL
  without a host are refused.
- Attestation identity: attestation_id, key_id and policy_digest
  missing, empty or of the wrong type are refused.
- The verifier refusal uses a working executable inside the project,
  so only the location guard can stop it.
- The first audit call uses the captured providers, never the engine
  loader.
- An archive install (getzilla.zip, no inherited PYTHONPATH) finds the
  scaffold templates through STACK_DIR.

Each test fails when its guard is removed (7 of 7 mutants killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(eu-ai-act): confirmation and review dates must be past YYYY-MM-DD (#191)

date.fromisoformat on Python 3.11+ also accepts 20261020 and week dates
such as 2026-W43-1, and str() let an integer through. A future date
also passed as a human confirmation.

- _iso_day accepts only a YYYY-MM-DD string.
- A class confirmation, an exemption confirmation and a high-residual
  accepted_by refuse a date after the evaluation day.
- A risk record's reviewed date uses the same parser and refuses a
  future date.

Tests cover the basic, integer, week and future forms for each caller.
8 of 8 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 25, 2026
… repo (#194)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(eu-ai-act): cover the deferred negative cases from #122

- accepted_by: missing, empty, short or non-string role; missing,
  empty, malformed or impossible date.
- Matrix sources: ftp, gopher, file and mailto schemes and a URL
  without a host are refused.
- Attestation identity: attestation_id, key_id and policy_digest
  missing, empty or of the wrong type are refused.
- The verifier refusal uses a working executable inside the project,
  so only the location guard can stop it.
- The first audit call uses the captured providers, never the engine
  loader.
- An archive install (getzilla.zip, no inherited PYTHONPATH) finds the
  scaffold templates through STACK_DIR.

Each test fails when its guard is removed (7 of 7 mutants killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(eu-ai-act): confirmation and review dates must be past YYYY-MM-DD (#191)

date.fromisoformat on Python 3.11+ also accepts 20261020 and week dates
such as 2026-W43-1, and str() let an integer through. A future date
also passed as a human confirmation.

- _iso_day accepts only a YYYY-MM-DD string.
- A class confirmation, an exemption confirmation and a high-residual
  accepted_by refuse a date after the evaluation day.
- A risk record's reviewed date uses the same parser and refuses a
  future date.

Tests cover the basic, integer, week and future forms for each caller.
8 of 8 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): guard every MCP write tool and bind GitHub writes to the repo (#193, #184)

SIDE_EFFECT_TOOL matched a write verb only right after `__`, so GitHub
MCP tools with the verb inside the name passed with no grant:
issue_write, add_issue_comment, push_files, resolve_review_thread,
enable_pr_auto_merge, fork_repository, actions_run_trigger and more.

- The regex now covers more leading verbs, a `write` token anywhere and
  actions_run. Read tools (get_*, list_*, search_*, *_read,
  get_check_run, run_secret_scanning) stay unguarded.
- A GitHub MCP write that names owner/repo must target the repository
  that grants bind to (case-insensitive). Another repository, a partial
  or non-string target, or a project without a GitHub origin is
  refused. A call without owner/repo keeps the tool-name grant check.

Tests pin 18 write tools, 13 read tools and the repository binding.
10 of 10 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): GitHub MCP tools fail closed; camelCase and hyphen verbs (#193 review)

The review of 172e4cf found write tools that the verb list still
missed: run_workflow, manage_*_subscription, star/unstar_repository,
reprioritize_sub_issue on GitHub, and camelCase or hyphen names on
other servers (executeWrite, createJiraIssue, batchUpdate,
management-projects-create). A GitHub server under another name also
skipped the repository binding.

- mcp_side_effect: a server whose name contains "github" treats every
  tool as a write unless it is a known read (get_, list_, search_,
  download_, *_read, actions/projects get/list, run_secret_scanning).
- Other servers: a verb also ends at an uppercase letter and may start
  a "-" token; manage, star, unstar, reprioritize, run and transition
  join the list.
- The repository binding covers every GitHub-named server, and owner
  and repo must be plain GitHub names ([A-Za-z0-9._-]).
- policy.sensitive_action uses the same mcp_side_effect.

14 of 14 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): keep actions_run on every server; match verbs in the action only (#193)

c65d5ea dropped the actions_run branch, so actions_run_trigger on a
GitHub server named without "github" (mcp__gh__) needed no grant again.
It also scanned the whole tool name, so a verb in a server name such as
cloud-run made every tool on that server a write.

- SIDE_EFFECT_TOOL matches ^actions_run again.
- mcp_side_effect scans only the action part of the tool name.

16 of 16 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): scan every segment after the MCP server name (#193 review)

5be3a29 scanned only the last `__` segment, so a verb in an earlier
segment (mcp__x__delete__file) passed as a read with no grant. The
action is now everything after the server segment.

A write hint that appears only in the server name (slack-post) is still
not seen; that limit is tracked in #195 and pinned as a known read.

17 of 17 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 26, 2026
…_copy (#197)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(eu-ai-act): cover the deferred negative cases from #122

- accepted_by: missing, empty, short or non-string role; missing,
  empty, malformed or impossible date.
- Matrix sources: ftp, gopher, file and mailto schemes and a URL
  without a host are refused.
- Attestation identity: attestation_id, key_id and policy_digest
  missing, empty or of the wrong type are refused.
- The verifier refusal uses a working executable inside the project,
  so only the location guard can stop it.
- The first audit call uses the captured providers, never the engine
  loader.
- An archive install (getzilla.zip, no inherited PYTHONPATH) finds the
  scaffold templates through STACK_DIR.

Each test fails when its guard is removed (7 of 7 mutants killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(eu-ai-act): confirmation and review dates must be past YYYY-MM-DD (#191)

date.fromisoformat on Python 3.11+ also accepts 20261020 and week dates
such as 2026-W43-1, and str() let an integer through. A future date
also passed as a human confirmation.

- _iso_day accepts only a YYYY-MM-DD string.
- A class confirmation, an exemption confirmation and a high-residual
  accepted_by refuse a date after the evaluation day.
- A risk record's reviewed date uses the same parser and refuses a
  future date.

Tests cover the basic, integer, week and future forms for each caller.
8 of 8 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): guard every MCP write tool and bind GitHub writes to the repo (#193, #184)

SIDE_EFFECT_TOOL matched a write verb only right after `__`, so GitHub
MCP tools with the verb inside the name passed with no grant:
issue_write, add_issue_comment, push_files, resolve_review_thread,
enable_pr_auto_merge, fork_repository, actions_run_trigger and more.

- The regex now covers more leading verbs, a `write` token anywhere and
  actions_run. Read tools (get_*, list_*, search_*, *_read,
  get_check_run, run_secret_scanning) stay unguarded.
- A GitHub MCP write that names owner/repo must target the repository
  that grants bind to (case-insensitive). Another repository, a partial
  or non-string target, or a project without a GitHub origin is
  refused. A call without owner/repo keeps the tool-name grant check.

Tests pin 18 write tools, 13 read tools and the repository binding.
10 of 10 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): GitHub MCP tools fail closed; camelCase and hyphen verbs (#193 review)

The review of 172e4cf found write tools that the verb list still
missed: run_workflow, manage_*_subscription, star/unstar_repository,
reprioritize_sub_issue on GitHub, and camelCase or hyphen names on
other servers (executeWrite, createJiraIssue, batchUpdate,
management-projects-create). A GitHub server under another name also
skipped the repository binding.

- mcp_side_effect: a server whose name contains "github" treats every
  tool as a write unless it is a known read (get_, list_, search_,
  download_, *_read, actions/projects get/list, run_secret_scanning).
- Other servers: a verb also ends at an uppercase letter and may start
  a "-" token; manage, star, unstar, reprioritize, run and transition
  join the list.
- The repository binding covers every GitHub-named server, and owner
  and repo must be plain GitHub names ([A-Za-z0-9._-]).
- policy.sensitive_action uses the same mcp_side_effect.

14 of 14 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): keep actions_run on every server; match verbs in the action only (#193)

c65d5ea dropped the actions_run branch, so actions_run_trigger on a
GitHub server named without "github" (mcp__gh__) needed no grant again.
It also scanned the whole tool name, so a verb in a server name such as
cloud-run made every tool on that server a write.

- SIDE_EFFECT_TOOL matches ^actions_run again.
- mcp_side_effect scans only the action part of the tool name.

16 of 16 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): scan every segment after the MCP server name (#193 review)

5be3a29 scanned only the last `__` segment, so a verb in an earlier
segment (mcp__x__delete__file) passed as a read with no grant. The
action is now everything after the server segment.

A write hint that appears only in the server name (slack-post) is still
not seen; that limit is tracked in #195 and pinned as a known read.

17 of 17 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(support): retry project_copy cleanup after a late .git write (#196)

A passing test failed on CI because TemporaryDirectory cleanup hit
"Directory not empty: .git": something still added a file under .git
while rmtree ran. Local probes (40 hook runs, loose-object count far
below gc.auto) did not reproduce it, so the root cause stays open.

project_copy now uses _test_directory: the stdlib cleanup (which also
resets read-only files on Windows) is retried up to four times with a
short pause, and the last failure is still raised. Tests cover one
transient failure and a lasting one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(support): turn off git auto maintenance in the git fixture (#196)

Review lead: git 2.46+ runs auto maintenance detached after a commit,
and it can still take a lock under .git/objects while the fixture is
removed. project_copy(git=True) now sets maintenance.auto=false and
gc.auto=0 before its first commit; the cleanup retry stays as a
backstop.

The lasting-failure test now detaches the TemporaryDirectory finalizer,
so it no longer emits an implicit-cleanup ResourceWarning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <noreply@anthropic.com>
Dimkox added a commit that referenced this pull request Oct 26, 2026
…199)

* grants: a new route keeps a branch-bound protected-path grant

Every prompt starts a new route, so binding protected-path grants to
the route voided them on each user message and forced a new grant for
the same edit again and again. A branch-bound grant is already held to
the repository, branch ref, anchor ancestry, exact files and TTL, so
it no longer checks the route. Production and external-write grants
keep their exact route, HEAD and tree binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: pin change_id and the scope guard in tests

The security review found that change_id is now the only task binding
of a branch-bound grant and that no test pinned it, nor the
protected-path scope guard. Add a test for each; both mutants are now
killed. Update the has_valid_approval docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* grants: bind branch-bound grants to the session, not the route

Skipping the route broke session isolation: a grant issued in one
session also served another session in the same worktree
(tests/test_control_plane_sessions failed on CI). A protected-path
grant now records its session and checks that instead of the route,
so later prompts of the same session keep it and another session never
gets it. Without a known session on both sides the exact route is
still required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* Bind grant sessions to the caller, not the route

A session without its own route pointer reads the shared route of another
session, and 'manual' is not a session. The check now uses the caller's
bound session and skips the route only when caller, route and grant name
the same session. getzilla_approve.py binds CLAUDE_CODE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: bring QUICKSTART, START_HERE, REFERENCE, CHANGELOG and state up to date

- QUICKSTART: launcher, runtime evidence without report commits, route
  reconcile, review snapshots, light lane, and a disk cleanup section.
- START_HERE and REFERENCE: current main a45fbdd, correct v2.3.0 link,
  continuation at issue #134; REFERENCE lists all getzilla commands.
- PROJECT_STATE: current_continuation moves to issue #134; the finished
  PR #79 contour is kept as historical_pr79_continuation.
- CHANGELOG: 2.3.1 lists the work merged since 2026-10-21 (#121-#179).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs(readme): list every getzilla command and the current source state

A Commands section lists all launcher commands by task, with their main
options. The source line names what 2.3.1 adds since v2.3.0, and the
file map says where review copies live and how they are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* docs: fix command wording found in review of #180

history takes a snapshot file, approve needs --reason, --add-reviewer
works with --reconcile, getzilla test falls back to serial unittest
without pytest-xdist, report overhead is per profile, and the state
record counts the merged PRs from #114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: close the #134 Low findings in inventory, snapshots and worktree checks

- Inventory: a linked .getzilla or runtime folder is not read; one entry
  budget covers the copy search and all copy sizes; an unreadable folder,
  a linked copy root or a cut size makes the report incomplete.
- Review snapshots: the parent-folder refusal has its own message, and
  the module says a snapshot holds committed content only.
- Tests pin the guards that mutants survived before: annotated tag
  peel, marker removal after a failed worktree add, the main-worktree
  base, the marker schema, the 0700 mode, unregistered folders, the
  refs/worktree check, a failed reflog read, the 2 s margins.
- getzilla status answers --help; a test runs --help for every command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* cleanup: count each copy entry once and accept a search that uses exactly the budget

Review of #181: the copy folder listing was counted by the search and
again by its size, and a search that used the full budget reported
itself incomplete. Tests pin both boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: every Getzilla defect goes to a GitHub issue

AGENTS.md now requires an issue in Dimkox/Getzilla for each defect in
Getzilla itself, also one that was worked around: search first, no
duplicates, Low findings into the collecting issue, no secrets or
exploit commands, and an exact external-write grant first. Consumer
projects get the same rule in the installed contract, with a ready
issue text for the owner when filing upstream is not allowed. The
delivery skill adds it as the last step of a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: own-repository issues are in the L5 standing consent

Review of #182: the new rule called issue writes an L5 process call
while the L5 scope named only merge, tag and release. The owner asked
for it, so the scope now covers the project's own GitHub issues; the
checks-green condition stays on merge, tag and release. The search
covers open and closed issues, the grant resource is named, consumer
consent is recorded as an exact grant, and a test pins the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* contract: issue grants follow the exact-HEAD binding; pin the secret bans

Security review of #182: the rule said a tool grant lasts its TTL, but
external-write grants stay bound to route, change, HEAD and tree. The
test now also pins the bans on secrets and customer data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* tests: give the Node transparency adapter tests room on loaded runners (#187)

test_node_adapter_behavior timed out after 10 s on a busy public linux
runner while it only checks adapter behavior. Both Node calls now allow
60 s; the neighbouring stubborn-child test already allows 45 s.

Fixes #187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: harness notices keep the route; spec and transition use the package (#185, #186)

- A prompt made only of <task-notification> or <system-reminder>
  envelopes keeps the active route like a short reply, and never
  creates a route. Text next to an envelope is a normal prompt.
- getzilla spec generate/validate --change-id use the package folder
  that change start created (the runtime one; legacy tracked packages
  stay readable), so no stray file lands in engineering/changes.
- getzilla change transition answers a refused move with exit 2 and
  the allowed next states, not a traceback.

Fixes #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* workflow: a notice skips routing only when no route exists (#188 review)

Review of #188: a notice next to a route of another session, or a
closed route, built no route, so the session fell back to that route.
Such a route is now replaced as before; the no-route rule applies only
when no route exists at all. The transition hint names allowed states
only for an invalid move, not for a gate refusal. Tests cover both
cases and envelope edge forms (zero-width tail, attributes, case,
crossed tags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: #134 slice B — runner verdicts, report escaping, protected-write session

- run_named_tests and run_trust_tests use _run_directory, so a failed
  cleanup of the run folder (a locked file on Windows) no longer
  replaces the test verdict.
- getzilla report prints journal route ids with control and format
  characters replaced by '?'.
- getzilla protected-write binds the caller session like approve, so a
  session-bound grant follows its later routes on that path too.
- Tests: cleanup error keeps both verdicts; hostile route id; the
  protected-write CLI with and without a session; the approve CLI
  GETZILLA_SESSION_ID fallback and CLAUDE_CODE_SESSION_ID precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix: escape the report profile too; the cleanup test removes its folders (#189 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(eu-ai-act): cover the deferred negative cases from #122

- accepted_by: missing, empty, short or non-string role; missing,
  empty, malformed or impossible date.
- Matrix sources: ftp, gopher, file and mailto schemes and a URL
  without a host are refused.
- Attestation identity: attestation_id, key_id and policy_digest
  missing, empty or of the wrong type are refused.
- The verifier refusal uses a working executable inside the project,
  so only the location guard can stop it.
- The first audit call uses the captured providers, never the engine
  loader.
- An archive install (getzilla.zip, no inherited PYTHONPATH) finds the
  scaffold templates through STACK_DIR.

Each test fails when its guard is removed (7 of 7 mutants killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(eu-ai-act): confirmation and review dates must be past YYYY-MM-DD (#191)

date.fromisoformat on Python 3.11+ also accepts 20261020 and week dates
such as 2026-W43-1, and str() let an integer through. A future date
also passed as a human confirmation.

- _iso_day accepts only a YYYY-MM-DD string.
- A class confirmation, an exemption confirmation and a high-residual
  accepted_by refuse a date after the evaluation day.
- A risk record's reviewed date uses the same parser and refuses a
  future date.

Tests cover the basic, integer, week and future forms for each caller.
8 of 8 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): guard every MCP write tool and bind GitHub writes to the repo (#193, #184)

SIDE_EFFECT_TOOL matched a write verb only right after `__`, so GitHub
MCP tools with the verb inside the name passed with no grant:
issue_write, add_issue_comment, push_files, resolve_review_thread,
enable_pr_auto_merge, fork_repository, actions_run_trigger and more.

- The regex now covers more leading verbs, a `write` token anywhere and
  actions_run. Read tools (get_*, list_*, search_*, *_read,
  get_check_run, run_secret_scanning) stay unguarded.
- A GitHub MCP write that names owner/repo must target the repository
  that grants bind to (case-insensitive). Another repository, a partial
  or non-string target, or a project without a GitHub origin is
  refused. A call without owner/repo keeps the tool-name grant check.

Tests pin 18 write tools, 13 read tools and the repository binding.
10 of 10 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): GitHub MCP tools fail closed; camelCase and hyphen verbs (#193 review)

The review of 172e4cf found write tools that the verb list still
missed: run_workflow, manage_*_subscription, star/unstar_repository,
reprioritize_sub_issue on GitHub, and camelCase or hyphen names on
other servers (executeWrite, createJiraIssue, batchUpdate,
management-projects-create). A GitHub server under another name also
skipped the repository binding.

- mcp_side_effect: a server whose name contains "github" treats every
  tool as a write unless it is a known read (get_, list_, search_,
  download_, *_read, actions/projects get/list, run_secret_scanning).
- Other servers: a verb also ends at an uppercase letter and may start
  a "-" token; manage, star, unstar, reprioritize, run and transition
  join the list.
- The repository binding covers every GitHub-named server, and owner
  and repo must be plain GitHub names ([A-Za-z0-9._-]).
- policy.sensitive_action uses the same mcp_side_effect.

14 of 14 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): keep actions_run on every server; match verbs in the action only (#193)

c65d5ea dropped the actions_run branch, so actions_run_trigger on a
GitHub server named without "github" (mcp__gh__) needed no grant again.
It also scanned the whole tool name, so a verb in a server name such as
cloud-run made every tool on that server a write.

- SIDE_EFFECT_TOOL matches ^actions_run again.
- mcp_side_effect scans only the action part of the tool name.

16 of 16 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* fix(policy): scan every segment after the MCP server name (#193 review)

5be3a29 scanned only the last `__` segment, so a verb in an earlier
segment (mcp__x__delete__file) passed as a read with no grant. The
action is now everything after the server segment.

A write hint that appears only in the server name (slack-post) is still
not seen; that limit is tracked in #195 and pinned as a known read.

17 of 17 guard mutants are killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(support): retry project_copy cleanup after a late .git write (#196)

A passing test failed on CI because TemporaryDirectory cleanup hit
"Directory not empty: .git": something still added a file under .git
while rmtree ran. Local probes (40 hook runs, loose-object count far
below gc.auto) did not reproduce it, so the root cause stays open.

project_copy now uses _test_directory: the stdlib cleanup (which also
resets read-only files on Windows) is retried up to four times with a
short pause, and the last failure is still raised. Tests cover one
transient failure and a lasting one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(support): turn off git auto maintenance in the git fixture (#196)

Review lead: git 2.46+ runs auto maintenance detached after a commit,
and it can still take a lock under .git/objects while the fixture is
removed. project_copy(git=True) now sets maintenance.auto=false and
gc.auto=0 before its first commit; the cleanup retry stays as a
backstop.

The lasting-failure test now detaches the TemporaryDirectory finalizer,
so it no longer emits an implicit-cleanup ResourceWarning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

* test(verifier): stub ruff and bandit where tests pin later PR checks (#198)

In PR mode a missing bandit is a required refusal, and every later
check is skipped. The cloud container has no bandit, so five tests that
pin factory-postgres-exit and the full-suite marker saw "not executed
after required refusal" instead of the result they check. CI has
bandit, so they passed there.

The verifier's fail-closed behavior is right; the tests now stub _ruff
and _bandit as passing (doctor tests via _installed_linters, scope
tests in _run), so the host's tools cannot change their outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhxEnEALZRsc7kaPwG2kiR

---------

Co-authored-by: Claude <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.

2 participants