opencode-workflow-guard 1.9.0 → 1.9.2
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.
- package/docs/accountability-evolution-plan.md +106 -0
- package/docs/assets/workflow-guard-tui-badge.png +0 -0
- package/docs/installation.md +163 -0
- package/docs/managed-deployment.md +15 -0
- package/docs/policies.md +211 -0
- package/docs/superpowers/plans/2026-08-28-title-settle-default.md +122 -0
- package/docs/superpowers/specs/2026-08-28-title-settle-default-design.md +25 -0
- package/docs/testing.md +56 -0
- package/docs/troubleshooting.md +84 -0
- package/package.json +4 -1
- package/src/workflow-guard-ui.ts +14 -14
- package/src/workflow-guard.ts +1 -1
- package/test/run-test.mjs +13 -0
- package/test/test-e2e.mts +288 -0
- package/test/test.mts +3481 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Accountability Evolution Plan
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Evolve Workflow Guard's existing policy, audit, verification, review, memory, and learning mechanisms into a clearer accountability model without turning Workflow Guard into an agent orchestrator.
|
|
6
|
+
|
|
7
|
+
The architectural boundary is deliberate:
|
|
8
|
+
|
|
9
|
+
- Core enforcement owns deterministic safety and workflow invariants at supported OpenCode policy boundaries.
|
|
10
|
+
- Evidence records what happened and whether required proof is fresh.
|
|
11
|
+
- Continuity preserves bounded state across sessions, compaction, and handoffs.
|
|
12
|
+
- Guidance helps the model or user understand state without assigning, prioritizing, delegating, retrying, or sequencing work.
|
|
13
|
+
- Behavior that crosses from enforcement/accountability into workflow preference or orchestration-adjacent behavior must be optional and independently toggleable. Such capabilities are add-ons, not new core authority.
|
|
14
|
+
|
|
15
|
+
Project memory and Socratic learning remain optional context features with their own lifecycle semantics. They may share provenance conventions with operational evidence, but they are not policy evidence and do not gain enforcement authority.
|
|
16
|
+
|
|
17
|
+
## Contracts
|
|
18
|
+
|
|
19
|
+
Policy decisions and operational evidence are separate concepts.
|
|
20
|
+
|
|
21
|
+
`PolicyDecision` is the result of evaluating whether an operation may proceed. It should provide a stable machine-readable status and code, an optional policy identifier, a human-readable message, and bounded structured details. Human block text, audit entries, `guard_why`, and controller-facing output should derive from this result rather than independently interpreting policy state.
|
|
22
|
+
|
|
23
|
+
`EvidenceRecord` represents operational proof or observation such as verification, secondary review, tool outcome, policy evaluation, or lifecycle state. Evidence records should carry common project/session/worktree provenance where applicable. Existing purpose-built stores remain authoritative; this work should introduce a common representation rather than another database.
|
|
24
|
+
|
|
25
|
+
Evidence must be bound to the subject it actually establishes. Where applicable, that means identifying the workspace/worktree, commit or worktree fingerprint, session/call, actor or reviewer, policy/version, and observation time. A historical fact such as "verification passed" is not sufficient to establish that the current worktree is verified unless its subject still matches current state.
|
|
26
|
+
|
|
27
|
+
Operational evidence should distinguish confidence classes rather than treating all machine-readable claims as equivalent:
|
|
28
|
+
|
|
29
|
+
- Deterministic observation: repository state, changed files, tool execution/outcome, test exit status, or another directly observed fact.
|
|
30
|
+
- Attestation: a reviewer, model, or human records a judgment such as approval. The fact that the attestation occurred is observable; the judgment is not converted into deterministic truth.
|
|
31
|
+
- Derived state: a reproducible conclusion such as "verification is fresh" computed from subject-bound observations and current state.
|
|
32
|
+
- Agent assertion: natural-language claims such as "tests pass" or "review is complete". These may be useful diagnostics but are never sufficient evidence for a safety or finalization gate by themselves.
|
|
33
|
+
|
|
34
|
+
Prefer outcome and state predicates over prescribed trajectories. Workflow Guard may require demonstrable end state or consequential checkpoints, but should not require one exact sequence of otherwise-valid agent actions when the desired state can be established directly.
|
|
35
|
+
|
|
36
|
+
Project memory and learning evidence remain separate from `EvidenceRecord`. Project memory represents durable repository knowledge. Learning evidence represents observations from real Socratic interactions. Neither should be forced into an allow/block or operational-proof model.
|
|
37
|
+
|
|
38
|
+
## Implementation Sequence
|
|
39
|
+
|
|
40
|
+
1. Add a structured `PolicyDecision` contract. Convert the internal policy dispatcher to return it while preserving current human-facing block behavior at the OpenCode hook boundary.
|
|
41
|
+
2. Make audit serialization consume the structured decision. Add stable decision codes and optional policy identifiers without removing useful human diagnostics.
|
|
42
|
+
3. Add an `EvidenceRecord` contract and projection helpers over existing verification, review, tool-outcome, policy, and lifecycle state. Make subject/provenance binding part of the initial contract rather than a later enhancement. Do not introduce a second evidence database.
|
|
43
|
+
4. Unify verification and review provenance/freshness comparison on top of the subject-bound evidence contract. Verification should carry the same relevant worktree provenance already used by review, and mutations must continue to invalidate parent/session evidence correctly.
|
|
44
|
+
5. Upgrade `guard_status` into a stable machine-readable accountability surface exposing effective configuration, high-signal evidence references/freshness, and outstanding requirements. Make `guard_why` return the real structured policy decision rather than parsing or reconstructing text. Prefer bounded summaries plus evidence references over dumping historical state into agent context.
|
|
45
|
+
6. Add optional operation profiles only after the contracts and freshness semantics settle. Start with a small set such as `interactive` and `autonomous` if concrete behavior differences justify them. Explicit feature settings override profile defaults.
|
|
46
|
+
7. Expand adversarial tests for stable decision codes, provenance invalidation, option/profile precedence, and machine-readable status. Add headless/server coverage proving core enforcement does not depend on the TUI or interactive approval.
|
|
47
|
+
8. Update policy, installation, and managed-deployment documentation as behavior becomes user-visible.
|
|
48
|
+
|
|
49
|
+
The accountability contracts now support an opt-in bounded autonomous-continuation add-on ("Ralph mode"). Its initial implementation only continues an agent while explicitly owned native todos remain unfinished; it does not interpret configured documents or invent completion criteria. It is disabled by default (including under the `autonomous` profile), user-interruptible, iteration-bounded, and preserves all core guardrails. It reports observable outcomes such as `completed`, `budget_exhausted`, or `user_stopped`; `blocked` is reserved for a demonstrable guard state rather than inferred from repeated idle events. Ralph remains an optional orchestration consumer rather than part of the accountability kernel.
|
|
50
|
+
|
|
51
|
+
## Configuration Boundary
|
|
52
|
+
|
|
53
|
+
Safety invariants stay core and are not weakened by operation profiles or optional add-ons. Examples include secret protection, protected-path/tamper protection, workspace boundaries, protected-branch safeguards, and deterministic checks needed to prevent bypass of those invariants.
|
|
54
|
+
|
|
55
|
+
Capabilities that express user workflow preference, add continuity/context, or approach orchestration must be configurable. Examples include project memory, Socratic learning, recovery checkpoints, review/documentation requirements, bounded continuation behavior, and future autonomous-operation conveniences. Optional capabilities must fail independently where practical so disabling or failing an add-on does not disable core enforcement.
|
|
56
|
+
|
|
57
|
+
Workflow Guard must not become responsible for planning work, choosing the next task, delegating tasks, autonomous retry loops, sequencing agents, or deciding when an external job should terminate. External SDK/server clients or dedicated add-ons own orchestration. The existing bounded `session.idle` continuation may enforce already-owned unfinished work but must not grow into an orchestration loop.
|
|
58
|
+
|
|
59
|
+
The same boundary applies to review. Workflow Guard can prove that a review occurred against a particular subject, preserve the reviewer's disposition and findings, and enforce a configured review requirement. It must not infer that an approval proves correctness, choose reviewers, launch review loops, negotiate findings, or decide what work should happen next.
|
|
60
|
+
|
|
61
|
+
## External Research Rationale
|
|
62
|
+
|
|
63
|
+
Recent OpenAI and Anthropic engineering/research material independently supports this architecture. These sources are design inputs rather than normative dependencies; Workflow Guard should continue to rely on supported OpenCode APIs and its own tested contracts.
|
|
64
|
+
|
|
65
|
+
- OpenAI's February 2026 [Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) describes mechanically enforced architectural invariants around agent autonomy. The applicable inference for Workflow Guard is to enforce boundaries centrally while leaving implementation strategy and task execution to the agent.
|
|
66
|
+
- OpenAI's March 2025 [Detecting misbehavior in frontier reasoning models](https://openai.com/index/chain-of-thought-monitoring/) reports coding agents exploiting evaluation loopholes, including weakening or altering verification. The applicable inference is to retain independent provenance about changed artifacts and verification subjects rather than trusting reported success or an undifferentiated test-pass signal.
|
|
67
|
+
- OpenAI's May 2025 [Introducing Codex](https://openai.com/index/introducing-codex/) emphasizes verifiable evidence from terminal/test activity and traceable results. The applicable inference is that guard decisions should reference operational facts that can be inspected independently of an agent's completion claim.
|
|
68
|
+
- OpenAI's August 2025 [Anthropic-OpenAI alignment evaluation exercise](https://openai.com/index/openai-anthropic-safety-evaluation/) notes errors in automated grading and the value of independent/manual inspection. The applicable inference is to record review/model judgments as attestations rather than elevate them to deterministic proof.
|
|
69
|
+
- Anthropic's January 2026 [Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) explicitly distinguishes a transcript from the final environment outcome and recommends deterministic graders where possible. The applicable inference is to prefer observable state/outcome evidence and avoid brittle enforcement of one expected action sequence.
|
|
70
|
+
- Anthropic's June 2025 [How we built our multi-agent research system](https://www.anthropic.com/engineering/built-multi-agent-research-system) recommends end-state evaluation for state-mutating agents and describes provenance-preserving artifacts, observability, and checkpoints. The applicable inference is to validate consequential state while leaving valid execution paths open.
|
|
71
|
+
- Anthropic's September 2025 [Effective context engineering for AI agents](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents) recommends the smallest high-signal context and just-in-time retrieval. The applicable inference is that `guard_status` should expose compact current state and stable evidence references rather than inject an ever-growing accountability transcript.
|
|
72
|
+
- Anthropic's December 2024 [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) separates deterministic workflows from model-directed agents, recommends simple composable mechanisms, and uses programmatic gates where checks are appropriate. The applicable inference is to keep Workflow Guard a guardrail/accountability layer, not an orchestrator-worker or evaluator-optimizer implementation.
|
|
73
|
+
|
|
74
|
+
The combined design principle is: Workflow Guard should answer, "May this operation happen, given what can actually be demonstrated about the current state?" It should not answer, "What should the agent do next?"
|
|
75
|
+
|
|
76
|
+
## OpenCode API Boundary
|
|
77
|
+
|
|
78
|
+
Use supported semantic OpenCode APIs wherever they provide enough information. `tool.execute.before` is the supported deterministic tool-blocking boundary. Current `session.idle` events and `experimental.text.complete` do not expose an explicit supported completion-veto result, so Workflow Guard must not emulate a hard completion gate through undocumented exception behavior.
|
|
79
|
+
|
|
80
|
+
Machine-readable controller outcomes should therefore come from Workflow Guard's own structured contracts, tools, and audit trail until OpenCode exposes a stronger lifecycle decision hook.
|
|
81
|
+
|
|
82
|
+
Shell matching remains defense in depth rather than a sandbox. Prefer structured tool/API boundaries when available, while retaining adversarial shell coverage for channels that only expose command text.
|
|
83
|
+
|
|
84
|
+
## Compatibility And Non-Goals
|
|
85
|
+
|
|
86
|
+
- Preserve existing human-readable block messages at the hook boundary while internal consumers migrate to structured decisions.
|
|
87
|
+
- Preserve existing verification history, review state, project-memory databases, learning profiles, and review-follow-up storage; migrate representations before considering storage changes.
|
|
88
|
+
- Do not merge project memory, learner evidence, and operational evidence into a universal event store.
|
|
89
|
+
- Do not claim managed deployment proves plugin provenance when OpenCode cannot provide that guarantee.
|
|
90
|
+
- Do not add a hard completion gate until OpenCode exposes a supported block/continue lifecycle hook.
|
|
91
|
+
- Do not pin dependency versions as part of this work. Dependency update policy remains intentionally separate from the accountability architecture.
|
|
92
|
+
- Keep one cohesive package. Orchestration-adjacent additions belong behind explicit options/add-on surfaces rather than expanding core policy authority.
|
|
93
|
+
|
|
94
|
+
## Acceptance Criteria
|
|
95
|
+
|
|
96
|
+
- Policy evaluation has one structured result from which blocking text and audit data are derived.
|
|
97
|
+
- External/controller-facing status does not require parsing human log messages.
|
|
98
|
+
- Operational evidence identifies the subject it establishes and does not silently transfer validity to a changed worktree, commit, session, or call.
|
|
99
|
+
- Deterministic observations, attestations, derived state, and agent assertions are not treated as interchangeable proof.
|
|
100
|
+
- Verification and review freshness are provenance-aware and invalidate consistently after relevant mutations.
|
|
101
|
+
- Safety and finalization gates depend on observable/derived evidence rather than an agent's natural-language assertion of success.
|
|
102
|
+
- Where an end-state predicate is sufficient, enforcement does not prescribe an unnecessary exact tool/action trajectory.
|
|
103
|
+
- Existing core guardrails behave the same unless a change is explicitly documented and tested.
|
|
104
|
+
- Preference-heavy and orchestration-adjacent behavior is independently toggleable and does not weaken unrelated enforcement when disabled.
|
|
105
|
+
- Memory, learning, and review follow-ups retain their current durable accountability value without becoming orchestration mechanisms.
|
|
106
|
+
- Unit/adversarial and headless integration coverage pass without requiring the optional TUI.
|
|
Binary file
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Installation Guide
|
|
2
|
+
|
|
3
|
+
`opencode-workflow-guard` runs as an OpenCode plugin.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- **OpenCode >= 1.18** (requires the `GET /session/:id/todo` endpoint and V1 `PluginModule` loader).
|
|
10
|
+
- **Node.js >= 22.18** or **Bun** (for running test scripts directly).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation Methods
|
|
15
|
+
|
|
16
|
+
### 1. From npm (Recommended)
|
|
17
|
+
|
|
18
|
+
Install the current published version with OpenCode's plugin command:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
VERSION=$(npm view opencode-workflow-guard version)
|
|
22
|
+
opencode plugin "opencode-workflow-guard@$VERSION" --global --force
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
OpenCode documents `opencode plugin <module>` as the command to install a plugin and update its config, `--global` as global installation, and `--force` as replacing an existing plugin version. Workflow Guard exposes both server and TUI targets, so OpenCode detects both and updates their global configuration files. Restart OpenCode after installation because configuration is loaded at startup. See the [OpenCode plugin CLI documentation](https://opencode.ai/docs/cli/#plugin).
|
|
26
|
+
|
|
27
|
+
Use the same two commands to upgrade after a Workflow Guard release. Keep the explicit version in OpenCode's configuration rather than relying on a bare package name or `@latest`: OpenCode caches npm plugins, and an already-populated `@latest` cache key may continue resolving to the version that originally populated it. An explicit new version creates a new package-cache key without manually deleting OpenCode's cache.
|
|
28
|
+
|
|
29
|
+
If you only want the server guard without the TUI companion, add the package to your project's `opencode.json` (or global `~/.config/opencode/opencode.json`):
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"$schema": "https://opencode.ai/config.json",
|
|
34
|
+
"plugin": [
|
|
35
|
+
"opencode-workflow-guard"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
OpenCode automatically installs configured npm plugins and their dependencies at startup. The npm package contains both server and TUI entrypoints, but OpenCode activates them from separate configuration files. See [OpenCode's npm plugin documentation](https://opencode.ai/docs/plugins/#from-npm) and [plugin installation behavior](https://opencode.ai/docs/plugins/#how-plugins-are-installed).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### 2. Manual Local-File Installation (Server Plugin)
|
|
45
|
+
|
|
46
|
+
The npm package entrypoint uses OpenCode's V1 plugin module shape (`{ id, server }`). For a source checkout, keep the modular server source outside the auto-loaded `plugins/` directory and add a small adapter with the same module shape:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mkdir -p .opencode/workflow-guard-source .opencode/plugins
|
|
50
|
+
cp src/workflow-guard.ts .opencode/workflow-guard-source/
|
|
51
|
+
cp -r src/lib src/policies .opencode/workflow-guard-source/
|
|
52
|
+
printf '%s\n' 'import { WorkflowGuard } from "../workflow-guard-source/workflow-guard.ts";' 'export default { id: "workflow-guard-local", server: WorkflowGuard };' > .opencode/plugins/workflow-guard.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
OpenCode automatically discovers the adapter from the project-level `.opencode/plugins/` directory at startup. For deterministic development and test fixtures, it can also be registered explicitly in `.opencode/opencode.json` as `"./plugins/workflow-guard.ts"`; OpenCode deduplicates the resulting file URL. npm installation is recommended for normal use; the local-file form is primarily useful when developing from a source checkout.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### 3. TUI Visual Indicator (Prompt Box Badge)
|
|
60
|
+
|
|
61
|
+
To enable the static `Workflow Guard 🛡️` prompt-bar badge, with block details reported separately through warning toasts:
|
|
62
|
+
|
|
63
|
+
The recommended `opencode plugin "opencode-workflow-guard@$VERSION" --global --force` command configures this automatically. For manual installation, use the same explicit package version in `~/.config/opencode/tui.json`:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
67
|
+
"plugin": [
|
|
68
|
+
"opencode-workflow-guard@1.7.2"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The version above is illustrative; replace it with the version you intend to install. The package exposes separate server and TUI entrypoints. In `tui.json`, configure the package spec; OpenCode resolves its exported `./tui` entrypoint automatically. Do not place `workflow-guard-ui.ts` under `plugins/`; the server loader rejects TUI-only modules.
|
|
74
|
+
|
|
75
|
+
The TUI badge uses OpenCode's Solid slot API (`@opentui/solid`). It is a runtime `dependency` of this package, so npm installs it automatically alongside `opencode-workflow-guard` - no extra install is needed.
|
|
76
|
+
|
|
77
|
+
## Recovery Checkpoints
|
|
78
|
+
|
|
79
|
+
Workflow Guard supports two optional operation profiles in `.opencode/workflow-guard.json` (JSONC is also supported): `interactive` is the default and preserves the standard behavior, while `autonomous` enables durable recovery checkpoints by default for longer-running agent work. Profiles only supply defaults for optional workflow behavior; they never weaken secret protection, protected-path protection, workspace confinement, protected-branch safeguards, or other deterministic policy checks.
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"profile": "autonomous"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Explicit feature settings take precedence over the profile. For example, an autonomous project can disable checkpoints with `{ "profile": "autonomous", "recoveryCheckpoints": false }`. Unknown profile values are treated as `interactive`. Project memory, Socratic learning, and Ralph mode remain independently configurable rather than being enabled by a profile.
|
|
88
|
+
|
|
89
|
+
Durable recovery checkpoints can also be enabled directly per project:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"recoveryCheckpoints": true
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For each genuine user run in a root session, Workflow Guard captures the pre-run tracked and untracked workspace state in private Git objects. The objects are kept reachable under `refs/workflow-guard/checkpoints/` and do not add entries to the user's stash list. Subagent runs and Workflow Guard's synthetic continuation messages do not create or replace checkpoints.
|
|
98
|
+
|
|
99
|
+
When the root session reaches idle, the checkpoint records the resulting workspace fingerprint. The `guard_recovery_restore` tool can then restore a selected run, but only for that same root session and only while the workspace still exactly matches the recorded idle boundary. Any intervening workspace change makes recovery refuse rather than overwrite newer work. Recovery checkpoints require an existing Git commit and deliberately fail open if a snapshot cannot be created.
|
|
100
|
+
|
|
101
|
+
With the optional TUI companion installed, open the command palette and choose **Workflow Guard: Project Options** (or run `/guard-options`) to toggle recovery checkpoints, project memory, learner mode, Ralph mode, or the title-settle workaround for the current project. The command writes the existing Workflow Guard project config location, defaulting to `.opencode/workflow-guard.json`; restart OpenCode after changing a setting so the server plugin can expose the matching tools.
|
|
102
|
+
|
|
103
|
+
The title-settle workaround is on by default because Workflow Guard's automatic continuation can race OpenCode's asynchronous native title generation. Workflow Guard briefly waits for native title generation before sending an automatic continuation for unfinished root-session work; set `titleSettleWorkaround: false` to opt out. The wait is bounded, applies only when that session owns unfinished todos, and never changes the title or provider/model parameters itself.
|
|
104
|
+
|
|
105
|
+
Ralph mode is also off by default, including under the `autonomous` profile. Enable it explicitly with `ralphMode: true`; `ralphMaxIterations` sets its positive integer continuation budget from 1 through 100 (default 10; invalid values use the default). Ralph only continues a root session while that session already owns unfinished native todos. It does not create, prioritize, select, sequence, or delegate work, and every resumed run remains subject to the normal guardrails. Genuine user input stops an active Ralph run instead of resetting its budget. Reaching the budget with unfinished work reports `budget_exhausted`; completion is reported only when owned todos are actually finished. A `blocked` outcome is reserved for a demonstrable guard state and is never inferred merely from repeated idle events. `guard_status` exposes the effective Ralph configuration and current calling-session outcome.
|
|
106
|
+
|
|
107
|
+
OpenCode's model/provider request timeout is separate from Workflow Guard. In OpenCode 1.18+, configure it under the selected provider's `options.timeout` in project `opencode.json[c]`; the default is 300000 ms (5 minutes), and `false` disables the provider request timeout. Workflow Guard does not impose a whole-session wall-clock limit.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Recommended Companion Configuration
|
|
112
|
+
|
|
113
|
+
For defense in depth, pair `opencode-workflow-guard` with OpenCode's native permission rules in `opencode.json` (see [OpenCode Permissions](https://opencode.ai/docs/permissions/)):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"$schema": "https://opencode.ai/config.json",
|
|
118
|
+
"permission": {
|
|
119
|
+
"edit": "allow",
|
|
120
|
+
"bash": {
|
|
121
|
+
"git push *main*": "deny",
|
|
122
|
+
"git push *master*": "deny",
|
|
123
|
+
"opencode auth*": "deny"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Optional Context Features
|
|
132
|
+
|
|
133
|
+
Project memory and Socratic learning are context modules, not part of Workflow Guard's policy authority or an agent orchestration loop. They can be disabled without weakening deterministic policy enforcement, and neither module selects or sequences agent work.
|
|
134
|
+
|
|
135
|
+
### Experimental Learning Mode
|
|
136
|
+
|
|
137
|
+
Socratic learning is disabled by default. Enable its agent-facing tools with the `learning: true` project option (including through `/guard-options`), or set `WORKFLOW_GUARD_LEARNING=1` in your user environment before OpenCode starts to enable it regardless of the project setting.
|
|
138
|
+
|
|
139
|
+
To customize the per-session intervention budget, add:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"maxLearningInterventions": 3
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
to `.opencode/workflow-guard.json` (JSONC is also supported). The intervention budget limits high-value checkpoints per session; learning remains advisory and does not alter policy enforcement or direct the workflow.
|
|
148
|
+
|
|
149
|
+
The global learner profile is stored at `$XDG_DATA_HOME/opencode/workflow-guard/learner-profile.json`, falling back to `~/.local/share/opencode/workflow-guard/learner-profile.json`. The parent directory and file are created with user-only permissions where supported. The profile contains distilled learning evidence and project/session references, so it can be backed up or removed separately from any repository.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### Project Memory
|
|
154
|
+
|
|
155
|
+
Project memory is optional supporting context, enabled by default, and requires no additional dependency or service. Set `projectMemory: false` (or toggle it through `/guard-options`) to disable its initialization and tools for a project without weakening policy enforcement. Its authoritative working index is a local SQLite database under `$XDG_DATA_HOME/opencode/workflow-guard/project-memory/`, falling back to `~/.local/share/opencode/workflow-guard/project-memory/`. Repositories use their Git common directory as the local identity where available, so linked worktrees share an index.
|
|
156
|
+
|
|
157
|
+
The plugin exposes `project_memory_search`, `project_memory_record`, `project_memory_export`, and `project_memory_import`. Recording is intended only for durable facts, decisions, constraints, and lessons; recognized secret content is rejected. Routine tool/test/verification output, transient errors and retries, successful operational telemetry, todos, and session progress belong in their existing operational stores rather than project memory.
|
|
158
|
+
|
|
159
|
+
Project-memory maintenance runs opportunistically when a project index is opened, at most once per day. Superseded memories and resolved review follow-ups are retained for 90 days and then pruned; current memories are not expired just because they are old. Maintenance checkpoints and compacts the active SQLite database. When the project-memory directory exceeds 100 MB, least-recently-used inactive project databases are evicted toward about 80 MB when eligible databases exist. The current project's database is never selected for global eviction, and maintenance failures do not disable core guard enforcement.
|
|
160
|
+
|
|
161
|
+
Portable project knowledge lives at the fixed path `.opencode/memory/project-memory.jsonl`. Importing that file bootstraps the local index, but exporting is always explicit and accepts selected record IDs rather than dumping private working memory. The plugin adds `.opencode/memory/` to `.git/info/exclude` for the local clone. To share promoted knowledge through Git, deliberately remove that local exclusion or force-add the JSONL file after reviewing its contents. Do not commit the SQLite database.
|
|
162
|
+
|
|
163
|
+
Compaction receives a bounded set of recent current local memories. Imported portable records are never injected automatically and require an explicit memory search. Path-backed local memories with a recorded Git commit are excluded when those paths have committed, staged, or unstaged changes since that commit; repository contents remain authoritative over remembered context. Memory supplies context rather than instructions and does not select, prioritize, or sequence work.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Managed Deployment
|
|
2
|
+
|
|
3
|
+
For organization-managed installations, deploy workflow-guard together with OpenCode's administrator-controlled managed configuration. Keep controls already enforced by OpenCode, such as provider allowlists and tool permissions, in managed OpenCode settings. Keep workflow invariants such as protected-branch, verification, review, and workspace-boundary checks in workflow-guard rather than duplicating OpenCode's configuration precedence.
|
|
4
|
+
|
|
5
|
+
As documented by OpenCode, managed configuration has the highest configuration precedence. Place `opencode.json` or `opencode.jsonc` in `/etc/opencode/` on Linux, `/Library/Application Support/opencode/` on macOS, or `%ProgramData%\opencode` on Windows. macOS can additionally use the `ai.opencode.managed` preference domain through MDM. Verify the resolved host configuration with `opencode debug config`.
|
|
6
|
+
|
|
7
|
+
A typical managed configuration should restrict organization-level capabilities, for example with `enabled_providers`, `disabled_providers`, `experimental.policies`, and `permission`. Install workflow-guard through your controlled OpenCode distribution or configuration alongside those settings, and pin/review the package version according to your normal software-supply-chain process.
|
|
8
|
+
|
|
9
|
+
Workflow Guard's project `profile` is a convenience for optional workflow defaults, not a security level. The `interactive` default and `autonomous` profile enforce the same core safety invariants; `autonomous` currently enables recovery checkpoints by default. Explicit project feature settings override profile defaults. Ralph mode is orchestration-adjacent and therefore remains disabled unless the project explicitly sets `ralphMode: true`; selecting `autonomous` does not enable it.
|
|
10
|
+
|
|
11
|
+
For headless or centrally operated environments, use `guard_status` and `guard_why` as the machine-readable accountability surfaces rather than scraping log prose or relying on the optional TUI. `guard_status` reports effective configuration, fresh verification/review references, bounded outstanding requirements, and Ralph configuration/outcome. `guard_why` evaluates the authoritative structured policy decision in simulation mode without performing verification, remote PR lookups, durable audit writes, or other mutation accounting. When a simulated Git operation passes local checks but its final result can depend on intentionally unqueried GitHub/Azure PR state, `guard_why` reports `needs_approval` with code `remote_state_unchecked` instead of claiming the operation is allowed. Core server enforcement does not depend on the TUI.
|
|
12
|
+
|
|
13
|
+
At startup workflow-guard writes an informational app-log diagnostic stating whether a file-based managed configuration was detected at the platform's standard location. This is observational only. OpenCode's V1 plugin API does not provide a supported provenance/trust signal proving that workflow-guard itself came from an administrator-controlled source, and managed configuration precedence does not prove that untrusted project plugins cannot also run.
|
|
14
|
+
|
|
15
|
+
Reference: https://opencode.ai/docs/config/#managed-settings (retrieved August 25, 2026).
|
package/docs/policies.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Enforced Policies & Guardrails
|
|
2
|
+
|
|
3
|
+
`opencode-workflow-guard` enforces workflow discipline through **deterministic TypeScript hooks** (`tool.execute.before`, `experimental.session.compacting`, `shell.env`, `permission.ask`, and `event` journaling) - not prompt rules that LLMs can ignore.
|
|
4
|
+
|
|
5
|
+
It is a policy and enforcement layer, not an agent harness. Policies may constrain actions, record or explain decisions, and preserve bounded context; they do not plan, prioritize, delegate, or autonomously sequence work. Where Workflow Guard resumes an idle session, it only enforces already-owned todo lifecycle state and does not choose what the agent works on next.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Policy Reference
|
|
10
|
+
|
|
11
|
+
### 1. Task Breakdown & Lifecycle (`todowrite`)
|
|
12
|
+
- **Pre-edit Gate:** All file-editing tools (`edit`, `write`, `apply_patch`) are blocked until the session has an active task list in OpenCode's native todo system (`todowrite`, persisted at `GET /session/:id/todo`).
|
|
13
|
+
- **Flexible Task Execution:** Tasks can be completed in any order as work concludes without artificial sequential blockers, maintaining maximum pair-programming DX.
|
|
14
|
+
- **No Silent Deletion:** Each `todowrite` update replaces the complete list. Active tasks must remain in subsequent updates until they are explicitly marked `completed` or `cancelled`.
|
|
15
|
+
- **No Silent Early Exit:** When an owning session becomes `session.idle` with unfinished native todos, the guard asynchronously asks OpenCode to resume the existing run. This enforces the session's existing lifecycle state: Workflow Guard does not choose the next task, reorder work, create work, or delegate it. Ordinary automatic continuations are capped at three consecutive attempts and the budget resets on genuine user input. The independently opt-in `ralphMode` uses `ralphMaxIterations` (1-100, default 10) instead; genuine user input terminates an active Ralph run, and unfinished work at the budget limit reports `budget_exhausted` rather than inferring a policy block. Ralph remains disabled by default under every operation profile. Use OpenCode's native `question` tool when user feedback or a decision is required; question and permission interactions wait inside the active run rather than producing an idle completion.
|
|
16
|
+
- **Subagent Inheritance & Role Confinement:** Subagents automatically inherit active tasks from their parent session via the `parentID` hierarchy. Subagents spawned with read-only roles (`reviewer`, `planner`, `advisor`, `critic`, `explorer`, `scout`, `evaluator`) are hard-confined: file mutations, lifecycle tools, and mutating shell commands are strictly blocked.
|
|
17
|
+
- **Handoff Safety:** An idle subagent whose effective todos are inherited from its parent is not auto-continued. Inherited work belongs to the parent and can be handed back normally.
|
|
18
|
+
- **Durable Planning Context Discovery:** `guard_next_tasks` prefers a repository-root `TODO.md`; when it is absent, it reads conventional `ROADMAP.md`, `PLAN.md`, `TASKS.md`, `BACKLOG.md`, their `docs/` counterparts, and Markdown files under `docs/plans/`. The tool exposes candidate planning context without selecting, prioritizing, or sequencing tasks. These files never replace OpenCode's native runtime todo state.
|
|
19
|
+
- **Subagent Mutation Budget:** Subagent sessions are protected by a mutation safety budget (`maxSubagentMutations` in project config or `WORKFLOW_GUARD_MAX_SUBAGENT_MUTATIONS` env, default 100) to terminate runaway edit loops deterministically.
|
|
20
|
+
- **Concurrent File Claims:** Direct `edit`, `write`, and `apply_patch` calls claim each canonical target for the tool-call lifetime. A different active session is blocked from racing the same file (including through symlink aliases); claims are released after the call, with session idle/deletion as stale-claim cleanup when an after hook is missed. Shell writers are not covered, and isolated worktrees remain preferred for substantial parallel mutations.
|
|
21
|
+
- **Stale-Write Protection:** A successful `read` records a same-session fingerprint for an existing regular file using its canonical path, filesystem identity, size, nanosecond mtime, and SHA-256 content digest. Before `edit` or `write` replaces an existing file, that fingerprint must exist and still match; otherwise the call is blocked and must re-read. New-file creation is allowed without a prior read. Fingerprints are not inherited between parent/subagent sessions and are cleared on session idle/deletion. `apply_patch` and shell writers are deliberately outside this bounded mechanism; the pre-execution comparison also cannot make external changes between the check and OpenCode's write atomic.
|
|
22
|
+
|
|
23
|
+
### 2. No Pushes to Protected Branches
|
|
24
|
+
- `git push ... main` and `git push ... master` are blocked in all shell commands, including direct refs, refspecs (`git push origin HEAD:main`, `git push origin feature/x:main`), deletion refspecs (`git push origin :main`), and forced refspecs (`git push origin +main`).
|
|
25
|
+
- Branches configured via `protectedBranches` in `.opencode/workflow-guard.json` receive the same destination-side push protection (`git push origin feature/x:release/prod` is blocked).
|
|
26
|
+
- Git global options (`-C`, `--git-dir`, `--work-tree`, `-c`) are parsed before matching, so `git -C /repo push origin main` is gated on `/repo`'s branch.
|
|
27
|
+
- Normal pushes and force-pushes to feature branches are allowed.
|
|
28
|
+
|
|
29
|
+
### 3. PR Changelog, Changesets & Lockfile Synchronization Requirement
|
|
30
|
+
- `gh pr create` and `az repos pr create` are blocked unless:
|
|
31
|
+
- The branch diff modifies a `CHANGELOG` file, OR
|
|
32
|
+
- The branch diff adds/modifies a `.changeset/*.md` fragment file (Changesets workflow), OR
|
|
33
|
+
- The PR description (`--body`, `--description`, or `--body-file`/`-F`) contains a release-information section headed `Changelog`, `Changes`, `Release notes`, or `Summary`. Multiline Bash ANSI-C `\n`/`\r` quoting is supported for inline descriptions; inline descriptions that would render Markdown line breaks as literal `\n`/`\r` text are blocked with guidance to use real newlines or a description file.
|
|
34
|
+
- **Lockfile Synchronization Gate:** Whenever package manifests (`package.json`, `Cargo.toml`, `go.mod`) are modified, PR creation is blocked until their corresponding lockfiles (`package-lock.json`/`bun.lock`/`pnpm-lock.yaml`/`yarn.lock`, `Cargo.lock`, `go.sum`) are updated.
|
|
35
|
+
- When multiple PR prerequisites fail at once, the guard reports all detected preflight failures together so they can be resolved before retrying PR creation.
|
|
36
|
+
|
|
37
|
+
### 4. Destructive CLI Operations Guard
|
|
38
|
+
- Blocks destructive filesystem, infrastructure, cloud, database, and git operations unless the **user** explicitly overrides via environment variable.
|
|
39
|
+
- **Blocked:**
|
|
40
|
+
- Filesystem & Disks: `rm -rf`, `rm -r`, forced deletion of system/home paths, disk wipes (`mkfs`, `wipefs`, `parted`, `sfdisk`), raw block writes (`dd of=/dev/...`, `shred /dev/...`), recursive permission/ownership clobbering (`chmod -R ... /`, `chown -R ... ~`), reverse shells & raw sockets (`/dev/tcp/...`, `nc -e`, `socat exec:`)
|
|
41
|
+
- Kubernetes: `kubectl delete`, `kubectl drain`, `kubectl cordon`, `kubectl rollout undo/restart`
|
|
42
|
+
- Helm: `helm uninstall`, `helm rollback`, `helm delete`
|
|
43
|
+
- IaC: `terraform destroy`, `tofu destroy`, `pulumi destroy`
|
|
44
|
+
- Containers: `docker rm`, `docker container/system/image/volume prune`, `docker volume rm`
|
|
45
|
+
- Cloud: `az ... delete/purge`, `aws ... delete/terminate`, `gcloud ... delete/abandon`
|
|
46
|
+
- Hosted Git: `gh repo/issue/pr/release/secret/variable delete|close`
|
|
47
|
+
- Databases: `psql/mysql/mariadb/mongosh/redis-cli/sqlite3` drop, delete, truncate, flushall; `prisma migrate reset`
|
|
48
|
+
- Remote HTTP: `curl -X DELETE` against non-localhost endpoints
|
|
49
|
+
- Git: `git push --force`, `git push +refspec`, `git clean -fdx`
|
|
50
|
+
- **Allowed:** Non-destructive mutations (`kubectl apply`, `terraform apply`, `helm upgrade`, `az create/update`, DB inserts, `curl POST/PUT/PATCH`, `docker ps`, single-file `rm`).
|
|
51
|
+
|
|
52
|
+
### 5. MCP Mutation Guard
|
|
53
|
+
- MCP tools that mutate live GitHub or Azure/DevOps systems (`create`, `update`, `delete`, `merge`, `push`, `close`, etc.) are blocked.
|
|
54
|
+
- Server names are tokenized on all non-alphanumerics, so `mcp__azure-devops__...`, `mcp__gh__...` and flat legacy names (`azure_devops_*`) are matched.
|
|
55
|
+
- Read-only tools (`get`, `list`, `search`, `query`, etc.) are permitted.
|
|
56
|
+
|
|
57
|
+
### 6. Settings Tamper Guard
|
|
58
|
+
- Prevents the agent from weakening its own permission gates **via shell or edit tools**:
|
|
59
|
+
- Shell writes/redirects to `opencode.json[c]`, `~/.config/opencode/*`, `.opencode/*` (including the `.opencode` directory itself), or the guard's own plugin/TUI files (`~/.config/opencode/plugins/*`, `~/.config/opencode/ui/*`)
|
|
60
|
+
- Direct edits of the same paths through the `edit`/`write`/`apply_patch` tools
|
|
61
|
+
- Shell commands invoking `opencode auth`, `opencode config`, `opencode permission`, or `opencode run --auto`
|
|
62
|
+
- Evasion-normalized: quote-concatenation (`open''code.json`), escapes (`open\c\ode`), and glob wildcards (`opencode.jso?`) are stripped before matching.
|
|
63
|
+
- **Read-only access is allowed** (`cat`, `less`, `grep`, `head`, `tail` on config files) - only modification attempts trigger the guard.
|
|
64
|
+
|
|
65
|
+
### 7. Feature-Branch Workflow
|
|
66
|
+
- When the workspace git repository is on `main` or `master`, all file mutations and history-changing git commands (`commit`, `merge`, `rebase`, `cherry-pick`, `revert`, `apply`, `am`, `reset`, `restore`, `stash pop`, `update-ref`, `filter-branch`, `branch -D/-M`) are blocked.
|
|
67
|
+
- Git global flags are parsed, so `git -C /repo commit` is gated on `/repo`'s branch, not the workspace's.
|
|
68
|
+
- The agent is prompted to create a feature branch first (`git switch -c feat/my-feature`).
|
|
69
|
+
|
|
70
|
+
### 8. Workspace Boundary Guard
|
|
71
|
+
- File modification tools (`edit`, `write`, `apply_patch`) and recognized shell mutations (redirection `>`, `>>`, `&>`, `>&`, `tee`, `sed -i`, `cp`/`mv`/`ln`, `touch`, `mkdir`, `rm`, `truncate`, `dd of=`, `git apply`/`git am`) are validated to ensure detected targets cannot escape the current workspace root via `../` traversal, symlinks, absolute paths, or shell expansions (`~`, `~user`, `$HOME`). Unresolvable `$VARIABLE` references fail closed. This is command-level enforcement, not filesystem sandboxing; arbitrary programs and dynamically computed paths require the OS isolation described under Known Limits.
|
|
72
|
+
- `tee` flag parsing generically skips option flags (`-a`, `--append`, `-ai`, `--`) and validates all specified target files against the boundary.
|
|
73
|
+
- `mv` sources are validated too: moving a file from outside the workspace (or a protected settings/plugin file to an innocuous name) is blocked, since `mv` mutates the source.
|
|
74
|
+
- External repository git writes (`git -C /other-repo commit`, `git --git-dir=/other-repo/.git push`, `GIT_DIR=/other-repo/.git git push`, `GIT_WORK_TREE=...`) are also confined to the workspace.
|
|
75
|
+
- The boundary has **no override**: `WORKFLOW_GUARD_ALLOW_LIVE=1` covers live-system commands only and never weakens the workspace confinement.
|
|
76
|
+
|
|
77
|
+
### 9. Script-Laundering Guard
|
|
78
|
+
- Content written via `edit`, `write`, or `apply_patch` is scanned for destructive CLI commands and settings tamper payloads.
|
|
79
|
+
- Prevents agents from bypassing shell guards by writing destructive commands to script files (e.g. `write deploy.sh` -> `bash deploy.sh`).
|
|
80
|
+
|
|
81
|
+
### 10. Evidence-Based Verification
|
|
82
|
+
- When the agent attempts to mark **every** task completed (finalizing the request), the guard requires passing verification evidence.
|
|
83
|
+
- Verification (`WORKFLOW_GUARD_VERIFY` env, project `verifyCommand`, or auto-detected test/typecheck runner across ecosystems: `package.json` (`npm test`, `npm run typecheck`, `npm run check`, `npm run build`), `Cargo.toml` (`cargo test`), `go.mod` (`go test ./...`), `pytest.ini`/`pyproject.toml` (`pytest`), or `deno.json` (`deno test`)) executes in an isolated environment with scrubbed credentials, output caps, token-efficient stdout/stderr snipping, and strict timeout protection. Environment configuration takes precedence over project configuration.
|
|
84
|
+
- Project `postEditValidators` can run faster targeted checks after a direct file edit actually changes the target. Each entry accepts a workspace-relative glob `pattern` (`*`, `**`, and `?` wildcards; brace expansion and character classes are not supported), a non-empty `command`, and an optional positive `timeoutMs`; invalid entries are reported as advisory feedback rather than silently ignored. Matching commands run from the workspace root with the same scrubbed verification environment. Validator failures and timeouts are appended to the edit result as advisory feedback and do not replace the blocking whole-project finalization verification. Multi-file `apply_patch` calls validate every changed target.
|
|
85
|
+
- With project `recoveryCheckpoints: true`, or the `autonomous` operation profile unless explicitly overridden, genuine root-session user runs capture a private Git checkpoint before agent work starts. Checkpoints preserve tracked and untracked state without using the user's stash list; subagents and generated continuation prompts do not replace them. At `session.idle`, the run's resulting workspace fingerprint is recorded before automatic continuation. `guard_recovery_restore` is provenance-bound to the same root session and refuses restoration after any intervening workspace change. Operation profiles only set optional workflow defaults and cannot weaken core safety policies.
|
|
86
|
+
- Automatic continuation remains synthetic. By default, an eligible root session with unfinished owned todos briefly waits for OpenCode's native asynchronous title generation before that continuation is sent; set `titleSettleWorkaround: false` to opt out. The bounded wait never rewrites titles or provider/model parameters.
|
|
87
|
+
- **Git State & Snapshot Binding:** Verification evidence records the current git commit hash (`git rev-parse HEAD`) and working tree status. If unverified edits or index modifications occur after verification, fresh verification is enforced.
|
|
88
|
+
- **Durable Disk Cache:** Passing verification results are cached to `~/.local/state/opencode/workflow-guard/last-verify.json`, allowing session restarts and multi-agent handoffs to retain valid verification evidence without redundant test runs. Durable evidence is **workspace-bound**: a cached result is only accepted for the workspace that produced it, so a passing run in one project can never satisfy finalization in another.
|
|
89
|
+
- If verification fails, finalization is blocked with a structured summary of error markers, stack traces, and failure output.
|
|
90
|
+
- OpenCode's current `lsp.client.diagnostics` event identifies the server and path but does not expose the diagnostics themselves, so the guard deliberately does not claim an LSP-clean finalization gate until the SDK provides a supported diagnostics source.
|
|
91
|
+
- The verify command is **disabled** when `WORKFLOW_GUARD_VERIFY` is empty (`""`).
|
|
92
|
+
|
|
93
|
+
### 11. Secret-Content Scan
|
|
94
|
+
- File payloads and recognized shell file-mutation commands are scanned for common credential material (AWS keys, private key headers, GitHub tokens, OpenAI/LLM keys, Google API keys, Slack tokens, explicit env-style assignments).
|
|
95
|
+
- Flagged content is blocked before the write with an actionable message. Secret-file reads also resolve existing symlink aliases, and copying/moving/linking a known secret path under another name is blocked.
|
|
96
|
+
|
|
97
|
+
### 12. Shell Environment Scrub
|
|
98
|
+
- Sensitive environment variables (`AWS_*`, `KUBE*`, `OPENAI*`, `ANTHROPIC*`, `GOOGLE_/GCP_`, `AZURE_`, `SLACK_`, `NPM_`, `DOCKER_`, `KUBECONFIG`, and fixed names like `OPENAI_API_KEY`, `KUBECONFIG`, `NPM_TOKEN`) are **emptied** in agent shells by the `shell.env` hook. GitHub tokens (`GITHUB_TOKEN`, `GH_TOKEN`) are preserved so `gh` CLI and git operations function without recurring login prompts.
|
|
99
|
+
- This prevents the agent from carrying live credentials by default. The user can still grant access through OpenCode's own permission system if a task genuinely needs it.
|
|
100
|
+
|
|
101
|
+
### 13. Command-Channel Audit
|
|
102
|
+
- User-triggered slash commands (`command.executed` events) are journaled to the audit trail so agents cannot perform hidden work through a user-facing side channel.
|
|
103
|
+
|
|
104
|
+
### 14. Audit Trail & Permission Journaling
|
|
105
|
+
- Every block/allow decision is appended to `~/.local/state/opencode/workflow-guard/workflow-guard.jsonl` (XDG_STATE_HOME respected) with a timestamp, session id, tool name, decision, subagent/parent session attribution, and reason - a durable record.
|
|
106
|
+
- Permission prompts are journaled at request time via the typed `permission.ask` plugin hook, and `permission.replied` outcomes (including rejections) are preserved instead of being labeled as allows.
|
|
107
|
+
- `client.app.log()` complements the on-disk trail with in-app logs.
|
|
108
|
+
|
|
109
|
+
### 15. Compaction State & Policy Context Preservation
|
|
110
|
+
- Integrates with OpenCode's `experimental.session.compacting` hook to inject bounded active policy state into `output.context` before context summarization:
|
|
111
|
+
- Active `todowrite` tasks with status badges and subagent hierarchy attribution.
|
|
112
|
+
- Active Git branch name and protected branch status.
|
|
113
|
+
- Test verification status (passed/failed, test command, and commit hash).
|
|
114
|
+
- Secondary review verdicts (reviewer name and approval status).
|
|
115
|
+
- Uncommitted mutation counts.
|
|
116
|
+
- Preserves policy-relevant context across session compactions without assigning, prioritizing, or sequencing tasks for the model.
|
|
117
|
+
|
|
118
|
+
### 16. TUI Visual Feedback & Static Status Badge
|
|
119
|
+
- Emits real-time warning toasts to the user interface via `tui.showToast` whenever a guard policy blocks a tool call.
|
|
120
|
+
- The companion TUI plugin (`workflow-guard-ui.ts`) keeps `Workflow Guard 🛡️` static in the prompt bar. Block details remain in the warning toast rather than changing badge content or color.
|
|
121
|
+
|
|
122
|
+
### 17. Secret-File READ Block & Safe Schema Masking
|
|
123
|
+
- Blocks reading sensitive credential files (`.env*`, `*.pem`, `*.key`, `id_rsa*`, `id_ed25519*`, `*kubeconfig*`, `*credentials*.json`, `service-account*.json`) through the `read` tool, shell commands (`cat`, `less`, `more`, `grep`, `awk`, `head`, `tail`, `base64`), or inline interpreter payloads (`python3 -c "open('.env')"`, `bash -c 'cat id_rsa'`).
|
|
124
|
+
- **Safe Schema Masking:** On `.env*` reads via the `read` tool, the guard parses the variable names and comments, and returns a safe redacted schema mask with secret values masked as `********`, enabling agents to inspect variable existence without leaking secrets into model context.
|
|
125
|
+
- Standard non-secret fixtures (`.env.example`, `.env.sample`, `.env.template`) remain readable.
|
|
126
|
+
|
|
127
|
+
### 18. Interpreter Inline Evasion Scanner
|
|
128
|
+
- Decodes and inspects inline interpreter payloads (`python -c`, `node -e`, `perl -e`, `ruby -e`, `osascript -e`, `bash -c`, `sh -c`, `zsh -c`, `powershell -enc`, `echo <base64> | base64 -d | sh`) to prevent smuggling live destructive commands, settings tampering, secret reads, or out-of-workspace writes past shell pattern checks.
|
|
129
|
+
|
|
130
|
+
### 19. Conflict-Free Pre-Flight Guard
|
|
131
|
+
- Evaluates `git merge-tree` against the base branch (`origin/main`, `origin/master`, `main`) before PR creation or final task handoff, ensuring changes can be merged cleanly without conflicts.
|
|
132
|
+
|
|
133
|
+
### 20. Merged Branch & Base Freshness Guard
|
|
134
|
+
- Blocks pushing to branches already merged or associated with closed PRs in GitHub or Azure DevOps.
|
|
135
|
+
- Blocks creating fresh feature branches when the local base branch is behind the remote, prompting the agent to pull latest changes first.
|
|
136
|
+
|
|
137
|
+
### 21. Documentation Review & Synchronization Guard
|
|
138
|
+
- Ensures that relevant documentation is updated whenever changes introduce new features, policies, or public tools before PR creation. Configurable via `.opencode/workflow-guard.json` (`requireDocumentation: true`) or `WORKFLOW_GUARD_REQUIRE_DOCS=1`.
|
|
139
|
+
- Only **README.md** (root or package level) and files under **`docs/`** count as documentation updates; arbitrary markdown (e.g. `.changeset/*.md` fragments or `CHANGELOG.md`) does not satisfy this gate.
|
|
140
|
+
|
|
141
|
+
### 22. Non-Interactive Shell & TTY Hang Guard
|
|
142
|
+
- Blocks commands that spawn interactive text editors (`nano`, `vim`, `emacs`), interactive pagers (`less`, `more`) including through common shell wrappers, interactive monitors (`top`, `htop`), interactive rebase/patch prompts (`git rebase -i`, `git add -p`), `sudo`, or package managers missing non-interactive confirmation flags (`npm init` without `-y`, `apt-get` without `-y`). Pager names appearing only in command arguments are not treated as pager execution.
|
|
143
|
+
- Prevents subshell agents from hanging indefinitely waiting for user stdin in background execution.
|
|
144
|
+
- **Desktop Notifications:** Emits native OS notifications (Linux `notify-send`, macOS `osascript`) when a policy blocks a command or when verification runs finish, keeping developers informed when their terminal is backgrounded. Configurable via `WORKFLOW_GUARD_NOTIFY=0`.
|
|
145
|
+
|
|
146
|
+
### 23. Package Supply-Chain & Dependency Hygiene Guard
|
|
147
|
+
- Blocks destructive package manager actions that break dependency trees or pollute machines:
|
|
148
|
+
- `npm audit fix --force`: Prevents major version downgrades that break application runtime.
|
|
149
|
+
- Global package installations (`npm i -g`, `pnpm add -g`, `yarn global add`): Directs agents to use project `devDependencies` or `npx`/`bunx`.
|
|
150
|
+
- Direct agent publishing (`npm publish`, `pnpm publish`): Enforces automated CI/CD release pipelines.
|
|
151
|
+
- `pip install --force-reinstall`: Enforces pinned requirements.
|
|
152
|
+
|
|
153
|
+
### 24. Completion Claims vs Evidence (Observability)
|
|
154
|
+
- When the assistant's final response text asserts completion or passing verification ("all tests pass", "work is done", "verified"), the guard compares the claim against recorded verification evidence for that session.
|
|
155
|
+
- Mismatches (failing verification, stale evidence due to mutations after the verify run, or no evidence at all) are journaled to the audit trail and logged at info level. This is **observability, not gating**: the response is never blocked, but a confident wrap-up cannot silently contradict a failing or absent verification state.
|
|
156
|
+
- Uses the `experimental.text.complete` hook; claim detection is a conservative phrase heuristic to avoid false positives on casual wording.
|
|
157
|
+
|
|
158
|
+
### Tool Description Honesty
|
|
159
|
+
- Via the `tool.definition` hook, the guard enriches the `todowrite` tool's description with its replacement-list lifecycle and finalization gates (including verification evidence required after the last mutation), so the model is not surprised by preventable lifecycle blocks. Other tools are untouched, and the enrichment is idempotent.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Custom Tools
|
|
164
|
+
|
|
165
|
+
Besides enforcement hooks, the guard registers companion tools in OpenCode:
|
|
166
|
+
|
|
167
|
+
### `guard_worktree_create`
|
|
168
|
+
- Creates an isolated git worktree for concurrent subagent execution: `guard_worktree_create(branch, baseBranch?)`.
|
|
169
|
+
- Branch names are validated with `git check-ref-format --branch`; protected branches (the built-in `main`/`master` plus any configured via `protectedBranches` in `.opencode/workflow-guard.json`) are refused.
|
|
170
|
+
- Worktrees are stored outside the repository under `~/.local/share/opencode/worktrees/<repo-name>/` (override with `WORKFLOW_GUARD_WORKTREE_DIR`), and the parent's `node_modules` is symlinked into the new worktree so tooling works without a fresh install.
|
|
171
|
+
- If the branch already exists (as a local branch), the worktree is checked out on it; otherwise it is created from `baseBranch` (default `HEAD`).
|
|
172
|
+
- Subject to the same todo gate as other mutations, and successful worktree mutations invalidate stale verification/review evidence.
|
|
173
|
+
|
|
174
|
+
### `guard_worktree_cleanup`
|
|
175
|
+
- Commits a final snapshot of any remaining changes (`chore(worktree): auto-snapshot before cleanup`, excluding the plugin-created `node_modules` symlink) and then removes the worktree directory with `git worktree remove --force`.
|
|
176
|
+
- **Ownership validation:** only registered worktrees of the current repository under the configured worktree storage directory can be cleaned up; arbitrary directories, other repositories' worktrees, and the primary working tree are refused. A failed `git worktree remove` is an error, never a fallback to raw directory deletion.
|
|
177
|
+
- **Lossless by construction:** if the snapshot commit cannot be established (failing hook, missing identity, lock error), cleanup aborts and the worktree is left fully intact.
|
|
178
|
+
|
|
179
|
+
### Hook-Context Safety
|
|
180
|
+
- All spawned git commands run with a sanitized environment: inherited git context variables (`GIT_INDEX_FILE`, `GIT_DIR`, `GIT_WORK_TREE`, ...) are stripped so the tools resolve the repository from their working directory alone. This makes them safe to invoke from inside git hooks, which export those variables.
|
|
181
|
+
|
|
182
|
+
### Inspection Tools
|
|
183
|
+
- `guard_status`, `guard_audit`, `guard_why`, `guard_review_rubric`, `record_review`, `guard_review_followups`, and `guard_review_followup_resolve` provide runtime introspection of guardrail state, audit entries, block explanations, reviewer rubric definitions, reviewer decisions, and durable local review debt. `guard_status` also exposes the effective Ralph opt-in, iteration budget, and calling-session outcome when applicable.
|
|
184
|
+
- **Priority-Ranked Review Gate:** `guard_review_rubric` includes P0-P3 severity tiers. When recording reviews via `record_review`, approvals that include active P0 (blocker) or P1 (major defect) issues are rejected until blockers are resolved.
|
|
185
|
+
- **Durable Lower-Priority Follow-ups:** Accepted review summaries containing a P2 or P3 finding are persisted in the project's local Workflow Guard SQLite database. Open findings are included in compacted agent context and remain open across sessions until explicitly resolved with `guard_review_followup_resolve`; they are not silently discarded with the review session.
|
|
186
|
+
- **Privacy-Safe Local Telemetry:** Guard decisions remain in the local audit JSONL. Tool after-hook observations add `callID` and duration records for local diagnosis and performance analysis; the current hook API does not distinguish success from every failure path, so these records do not claim a success outcome. Command and patch bodies are never written to the journal; only their byte count and SHA-256 fingerprint are retained for correlation.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Overrides ("Unless Otherwise Specified")
|
|
191
|
+
|
|
192
|
+
The ONLY override is the **`WORKFLOW_GUARD_ALLOW_LIVE=1` environment variable**, which the **user** must set before launching the agent. It affects:
|
|
193
|
+
- Destructive CLI commands (Policy 4)
|
|
194
|
+
- MCP mutations (Policy 5)
|
|
195
|
+
- File payloads containing destructive commands (Policy 9)
|
|
196
|
+
|
|
197
|
+
Verification command selection uses **`WORKFLOW_GUARD_VERIFY`** (empty = auto-detect off, string = your command).
|
|
198
|
+
|
|
199
|
+
There is **deliberately no in-command override** (no `# allow-live` marker): an override the agent can append to its own command - or is told about in an error message - is an override the agent will use.
|
|
200
|
+
|
|
201
|
+
**Workflow & security gates (Policies 1, 2, 3, 6, 7, 8) have no override** - by design.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Known Limits (Defense in Depth)
|
|
206
|
+
|
|
207
|
+
These guards match on command strings, file structures, and interpreter payloads (including base64 pipeline decoding and inline eval scans under Policy 18). While inline scripts are decoded and analyzed, compiled binaries or multi-stage external downloads require OS-level sandboxing. This plugin is a **hardened deterrent against forgetful, complacent, or prompt-injected agents**, not an OS kernel sandbox. Pair it with:
|
|
208
|
+
- GitHub branch protection/rulesets server-side
|
|
209
|
+
- OS-level workspace isolation (containers, read-only mounts)
|
|
210
|
+
|
|
211
|
+
for hard guarantees.
|