opencode-secrets-obfuscation 0.1.0 → 0.1.1

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 (104) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1 -31
  3. package/package.json +29 -23
  4. package/plugin.mjs +3841 -0
  5. package/.editorconfig +0 -12
  6. package/.github/workflows/ci.yml +0 -75
  7. package/.github/workflows/release.yml +0 -50
  8. package/.node-version +0 -1
  9. package/AGENTS.md +0 -23
  10. package/INSTALL.md +0 -104
  11. package/docs/checkpoint.md +0 -68
  12. package/docs/history/1-spec-review.md +0 -233
  13. package/docs/history/10-p0-launch-prerequisites-review.md +0 -186
  14. package/docs/history/11-p0-job-containment-review.md +0 -188
  15. package/docs/history/12-p0-api-client.md +0 -15
  16. package/docs/history/13-p0-status-review.md +0 -122
  17. package/docs/history/14-p0-containment-final-review.md +0 -84
  18. package/docs/history/15-config-source-implementation.md +0 -27
  19. package/docs/history/16-sanitizer-implementation.md +0 -65
  20. package/docs/history/17-store-policy-implementation.md +0 -36
  21. package/docs/history/18-pure-mvp-review.md +0 -182
  22. package/docs/history/19-pure-mvp-re-review.md +0 -142
  23. package/docs/history/2-p0-runtime-prerequisites.md +0 -163
  24. package/docs/history/20-pure-mvp-final-review.md +0 -116
  25. package/docs/history/21-pure-mvp-review-final.md +0 -109
  26. package/docs/history/22-pure-mvp-signoff-review.md +0 -113
  27. package/docs/history/23-pure-mvp-signoff-review.md +0 -75
  28. package/docs/history/24-owned-tools-implementation.md +0 -27
  29. package/docs/history/25-v2-plugin-integration.md +0 -53
  30. package/docs/history/26-owned-tools-review.md +0 -138
  31. package/docs/history/27-owned-tools-final-review.md +0 -93
  32. package/docs/history/28-owned-tools-signoff.md +0 -98
  33. package/docs/history/3-p0-harness-foundation.md +0 -105
  34. package/docs/history/4-p0-source-opening.md +0 -78
  35. package/docs/history/5-qualification-gate-review.md +0 -133
  36. package/docs/history/6-p0-infrastructure-review.md +0 -275
  37. package/docs/history/7-p0-job-containment.md +0 -29
  38. package/docs/history/8-p0-evidence-support.md +0 -52
  39. package/docs/history/9-p0-launch-plan.md +0 -88
  40. package/docs/spec/design.md +0 -111
  41. package/docs/spec/intent.md +0 -81
  42. package/docs/spec/plan.md +0 -78
  43. package/docs/testing.md +0 -157
  44. package/scripts/build-plugin.mjs +0 -84
  45. package/scripts/install-opencode-ci.ps1 +0 -39
  46. package/scripts/install-opencode-ci.sh +0 -27
  47. package/scripts/smoke.mjs +0 -105
  48. package/scripts/verify-package.mjs +0 -18
  49. package/src/README.md +0 -22
  50. package/src/budgets/index.ts +0 -49
  51. package/src/config/README.md +0 -4
  52. package/src/config/options.ts +0 -76
  53. package/src/diagnostics/README.md +0 -4
  54. package/src/opencode/v2/filter.ts +0 -350
  55. package/src/opencode/v2/index.ts +0 -86
  56. package/src/opencode/v2/runtime.ts +0 -207
  57. package/src/opencode/v2/transports/README.md +0 -5
  58. package/src/runtime/http-transport.ts +0 -20
  59. package/src/sanitize/README.md +0 -4
  60. package/src/sanitize/index.ts +0 -454
  61. package/src/sources/README.md +0 -5
  62. package/src/sources/dotenv.ts +0 -133
  63. package/src/sources/native/README.md +0 -5
  64. package/src/store/index.ts +0 -64
  65. package/tests/README.md +0 -26
  66. package/tests/contract/plugin-v2.test.ts +0 -29
  67. package/tests/fixtures/README.md +0 -9
  68. package/tests/fixtures/job-child.mjs +0 -18
  69. package/tests/fixtures/plugins/README.md +0 -8
  70. package/tests/fixtures/process-child.mjs +0 -20
  71. package/tests/fixtures/services/README.md +0 -10
  72. package/tests/integration/components.test.ts +0 -5
  73. package/tests/integration/transparent-runtime.test.ts +0 -97
  74. package/tests/integration/windows-job.test.ts +0 -71
  75. package/tests/p0-host/README.md +0 -24
  76. package/tests/p0-host/linux-job.sh +0 -61
  77. package/tests/p0-host/windows-job.ps1 +0 -51
  78. package/tests/p0-native/README.md +0 -33
  79. package/tests/p0-native/windows-handle-loader.tests.ps1 +0 -191
  80. package/tests/scaffold/repository.test.ts +0 -105
  81. package/tests/smoke/README.md +0 -37
  82. package/tests/smoke/cases.json +0 -17
  83. package/tests/smoke/qualification.json +0 -20
  84. package/tests/smoke/runtime-candidate.json +0 -111
  85. package/tests/support/README.md +0 -32
  86. package/tests/support/child-process.ts +0 -235
  87. package/tests/support/host-api.ts +0 -123
  88. package/tests/support/opencode-environment.ts +0 -239
  89. package/tests/support/qualification.ts +0 -154
  90. package/tests/support/recording-endpoint.ts +0 -242
  91. package/tests/support/run-directory.ts +0 -179
  92. package/tests/support/smoke-cases.ts +0 -289
  93. package/tests/unit/budgets.test.ts +0 -35
  94. package/tests/unit/config.test.ts +0 -40
  95. package/tests/unit/dotenv.test.ts +0 -72
  96. package/tests/unit/filter-size.test.ts +0 -132
  97. package/tests/unit/harness.test.ts +0 -228
  98. package/tests/unit/host-api.test.ts +0 -122
  99. package/tests/unit/opencode-environment.test.ts +0 -161
  100. package/tests/unit/qualification.test.ts +0 -128
  101. package/tests/unit/recording-endpoint.test.ts +0 -188
  102. package/tests/unit/sanitize.test.ts +0 -159
  103. package/tests/unit/smoke-cases.test.ts +0 -20
  104. package/tsconfig.json +0 -20
