diff --git a/docs/adr/0008-canonical-opencode-project-configuration.md b/docs/adr/0008-canonical-opencode-project-configuration.md new file mode 100644 index 0000000..8d8ab54 --- /dev/null +++ b/docs/adr/0008-canonical-opencode-project-configuration.md @@ -0,0 +1,149 @@ +# ADR-0008: Canonical OpenCode project configuration + +- Status: Accepted +- Date: 2026-06-17 +- Approved by: Repository owner +- Approved at: 2026-06-17T19:17:43Z +- Related finding: REPO-AUDIT-F-004 +- Related redesign blocker: B-4 + +## Context + +The repository currently contains both `opencode.json` and `opencode.jsonc`. +They define materially different defaults and permissions: + +- `opencode.json` selects `mobile-ui-orchestrator`, supplies project instruction + files, permits several read-only tools, permits skills, and requires approval + for delegated tasks. +- `opencode.jsonc` selects `plan`, applies path-sensitive secret protections, + denies skills and delegated tasks, and blocks destructive or publishing + commands. + +OpenCode 1.17.7 merges both project files. The currently resolved configuration +therefore contains values from both files, with `opencode.jsonc` overriding +conflicting values. + +This makes repository policy dependent on undocumented merge behavior. A +maintainer can edit one file believing it is authoritative while the other file +changes the effective result. + +The repository documentation already instructs operators to start in the +default `plan` agent. The external repository audit also recommends retaining +the stricter permission baseline. + +## Decision + +The repository will use exactly one canonical project configuration: + +- Canonical file: `opencode.jsonc` +- Default agent: `plan` +- OpenCode version: exactly `1.17.7` +- `opencode.json` will be removed + +The canonical configuration must preserve the security-sensitive policy from +the existing `opencode.jsonc`: + +- secret, credential, private-key, environment-file, and Git metadata read + restrictions; +- equivalent edit restrictions; +- delegated task and skill denial; +- approval for unclassified shell commands; +- explicit denial of destructive Git, infrastructure, publication, privilege, + and destructive filesystem commands; +- disabled sharing; +- denied external-directory access. + +The canonical configuration must also preserve non-conflicting values that are +part of the currently resolved project configuration: + +- project instruction files: + - `AGENTS.md` + - `.ui-redesign/adapter/REPOSITORY_ADAPTER.md` + - `.ui-redesign/decisions/DECISION_LEDGER.md` +- explicit read-only tool allowances for: + - `glob` + - `grep` + - `list` + - `lsp` + - `todowrite` + - `question` + +The canonical file will retain the existing `opencode.jsonc` watcher, +tool-output, compaction, snapshot, update-notification, and permission settings +unless a separately reviewed change documents a reason to alter them. + +The root package will pin `opencode-ai` to exact version `1.17.7`. Repository +validation will invoke the local package binary rather than relying on an +unversioned user-level installation. + +An effective-configuration smoke test will verify at minimum: + +1. exactly one root OpenCode configuration exists; +2. the local OpenCode version is `1.17.7`; +3. the resolved default agent is `plan`; +4. sharing remains disabled; +5. all required instruction files are loaded; +6. task and skill permissions remain denied; +7. external-directory access remains denied; +8. protected read and edit patterns remain denied; +9. destructive and publishing commands remain denied; +10. expected read-only tools remain allowed. + +The smoke test must isolate or explicitly disclose user-level and environment +configuration sources so a developer-specific override cannot make the +repository test pass accidentally. + +## Consequences + +### Positive + +- Project policy has one authoritative source. +- The effective default agent and permissions are reviewable without relying on + configuration merge order. +- OpenCode runtime behavior is tied to a reproducible version. +- Regression testing detects permission or default-agent drift. +- The mobile redesign instruction files remain active while the stricter + permission baseline is preserved. + +### Negative and risks + +- Installing the pinned CLI adds platform-specific optional dependencies and a + package postinstall step. +- Developers with unsupported platforms must use a separately reviewed + installation method. +- User-level, environment-provided, or future global OpenCode configuration can + still affect an interactive session. +- Setting `task` and `skill` to `deny` intentionally prevents delegation from + this default configuration. Any future delegation-enabled profile requires a + separate reviewed configuration and decision. + +## Alternatives considered + +### Keep both files and document merge order + +Rejected. The effective policy would remain dependent on version-specific merge +behavior and would still be easy to edit incorrectly. + +### Make `opencode.json` canonical + +Rejected. It has weaker secret-path controls, permits skills, permits delegated +tasks subject to approval, and selects a redesign-specific orchestrator as the +general project default. + +### Keep only the current `opencode.jsonc` without copying resolved values + +Rejected. Removing `opencode.json` would also remove the project instruction +list and explicit read-only tool allowances that are currently contributed by +that file. + +### Do not pin OpenCode + +Rejected. Configuration semantics and effective permissions could change when +the developer installation changes. + +## Rollback + +Revert the consolidation commit and stop OpenCode use until the conflicting +configuration state is reviewed. Restoring both root configuration files while +continuing normal OpenCode operation is not an acceptable steady-state +rollback. diff --git a/opencode.json b/opencode.json deleted file mode 100644 index 9153887..0000000 --- a/opencode.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "default_agent": "mobile-ui-orchestrator", - "share": "disabled", - "snapshot": true, - "instructions": [ - "AGENTS.md", - ".ui-redesign/adapter/REPOSITORY_ADAPTER.md", - ".ui-redesign/decisions/DECISION_LEDGER.md" - ], - "permission": { - "read": "allow", - "glob": "allow", - "grep": "allow", - "list": "allow", - "lsp": "allow", - "todowrite": "allow", - "question": "allow", - "skill": "allow", - "edit": "ask", - "bash": "ask", - "task": "ask", - "webfetch": "ask", - "websearch": "ask", - "external_directory": "deny" - }, - "watcher": { - "ignore": [ - "node_modules/**", - "dist/**", - "build/**", - "coverage/**", - ".git/**", - ".ui-redesign/evidence/raw/**" - ] - }, - "compaction": { - "auto": true, - "prune": true, - "reserved": 12000 - } -} diff --git a/opencode.jsonc b/opencode.jsonc index 606d8ae..fb9df64 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -1,28 +1,28 @@ { "$schema": "https://opencode.ai/config.json", + // Canonical project configuration. Keep exactly one root OpenCode config. + // Change this policy only with an effective-config regression test. "default_agent": "plan", "share": "disabled", "snapshot": true, "autoupdate": "notify", + "instructions": [ + "AGENTS.md", + ".ui-redesign/adapter/REPOSITORY_ADAPTER.md", + ".ui-redesign/decisions/DECISION_LEDGER.md", + ], "watcher": { - "ignore": [ - ".git/**", - "node_modules/**", - "dist/**", - ".turbo/**", - "coverage/**", - ".next/**" - ] + "ignore": [".git/**", "node_modules/**", "dist/**", ".turbo/**", "coverage/**", ".next/**"], }, "tool_output": { "max_lines": 500, - "max_bytes": 40000 + "max_bytes": 40000, }, "compaction": { "auto": true, "prune": true, "tail_turns": 3, - "reserved": 12000 + "reserved": 12000, }, "permission": { "read": { @@ -40,8 +40,14 @@ "*credentials*": "deny", "**/*credentials*": "deny", ".git/**": "deny", - "**/.git/**": "deny" + "**/.git/**": "deny", }, + "glob": "allow", + "grep": "allow", + "list": "allow", + "lsp": "allow", + "todowrite": "allow", + "question": "allow", "edit": { "*": "ask", "*.env": "deny", @@ -55,7 +61,7 @@ "*credentials*": "deny", "**/*credentials*": "deny", ".git/**": "deny", - "**/.git/**": "deny" + "**/.git/**": "deny", }, "external_directory": "deny", "webfetch": "ask", @@ -92,7 +98,7 @@ "kubectl apply*": "deny", "helm upgrade*": "deny", "npm publish*": "deny", - "pnpm publish*": "deny" - } - } + "pnpm publish*": "deny", + }, + }, } diff --git a/package.json b/package.json index dcbf82f..1344040 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.57.0", + "opencode-ai": "1.17.7", "pg": "^8.13.0", "prettier": "^3.4.0", "tsx": "^4.22.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eefe8e..9f6b699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: eslint: specifier: ^8.57.0 version: 8.57.1 + opencode-ai: + specifier: 1.17.7 + version: 1.17.7 pg: specifier: ^8.13.0 version: 8.21.0 @@ -3131,6 +3134,111 @@ packages: zod: optional: true + opencode-ai@1.17.7: + resolution: + { + integrity: sha512-5oMjuqlVL78JhvXshwp2NCXCI+CHr24wWi7/5aI0CZoHnI44qTqssWOBUW59dPTpRGSOQmXTDTOOsPVYW38JPg==, + } + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + + opencode-darwin-arm64@1.17.7: + resolution: + { + integrity: sha512-/uoZpJvnxY1jtRXAASQTIn0goya61M1RJhX0Zx2RwO+sdnrfvYYX6p7iL82Rl+Sp+TAS8y2NBvN+p/OLAnxsqg==, + } + cpu: [arm64] + os: [darwin] + + opencode-darwin-x64-baseline@1.17.7: + resolution: + { + integrity: sha512-nvaY4qQgS9ZSkCvw9+DOrQQeycbW8/AEcD/Q0suleMuUgFIqfrsVa4cdsmYrUh3BH+y2NRaVgMSIfU9gPqXAKA==, + } + cpu: [x64] + os: [darwin] + + opencode-darwin-x64@1.17.7: + resolution: + { + integrity: sha512-v60XhJae1eKn/Kjhy2PLOY+ss7peSox8ILZFz7fwBzRgz4q61gIo1vM9WzXQ6Vt+5Oj8etYbPl3xmt1XDLZtEA==, + } + cpu: [x64] + os: [darwin] + + opencode-linux-arm64-musl@1.17.7: + resolution: + { + integrity: sha512-bIySXi+XNLHL5m8lS1ljL5XZzQ0iMdf/X6KKaqHbDZQ3E0Qu7ERIHZjohx8S7htvCdPztuzeSr2udS0emumf6g==, + } + cpu: [arm64] + os: [linux] + + opencode-linux-arm64@1.17.7: + resolution: + { + integrity: sha512-HTH5Z5V7xiAD+/nYn9wQYwM/LDokBZu8Ig+npwJrhGWzZGM+lChbv2+griYyRoaNEZ+zRsCvwyv96TTfb6nWDQ==, + } + cpu: [arm64] + os: [linux] + + opencode-linux-x64-baseline-musl@1.17.7: + resolution: + { + integrity: sha512-kTuZpRxMOzKt+ztp6yb+cSN8L69UYWN1x7Na4egX2d26IU0xK+RlXE9HjHzF/EjsmSBMc3DR8MvKUVldQ2XdbA==, + } + cpu: [x64] + os: [linux] + + opencode-linux-x64-baseline@1.17.7: + resolution: + { + integrity: sha512-fj62eWDQSygxS3Q5S3Gm4VYOsHrlTnje46bXoWc9IXfFNwFHAyL+izKzpU1SePCpCCEdsjy//nCKOnqN4PPK5g==, + } + cpu: [x64] + os: [linux] + + opencode-linux-x64-musl@1.17.7: + resolution: + { + integrity: sha512-iKUBKzVD1ybMmAy3KW6cjfst18+glp3Fgtd7POGEAaQO7cWzwSmOnFHN3uCAi/wYrfrvYd4zXxHsFP0yMJRUmA==, + } + cpu: [x64] + os: [linux] + + opencode-linux-x64@1.17.7: + resolution: + { + integrity: sha512-UIxkdA/8281EHbHYVr5PSD+eVoMdlyfkmXiZp3u9duttsMHdf1F6lw0XjYmDRBCPp8zQM1D1RLCABuRA/kUX+Q==, + } + cpu: [x64] + os: [linux] + + opencode-windows-arm64@1.17.7: + resolution: + { + integrity: sha512-BVlfloqHrjPhpDvbm3u1vQuEn063lbT3lcT7HBLmHpvwJd6FJutjPpm5/3xYxSusmXRGL7bmMZ3v1KPOeY0tBQ==, + } + cpu: [arm64] + os: [win32] + + opencode-windows-x64-baseline@1.17.7: + resolution: + { + integrity: sha512-Gui/cezrLsLEZb1rUwNoKGXIiuZA0FsaGN3L/qR3/Qpce3e+hhqfqLHQXqlh0PFU1dSRKVRF8ukwWVx+2G5CPQ==, + } + cpu: [x64] + os: [win32] + + opencode-windows-x64@1.17.7: + resolution: + { + integrity: sha512-MAykQj6ouoZ2rMt+q8ujBTnc4sD86WfLnPom28CTD+KgmI1s2D2qka7J6DjpK6A3j8PpkC0bEBIqVBciVgY6EA==, + } + cpu: [x64] + os: [win32] + openid-client@6.8.4: resolution: { @@ -6019,6 +6127,57 @@ snapshots: optionalDependencies: ws: 8.21.0 + opencode-ai@1.17.7: + optionalDependencies: + opencode-darwin-arm64: 1.17.7 + opencode-darwin-x64: 1.17.7 + opencode-darwin-x64-baseline: 1.17.7 + opencode-linux-arm64: 1.17.7 + opencode-linux-arm64-musl: 1.17.7 + opencode-linux-x64: 1.17.7 + opencode-linux-x64-baseline: 1.17.7 + opencode-linux-x64-baseline-musl: 1.17.7 + opencode-linux-x64-musl: 1.17.7 + opencode-windows-arm64: 1.17.7 + opencode-windows-x64: 1.17.7 + opencode-windows-x64-baseline: 1.17.7 + + opencode-darwin-arm64@1.17.7: + optional: true + + opencode-darwin-x64-baseline@1.17.7: + optional: true + + opencode-darwin-x64@1.17.7: + optional: true + + opencode-linux-arm64-musl@1.17.7: + optional: true + + opencode-linux-arm64@1.17.7: + optional: true + + opencode-linux-x64-baseline-musl@1.17.7: + optional: true + + opencode-linux-x64-baseline@1.17.7: + optional: true + + opencode-linux-x64-musl@1.17.7: + optional: true + + opencode-linux-x64@1.17.7: + optional: true + + opencode-windows-arm64@1.17.7: + optional: true + + opencode-windows-x64-baseline@1.17.7: + optional: true + + opencode-windows-x64@1.17.7: + optional: true + openid-client@6.8.4: dependencies: jose: 6.2.3