Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Cargo.lock.bak

# local state
/.codex-runs/
/.urd/
/.gommage
*.ed25519
*.sqlite
Expand Down
14 changes: 7 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ Versioning: [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) —
`time`, and `uuid`; this class of change requires a green determinism suite
before merge.
- `gommage tui` now uses a focused Overview, Approvals, and Inspect workflow
instead of an eight-tab information dump. The approval workbench keeps the
selected request, scope, Picto boundary, draft, confirmation, and result in
view; terminals below 80x24 safely fall back to a compact guide.
- Snapshot and watch inspection no longer initialize Gommage homes or migrate
Picto databases. Read-only operator views load a captured inspection model,
while commands that need runtime state retain the explicit initialization
path.
instead of an eight-tab information dump. It captures state before rendering,
keeps the selected request, scope, input-binding boundary, draft,
confirmation, and result in view, and falls back safely below 80x24.
- Snapshot and bounded watch inspection now use non-initializing policy reads
and strict read-only Picto SQLite access. They cannot create a home, generate
a key, migrate a legacy database, or leave WAL/SHM sidecars; commands that
need runtime state retain the explicit initialization path.

### Added

Expand Down
17 changes: 17 additions & 0 deletions capabilities/bash.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@
# refspec position and slip a push past the branch gate. The mapper also
# strips redirections from the per-segment candidate (see collect_candidates);
# this charset is the defense-in-depth backstop on the raw command.
# A refspec with an empty source side deletes the destination branch:
# `git push origin :main` is the classic way to delete a remote branch. The
# generic mapper below captures the whole refspec, so that command emitted
# `git.push:refs/heads/:main`, which matches no branch gate. Deleting a
# protected branch therefore slipped through while a plain push to the same
# branch was gated. This mapper emits the destination branch so the existing
# branch gates apply, plus a dedicated capability so policy can gate deletion
# of any remote branch independently of the branch name.
- name: bash-git-push-delete-refspec
tool: Bash
match_input:
command: "^\\s*git\\s+push(?:\\s+[-\\w]+)*\\s+(?P<remote>[\\w.-]+)\\s+\\+?:(?P<ref>[^\\s<>&|;]+)"
emit:
- "git.push:refs/heads/${ref}"
- "git.push.delete:${ref}"
- "net.out:github.com"

- name: bash-git-push
tool: Bash
match_input:
Expand Down
17 changes: 17 additions & 0 deletions crates/gommage-stdlib/capabilities/bash.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@
# refspec position and slip a push past the branch gate. The mapper also
# strips redirections from the per-segment candidate (see collect_candidates);
# this charset is the defense-in-depth backstop on the raw command.
# A refspec with an empty source side deletes the destination branch:
# `git push origin :main` is the classic way to delete a remote branch. The
# generic mapper below captures the whole refspec, so that command emitted
# `git.push:refs/heads/:main`, which matches no branch gate. Deleting a
# protected branch therefore slipped through while a plain push to the same
# branch was gated. This mapper emits the destination branch so the existing
# branch gates apply, plus a dedicated capability so policy can gate deletion
# of any remote branch independently of the branch name.
- name: bash-git-push-delete-refspec
tool: Bash
match_input:
command: "^\\s*git\\s+push(?:\\s+[-\\w]+)*\\s+(?P<remote>[\\w.-]+)\\s+\\+?:(?P<ref>[^\\s<>&|;]+)"
emit:
- "git.push:refs/heads/${ref}"
- "git.push.delete:${ref}"
- "net.out:github.com"

- name: bash-git-push
tool: Bash
match_input:
Expand Down
12 changes: 12 additions & 0 deletions crates/gommage-stdlib/policies/20-git.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
- "git.stage:bulk"
reason: "Do not stage the whole tree. Stage explicit paths instead, for example `git add path/to/file.rs path/to/test.rs`."

# Deleting a remote branch destroys shared work and is not recoverable from
# the local clone alone. Gate it for every branch, not only the protected ones:
# an agent that deletes a colleague's feature branch has done real damage even
# though pushing to that same branch is allowed.
- name: gate-remote-branch-delete
decision: ask_picto
required_scope: "git.push.delete"
match:
any_capability:
- "git.push.delete:*"
reason: "deleting a remote branch destroys shared history; require a signed picto (scope git.push.delete)"

- name: gate-main-push
decision: ask_picto
required_scope: "git.push:main"
Expand Down
33 changes: 19 additions & 14 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,29 @@ to capture a human-readable report without ANSI control sequences. Automation
should still parse the JSON commands below instead of the TUI.

