Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
50349e3
Read agent launches in CI workflows (#823)
pengfei-threemoonslab Sep 22, 2026
45795a0
Read no widening rule from an agent input that holds an expression (#…
pengfei-threemoonslab Sep 22, 2026
d4b2d7a
Address review cycle 1 on agent launches in CI (#823)
pengfei-threemoonslab Sep 22, 2026
aa3bc99
Address review cycle 1 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
2e36553
Address review cycle 2 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
5bc648f
Address review cycle 3 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
81d6930
Address review cycle 2 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
846763f
Address review cycle 3 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
de9b965
Name an unchanged instruction limit reached through an in-tree link i…
pengfei-threemoonslab Sep 23, 2026
8c6b816
Address review cycle 1 on linked unchanged limits (#822)
pengfei-threemoonslab Sep 23, 2026
62cadf4
Address review cycle 4 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
321d172
Address review cycle 5 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
70e7dd5
Address review cycle 6 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
3986c93
Address review cycle 7 on agent launches in CI (#823)
pengfei-threemoonslab Sep 23, 2026
e61eba6
Merge main and preserve linked-limit and hook-detail documentation
pengfei-threemoonslab Sep 23, 2026
f3ca453
Fix CLI option boundaries and integrate hook detail baseline semantics
pengfei-threemoonslab Sep 23, 2026
7566f00
Integrate linked-limit evidence with workflow launch comparison
pengfei-threemoonslab Sep 24, 2026
21c5eb0
Integrate reviewed dependency updates from main
pengfei-threemoonslab Sep 24, 2026
52bf1c9
Align reviewed integration with current main
pengfei-threemoonslab Sep 24, 2026
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- **The problem.** A pull request that added a Cursor plugin's `mcp.json`, removed a `beforeShellExecution` guard from `.cursor/hooks.json`, gave a dotfiles package's `claude/.claude/settings.json` `Bash(*)`, or moved a marketplace plugin's pinned `sha` printed `No static host-grant changes detected`, as a docs-only change does. Re-running a 23-PR public corpus after #812 found 11 of 23 pull requests were such coverage gaps: 0 of the 9 comparable zero-row results named the changed relevant file, and 4 of them named a file the pull request did not touch while omitting the one it did.
- **What is named.** `diff`, `verify` and the manifest-free PR comment list, under `What this run established`, each path in the comparison's own changed-file set that a bounded, documented candidate rule recognises and no reader of this entry read: `mcp.json` in a plugin directory, a plugin manifest's `mcpServers`, a Codex, Cursor or Copilot manifest's `hooks` and the hook files it names, a manifest or marketplace that does not parse, `.cursor/hooks.json`, host settings below the repository root, and an external marketplace plugin source — `plugins/demo/mcp.json (cursor): added, not read by this entry: MCP configuration in a plugin directory; no row, and loading is not established`. An external source names what it now points at, redacted, and is never fetched. The block's first line says the list includes them. Ordinary documentation, an unrelated `*.json` and an unchanged candidate name nothing.
- **What it is not.** Never a row, a widening, a `check` violation or a claim that a host loads the file. Nothing is fetched or run, only plugin manifests and marketplaces are read, and at most 32 candidates are examined; the rest, and any whose rule needed a file that was not read or did not parse, are counted as not examined, on a line that names both causes. The rules are listed in `docs/host-boundary-support.md` under *Changed inputs named but not read*.
- **JSON.** A `changed_not_read` coverage item with its `candidate` rule, ranked right after the blocking limits and inside the existing cap; `read_sources_only` is `false` while one is named, and `unread_candidates` / `unread_candidates_not_examined` say whether the change set was examined. Verifier `0.20` → `0.21`, capability diff `0.3` → `0.4`, runtime contract 40 → 41; host-grants does not move for it (#819, below, moves it to `0.7` in the same contract), and `minimum_control_contract_version` stays `21`. A `0.20` verifier reads with the search not recorded.
- **JSON.** A `changed_not_read` coverage item with its `candidate` rule, ranked right after the blocking limits and inside the existing cap; `read_sources_only` is `false` while one is named, and `unread_candidates` / `unread_candidates_not_examined` say whether the change set was examined. Verifier `0.20` → `0.21`, capability diff `0.3` → `0.4`, runtime contract 40 → 41; host-grants does not move for it (#819 and #823, below, move it to `0.7` in the same contract), and `minimum_control_contract_version` stays `21`. A `0.20` verifier reads with the search not recorded.
- **One route moves, on `verify` and `verify --preview` alike.** A manifest-free `verify` whose only host-relevant change is such an input, or a changed candidate it counts as not examined, now publishes the host comparison (advisory, exit `0`) instead of the setup route, which said nothing about the change. `verify --preview` moves the same way: its next action is now `discover` (`audit --host`) with the comparison published, where it was `initialize` (`init --write`) with none. That includes an agent-related workspace, as it already did when the change edited a host file this entry reads. The pilot ledger's source-tree column was re-measured for contract 41. Rows, digests, baselines, `audit --host`, `check` and the benchmark replays are unchanged.
- A hook row now names what changed in the hook, and an MCP row names a change to the server's launch arguments. Before, `diff`, `verify`, the manifest-free PR comment and `check` printed `PostToolUse → PostToolUse` whether the edit was to the hook's matcher, its command or its timeout, and an MCP server whose version pin moved from `example-mcp-server@1.2.3` to `@latest` read `docs: no difference in the command name npx, env key names or header key names; the change is in a detail this output does not show, such as the command's path or arguments`: the grants carried none of it, and only `config_sha256` saw the edit. On five of 23 public pull requests measured on 2026-09-15, the hook rows showed only event names. (#819, slice 2 of #795; direction is #820, an unpinned-launch note #825)
- **The rows a reviewer reads:** `PostToolUse: matcher Edit → Edit|Write|Bash`, `PostToolUse: command changed (lint.sh sha256:d075f5f4772e → curl sha256:a510416cbecc)`, `PostToolUse: timeout 10 → 600` and `docs: package example-mcp-server@1.2.3 → example-mcp-server@latest`, in `diff`, `verify` text, the PR comment and `check` text, and in `review.changes[].change` in `diff --json` and `verifier.json`; any other launch argument edit reads `launch arguments changed (sha256:… → sha256:…)`, a digest printed as its first twelve hex digits. A timeout written as text prints quoted, so it never reads as a number or a boolean: `timeout 5 → "5"`, `timeout true → "true"`. With several handlers under one event the entry names which one (`handler 2 timeout 5 → 50`), and an added or removed handler is listed as such. The same published handlers in another order read `the published handlers in a different order; a detail this output does not show may also differ, such as …`, never that they are the same handlers. An added or removed hook names its handlers, `SessionEnd (command cleanup.sh sha256:18d2c7ec39bc)`, and an added MCP server its package. When none of the published fields differ, the entry says the change is in a detail it does not show — another hook setting such as `async`, or a redacted or shortened matcher or timeout; for an MCP server, the command's path or another setting such as `cwd` — instead of repeating the same values. The row's direction, severity, `why` and loading basis are unchanged: plugin-selected (#714) and Codex hooks read as before, and no entry claims a direction (#820), runtime loading or what a command does.
Expand All @@ -20,6 +20,8 @@
- **Unchanged:** grant equality and every inventory digest leave the new members out, so a change is a row exactly when it was one before, through `config_sha256`; a value the digest's own input redacts (after `--token`, `--api-key` or `--password`, a `--password=…` value, an `X-Api-Key:` header value, a URL's path) moves no published digest, so a change confined to it is no row, as on 1.1.0; every row value, the row count, `check`'s boundary result and the control envelope's `capability_rows` publish what they did; verifier `0.21` and capability diff `0.4` do not move for it; the host-config and cold-start benchmark replays reproduce their run-of-record scores. The digest's credential-assignment rule gained a lookahead that removes its quadratic time on a long run of name characters and matches exactly what it matched, so every `config_sha256` is unchanged. Re-running `diff --json` on the 80 vendored benchmark cases with the prepared `1.1.0` commit and this tree on 2026-09-23 gave byte-identical rows on all 80; 23 entries on 22 cases changed, and each of the 7 changed hook or MCP entries (six repositories; one is vendored in both benchmarks) that read `PreToolUse → PreToolUse` or `no difference in the command name …` now names the field that changed, such as `mcp-outline: package mcp-outline==1.10.0 → mcp-outline==1.10.1` or `PreToolUse: handler 2 timeout 30 → 120`.
- **Compatibility:** a `0.6` baseline stays comparable with no new row or reason, and `audit --host --save-baseline` may now replace it; an older one is still refused, as before. Validators pinned to the `0.6` schemas reject a `0.7` inventory, baseline or drift payload; the `0.6` files stay published. See the [migration note](STABILITY.md#hook-mcp-detail-fields-819).

- Read how a coding agent is launched inside a CI workflow. A documented agent action's permission inputs (`anthropics/claude-code-action`, `anthropics/claude-code-base-action`, `openai/codex-action`), the permission flags of a `run:` that is one plain `claude -p` or `codex exec` command, and each `actions/checkout` step's `with.ref` are listed on the workflow grant and compared as text, never executed. Changing plain `claude_args` from `--allowedTools Read` to `--permission-mode bypassPermissions --allowedTools Bash`, adding a `claude -p --permission-mode acceptEdits Summarize` step, or checking out `${{ github.event.pull_request.head.sha }}` in a `pull_request_target` job each gave no row and now gives one naming `job/step` and both values in `diff`, `check`, manifest-free `verify` and the PR comment. Only a documented rule a job's launches gain widens — bypassed permission checks (a flag, or JSON settings whose `defaultMode` is `bypassPermissions`), a bypassed or `danger-full-access` sandbox (`permission-profile: :danger-full-access` included), `safety-strategy: unsafe`, or a user gate opened to `*` — and every other edit is `changed`; a workflow row that runs an agent now names the untrusted-input trigger, write scopes, secrets and pull request checkout beside each agent step, so a move to `issue_comment` with `pull-requests: write` says which agent step it reaches. Shell is not parsed: a `run:` is read only when it is one line of plain words (no quote, expansion, operator, redirection, comment or continuation) run by `bash` or `sh` (a `shell:` template only when it runs the script alone, never `bash -c '…' {0}`), and `claude_args` / `codex-args` only when they are a plain list of words with no `--settings` or `--mcp-config` flag. Any other `run:` that mentions `claude` or `codex` is listed in `unread_agent_runs` and named as a non-blocking limit in `audit --host`; it publishes none of its text, is never compared and gives no row, and the workflow's line in `What this run established` names what its grant does not read instead of env values and `apiKeyHelper`. Any other argument input, the #823 reproduction's quoted `--allowedTools "Read"` included, is `unread_arguments`: compared by a digest, so editing it is a `changed` row, and read for no rule. The rules each launch meets are read from the declared text and published as `widening_rules`, so redaction never hides one; one a launch already met in a job it left (a renamed job, a moved step) is moved, not gained, though never while that job keeps any unread step of that agent or a launch of it with an unread input the rule is read from, nor while any other job but the receiving one holds more of them than before, so renaming a job while quoting its launch, as another job adds that launch plainly, is a widening; one the job's unread step or unread input may already have met is named, not claimed; and an unread step that remains takes no gain from another launch. A launch that becomes one this audit does not read (`npx`, quoting, `codex` options before `exec`) is worded as no longer declaring a launch this audit reads, never as no longer starting an agent. A JSON object in a `settings` or `mcp_config` input publishes its shape and none of its free text: key names, with `env` and `headers` values and `apiKeyHelper` redacted and every other string a `<withheld:…>` digest, except the strings a host reader publishes (a permission rule, a documented setting's value, an MCP server's command name and URL host), so an MCP server's arguments and a hook's command are compared but never published, and a value there that is neither a JSON object nor a plain file path publishes only a digest; a codex `--config` override publishes its key and, except for the sandbox, permission profile, approval policy and model, only a digest or `<redacted>` for its value; a URL elsewhere publishes its scheme and host; other credential-shaped text in a setting, prose such as "never print bearer tokens" included, is published redacted, compared as published and named as a non-blocking limit, while a redacted checkout ref refuses as a redacted step reference does. Every published value goes through the #802 label redaction. Host-grants inventory, baseline and drift move to `0.7`, because `0.6` shipped in 1.1.0, and the unreleased runtime contract 41 (#821) is extended in place; a `0.4`–`0.6` baseline holding a workflow grant is incomparable (`baseline_workflow_agent_launches_unavailable`), and the verifier `0.21` and capability diff `0.4` #821 minted do not move here. See the `Migration Note: Unreleased` entry in [`STABILITY.md`](STABILITY.md#workflow-agent-launches-contract-v41-823). (#823)

- A plugin directory that cannot be compared no longer hides the host changes outside it. (#808)
- **The problem.** A pull request that broke `plugins/demo/.claude-plugin/plugin.json` and also dropped a `deny` rule from `.claude/settings.json` printed `Cannot compare against main: head_inventory_incomplete` and no row on `diff`, `verify` and the manifest-free PR comment, where the published `1.0.0` showed the removed denial. The plugin-reference limit #714 introduced refused the whole comparison, including files that plugin cannot reach.
- **What changes.** When every blocking limit that refused a comparison is a plugin-reference limit bounded by its plugin directory — a reference is followed only inside it, so that is all it can hide — and nothing outside the directory depends on it, the comparison is `partial`: the directory is left uncompared on both sides and named, and the rows outside it are published. `diff` opens with `Partial comparison against main (…) -> working tree: head_inventory_incomplete` and `Not compared: plugins/demo, a plugin directory this entry could not read completely, …` before any row; `verify` and the PR comment open with `Host capability comparison partial: …` and the same line. A partial result with no row says it is not a no-change answer and never prints `No static host-grant changes detected`.
Expand Down
Loading
Loading