@uchibeke/gallivant 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CONTRIBUTING.md +26 -0
  2. package/LICENSE +204 -0
  3. package/README.md +201 -2
  4. package/SECURITY.md +19 -0
  5. package/bin/gallivant.mjs +257 -0
  6. package/bin/package-plugin.mjs +16 -0
  7. package/bin/release-live.mjs +29 -0
  8. package/bin/release-package.mjs +23 -0
  9. package/bin/session-runtime.mjs +13 -0
  10. package/bin/task-delivery-state.mjs +12 -0
  11. package/docs/README.md +70 -0
  12. package/docs/agent-setup.md +214 -0
  13. package/docs/app.md +111 -0
  14. package/docs/architecture.md +265 -0
  15. package/docs/chains.md +102 -0
  16. package/docs/codex-cloud.md +284 -0
  17. package/docs/commands.md +620 -0
  18. package/docs/config.md +430 -0
  19. package/docs/execution.md +796 -0
  20. package/docs/guardrails.md +311 -0
  21. package/docs/install.md +338 -0
  22. package/docs/launch.md +42 -0
  23. package/docs/learning.md +369 -0
  24. package/docs/network-recovery.md +101 -0
  25. package/docs/openai-agents.md +457 -0
  26. package/docs/prd.md +246 -0
  27. package/docs/preparation.md +110 -0
  28. package/docs/release-qualification.md +196 -0
  29. package/docs/releasing.md +140 -0
  30. package/docs/runtime-environment.md +113 -0
  31. package/docs/task-delivery.md +476 -0
  32. package/docs/tasks.md +739 -0
  33. package/lib/aport.mjs +95 -0
  34. package/lib/attribution.mjs +95 -0
  35. package/lib/block.mjs +89 -0
  36. package/lib/capabilities.mjs +3 -0
  37. package/lib/chains.mjs +222 -0
  38. package/lib/codex_cloud.mjs +160 -0
  39. package/lib/codex_cloud_config.mjs +20 -0
  40. package/lib/codex_cloud_input.mjs +49 -0
  41. package/lib/codex_cloud_result.mjs +74 -0
  42. package/lib/commands/doctor.mjs +457 -0
  43. package/lib/commands/init.mjs +34 -0
  44. package/lib/commands/learn.mjs +54 -0
  45. package/lib/commands/new.mjs +47 -0
  46. package/lib/commands/ready.mjs +45 -0
  47. package/lib/commands/register.mjs +195 -0
  48. package/lib/commands/resume.mjs +224 -0
  49. package/lib/commands/serve.mjs +349 -0
  50. package/lib/commands/start.mjs +247 -0
  51. package/lib/commands/start_services.mjs +22 -0
  52. package/lib/commands/status.mjs +150 -0
  53. package/lib/commands/task.mjs +73 -0
  54. package/lib/commands/up.mjs +228 -0
  55. package/lib/commands/why.mjs +143 -0
  56. package/lib/config.mjs +461 -0
  57. package/lib/context.mjs +100 -0
  58. package/lib/control.mjs +202 -0
  59. package/lib/control_contract.mjs +16 -0
  60. package/lib/coordinator.mjs +19 -0
  61. package/lib/credential.mjs +161 -0
  62. package/lib/daemon.mjs +156 -0
  63. package/lib/deliverable.mjs +357 -0
  64. package/lib/events.mjs +261 -0
  65. package/lib/executable.mjs +31 -0
  66. package/lib/execution.mjs +631 -0
  67. package/lib/execution_contract.mjs +15 -0
  68. package/lib/execution_input.mjs +4 -0
  69. package/lib/execution_io.mjs +94 -0
  70. package/lib/execution_journal.mjs +69 -0
  71. package/lib/execution_preparation.mjs +29 -0
  72. package/lib/execution_result.mjs +266 -0
  73. package/lib/fs.mjs +133 -0
  74. package/lib/gh.mjs +289 -0
  75. package/lib/git.mjs +434 -0
  76. package/lib/harness.mjs +521 -0
  77. package/lib/harness_log.mjs +432 -0
  78. package/lib/hash.mjs +16 -0
  79. package/lib/identity.mjs +98 -0
  80. package/lib/learning.mjs +654 -0
  81. package/lib/learning_contract.mjs +2 -0
  82. package/lib/learning_data.mjs +135 -0
  83. package/lib/learning_input.mjs +111 -0
  84. package/lib/learning_qualification.mjs +67 -0
  85. package/lib/learning_signer.mjs +151 -0
  86. package/lib/learning_store.mjs +88 -0
  87. package/lib/log.mjs +53 -0
  88. package/lib/network_diagnostics.mjs +64 -0
  89. package/lib/network_recovery.mjs +42 -0
  90. package/lib/notify.mjs +58 -0
  91. package/lib/openai_agents.mjs +373 -0
  92. package/lib/openai_agents_config.mjs +14 -0
  93. package/lib/openai_agents_http.mjs +87 -0
  94. package/lib/openai_agents_input.mjs +15 -0
  95. package/lib/openai_agents_sources.mjs +112 -0
  96. package/lib/owner_delivery.mjs +443 -0
  97. package/lib/plugin_package.mjs +340 -0
  98. package/lib/prd.mjs +635 -0
  99. package/lib/preparation_agents.mjs +33 -0
  100. package/lib/preparation_codex.mjs +212 -0
  101. package/lib/probe.mjs +126 -0
  102. package/lib/prompt.mjs +238 -0
  103. package/lib/provider_health.mjs +55 -0
  104. package/lib/public-format.mjs +31 -0
  105. package/lib/public_output.mjs +49 -0
  106. package/lib/release_package.mjs +154 -0
  107. package/lib/repository_files.mjs +86 -0
  108. package/lib/run.mjs +385 -0
  109. package/lib/run_log.mjs +94 -0
  110. package/lib/runtime-environment.mjs +87 -0
  111. package/lib/runtime_dependencies.mjs +15 -0
  112. package/lib/runtime_node.mjs +107 -0
  113. package/lib/runtime_state.mjs +174 -0
  114. package/lib/sources/index.mjs +27 -0
  115. package/lib/sources/prd.mjs +50 -0
  116. package/lib/start_contract.mjs +37 -0
  117. package/lib/state.mjs +246 -0
  118. package/lib/task_allowance.mjs +31 -0
  119. package/lib/task_completion.mjs +61 -0
  120. package/lib/task_contract.mjs +450 -0
  121. package/lib/task_delivery.mjs +219 -0
  122. package/lib/task_delivery_cancel.mjs +34 -0
  123. package/lib/task_delivery_contract.mjs +61 -0
  124. package/lib/task_delivery_mcp.mjs +40 -0
  125. package/lib/task_delivery_store.mjs +43 -0
  126. package/lib/task_delivery_view.mjs +88 -0
  127. package/lib/task_environment.mjs +281 -0
  128. package/lib/task_prepare.mjs +224 -0
  129. package/lib/task_store.mjs +38 -0
  130. package/lib/tasks.mjs +513 -0
  131. package/lib/tick.mjs +1635 -0
  132. package/lib/time.mjs +36 -0
  133. package/lib/tracker/assets.mjs +43 -0
  134. package/lib/tracker/pages.mjs +320 -0
  135. package/lib/tracker/runs.mjs +102 -0
  136. package/lib/tracker/stream.mjs +74 -0
  137. package/lib/tracker_process.mjs +107 -0
  138. package/lib/verification_sandbox.mjs +181 -0
  139. package/node_modules/smol-toml/LICENSE +24 -0
  140. package/node_modules/smol-toml/README.md +605 -0
  141. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  142. package/node_modules/smol-toml/dist/date.js +156 -0
  143. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  144. package/node_modules/smol-toml/dist/error.js +67 -0
  145. package/node_modules/smol-toml/dist/extract.js +289 -0
  146. package/node_modules/smol-toml/dist/index.cjs +770 -0
  147. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  148. package/node_modules/smol-toml/dist/index.js +34 -0
  149. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  150. package/node_modules/smol-toml/dist/parse.js +164 -0
  151. package/node_modules/smol-toml/dist/primitive.js +151 -0
  152. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  153. package/node_modules/smol-toml/dist/stringify.js +219 -0
  154. package/node_modules/smol-toml/dist/struct.js +156 -0
  155. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  156. package/node_modules/smol-toml/dist/util.js +57 -0
  157. package/node_modules/smol-toml/package.json +68 -0
  158. package/npm-shrinkwrap.json +1823 -0
  159. package/package.json +105 -4
  160. package/templates/claude-settings.json +18 -0
  161. package/templates/passport.gallivant.json +83 -0
  162. package/templates/plugin/bundled-runtime.json +26 -0
  163. package/templates/plugin/schemas/mcp.schema.json +120 -0
  164. package/templates/plugin/schemas/plugin.schema.json +65 -0
  165. package/templates/plugin/task-delivery.SKILL.md +60 -0
  166. package/templates/prd.md +32 -0
  167. package/templates/tracker/tracker.css +301 -0
  168. package/templates/tracker/tracker.js +721 -0
