Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Changes

- **An OpenShell policy row names the declarations that changed.** Replaying openshift-kni/ai-sandbox#29, a selected policy that removed `/sandbox/.uv/python` from `filesystem_policy.read_only`, changed binary selectors and added GitHub release destinations printed `authored, OpenShell 0.1.2, 6 endpoint(s), facts <digest> → …7 endpoint(s), facts <digest>`, which names none of them. `diff` (text and `--json`), `verify`, `check` and the PR comment now print the difference: `filesystem read_only -/sandbox/.uv/python`, `destination aiplatform.googleapis.com:443 (l4) binaries +/tmp/**/node_modules/…/claude -/usr/local/bin/claude`, `removed destination oauth2.googleapis.com:443 (l4) via /usr/local/bin/claude` and `added destinations objects.githubusercontent.com:443, release-assets.githubusercontent.com:443 (l4) via …`, with changed request declarations, `include_workdir`, Landlock compatibility and process identity named the same way. Each list names its first entries and counts the rest. Only values the OpenShell inventory already publishes are named, and a value that would change under redaction prints as `<redacted>`. Query and parameter constraints and endpoint options appear by field name, not value. A policy selected or removed whole lists what it declares and which sections and inspected endpoints took an upstream default, as a description and not a proof that authority grew. Display only: the published `rows[]`, direction, `expands`, severity, expansion signals and `check` decisions are unchanged, the existing unknown-direction and runtime-limit wording stays in each row's reason, and no schema changes. (#968)
- **`diff` classifies a large base tree about eight times faster.** Translating each boundary glob into a regular expression was repeated for every path in the tree and was 74–86% of first-run `diff` time on two large repositories (#698's measurement). The compiled pattern is now cached per pattern; which paths match is unchanged, and a test checks the cached matcher against the previous implementation on over 10,000 random pattern and path pairs. (#698)
- **A hook row names a change to its `args` or to a background setting.** Changing only a command hook's exec-form arguments (`command: "python3", args: ["guard-readonly.py"]` → `["guard-write.py"]`), or only `"async": true` to `"asyncRewake": true`, was a `changed` row reading `no difference in the matcher, command or timeout; the change is in a detail this output does not show`. Each handler now publishes its `args` as a script path and a digest (host-grants `0.9`, extended in place): the first argument shaped as a relative script path that no redaction rule rewrites, alone or after the arguments before it, and a digest of the rest, as an MCP server's arguments are published, so no flag, value, absolute path or URL is shown. It also publishes the documented `type`, `async`, `asyncRewake`, `shell` and `once` settings it declares. The rows read `PreToolUse: args script guard-readonly.py → guard-write.py`, `args changed (sha256:… → sha256:…)` and `PostToolUse: async true → (none); asyncRewake (none) → true` on `diff` (text and `--json`), `verify`, the PR comment and `check`, and an added hook's cell lists them. The direction stays unknown, no script effect is inferred, and the fields are left out of grant equality, the digests and saved baselines, so no row, severity, expansion signal, loading basis or `check` decision moves, and rotating a value the digest already redacts is still no row. A change confined to `if`, `statusMessage` or another unpublished field still says it is not shown, and now names them. See [STABILITY.md](STABILITY.md#hook-args-and-settings-972). (#972, #971)
- **A hook row describes an inline command's structure, or says the digest moved and the config has to be opened.** Replaying CirrusRedOrg/EntityFrameworkCore.Jet#303, an edited `PreToolUse` script read `command changed (<not-shown> sha256:f23acba4b10f → <not-shown> sha256:3f1dc36980b7)`: two digests, and no name because the script opens with an assignment. Each handler's `command` now also publishes a `shape` (host-grants `0.9`, extended in place), read by a bounded, static reader that never runs the command: the plain-token programs at its command positions, the counts of commands, pipes, `$( … )` substitutions, `if`/`while`/`for` keywords and quoted strings, the redirects to a file (the target only for `/dev/null` or a repository-relative path), and a script path published as `args` publish one. A command the reader refuses whole (a here-document, a backquote, `case`, a function, an unterminated quote, more than 8,192 characters, or a `shell` other than `bash`) is `shape_limit` instead. The Jet row now reads `…; same programs (cat, printf, sed, grep, echo, true); simple commands 15 → 25; pipes 5 → 9; conditionals and loops 4 → 7; quoted strings 18 → 32)`; an added pipeline stage reads `programs +sh; pipes 1 → 2`, an added redirect `redirects +>> logs/x.log`, a moved script `script a.py → b.py`; an edit that keeps the structure says `same programs and structure; the change is in an argument or in quoted text this output does not show, open the config to read the change`, and an undescribed command `not described: head command uses shell syntax this output does not describe; the digest moved, open the config to read the change`. `diff` (text and `--json`), `verify`, the PR comment and `check` print the same entry. No argument, quoted string, URL, credential or absolute path is published (every shape #987 withholds stays withheld, and its rotations stay quiet), a program name is never inferred to widen or narrow anything, and the shape is left out of grant equality, the digests and saved baselines, so no row, severity, direction, expansion signal or `check` decision moves and the row still says `hook edit; authority direction is unknown`. See [STABILITY.md](STABILITY.md#hook-command-shape-934). (#934)
Expand Down
24 changes: 24 additions & 0 deletions docs/openshell-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,30 @@ or request-partition cells. Unsupported semantics never become inferred safe
narrowing. An HTTP method or MCP tool name still supplies no business effect,
approval, argument restriction or deployed agent binding.

## Naming the changed declarations

A changed policy row names what differs, beside the unchanged limits in its
reason, instead of two endpoint counts and a facts digest. The text is
`review.changes[].change` in `diff --json` and the `verify` host comparison, and
the entry in `diff`, `check` and the PR comment: filesystem paths added or
removed per list (`read_only`, `read_write`) and a changed `include_workdir`;
the Landlock compatibility and process identity; and, per destination, each
binary selector added or removed and each destination or request declaration
(protocol, enforcement, access preset, allowed and denied method/path pairs)
added, removed or edited. Destinations that changed alike share one entry. Each
list names its first five or eight entries, then counts the rest.

Only values the inventory already publishes are named. Query and parameter
constraints, endpoint options and credential options appear by field name, never
by value, and a value that would change under redaction is printed as
`<redacted>`. A change confined to an option value says `other options changed
(not shown)`. A policy whose changed fields none of these cover says so. A
newly selected or removed document lists what it declares, with the sections and
inspected endpoints left to an upstream default named. That list is a
description: it never marks the change as an expansion, and a row's direction,
expansion signals, severity and `check` outcome are those of the comparison
above. The published `rows[]` keep their counts and digest.

## Read limits and coverage

The shared identity-bound host reader limits individual files to 1 MiB, along
Expand Down
26 changes: 26 additions & 0 deletions src/agents_shipgate/core/capability_diff_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
from agents_shipgate.core.host_settings import rate_claude_setting, setting_value_text
from agents_shipgate.core.mcp_host_selection import UNATTRIBUTED_MCP_HOST
from agents_shipgate.core.openshell_compare import compare_openshell_grants
from agents_shipgate.core.openshell_row_detail import (
openshell_policy_change,
openshell_policy_declarations,
)
from agents_shipgate.core.permission_lattice import (
exec_equivalent_argument,
parse_rule,
Expand Down Expand Up @@ -707,6 +711,17 @@ def _call_change_reasons(changes: list[CallChange]) -> list[str]:
return reasons


def _openshell_cell(cell: str, grant: dict[str, Any] | None) -> str:
"""A wholly added or removed OpenShell policy, with what it declares beside its digest (#968).

The counts and the facts digest stay, so the cell still identifies the
document; the declarations are display only and imply no direction.
"""

declared = openshell_policy_declarations(grant)
return f"{cell}; declared: {declared}" if declared else cell


def _grant_value(
grant: dict[str, Any] | None,
*,
Expand Down Expand Up @@ -2383,6 +2398,17 @@ def capability_diff_rows(
before=_hook_cell(row.before, before_grant),
after=_hook_cell(row.after, after_grant),
)
elif kind == "openshell_policy" and before_grant and after_grant:
view = _RowView(
before=row.before,
after=row.after,
change=openshell_policy_change(before_grant, after_grant),
)
elif kind == "openshell_policy":
view = _RowView(
before=_openshell_cell(row.before, before_grant),
after=_openshell_cell(row.after, after_grant),
)
else:
view = _RowView(before=row.before, after=row.after)
# Kept with the row through sorting, never emitted as row fields.
Expand Down
Loading
Loading