package/.editorconfig DELETED
@@ -1,12 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- insert_final_newline = true
7
- indent_style = space
8
- indent_size = 2
9
- trim_trailing_whitespace = true
10
-
11
- [*.md]
12
- trim_trailing_whitespace = false
@@ -1,75 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main, master]
6
- pull_request:
7
- branches: [main, master]
8
-
9
- permissions:
10
- contents: read
11
-
12
- jobs:
13
- check:
14
- name: Node ${{ matrix.node }} / ${{ matrix.os }}
15
- runs-on: ${{ matrix.os }}
16
- strategy:
17
- fail-fast: true
18
- matrix:
19
- os: [ubuntu-24.04]
20
- node: ['24.14.1']
21
-
22
- steps:
23
- - name: Checkout
24
- uses: actions/checkout@v5
25
-
26
- - name: Setup Node
27
- uses: actions/setup-node@v5
28
- with:
29
- node-version: ${{ matrix.node }}
30
- cache: npm
31
-
32
- - name: Install dependencies
33
- run: npm ci
34
-
35
- - name: Check
36
- run: npm run check
37
-
38
- - name: Build plugin
39
- run: npm run build
40
-
41
- - name: Verify publication package
42
- run: npm run verify:package
43
-
44
- - name: List smoke inventory
45
- run: npm run test:smoke:list
46
-
47
- real-smoke-ubuntu:
48
- name: Real-host smoke / Ubuntu
49
- needs: check
50
- runs-on: ubuntu-24.04
51
- timeout-minutes: 30
52
- steps:
53
- - name: Checkout
54
- uses: actions/checkout@v5
55
-
56
- - name: Setup Node
57
- uses: actions/setup-node@v5
58
- with:
59
- node-version: '24.14.1'
60
- cache: npm
61
-
62
- - name: Install dependencies
63
- run: npm ci
64
-
65
- - name: Build plugin
66
- run: npm run build
67
-
68
- - name: Verify publication package
69
- run: npm run verify:package
70
-
71
- - name: Install verified OpenCode host
72
- run: bash scripts/install-opencode-ci.sh "$RUNNER_TEMP/opencode-host"
73
-
74
- - name: Run real-host smoke
75
- run: npm run test:smoke -- --run all
@@ -1,50 +0,0 @@
1
- name: Release plugin
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v[0-9]+.[0-9]+.[0-9]+'
7
- - 'v[0-9]+.[0-9]+.[0-9]+-*'
8
-
9
- permissions:
10
- contents: read
11
- id-token: write
12
-
13
- jobs:
14
- publish:
15
- name: Verify, smoke and publish npm package
16
- runs-on: ubuntu-24.04
17
- timeout-minutes: 45
18
- steps:
19
- - name: Checkout tag
20
- uses: actions/checkout@v5
21
-
22
- - name: Setup Node
23
- uses: actions/setup-node@v5
24
- with:
25
- node-version: '24.14.1'
26
- registry-url: https://registry.npmjs.org
27
- cache: npm
28
-
29
- - name: Install dependencies
30
- run: npm ci
31
-
32
- - name: Check
33
- run: npm run check
34
-
35
- - name: Build tagged package
36
- env:
37
- PLUGIN_VERSION: ${{ github.ref_name }}
38
- run: PLUGIN_VERSION="${PLUGIN_VERSION#v}" npm run build
39
-
40
- - name: Verify package contents
41
- run: npm run verify:package
42
-
43
- - name: Install verified OpenCode host
44
- run: bash scripts/install-opencode-ci.sh "$RUNNER_TEMP/opencode-host"
45
-
46
- - name: Run real-host smoke
47
- run: npm run test:smoke -- --run all
48
-
49
- - name: Publish with npm provenance
50
- run: npm publish ./dist --provenance --access public
package/.node-version DELETED
@@ -1 +0,0 @@
1
- 24.14.1
package/AGENTS.md DELETED
@@ -1,23 +0,0 @@
1
- # Engineering rules
2
-
3
- ## Where things live
4
- - `docs/spec/`: intent, design, implementation plan and release gates.
5
- - `docs/history/`: historical reviews; preserve findings and original context.
6
- - `docs/testing.md`: test-first workflow, isolation and evidence rules.
7
- - `src/`: implementation modules; currently placeholders.
8
- - `tests/`: unit, contract, integration and real-host smoke suites; shared harness in `support/`, synthetic fixtures in `fixtures/`.
9
-
10
- ## Design and production values
11
- - Apply SOLID: one responsibility per module; extend through small source/transport adapters; preserve contracts; prefer narrow interfaces; inject filesystem, network, clock and runtime dependencies.
12
- - Keep policy, secret storage, sanitization and OpenCode integration separate. Avoid speculative abstractions and unrelated refactors.
13
- - Fail closed within the declared boundary. Never silently downgrade protection or label an uncovered path protected.
14
- - Keep secrets out of public arguments, state, logs and errors. Sanitize exact sink representations, not just string leaves. Bound allocations, concurrency, execution time and cleanup.
15
- - Use native V2 APIs and pinned, verified dependencies. Never guess hooks or treat documentation/source inspection as runtime qualification.
16
-
17
- ## Mandatory gates
18
- - Write a meaningful failing test before implementing behavior. Build the isolated real-host smoke harness before **claiming host qualification**, but do not block pure plugin components on deferred host infrastructure; follow the plan's profile-specific gates.
19
- - Run `npm run check` for every change and relevant integration/smoke tests for affected boundaries. TODOs, skips and scaffold passes are not security evidence; missing required smoke coverage blocks qualification.
20
- - Use synthetic credentials and isolated services only. Never read user credentials/config, attach to the shared service, or publish raw captures. Follow `docs/testing.md` for cleanup and evidence.
21
- - Review `git diff --check`, changed files and staged content before committing. Record tests run, failures and unverified assumptions honestly.
22
- - Commit along the way: small, coherent, tested milestones with descriptive messages. Stage explicit task-owned paths; never include unrelated user changes or sensitive artifacts. Do not amend/rewrite history or push without an explicit request.
23
- - Update specs/tests with contract changes. Do not ship a no-op plugin, invented guarantee, or passing stub as completed functionality.
package/INSTALL.md DELETED
@@ -1,104 +0,0 @@
1
- # Installation
2
-
3
- ## Build
4
-
5
- ```bash
6
- npm ci
7
- npm run check
8
- npm run build
9
- ```
10
-
11
- The build produces `dist/plugin.mjs`. The current qualification target is
12
- OpenCode V2 (`@opencode/plugin` 2.0.28 / Effect 4.0.1).
13
-
14
- ## Published distribution
15
-
16
- Release tags use the form `vMAJOR.MINOR.PATCH`. The release workflow derives the
17
- package version from the tag, verifies the generated package contents, runs the
18
- Ubuntu real-host smoke tests, and publishes only `dist/` to npm with provenance.
19
-
20
- After a release, install the exact package version with:
21
-
22
- ```bash
23
- opencode plugin add opencode-secrets-obfuscation@1.2.3
24
- ```
25
-
26
- The npm package contains only the bundled plugin, README, license and metadata.
27
- The repository root remains private and is not published. Git/local-directory
28
- plugin entries remain development/testing distribution methods.
29
-
30
- Before the first release, configure npm trusted publishing for this GitHub
31
- repository and `.github/workflows/release.yml`; no long-lived npm token belongs
32
- in GitHub Actions.
33
-
34
- ## V2 configuration
35
-
36
- The V2 host loads a **directory** containing `index.js`. Copy the bundle into a
37
- private plugin directory and configure that directory with plugin options:
38
-
39
- ```json
40
- {
41
- "$schema": "https://opencode.ai/config.json",
42
- "plugins": [
43
- {
44
- "package": "/absolute/path/to/opencode-secrets-obfuscation/plugin",
45
- "options": {
46
- "version": 1,
47
- "sources": [
48
- {
49
- "id": "project",
50
- "type": "file",
51
- "format": "dotenv",
52
- "path": ".env",
53
- "required": true,
54
- "keys": ["EXAMPLE_TOKEN"]
55
- }
56
- ],
57
- "limits": {
58
- "sourceBytes": 65536,
59
- "totalSourceBytes": 131072,
60
- "sourceCount": 2,
61
- "secretCount": 50,
62
- "secretBytes": 4096,
63
- "indexBytes": 1048576,
64
- "inputBytes": 32768,
65
- "outputBytes": 65536,
66
- "traversalDepth": 8,
67
- "traversalNodes": 1000,
68
- "activeSessions": 4,
69
- "handlesPerSession": 64,
70
- "activeCalls": 2,
71
- "workingBytes": 1048576,
72
- "callTimeoutMs": 5000,
73
- "idleTimeoutMs": 1000
74
- }
75
- }
76
- }
77
- ]
78
- }
79
- ```
80
-
81
- The source path is relative to the OpenCode location and must remain inside it.
82
- Use synthetic credentials while testing. The plugin loads the configured values
83
- privately, assigns opaque `secret-<uuid>` substitutions, and applies them to
84
- existing OpenCode session/model/http/tool surfaces. It does **not** register
85
- model-visible tools and does **not** make credential or other plugin-owned HTTP
86
- requests.
87
-
88
- ## Verify
89
-
90
- Run the real-host sandbox proof instead of installing into a live user config:
91
-
92
- ```bash
93
- npm run test:smoke -- --run P5-TRANSPARENT-ROUNDTRIP
94
- ```
95
-
96
- The smoke case verifies that existing provider traffic contains an opaque
97
- replacement and never contains the raw synthetic value. A passing smoke case is
98
- not yet a release qualification; `tests/smoke/qualification.json` remains
99
- unqualified until the remaining sink, source-loader and host-boundary gates pass.
100
-
101
- ## Uninstall
102
-
103
- Remove the plugin entry from the V2 config and delete the copied plugin directory.
104
- The plugin never changes the host's existing provider or credential endpoints.
@@ -1,68 +0,0 @@
1
- # Resume checkpoint — transparent substitution E2E
2
-
3
- Updated: 2026-10-16.
4
-
5
- ## Product boundary
6
-
7
- The plugin is a transparent OpenCode V2 hook layer. It loads configured dotenv
8
- values privately, maps each value to an opaque `secret-<uuid>` replacement, and
9
- sanitizes existing OpenCode session/model/http/tool/error surfaces.
10
-
11
- It does **not** register model-visible tools, perform credential or other
12
- plugin-owned network requests, broker GitHub/resource operations, or expose
13
- authorization/capability/tool-broker options. The earlier owned-tool
14
- implementation was removed as a product correction; its history documents
15
- remain historical only.
16
-
17
- ## Current state
18
-
19
- - `P5-TRANSPARENT-ROUNDTRIP` passes against real opencode 2.0.28 with one CLI
20
- prompt and a loopback provider.
21
- - Evidence: two existing provider posts, raw canary absent, opaque
22
- `secret-<uuid>` replacement present, no plugin-owned request.
23
- - `npm run check`: green, 139 tests, 111 pass, 0 fail, 2 skipped, 26 TODO.
24
- - `tests/smoke/qualification.json` remains intentionally `unqualified` with
25
- null pins/evidence.
26
- - Live config remains untouched with `"plugins": []`.
27
- - Correction work is currently uncommitted; run `git diff --check` and commit
28
- after final verification.
29
-
30
- ## Corrected configuration
31
-
32
- Options now contain only `version`, relative dotenv `sources`, and bounded
33
- `limits`. There is no authorization, capabilities, `credentials_read`,
34
- `credential_http`, owned transport or endpoint override.
35
-
36
- ## E2E command
37
-
38
- ```text
39
- npm run build
40
- npm run test:smoke -- --run P5-TRANSPARENT-ROUNDTRIP
41
- ```
42
-
43
- The smoke copies the built artifact into an isolated host, creates a synthetic
44
- `.env`, sends a base64 representation in the prompt (the host logs argv before
45
- plugin hooks), and verifies provider bytes contain an opaque handle rather than
46
- the raw canary. The recorder is provider-only; it is not a credential verifier.
47
-
48
- ## Remaining backlog
49
-
50
- 1. Expand E2E sink coverage: context, compaction, title, generate, tool results,
51
- errors, logs, persisted state and forced cross-part/structured serialization.
52
- 2. Prove unsupported routes and ordinary tools are accurately scoped; do not
53
- claim protection for paths the plugin cannot intercept.
54
- 3. Qualify source opening on intended filesystems and test replacement/link/ACL
55
- races before making source-integrity claims.
56
- 4. Exercise budgets, expansion, cancellation and concurrency.
57
- 5. Refresh release docs and compatibility manifest; populate qualification pins
58
- only after the declared active-only envelope is covered.
59
-
60
- DNS pinning, proxy policy and authenticated transport qualification are not MVP
61
- items because the plugin does not own credential requests.
62
-
63
- ## Safety invariants
64
-
65
- - Synthetic credentials only; no user config or credentials are read.
66
- - No raw secret in public args, provider bodies, stdout/stderr or evidence.
67
- - Fail closed on invalid options/source/admission/budget conditions.
68
- - Do not reinstall into the live host or push without explicit user request.
@@ -1,233 +0,0 @@
1
- # Adversarial specification review
2
-
3
- Date: 2026-10-13
4
-
5
- Reviewed: `spec/intent.md`, `spec/design.md`, `spec/plan.md`, and necessary OpenCode V2 source/documentation.
6
-
7
- Relocation note: the specifications now live under [`docs/spec/`](../spec/intent.md). Paths and line numbers below refer to the original reviewed snapshot. Subsequent specification decisions and still-pending proof gates are tracked in [the remediation table](../spec/plan.md#review-remediation-gates).
8
-
9
- **This is a specification/source review, not an executed implementation security audit.** This repository has no implementation, package manifest, lockfile, or executable qualification fixture. Counterexamples below are analytical; no plugin, provider, filesystem-race, or security integration tests were executed.
10
-
11
- ## Executive verdict
12
-
13
- **Proceed with the P0 feasibility spike, but do not freeze this specification or approve a guarantee-qualified MVP release yet.** A native V2 plugin can implement the proposed private, constrained authenticated round trip. The selected plugin APIs are substantially correct. The remaining blockers are security-contract defects, not a need to revert to V1 or substitute credentials into shared tool arguments.
14
-
15
- The highest-priority issues are:
16
-
17
- 1. Sanitizing individual strings does not make their later serialization/concatenation safe; the pinned host can reconstruct a credential in its generic spill writer.
18
- 2. Leftmost-longest replacement does not meet the promised overlapping-secret suffix protection, and unrestricted short values conflict with placeholder preservation and literal-absence assertions.
19
- 3. The generic HTTP tool has no concrete response classifier capable of enforcing its derived-credential promise.
20
- 4. Capability preauthorization and host permission rules are separate authorities, but the required approval mode is not represented in the configuration contract.
21
- 5. Failed reload can run the old policy in a **new** activation. Invalidating old handles does not prevent renewed authority under a revoked policy.
22
- 6. MVP protected-route rejection relies on enforcement the plan assigns to production. A live HTTP hook is not a durable readiness gate.
23
-
24
- The narrow adapter-only MVP is feasible after these contracts are resolved. Universal model/state confidentiality is not achievable with the specified stock-plugin deployment, and the specifications correctly recognize much of that limitation. Several acceptance statements nevertheless exceed that narrower boundary.
25
-
26
- ### Priority summary
27
-
28
- | ID | Severity | Release impact |
29
- | --- | --- | --- |
30
- | R1 | High | Blocks claimed structured-output and generic-spill confidentiality. |
31
- | R2 | High | Blocks sanitizer correctness contract and its property-test oracle. |
32
- | R3 | High | Blocks safe-result guarantee for a configurable generic HTTP adapter. |
33
- | R4 | High | Blocks deployments requiring host resource denial/approval semantics. |
34
- | R5 | High | Blocks lifecycle claim that revoked authority cannot return after failed replacement. |
35
- | R6 | High | Blocks the protected-route designation; a narrower active-plugin-only release is possible. |
36
- | R7 | Medium | Resource bounds are insufficiently defined for an untrusted-input service. |
37
- | R8 | Medium | Source-file portability/security feasibility has not been included in the initial proof gate. |
38
-
39
- “High” means a concrete disclosure/authorization failure or an unresolved condition necessary for an advertised guarantee. It does not mean an exploit was reproduced against an implementation.
40
-
41
- ## Evidence and verification
42
-
43
- ### Materials examined
44
-
45
- - Read the supplied global instructions and checked the project/ancestor paths for applicable `AGENTS.md`; no additional project instructions were present. Read applicable instructions in the external OpenCode checkout used for source inspection.
46
- - Read all three specifications and the two-line README. The specs were already untracked user work; they were not modified.
47
- - Checked that this report did not exist before creating it.
48
- - Loaded the OpenCode skill and fetched official **V2** pages: [plugin framework](https://opencode.ai/v2/docs/build/plugins), [Effect API](https://opencode.ai/v2/docs/build/plugins/effect), [plugin loading/reload](https://opencode.ai/v2/docs/plugins), [permissions](https://opencode.ai/v2/docs/permissions), and [configuration](https://opencode.ai/v2/docs/config).
49
- - Independently inspected the local OpenCode checkout at `C:\Users\jfomh\source\deleteme\opencode`. `git rev-parse HEAD` returned `e9def88a7790efc6abace15b67f3625ff4d66237`; its working tree was clean. This matches the research commit in the specs, **not an independently qualified published release**.
50
- - No changes were made to that checkout, the specifications, or other project files.
51
-
52
- ### Pinned source references
53
-
54
- All source references below use commit `e9def88a7790efc6abace15b67f3625ff4d66237`:
55
-
56
- | Ref | Source and verified fact |
57
- | --- | --- |
58
- | S1 | [`packages/plugin/src/effect/tool.ts:20–59`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/plugin/src/effect/tool.ts#L20-L59): before-hook can fail with `Tool.Error`; after-hook handles completed/error outcomes and has failure type `never`. |
59
- | S2 | [`packages/plugin/src/effect/session.ts:14–172`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/plugin/src/effect/session.ts#L14-L172): prompt, four request kinds, HTTP and WebSocket events exist with distinct shapes. |
60
- | S3 | [`packages/core/src/tool.ts:103–155,225–295`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/tool.ts#L103-L155): before/after ordering, post-hook normalization, direct and nested Code Mode dispatch, and whole-tool permission filtering. |
61
- | S4 | [`packages/core/src/tool/runtime.ts:29–60,88–132,261–273`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/tool/runtime.ts#L29-L60): host schema processing and content normalization surround the executor; missing content can be synthesized with `JSON.stringify(output)`. |
62
- | S5 | [`packages/core/src/tool-output.ts:67–116`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/tool-output.ts#L67-L116): generic truncation joins text parts with a newline and writes the joined text to disk. |
63
- | S6 | [`packages/core/src/session/runner/step.ts:95–137`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/session/runner/step.ts#L95-L137): provider event publication precedes local execution; provider-executed tool calls skip local execution; generic truncation follows execution. |
64
- | S7 | [`packages/core/src/plugin.ts:47–86,125–169`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/plugin.ts#L125-L169): reload closes earlier scopes and may call `load(fallback.plugin)` after replacement failure; load errors can log their causes. |
65
- | S8 | [`packages/core/src/plugin/hooks.ts:69–103`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/plugin/hooks.ts#L69-L103): scope cleanup removes hook registrations; trigger uses the current callback collection and later callbacks can edit the same event. |
66
- | S9 | [`packages/core/src/session/model-request.ts:304–399`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/session/model-request.ts#L304-L399): HTTP guard precedes `handler(sent)` on this route; HTTP-hook presence is checked during preparation; WebSocket has a separate binding. |
67
- | S10 | [`packages/schema/src/tool.ts:14–20,27–30,61–101`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/schema/src/tool.ts#L14-L101) and [`packages/plugin/src/effect/permission.ts:7–24`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/plugin/src/effect/permission.ts#L7-L24): Effect tool context has no `ask` or `signal`; permission domain hooks evaluation and manages existing requests. |
68
- | S11 | [`packages/core/src/session/move.ts:119–157`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/session/move.ts#L119-L157) and [`packages/core/src/session/runner/llm.ts:79–111`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/core/src/session/runner/llm.ts#L79-L111): ordinary moves are admitted controls applied at runner boundaries; exceptional unavailable-source recovery can publish a move directly. |
69
- | S12 | [`packages/plugin/src/effect/aisdk.ts:5–22`](https://github.com/anomalyco/opencode/blob/e9def88a7790efc6abace15b67f3625ff4d66237/packages/plugin/src/effect/aisdk.ts#L5-L22): SDK/language hooks are construction hooks with model identity, not session identity. |
70
-
71
- Documentation is mutable. Source ordering above is verified at the pinned commit; compatibility with any released artifact and end-to-end failure behavior remain unverified.
72
-
73
- ## Prioritized findings
74
-
75
- ### R1 — Safe string leaves can become unsafe serialized output
76
-
77
- **Severity:** High. **Evidence:** source-backed counterexample; implementation behavior untested.
78
-
79
- **Spec references:** `spec/design.md:254–264`, `spec/design.md:344–346`, `spec/design.md:401–406`; `spec/plan.md:84–88`, `spec/plan.md:119`.
80
-
81
- **Failure scenario:** Register the multiline secret `alpha\nbeta` (an actual newline). A normal tool returns two text parts, the first ending in `alpha` and the second beginning with `beta`. Neither part individually contains the secret. A recursive string sanitizer can therefore approve both. The pinned generic truncator then computes `content.flatMap(...).join("\n")` (S5), recreating the exact credential. With sufficient harmless padding, that credential is written to a generic spill file. This is precisely a path the MVP says its after-hook protects, not the already-excluded shell capture path.
82
-
83
- A related omission is non-string scalar output. A registered dotenv value `123456` may return as JSON number `123456`, or a value `true` as a boolean. Filtering string values and keys does not sanitize the independently persisted structured `output`/metadata. S3–S4 also show that host normalization can generate text from structured output. Sanitizing the existing display text does not make the structured copy safe.
84
-
85
- **Consequence:** An implementation following the traversal description can pass leaf-level tests and still disclose full registered values in claimed protected artifacts.
86
-
87
- **Recommended fix:** Define safety at each concrete sink representation. For generic text results, canonicalize/join using the host's exact rules before matching, and return a safe representation that cannot reintroduce matches during subsequent normalization. Explicitly classify numeric/boolean/null values and serialized keys/punctuation; omit/block an object if preserving its required shape would disclose a value. Keep `content`, `output`, and metadata safe independently. Do not invoke arbitrary user serialization to do this.
88
-
89
- **Required gate:** In the real host, force a spill with a credential spanning text-part boundaries; inspect the actual file, structured session output, and provider payload. Add scalar echoes and normalization after an after-hook replaces content. Assert sink bytes, not only sanitizer leaf outputs.
90
-
91
- ### R2 — The matching contract has incompatible requirements
92
-
93
- **Severity:** High. **Evidence:** deterministic algorithmic counterexamples.
94
-
95
- **Spec references:** `spec/design.md:119`, `spec/design.md:198`, `spec/design.md:224–227`, `spec/design.md:244–258`; `spec/plan.md:81–88`.
96
-
97
- **Failure scenario A — overlapping suffixes:** With registered values `abc` and `bcdef`, the input `abcdef` is processed by ordinary leftmost-longest matching as `[handle-for-abc]def`. The suffix `def` of the second registered credential is exposed. Longest-at-the-same-start handles prefixes, not overlapping intervals with different starts. The design explicitly says overlaps must not leak suffixes (`design.md:246`).
98
-
99
- **Failure scenario B — short values and reserved output:** A source containing `TOKEN=e` is required to be registered, but every `secret-<UUID>` contains `e`. Preserving issued placeholders leaves a registered literal in output; replacing it breaks placeholder preservation/idempotence. The reserved-value rejection clause might be intended to reject this source, but it does not define the conflict predicate and contradicts an unqualified expectation that every short value is supported. Structural fields and fixed error codes introduce similar unavoidable collisions.
100
-
101
- **Consequence:** The proposed P3 property “supported representations do not remain in safe outputs” has no unambiguous oracle. Implementers must silently choose which requirement to violate. A whole-token absence scan also misses the expressly forbidden overlapping suffix disclosure.
102
-
103
- **Recommended fix:** Specify a replacement rule covering the union of all overlapping match intervals; overlapping unions can use a redaction-only identity rather than a resolvable entry handle. Define a precise admissible-value/conflict rule covering generated placeholders, fixed protocol fields, and safe errors. Explicit safe source rejection is acceptable; silently excluding short values is not. Define whether safety means absence of secret-origin bytes or absence of coincidentally equal literals, and make claims/tests consistent with that choice. Check replacement boundaries for newly formed matches without recursively interpreting handles.
104
-
105
- **Required gate:** Include the two counterexamples, overlapping chains, input adjacent to an issued placeholder, and collisions with protocol/error text. Test union coverage and the chosen admissible-value rules, not just full-string absence.
106
-
107
- ### R3 — Generic HTTP success lacks an enforceable result-classification contract
108
-
109
- **Severity:** High. **Evidence:** specification gap; response scenarios are hypothetical and service-dependent.
110
-
111
- **Spec references:** `spec/design.md:82–95`, `spec/design.md:121`, `spec/design.md:278–303`, `spec/design.md:389–393`; `spec/intent.md:68`; `spec/plan.md:96–100`.
112
-
113
- **Failure scenario:** An operator allowlists an exact HTTPS GET resource that returns an object with a download URL or a nested stored credential. The URL's signature is unrelated to the original bearer token. This can be an ordinary read endpoint rather than a token-issuing operation. The response is valid bounded JSON and contains none of the registered literals. The generic adapter returns the new capability into the transcript/model.
114
-
115
- The design requires service-specific classification and rejection of unknown semantics, but its tool/configuration describes arbitrary exact origins/paths with no classifier, response schema, or approved output projection. A MIME type and a denylist of known issuing operations cannot establish those semantics.
116
-
117
- **Consequence:** Either the implementation returns unsafe unknown data, or it correctly rejects essentially all configurable endpoints and cannot provide the promised useful generic round trip. Trusting an endpoint to receive a credential does not make every value it returns safe for the model; remote responses are explicitly untrusted (`intent.md:76`).
118
-
119
- **Recommended fix:** Make the first adapter genuinely service-specific: select a reviewed operation ID and versioned response schema, return an allowlisted projection, and reject unknown fields/types where necessary. Alternatively make the MVP status-only, with an explicitly public result such as authentication success. Add reviewed text-echo handling as a distinct fixture contract. Do not expose arbitrary text/JSON bodies until an actual classification mechanism is specified. Derived bearer values should be withheld or registered privately before any public result is built.
120
-
121
- **Required gate:** The service fixture returns a signed URL, a new token under a benign nested key, a token in an error body, and a public boolean. Only the explicitly public projection may pass. Verify the released configuration cannot select an unreviewed classifier/operation.
122
-
123
- ### R4 — Required host approval is not a configuration or execution decision
124
-
125
- **Severity:** High for host-policy/approval deployments. **Evidence:** verified public API and registry behavior; missing specification branch.
126
-
127
- **Spec references:** `spec/design.md:124`, `spec/design.md:293–295`, `spec/design.md:348`; `spec/plan.md:43`, `spec/plan.md:53–54`, `spec/plan.md:99`.
128
-
129
- **Failure scenario:** A deployment sets a resource-specific host `deny`, or `ask`, for the action assigned to `credential_http`, while plugin options grant that endpoint. The pinned registry's `whollyDisabled` checks whether the last action-matching rule is `resource: "*", effect: "deny"` (S3, `tool.ts:292–295`). A resource-specific denial/ask leaves the tool visible. The custom executor has no automatic permission assertion (S10) and can execute using only its plugin grant.
130
-
131
- The specifications correctly say visibility is not approval. However, “a deployment requiring per-call host approval must block” is not implementable from the described options: there is no authorization-mode field, required-approval policy input, approved request adapter, or specified detection of incompatible effective host policy. This also affects captured tools after host policy changes; a prior visibility check is not a fresh authorization decision.
132
-
133
- **Consequence:** Users can believe their host denial/approval rules constrain the credential tool when the specified executable policy does not evaluate them. This is not a claim that explicit plugin preauthorization is inherently wrong; it is an undefined interaction between two policy systems.
134
-
135
- **Recommended fix:** Specify one explicit MVP authorization mode and its authority. If plugin grants are preauthorization, require an explicit configuration declaration and document the exact host-rule limitations; reject a requested host-approval mode until the public integration exists. If host rules must remain authoritative, prove a supported evaluation/approval path or block release for that profile. Define how agent/session restrictions are intersected at execution, not just how the tool appears in the catalog.
136
-
137
- **Required gate:** Test whole-tool deny, resource-specific deny, ask, a host policy change after tool snapshot capture, and a plugin grant without the required approval mode. Assert no network connection or credential resolution for denied/unapproved operations.
138
-
139
- ### R5 — Invalidating old handles does not prevent policy rollback on failed reload
140
-
141
- **Severity:** High. **Evidence:** verified source behavior plus contradictory lifecycle acceptance scope.
142
-
143
- **Spec references:** `spec/intent.md:42`, `spec/intent.md:93–95`; `spec/design.md:125`, `spec/design.md:234–237`, `spec/design.md:363–365`, `spec/design.md:403–405`; `spec/plan.md:73`, `spec/plan.md:120`, `spec/plan.md:134–135`.
144
-
145
- **Failure scenario:** Generation A allows an endpoint. The operator changes configuration/package revision to B to remove that grant, but B fails activation. OpenCode closes A and then calls `load(fallback.plugin)` (S7). That is a **fresh activation of the old definition**, not reuse of the invalidated closure. The old definition can reload the source and issue new handles under A's permission policy.
146
-
147
- The proposed live-validity flag correctly denies captured executors from A. It does not tell the newly created fallback activation that A's policy has been revoked. No activation-local memory can independently provide that monotonic knowledge across destruction/recreation. Expected source-validation errors may be caught as unavailable, but package/setup/transform failures remain required scenarios.
148
-
149
- **Consequence:** Tests that only retry an old handle can pass while the model obtains a fresh handle and regains revoked authority. P2 defers prevention of policy rollback to the enforced profile, while P5 broadly requires no credential authority to survive invalidation. Those are different guarantees.
150
-
151
- **Recommended fix:** Separate “old references never resolve” from “superseded policy never authorizes again.” For the latter, move a required policy-generation/readiness check into a host-controlled component available in the MVP, or explicitly remove that claim from the plugin-only release. A nonsecret desired revision may be durable; that does not require persisting the secret map. Explain which component owns the authoritative revision and how it is consulted immediately before resolution.
152
-
153
- **Required gate:** After deliberately failing a revoking replacement, call `credentials_read` again and attempt the revoked operation with a **new** handle. Also exercise transform failure and reordering/replacement of preceding plugins. Inspect active versus requested revisions, not just presence of the plugin ID.
154
-
155
- ### R6 — Protected-route refusal and required readiness have different milestone owners
156
-
157
- **Severity:** High for the protected-route claim. **Evidence:** verified hook removal/dispatch structure; complete runtime race behavior remains a P0 validation requirement.
158
-
159
- **Spec references:** `spec/intent.md:36–40`, `spec/intent.md:68–72`; `spec/design.md:353–357`, `spec/design.md:363–365`, `spec/design.md:381–385`; `spec/plan.md:11–12`, `spec/plan.md:41–47`, `spec/plan.md:105–106`, `spec/plan.md:134–146`.
160
-
161
- **Failure scenario:** The user switches from the qualified native HTTP route to an unqualified SDK/WebSocket route, or configuration disables/unloads the plugin while work is pending. MVP language requires unverified protected routes to be rejected before dispatch. Yet required-plugin readiness, mandatory egress ordering, and downgrade protection are assigned to P6.
162
-
163
- At the pinned source, hook cleanup removes the callback (S8). HTTP preparation determines whether to install a wrapper; the wrapper triggers the currently registered callbacks and then calls the handler (S9). There is no security-plugin liveness assertion in that code. Prepared requests and raw body overlays therefore need explicit qualification across unload/reload, not an assumption that the earlier `context` hook or `http.request` registration remains enforcing. S6 also confirms provider-executed calls do not pass through the local tool guard, despite the unqualified “every tool dispatch” language.
164
-
165
- **Consequence:** “Unsupported” is only a documentation status unless something refuses the route. The specs currently allow the same feature to be a mandatory MVP protection and a production-only host requirement. A passing HTTP happy-path test cannot resolve this.
166
-
167
- **Recommended fix:** Define an exact MVP deployment envelope: provider package/protocol/version, allowed transports/features, plugin ordering, readiness lifetime, and the owner of route rejection. Bring the smallest necessary mandatory-readiness/egress integration forward if the release is to be called protected. Otherwise limit the claim to filtering while a particular activation is active and to the owned executor's outputs; do not label unmediated routes as blocked. Exclude provider-executed tools and opaque checkpoints/uploads unless independently qualified.
168
-
169
- **Required gate:** Pause immediately before transmission, unload/replace the plugin, and resume; repeat with a late body overlay containing a synthetic credential. Attempt an unqualified route, HTTP fallback, provider-executed tool, and sessionless generation. Observe the network and diagnostics. P0 must prove safe abort behavior or record a blocking host dependency. Source inspection proves the normal hook position, not absence of every retry/fallback leak.
170
-
171
- ### R7 — Per-input limits do not bound aggregate or post-redaction memory
172
-
173
- **Severity:** Medium. **Evidence:** missing resource contract and straightforward expansion scenario.
174
-
175
- **Spec references:** `spec/design.md:97–103`, `spec/design.md:198–200`, `spec/design.md:249–262`; `spec/plan.md:70`, `spec/plan.md:86–89`, `spec/plan.md:100`.
176
-
177
- **Failure scenario:** Register the valid short value `!`. A permitted 1 MiB text response consisting of that byte expands to approximately 43 MiB when each match becomes `secret-<UUIDv4>`. Concurrent calls multiply that allocation before generic truncation. Likewise, many sessions/Locations can create maps and concurrent response buffers; the configuration contains no explicit aggregate source/index/session/concurrency limits or behavior at those limits.
178
-
179
- **Consequence:** An endpoint can exhaust the shared service without exceeding `resultBytes`. An implementation can be locally bounded yet unbounded at service scale. Resource exhaustion then exercises the very defect/cleanup paths on which confidentiality depends.
180
-
181
- **Recommended fix:** Define pre- and post-transformation byte budgets, aggregate index/source budgets, bounded traversal depth/node count, active-call and session-handle quotas, and allocation/queue behavior. Enforce expansion bounds while constructing output rather than after allocating it. Define safe denial/eviction semantics and which quotas are Location-local versus service-wide. Count decompressed bytes for any supported compression.
182
-
183
- **Required gate:** Maximum-expansion responses, deeply nested JSON, many concurrent sessions/calls, slow bodies, and cancellation while at the quota. Measure peak allocation and verify safe denial without partial publication.
184
-
185
- ### R8 — Cross-platform opened-file security needs an early feasibility proof
186
-
187
- **Severity:** Medium. **Evidence:** specification implementation gap; no OS bypass was executed.
188
-
189
- **Spec references:** `spec/design.md:122`, `spec/design.md:190–201`; `spec/plan.md:36–47`, `spec/plan.md:65–66`, `spec/plan.md:121`, `spec/plan.md:219`.
190
-
191
- **Failure scenario:** A loader checks a path/ACL, then opens it after a directory junction or file replacement. A path-only preflight is not the opened-object identity/ACL guarantee the design requires. Windows inherited ACLs, owner identity, ancestor traversal/replacement rights, reparse points, and hard-link aliases require explicit semantics. The plan promises Linux and Windows qualification but does not choose the necessary runtime/OS facilities before package implementation begins.
192
-
193
- This is not an objection that same-user ACLs should sandbox generated code: the specs correctly defer that to isolation. It is a separate question of whether the loader actually validates the object it consumes under the stated source trust policy.
194
-
195
- **Consequence:** The project can build most of P1–P4 before discovering that its chosen filesystem API/dependency cannot enforce P2 on a supported OS. Accepting an approximation would silently weaken the source guarantee.
196
-
197
- **Recommended fix:** Add a small source-opening feasibility fixture to P0. Choose the supported OS/filesystem matrix and a concrete opened-handle validation strategy; decide whether a narrowly scoped native helper is needed. Specify acceptable ACL principals and ancestor permissions, handling of hard links, and whether unsupported filesystems are safely rejected. Keep this independent from the later generated-code sandbox.
198
-
199
- **Required gate:** An opened-handle test on Windows and Linux covering file/directory swaps, inherited broad grants, ACL changes, reparse/symlink traversal, and aliases. Verify with synthetic files and safe diagnostics; do not infer Windows ACL protection from POSIX mode bits.
200
-
201
- ## Additional unresolved questions and validation requirements
202
-
203
- These are explicit design decisions or source/runtime investigations still needed, not established vulnerabilities:
204
-
205
- 1. **Exact artifacts:** P0 must identify an actually published V2 release, its source commit, host Effect version, and compatible plugin package. The pinned checkout alone does not establish distribution compatibility. Current documentation examples must not substitute for testing the packed package.
206
- 2. **Who receives a session grant?** `design.md:225–230,272–274` describes session-specific handles and an “authorized” catalog, but the policy has no enrollment rule. Does every session in a Location receive every configured capability by calling `credentials_read`? If yes, say that precisely: cross-session handle rejection prevents replay, not independent reacquisition. Define parent/child/fork/import and agent-change behavior. If no, specify the trusted granting event and state owner.
207
- 3. **Move semantics:** Verify actual placement through the public session API, including its response envelope. Normal movement is a step-boundary control (S11), so do not assume every move is a mid-executor race. Test A→B→A, delayed/lost cleanup-event observation, unavailable-source recovery, and queued work. Current Location equality alone cannot prove that a grant was never revoked during an intervening move. Specify the authoritative move epoch or synchronization used for that promise.
208
- 4. **Source rotation and old redaction state:** `design.md:201,234–237` retains old matching state only until old calls drain. What protects a queued prompt, later synthetic notification, imported result, or request already assembled with the previous value? Either retain bounded redaction generations with explicit ownership or reject affected work. This is already a production concern; state exactly which queued/resumed flows the MVP excludes.
209
- 5. **HTTP mechanics:** Select the real HTTP client and prove DNS validation applies to the address actually connected to, including multiple DNS answers, IPv4-mapped IPv6, TLS hostname verification, connection reuse, proxy environment, and redirects. Define URL normalization and response decompression/content-type handling. “Constrain DNS” is a requirement, not yet an algorithm. Also define which status/header fields may be public.
210
- 6. **Runnable test services:** The planned synthetic service and recording provider need a concrete transport fixture. A default localhost HTTP server conflicts with the owned tool's HTTPS/public-address-only policy. Use a test-only connector/controlled HTTPS setup that keeps authorization and production defaults intact, plus a real network test of the address policy. Do not solve the fixture by shipping a permissive production exception.
211
- 7. **Defects, schemas, and cancellation:** S4 shows host input validation, output encoding, and normalization outside the executor's catch block; input validation errors can quote the entire input. Verify sanitizer failure, output-schema mismatch, raw public-argument rejection, cancellation, and captured executors after hook removal separately. Ensure scoped finalizer order invalidates authority before resources/guards disappear and retains matchers until raw buffers are discarded. No secure-zeroization claim should be added.
212
- 8. **Evidence quality:** P5 should require a per-case table linking each claimed guarantee to actual provider bytes, event/DB/log/spill captures, and authorization/network assertions. Mark known-gap tests as expected gaps, never as successful protection cases. Include independent oracles for overlap coverage, source isolation, policy revocation, and destination binding. Canary scans alone cannot prove those properties. Restrict failing captures; use synthetic credentials only.
213
-
214
- ## Sound choices to preserve
215
-
216
- - Private resolution inside a plugin-owned executor, with immutable public arguments, is the right boundary. Unrestricted string substitution would undermine the entire design.
217
- - Activation-local secret ownership, no durable map/indexes, CSPRNG handles, and separate redaction-only versus capability handles correctly avoid accidental persistence and permission union for duplicate values.
218
- - Native Effect registration, typed before-hook denial, four distinct request hooks, and separation of HTTP/WebSocket/SDK surfaces align with the inspected V2 APIs. No nonexistent `context.ask`, V1 result envelope, or identity-free shell injection is required.
219
- - The specifications correctly distinguish normal after-hook/generic spill protection from shell captures, earlier progress, provider output publication, imports, and independent generation. Filesystem/process isolation is rightly required for stronger claims.
220
- - Immutable generations, old-executor rechecks, bounded owned-tool buffering, safe replacement errors, exact HTTP audiences, and refusal of redirects are strong foundations. Keep them while fixing the gaps above.
221
-
222
- ## Recommended remediation order
223
-
224
- 1. **Make the release contract executable.** Decide authorization mode, session enrollment, active-only versus mandatory protection, and behavior on failed replacement. Resolve R4–R6 before claiming a protected MVP.
225
- 2. **Run P0 against exact artifacts.** Include safe HTTP abortion, unload/fallback behavior, captured execution, and the Windows/Linux source-opening feasibility fixture. Record unresolved host integrations as blocking dependencies.
226
- 3. **Replace the sanitizer oracle with sink-level rules.** Resolve R1–R2 and specify scalar handling, overlapping intervals, reserved-output conflicts, and final serialization. Add aggregate/expansion bounds from R7 before choosing the indexing implementation.
227
- 4. **Reduce the authenticated MVP to one reviewed operation.** Add a concrete response projection/classifier, pinned transport behavior, and runnable fixtures. Close R3 rather than implementing an apparently generic safe HTTP client.
228
- 5. **Implement and qualify the packaged plugin.** Exercise the exact negative cases above alongside successful authentication; include fresh-handle acquisition after failed revocation, cross-part spill reconstruction, and unsupported-route attempts.
229
- 6. **Keep production enforcement separately gated.** Broker ownership, isolation, inbound streaming, complete publication mediation, historical rotation coverage, and every enabled transport still require P6–P9. Passing the smaller release must not automatically satisfy those gates.
230
-
231
- ## Review outcome
232
-
233
- The V2 integration approach is credible, and the specification has already avoided several serious architectural mistakes. It is not yet precise enough to make its strongest MVP assertions testable or true. Resolve the six high-priority findings or explicitly narrow the affected release claims, prove the listed P0 dependencies, and then proceed with implementation.