@@ -0,0 +1,311 @@
1
+ # Guardrails
2
+
3
+ Review learning adds a separate signature boundary, not a new permission source.
4
+ Workers may propose data but cannot enroll keys, write trust/checkpoints or activate
5
+ memory. File modes and TTYs alone do not isolate a same-UID arbitrary-shell worker;
6
+ native escalation and control routes must also be excluded. Current adapters are
7
+ UNQUALIFIED for learning application and remain collection only. OpenSSH signing
8
+ pins the executable, removes agent/askpass/loader variables, requires an encrypted
9
+ dedicated key and verifies exact canonical content before activation and dispatch.
10
+ An explicit `resume --fresh-session` requires settled ownership and cannot clear
11
+ unrelated stop fences or reset cumulative execution allowance. Lost replies use
12
+ the same session-identity checks as submission. This does not qualify an otherwise
13
+ unsupported learning profile.
14
+ See [learning](learning.md) for limits, recovery and actual missing qualification.
15
+
16
+ Unattended runs act with your GitHub identity on your repository. Controls depend
17
+ on the execution provider: local Claude and Codex have the sandbox/hook setup
18
+ described below; Gemini, OpenCode and Goose retain their native permission models
19
+ and do not inherit those guarantees merely because an adapter exists. Hosted
20
+ Agents has a separate sandbox/artifact-publication boundary described next.
21
+ The APort pre-tool hook and deliverable-completion gate are separate controls.
22
+
23
+ ## Hosted execution and preparation
24
+
25
+ Hosted Agents receives the approved source snapshot and configured network/package
26
+ policy, not the coordinator's GitHub/API credentials, private context directories
27
+ or local hook files. The coordinator checks ownership, input revision, artifact
28
+ paths/hashes and mandatory Verify results before publishing with its own scoped
29
+ GitHub credential. Native sandboxed verification must qualify on the coordinator
30
+ host; it never falls back to running repository commands unrestricted.
31
+ Provider success is not a completion verdict. See [execution](execution.md) and
32
+ [Agents setup](openai-agents.md) for the exact contract and remaining live gates.
33
+
34
+ A remote provider is refused when the task requires a local policy it cannot
35
+ enforce. Codex Cloud currently refuses all new dispatch because the selected
36
+ Cloud environment cannot be verified against the intended repository. Neither a
37
+ completion receipt nor a manual UI observation substitutes for that binding.
38
+
39
+ Natural-language preparation uses a narrower boundary than implementation:
40
+ Claude tools-off, isolated Codex profile or Agents text-only/no-environment.
41
+ Selected text grants no implementation authority. Unknown or unsupported
42
+ preparation capabilities fail closed. See [preparation](preparation.md).
43
+
44
+ ## Local execution controls
45
+
46
+ This table describes local Claude/Codex setup unless another provider is named.
47
+
48
+ | Guarantee | Control | APort's part |
49
+ |---|---|---|
50
+ | Filesystem access is bounded | Claude: the Bash sandbox in the Gallivant settings file. Codex: `workspace-write`, with boundary-crossing commands reviewed by Codex's automatic reviewer. An approved command runs outside that sandbox; the reviewer is a decision, not filesystem isolation. | Denies Write and Edit tool calls outside `allowed_paths`. Does not see `echo x > ~/.zshrc` inside a Bash command. |
51
+ | Reads of secrets are restricted | The sandboxes and each harness's denied reads; Codex's automatic reviewer checks escalation requests for credential probing. This is not a guarantee about an approved command running outside the sandbox. | Applies default sensitive-file restrictions and `data.file.read.blocked_patterns` to represented file reads. Directory enumeration and unrepresentable calls can be refused. Does not see `cat .env` inside Bash. |
52
+ | Network limited | Claude: `sandbox.network.allowedDomains`, with the macOS certificate-service exception described below. Codex: none once `network_access=true`, which `git push` needs. | Denies WebFetch and WebSearch outside `allowed_domains`. Does not see `curl` inside Bash. |
53
+ | No push to the default branch, no force push | A GitHub ruleset on each repo: no deletion, no force push, pull request required, no bypass actors. `doctor` prints the call. Sessions are also told not to rebase, because keeping a branch on its base is the daemon's job and it does it with a merge commit, which needs no force. | `blocked_patterns` checks command text using word-boundary/glob matching for single words and literal matching for phrases such as `push --force`. Best effort; APort does not parse refspecs. |
54
+ | Blast radius of a successful injection | Optional but recommended: a fine-grained PAT limited to the participating repos with no Workflows permission (`github_token_file`). By default runs act as your own login, which reaches every repo it can. Either way the credential is resolved by the daemon and handed to the run in its environment, so `~/.git-credentials`, the keychain and `~/.config/gh` stay out of a run's path. | An audit log of every tool call, for reconstruction. |
55
+ | Runtime and dispatch bounds | Claude's `--max-budget-usd`, `run_timeout_minutes` for local runs (per repository or global; default 720), and `max_iterations` per PR. Other local harnesses have no native dollar hard cap. Remote reservations bound further dispatch, not provider billing. | none |
56
+ | No harness attribution in the history | The coordinator attempts to install a `commit-msg` hook in each checkout's `.git/hooks` at clone time and on reuse. Installation failures warn rather than abort; inspect those warnings. With the hook installed, a message with a `Co-Authored-By:` trailer naming a harness or its vendor, or a "Generated with" / robot line, exits non-zero and no commit object is created. The hook applies to local commits regardless of which harness invoked Git; hosted results also pass coordinator public-output validation. | none |
57
+ | Commits are yours, not a bot's | `user.name` and `user.email` in the run env, on git's command line where no repo can override them, resolved per run from your own git config by the daemon. No identity on the machine means the run is refused before it is spawned. | none |
58
+
59
+ ## Codex Git writes need automatic approval
60
+
61
+ Codex protects `.git` even inside a `workspace-write` checkout. Gallivant therefore pins
62
+ `approval_policy="on-request"` and `approvals_reviewer="auto_review"` for **both start and resume**. The sandbox
63
+ stays enabled. Codex's own reviewer decides whether an escalation may run, without waiting for an operator click.
64
+ A reviewer or guardrail refusal still means stop and report; changing permissions or bypassing the sandbox is forbidden.
65
+ Start, manual resume and the daemon check the configured binary's version and sign-in before launching work. An old
66
+ or unreadable version stops the launch with an upgrade instruction; the minimum is enforced, not just listed by `doctor`.
67
+ This is the [documented automatic-review path](https://learn.chatgpt.com/docs/sandboxing/auto-review), not a grant
68
+ of unrestricted access. Review calls can add usage and latency, and managed Codex policy can refuse them.
69
+
70
+ The previous `approval_policy="never"` stranded runs at `.git/index.lock`, `.git/config`, and remote-tracking ref
71
+ updates. Saved command allow rules made some simple Git commands work, while compound commands containing heredocs
72
+ still failed. A successful remote push could even exit zero after its local tracking update failed. Gallivant's shared
73
+ prompt now distinguishes a sandbox error from a policy denial, asks for the supported approval path, and requires
74
+ checking the staged diff, remote HEAD, and upstream. No personal command allow rules are required by Gallivant.
75
+
76
+ The real start/resume regression probe uses the adapter and run environment against a disposable local bare remote:
77
+
78
+ ```bash
79
+ node --test test/live-codex-git.mjs
80
+ ```
81
+
82
+ Run it from a Gallivant clone with Codex signed in. It spends model calls, makes no GitHub writes, and prints the
83
+ temporary directory holding its logs and test repository. It is deliberately separate from `npm test`. `doctor`'s
84
+ credential probe uses `git ls-remote`; that checks authentication, not Codex's Git write permissions.
85
+
86
+ ## Claude on macOS needs certificate verification
87
+
88
+ Claude's macOS sandbox blocks the Apple trust service used by Go programs such as `gh`. A run can therefore fail
89
+ with `tls: failed to verify certificate: x509: OSStatus -26276` even when its GitHub credential is valid and `curl`
90
+ works. The Claude settings template enables `sandbox.enableWeakerNetworkIsolation` to permit
91
+ `com.apple.trustd.agent`. This is a native Claude setting in the existing operator-owned settings file, loaded for
92
+ both start and resume. `doctor` detects its absence on macOS; Linux does not require it.
93
+
94
+ This permission has a real cost: [Anthropic's sandbox runtime documentation](https://github.com/anthropics/sandbox-runtime#configuration-options)
95
+ warns that it opens a potential exfiltration route through the trust service. The filesystem sandbox and domain
96
+ allowlist remain configured, but the allowlist does not bound this additional route. TLS certificate verification
97
+ stays enabled. Keep this grant in Gallivant's operator-owned Claude settings, outside the agent's writable checkout.
98
+ Do not disable the sandbox or exclude `gh` from it to repair certificate errors.
99
+
100
+ Harness adapters already translate Gallivant's shared run lifecycle into each CLI's native permissions. This
101
+ Claude/macOS requirement belongs to that setup; it adds no task-specific switch or alternative completion path.
102
+ APort's pre-tool policy and Gallivant's deliverable-completion gate remain separate checks.
103
+
104
+ ## How a run gets a GitHub credential
105
+
106
+ For local CLI implementation, the run's Git credential helper does not read a credential store. The daemon resolves a credential and puts it in the run's environment as `GH_TOKEN`, where a git credential helper carried in the same environment reads it. No file outside the worktree is named, so sandboxed Git does not need the keychain, the Linux secret service, `~/.git-credentials` or `~/.config/gh`. Agent runs also set `GH_CONFIG_DIR` inside the private clone so the GitHub CLI can use that token without loading the operator's personal config; this applies to start, resume and fix rounds. Order and details are in [config.md](config.md#github-credentials).
107
+
108
+ What bounds the credential, and what does not:
109
+
110
+ - **A scoped token still wins.** `github_token_file` is checked before anything else, so a token you deliberately narrowed is never replaced by a wider one the daemon could also have found.
111
+ - **The token is not in argv.** `ps` shows argv to every process on the machine, including processes owned by other users.
112
+ - **Launch records omit the token.** `cmd.json` records the command and arguments, not its environment. Raw child output goes to private `provider.log`; the coordinator redacts recognized credentials and supplied secret values before producing tracker logs. A child can print its environment or other sensitive data, so raw diagnostics may contain secrets and redaction is not a complete data-loss boundary.
113
+ - **Credential resolution stays in the coordinator.** The passport's represented file-read restrictions and the selected harness's sandbox restrict access to coordinator settings and credential files. These controls do not make the token already in the child's environment unreadable, and Gemini, OpenCode and Goose do not inherit Claude/Codex's sandbox controls. Use the token's scope and repository rules to limit what the credential can authorize.
114
+ - **The run's git config reaches nothing outside the worktree.** The four settings a run needs, credential helper included, travel in the environment as git's own `GIT_CONFIG_COUNT` pairs. `GIT_CONFIG_NOSYSTEM=1` drops git's system file, which on a Mac with the Xcode command line tools sets `credential.helper = osxkeychain`; without it a run had a path to your keychain whatever else was configured.
115
+ - **What it does not bound.** The credential is in the environment of a process the agent controls, so a successful injection can read it and use it for anything the token permits. That is also true of a token file the run could read, and of a credential helper the run could invoke; the environment is not worse, it is the same exposure with one fewer copy on disk. Nor does the run's git config bound it: a run owns its worktree, so it can add its own `credential.helper` to `.git/config`, which git consults first because that key is multi-valued. That was equally true when the helper lived in a global file. The controls that actually bound it are the token's own scope, which is why a fine-grained PAT limited to the participating repos is the recommendation, and the GitHub ruleset, which stops a push to the default branch whatever the token permits. Transport is not a control; scope and the ruleset are.
116
+
117
+ A local run that cannot get a credential does not start. Hosted publication
118
+ resolves that credential only in the coordinator; it is not copied into the hosted
119
+ sandbox. Spending a budget on work that cannot be published is not a useful default.
120
+
121
+ `doctor`'s `run git push` line checks the credential transport. It runs `git ls-remote` against the first configured repository with the run's environment and `HOME` pointed at a path that does not exist. It does not run inside Codex's sandbox or prove Git write access. On failure it prints git's own words: the message git prints when it has no credential names `~/.git-credentials`, which is not always the file it failed on.
122
+
123
+ ## What must be delivered
124
+
125
+ When a deliverable contract is configured, the daemon evaluates the PR body's
126
+ completion receipt against the approved criteria through
127
+ `deliverable.task.complete.v1`. It checks the required attestations, their `met`
128
+ values and nonempty evidence, the summary, configured output patterns and observed
129
+ CI state. It does not dereference evidence links or independently prove that an
130
+ attestation is true. Local Verify results in a receipt remain agent reports unless
131
+ supported by independently observed CI or review. Hosted publication additionally
132
+ executes the approved Verify commands in the coordinator's enforced OS sandbox
133
+ and records their actual exits, output and revision. Even those commands prove
134
+ only what they test. See [prd.md](prd.md) and [execution](execution.md).
135
+
136
+ `deliverable: null` disables the completion-receipt gate; reviewer and CI settings
137
+ also affect what is required for readiness. A ready state is therefore a result
138
+ under the configured gates, not a general security or semantic-correctness proof.
139
+ The pre-tool policy, provider sandbox and human merge decision remain separate.
140
+
141
+ ## What reaches a prompt
142
+
143
+ GitHub feedback is filtered by `trusted_authors`, the configured reviewer bot
144
+ and the shared event cursor. Ordinary issue comments trigger work only through a
145
+ recognized fix command. The daemon's marked comments and the harness's own thread
146
+ replies are excluded. Editing an old comment does not make it new: creation time,
147
+ not edit time, governs the cursor. Feedback received during a run is handled under
148
+ the same deduplication and head/receipt rules; receipt-only corrections preserve
149
+ pending feedback rather than discarding it.
150
+
151
+ Failing checks are named with their run ID; local harnesses fetch their logs.
152
+ Quoted GitHub text uses a four-backtick fence with internal fences neutralized
153
+ and a 4,000-character cap. This is prompt hygiene, not an injection guarantee;
154
+ provider permissions, token scope and repository rules remain the authority
155
+ boundary.
156
+
157
+ ## Extra read directories are a widening, not a feature
158
+
159
+ `context_paths` lets you hand a session read access to directories outside its checkout: a sibling repository it has
160
+ to call into, a folder of design notes, an API schema it must match. Writes do not move. On Claude Code the paths go
161
+ through `--add-dir`, and the sandbox in the Gallivant settings file still bounds every write to the worktree.
162
+
163
+ What it costs. Reading is an exfiltration path. Anything the agent reads can come back out in a commit, a PR body,
164
+ a comment or a run log, and a prompt injection in a review comment is enough to ask for it. A private key in a
165
+ directory you added is a private key in a public PR. So the recommendation is a narrow one: name the directory the
166
+ brief actually needs, not the parent that happens to contain it. `~/work/schemas` rather than `~/work`.
167
+ `~/Projects/design-system` rather than `~/Projects`.
168
+
169
+ Five paths are refused outright, and each refusal names what it protects:
170
+
171
+ | Refused | Because |
172
+ |---|---|
173
+ | The Gallivant home, or any parent of it | It holds the registry, the run logs and `claude/settings.json`, the sandbox definition that bounds the run's writes. A run that can read it can read the rules it is being held to. |
174
+ | `~/.claude`, `~/.codex`, `~/.config/opencode` | Harness credentials and config. These are already in the passport's `blocked_paths`; refusing them here means the read never gets as far as the hook. |
175
+ | `~/.aport` | The passport itself, and the audit trail. |
176
+ | Your home directory | Every credential, token, key and browser profile on the machine. If the answer to "which directory does this brief need" is `$HOME`, the brief needs splitting, not more access. |
177
+ | Anything relative, or that does not exist | A relative path means something different depending on where the daemon was started. A missing one is a typo, and silently granting nothing would hide it. |
178
+
179
+ Every one of those refusals compares the path with its symlinks resolved, not the path as written, because a link to `$HOME` or to `~/.claude` is the thing being refused however it is spelled.
180
+
181
+ A parent of a refused directory is refused too, since granting the parent grants the child.
182
+
183
+ What each harness does with the list:
184
+
185
+ | Harness | Effect |
186
+ |---|---|
187
+ | Claude Code | `--add-dir <dir>` per path. Read access widens, the sandbox still bounds writes. |
188
+ | Codex | Refused. `--add-dir` makes a directory **writable**, and the only read permission Codex documents is `disk-full-read-access`, the whole disk. Giving a run the entire filesystem to read in order to hand it one directory is worse than the problem; the run fails with that explanation instead. |
189
+ | Gemini CLI | Refused, for the same shape of reason. `--include-directories` adds to the *workspace*, and Gemini's own file-system tool reference says every file tool, `write_file` included, operates inside the workspace root. There is no read-only spelling. |
190
+ | opencode, goose | No directory flag exists, so the list has no effect. The run proceeds unchanged rather than failing over a setting the CLI cannot honour, and `gallivant doctor` says so per harness when you have a list configured. |
191
+ | Hosted providers | Private local `context_paths` are refused. Supply authorized portable repository documents through the supported source snapshot instead. |
192
+
193
+ Nothing here escalates quietly. A harness that cannot do what was asked either refuses the run or reports that the
194
+ setting did nothing. In the daemon a refusal stops that entry with the reason `context-paths`, since the same
195
+ refusal would come back on every poll; `gallivant why <handle>` prints it.
196
+
197
+ A PRD can add to the list for itself with a `context_paths:` key in its frontmatter, held to exactly the same
198
+ refusals. It is the operator's list either way: the daemon's fix rounds read the snapshot of the brief taken when
199
+ the run started, not the copy in the checkout, so a session cannot widen its own reads by editing the PRD it was
200
+ given.
201
+
202
+ ## Recovery from GitHub is guarded
203
+
204
+ On boot, `up` registers only PRs authored by the token's login, with same-repo heads, on `work/*` or legacy `gallivant/*` branches, whose decoded recovery block passes strict validation (harness-specific session, repository-relative Markdown PRD path, known harness). Iteration is recovered by counting the daemon's own marked comments, never read from the block, so editing a PR body cannot reset the cap.
205
+
206
+ ## The passport
207
+
208
+ `templates/passport.gallivant.json`: capabilities for file read and write, command execution, web fetch and subagent creation. `allowed_commands` is `["*"]` on purpose: a restrictive list denies any command containing `&&`, `;` or `|`, so the first `git add -A && git commit` of every run would fail. Write `allowed_paths` is the worktrees root.
209
+
210
+ `data.file.write.blocked_paths` and `data.file.read.blocked_patterns` cover
211
+ `~/.claude`, `~/.codex`, `~/.config/opencode`, `~/.gallivant` and `~/.aport`.
212
+ These are different policy fields: the local read evaluator does not enforce
213
+ `blocked_paths`. Existing local passports copied from older templates must move
214
+ their read list to `blocked_patterns`; rerun `doctor` afterward. The directories
215
+ hold credentials and operator-owned rules, so the represented read and write
216
+ tools need both restrictions. Shell commands remain subject to the separate
217
+ sandbox and command policy, as described above.
218
+
219
+ That template is the **local-mode default**, and `doctor` checks both policy
220
+ fields on `<aport dir>/passport.json`, naming missing restrictions. A **hosted**
221
+ passport has limits in the APort dashboard; `doctor` reports the configured
222
+ identity but cannot inspect or change those limits. Configure and verify the
223
+ hosted read/write policies separately rather than treating a local check as proof
224
+ of hosted enforcement.
225
+
226
+ ## What a session is told it may not do
227
+
228
+ The sandbox and the ruleset stop what they can see. Three things they cannot see are covered by the rules every prompt carries (`lib/prompt.mjs`), which means they are asked for rather than enforced, and the run log is where you check they were honoured.
229
+
230
+ - **No merge, no default branch, no new PRs, no force-push, no rebase.** Keeping the branch on its base is the daemon's job, done with a merge commit, so a rebase is not help; it rewrites commits a reviewer has already commented on.
231
+ - **No `git stash`, in any form.** This one is not about the branch. The stash stack belongs to the repository, not to a worktree: every worktree of the same clone shares one stack. So a `stash pop` in a run can drop another worktree's entry into its tree, and a `stash push -u` sweeps up untracked files nobody meant to move. The quiet failure is a session that stashes and is then killed or runs out of budget before popping, which leaves your tree changed with nothing in the run's state saying so. A real run did this twice, to find out whether a test failure was pre-existing. That is a fair question, so the rule names the answers that do not touch shared state: a second tree with `git worktree add <temp path> <base>`, or one file at the base with `git show <base>:<path>`.
232
+ - **No harness attribution.** No `Co-Authored-By:` trailer naming a model or its vendor, and no "Generated with" or robot-emoji line, in a commit message or a PR body. This is the one rule in this list that is also enforced: a `commit-msg` hook refuses the commit, so a session that forgets finds out at once rather than on a public pull request, which is where a live run's four commits carried `Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>`. Naming a harness in prose is allowed, because a change that is genuinely about an adapter has to be describable. The PR body half is not enforceable locally and is asked for in the prompt; see below.
233
+ - **A denial is a boundary, not an obstacle.** A reviewer or guardrail refusal means stop and report, not re-spell the command, move it into a script, or find another path to the same effect. A sandbox permission error may use the harness's supported approval request first; that is how authorized Codex Git writes proceed. A real run hit a blocked-pattern rule on the literal names of two environment variables set inline, wrote a shell script that set the same variables, and ran it five times. The prompt forbids that route around a policy denial and asks for the reason in the final message.
234
+
235
+ ## Who commits, and what the message may say
236
+
237
+ A run commits **as you**. `user.name` and `user.email` are resolved per run from your own git config and passed on git's
238
+ command line, where nothing in a repository can override them. There is no bot identity and no flag to ask for one: you
239
+ wrote the brief and you are shipping the work, and the harness is a tool in the way an editor is. A `gallivant` committer
240
+ would tell your reviewers a robot did it, which is both wrong about who is responsible and the thing that gets a pull
241
+ request discounted in review.
242
+
243
+ The values cannot simply be read by the run. Your identity is in `~/.gitconfig`, and `GIT_CONFIG_NOSYSTEM=1` plus
244
+ `GIT_CONFIG_GLOBAL=/dev/null` drop exactly that file, deliberately: a sandboxed session cannot read outside its worktree,
245
+ and a run pointed back at `~/.gitconfig` fails every git command, `git status` included, on a file it is denied. So the
246
+ daemon reads the identity where there is no sandbox and passes the values through. No file outside the worktree is named
247
+ and the sandbox stays as strict as it was.
248
+
249
+ With no identity configured, `gallivant start` and `gallivant resume` refuse before anything is spawned:
250
+
251
+ ```
252
+ error: no git identity on this machine. Gallivant commits as you, so it needs one.
253
+ git config --global user.name "Your Name"
254
+ git config --global user.email "you@example.com"
255
+ ```
256
+
257
+ Early, for the reason the base-branch check is early: no money on a session that cannot commit what it writes.
258
+
259
+ The message body is a separate problem with a separate mechanism. Harnesses add their own `Co-Authored-By:` trailer, and
260
+ a live pull request carried one on all four of its commits. That is a **hook**, not a line in the prompt, because a
261
+ prompt is a request a session can forget and forgetting it is exactly how those commits happened. `commit-msg` in each
262
+ checkout's `.git/hooks` matches the trailer and the "Generated with" line, prints which line is the problem and what to
263
+ do, and exits non-zero, so git creates no commit object at all. The same hook covers local CLI commits because it fires on whatever process
264
+ runs `git commit`; coordinator publication validates hosted output separately.
265
+
266
+ `.git/hooks` rather than a directory inside the checkout. A relative `core.hooksPath` would also work and would keep the
267
+ run env free of absolute paths, but it puts a directory in the working tree, where it shows up as untracked in
268
+ `git status`, in the `git diff --check` briefs run, and in the uncommitted-work count a timeout kill reports. A guard
269
+ that makes a clean tree look dirty is one the first session wanting a clean `git status` would trade away. `.git/hooks` is
270
+ git's own default, needs no config, sits outside the working tree, and dies with the clone.
271
+
272
+ The Git hook does not intercept `gh pr create`. Public-output validation is a
273
+ separate control: coordinator GitHub writes validate public text, hosted artifacts
274
+ are checked before publication, and the review loop checks existing PR text before
275
+ readiness. A local agent's direct GitHub call can therefore create text that later
276
+ needs correction; the prompt alone is not a pre-write boundary on that call.
277
+ Neutral status labels and encoded recovery metadata preserve tracking without
278
+ promotional attribution. See [public output](commands.md#public-formatting-and-compatibility).
279
+
280
+ ## Reading a denial
281
+
282
+ `gallivant why` prints each denied call with the guardrail's own reason on the line beneath it, and the APort audit lines for the run's window below that. You need both, because neither half is complete:
283
+
284
+ ```
285
+ permission denials (the call, and the guardrail's reason where the run recorded it):
286
+ - Bash: {"command":"grep -n \"productPages\" web/src/lib/products.config.ts"}
287
+ APort denied this tool call. Policy: bash. Reason: oap.blocked_pattern. Detail: Command contains dangerous pattern: Access to sensitive configuration files.
288
+ ```
289
+
290
+ The audit line for that decision names the pattern and carries no path. The denial list names the path and carries no pattern. Paired, they say what the audit line alone could not: the pattern is broad enough to catch a `grep` of a project source file whose name ends `.config.ts`. That is a block working as written and still worth changing, which is a judgement only an operator can make and only from both halves. Both come out of the run's own log, joined on the tool-call id they share, so nothing extra is recorded to get this.
291
+
292
+ ## Codex
293
+
294
+ The current setup guide and `doctor` recommend warn mode for the Codex hook based
295
+ on recorded unsupported multi-file `apply_patch` and listing-tool behavior.
296
+ Warn mode can downgrade policy denials, but adapter failures may still block calls
297
+ before policy evaluation. Verify the installed guardrail version and actual audit
298
+ records before changing enforcement. Local sandbox permissions, reviewed escalation,
299
+ token scope and GitHub rules remain separate controls; a hook warning is not proof
300
+ that a tool call was denied. The historical findings are linked below.
301
+
302
+ ## Known limits
303
+
304
+ - The coordinator runs as its OS user on macOS or Linux. Sandboxes, token scope and repository rules constrain worker actions; they do not isolate an arbitrary same-user process from all coordinator state. A dedicated OS user can narrow that host exposure.
305
+ - Codex `network_access=true` removes the network boundary inside the checkout. Accepted on day one; the token scope and the ruleset bound what a push can do.
306
+ - `context_paths` widens what a run can read and therefore what an injected run can leak. The refusals above stop the worst of it; the narrowness of the paths you choose is the rest.
307
+ - Logs may contain sensitive tool inputs. Private provider diagnostics are separate from the redacted projections served by the tracker, but redaction is not a guarantee that all sensitive application data is removed. Protect the Gallivant home and tracker access. Do not delete active run directories: ownership journals, acceptance records and evidence are required for safe recovery. Back up matching state before retention cleanup or migration; see [execution storage](execution.md#journal-projections-and-retention).
308
+
309
+ ## Follow-ups filed against APort
310
+
311
+ The historical [decision record](https://github.com/uchibeke/gallivant/blob/main/docs/audit/DECISIONS.md) lists: multi-file `apply_patch` in the Codex hook, `git push` refspec parsing, control-plane paths blocked by default, a non-interactive local passport install, and a Repository Guard slice that blocks pushes rather than reporting merges.