`gommage tui --view dashboard|approvals|policies|audit|capabilities|recovery|onboarding|metrics|all`
selects operator views. `--view all` is the most useful issue-report snapshot:
it includes readiness, pending approvals, policy inventory, signed audit
summary, mapper inventory, recovery shortcuts, and local metrics. `gommage tui
--watch` prints the same report repeatedly as plain text; use
`--watch-ticks <n>` to bound demos, CI artifacts, and issue-report captures.
selects operator views. A single named snapshot prints only that view; use
`--view all` for the full issue-report capture with readiness, pending
approvals, policy inventory, signed audit summary, mapper inventory, recovery
shortcuts, and local metrics. `gommage tui --watch` repeats the selected
plain-text view; use `--watch-ticks <n>` to bound demos, CI artifacts, and
issue-report captures.
`gommage tui --stream` prints a compact live decision/event feed using daemon
IPC when the daemon is reachable and the signed audit log otherwise. Stream and
snapshot output include daemon reachability, active picto counts, pending
approval counters, webhook DLQ counts, decision counters, and audit anomaly
counts when verification is available. Interactive mode uses `1` for
Overview, `2` for Approvals, and `3` for Inspect. Keys `3`-`8` jump
directly to an inspection section, while `[` and `]` move between those
sections. In the approvals view, `t/T` changes the TTL preset, `u/U` changes
the use-count preset, `i` reveals technical request context, and `A` /
`D` stage an approve/deny action for the selected request. The preview shows
the tool, scope, Picto binding, reason, and proposed grant; technical context
adds the request ID, input hash, policy version, and matched rule. `y` is
required only from a visible confirmation dialog before mutating state.
counts when verification is available. Interactive mode keeps three primary
areas: `1` Overview, `2` Approvals, and `3` Inspect. Keys `3`-`8` jump
directly to policy, audit, capability, recovery, onboarding, and metrics
inspection; `[` / `]` cycle those sections. In Approvals, the preview shows
the tool, scope, input-binding mode, reason, and proposed TTL/use grant before
confirmation. `i` reveals the request ID, input hash, policy version, and
matched rule; `t/T` changes TTL, `u/U` changes uses, and `A` / `D` stage the
action. `y` is required only from the visible confirmation dialog before any
state changes.
Sanitized demo assets live at `docs/assets/tui-dashboard.gif` and
`docs/assets/tui-dashboard.svg`; update both whenever the TUI's primary
sections or vocabulary change.

`gommage doctor` is the lower-level operator installation health check. Use the default text output for humans and `gommage doctor --json` when you need only filesystem/runtime diagnostics.

`gommage agent status <claude|codex>` is the host-agent integration check. Use
Expand Down
13 changes: 8 additions & 5 deletions docs/pictos.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,14 @@ gommage tui --watch --watch-ticks 3 --view approvals
gommage tui --stream --stream-ticks 5
```

Interactive TUI approval is intentionally two-step. Operators can use `t/T` to
cycle TTL presets and `u/U` to cycle use-count presets, then `A` or `D` stages
the selected pending request. `y` is required before Gommage mints a picto or
records a denial. Snapshot and bounded watch modes are read-only and include
selected-request detail plus replay/evidence commands for support.
Interactive TUI approval is intentionally two-step. The approval workbench
shows the tool, scope, scope-only versus exact-input boundary, policy reason,
and chosen TTL/use grant before any forensic detail. Operators can use `t/T`
to cycle TTL presets, `u/U` to cycle use-count presets, `i` to reveal
technical request context, then `A` or `D` to stage the selected pending
request. `y` is required before Gommage mints a picto or records a denial.
Snapshot and bounded watch modes are read-only and include selected-request
detail plus replay/evidence commands for support.

Replay and evidence commands are for debugging and support. Replay evaluates the
stored request capabilities against the current policy, so an operator can see
Expand Down
20 changes: 20 additions & 0 deletions examples/policy-fixtures.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ cases:
required_scope: git.push.force
matched_rule: no-force-push

- name: gate_delete_protected_branch_by_refspec
description: An empty-source refspec deletes the remote branch. This used to be allowed because the ref capture emitted refs/heads/:main, which matched no branch gate, so deleting main slipped through while pushing to it did not.
tool: Bash
input:
command: "git push origin :main"
expect:
decision: ask_picto
required_scope: git.push.delete
matched_rule: gate-remote-branch-delete

- name: gate_delete_feature_branch_by_refspec
description: Deleting any remote branch is gated, not only protected ones, because removing a colleague's branch destroys shared work even where pushing to it is allowed.
tool: Bash
input:
command: "git push origin :feat/some-work"
expect:
decision: ask_picto
required_scope: git.push.delete
matched_rule: gate-remote-branch-delete

- name: ask_web_fetch
description: Agent-native web fetches cross the local trust boundary and should require approval.
tool: WebFetch
Expand Down
12 changes: 12 additions & 0 deletions policies/20-git.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
- "git.stage:bulk"
reason: "Do not stage the whole tree. Stage explicit paths instead, for example `git add path/to/file.rs path/to/test.rs`."

# Deleting a remote branch destroys shared work and is not recoverable from
# the local clone alone. Gate it for every branch, not only the protected ones:
# an agent that deletes a colleague's feature branch has done real damage even
# though pushing to that same branch is allowed.
- name: gate-remote-branch-delete
decision: ask_picto
required_scope: "git.push.delete"
match:
any_capability:
- "git.push.delete:*"
reason: "deleting a remote branch destroys shared history; require a signed picto (scope git.push.delete)"

- name: gate-main-push
decision: ask_picto
required_scope: "git.push:main"
Expand Down
Loading