- Copy the block below to the end of the
## Decisionssection. - Use the next free four-digit ID. IDs are never reused and never renumbered.
- Date the record the day the decision was actually made (
YYYY-MM-DD). - Record the alternatives you rejected and why. A decision without context is a rumour, not a record.
- Never rewrite history. If a decision changes, append a superseding record.
### D-0001 — Short imperative title
- **Date:** YYYY-MM-DD
- **Status:** Accepted
- **Context:** the situation and the constraint that forced a choice
- **Alternatives Considered:** option A (rejected because ...), option B (rejected because ...)
- **Decision:** what was decided, in one or two sentences
- **Rationale:** why this option wins, including what it costs
- **Consequences:** what follows from it — new constraints, follow-up work, risks
- **Supersedes:** NoneAllowed statuses: Proposed, Accepted, Superseded, Deprecated, Rejected.
- Date: 2026-10-16
- Status: Accepted
- Context: The lab hardcoded three plugins, their tools, ids, configs and specialized tests. A fresh user could not use it without those plugins.
- Alternatives Considered: Environment variables only (rejected: no structure, no validation, awkward for multiple plugins); a TypeScript config module (rejected: executes code before anything is validated, and adds a build step to the host); keeping hardcoded defaults plus env overrides (rejected: still assumes the three plugins).
- Decision: One
lab.config.json(JSONC) per manifest declares plugins, sources, capabilities, expected tools, configurations and extensions, validated againstschema/lab.config.schema.json. - Rationale: Data is inspectable, diffable, lintable and portable. A single
loader (
harness/lib/manifest.mjs) is shared by every script, so staging, discovery, build and the harness cannot diverge. Cost: one more concept to learn, mitigated by a validator with actionable errors and examples. - Consequences: All downstream tooling is manifest-driven; the old
plugins.tsvand per-config JSONC files become reference artefacts. - Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: Configs were baked JSONC templates with hardcoded plugin paths.
- Alternatives Considered: Templating the old JSONC files with placeholders
(rejected: still per-plugin files, still requires editing); baking generated
configs at build time (rejected: cannot support a manifest supplied at run
time, e.g.
make test-plugin). - Decision:
harness/lib/opencode.mjs#generateConfigbuilds eachopencode.jsonfrom the manifest, appending the lab inspector last. An optional JSONCtemplatesupplies non-plugin settings. - Rationale: The plugin list lives in exactly one place (the manifest);
non-plugin settings remain expressible. The old per-plugin JSONC files are kept
under
profiles/gitdamnit/configs/legacy/for reference, not deleted. - Consequences: Templates may only carry non-plugin keys; the generated list
overrides any
pluginskey in a template. - Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The three original plugins must remain reproducible, but must not
be required by a fresh clone, and public manifests must not contain
/home/evanpaths. - Alternatives Considered: Delete the old tests (rejected: destroys working regression coverage and history); keep them in the default manifest (rejected: re-creates the dependency); vendor the plugin sources (rejected: they are independent products under their own licences).
- Decision: Relocate the plugin-specific tests and configs into
profiles/gitdamnit/, whose manifest points at sources via${CHECKPOINT_ORCHESTRATOR_DIR:-$HOME/Projects/...}style variables. - Rationale: The default lab stays dependency-free; the profile is activated
with
PROFILE=gitdamnitand is fully overridable without editing files. - Consequences: Profile tests import the harness via a relative path; the profile is documented as maintainer-oriented, not the default.
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The default manifest must include a real
multi-pluginconfiguration, and the lab must not assume every plugin registers tools. - Alternatives Considered: One example plugin (rejected:
multi-pluginwould be meaningless); list one plugin twice (rejected: that is a duplicate-entry test, not multi-plugin compatibility); reuse a@gitdamnitplugin (rejected: re-creates the dependency). - Decision: Ship
examples/example-plugin(compiled TypeScript, registers one safe tool, lifecycle logging) andexamples/example-plugin-2(prebuilt JavaScript, no tools). - Rationale: It exercises both build shapes, gives
multi-pluginreal content, and demonstrates capability-aware skipping in the default suite. - Consequences: Two tiny example packages are maintained in-tree; they are deliberately not full-featured projects.
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: Plugins differ: some register tools, some do not; some redact, some persist state. Asserting everything for everyone would produce false failures or meaningless passes.
- Alternatives Considered: Require tool registration (rejected: excludes valid plugins); assert nothing when uncertain (rejected: weakens the suite).
- Decision: Plugins declare
capabilitiesandexpectTools; the harness asserts only declared capabilities and records the rest asskippedwith a reason. A redaction assertion runs only for plugins declaringcapabilities.redaction. - Rationale: PASS/FAIL/SKIP are distinguishable and honest; the model-path control experiment still proves the capture path observes real traffic.
- Consequences: Reports contain a non-zero skip count by design; reviewers must read skip reasons, which the report prints.
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: Building third-party plugin sources installs dependencies, which may run arbitrary install scripts — a supply-chain risk.
- Alternatives Considered: Run scripts by default to match upstream build behaviour (rejected: least privilege lost silently); forbid scripts entirely (rejected: some legitimate plugins need them).
- Decision: Dependency install runs with
--ignore-scriptsunless the manifest setsinstall.scripts: true.source.type: npmis never fetched unlessallowRemote: true. - Rationale: Least privilege is the default; opting in is explicit, documented and visible in the manifest.
- Consequences: A plugin that needs install scripts reports a build failure
or skip until it opts in; the risk is documented in
docs/SECURITY.md. - Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The original
145 passedrun is valuable evidence and must not be overwritten or relabelled. - Alternatives Considered: Regenerate everything into
reports/(rejected: destroys evidence); keep only a summary (rejected: less verifiable). - Decision: Commit the original report and RC evidence unchanged under
reports/historical/gitdamnit-2026-10-17/; every new run writesreports/runs/<run-id>/and never overwrites an earlier run. - Rationale: Historical and current evidence coexist without ambiguity.
- Consequences: Generated reports are gitignored except the historical tree
and the example
sample-report.md. - Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The lab is prepared for public release; it stages third-party plugins but does not redistribute them.
- Alternatives Considered: Apache-2.0 (rejected: heavier, and the lab does not need patent terms); no licence (rejected: not open-source-ready).
- Decision: MIT for the lab and the example plugins;
THIRD_PARTY_NOTICES.mdrecords the licences of build-time dependencies and the optional profile's plugins. - Rationale: Maximum compatibility; the bundled example plugins are lab-owned.
- Consequences: Attribution is documented; the profile's plugins keep their own licences (MIT / Apache-2.0).
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: OpenCode V2 ships frequently (2.0.21 → 2.0.3x within weeks). Assuming a newer version passes is unsafe.
- Alternatives Considered: Track
latest(rejected: non-reproducible); hardcode one version with no override (rejected: prevents evaluation). - Decision: Manifests pin 2.0.21 as the tested baseline; another version is
selected explicitly with
OPENCODE_VERSION=…; the installed version is recorded in every report. Compatibility with a newer version is projected, not claimed. - Rationale: Reproducibility plus a documented path to evaluate newer runtimes.
- Consequences: A version bump is a deliberate, recorded action, not drift.
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The lab could be mistaken for an OpenCode plugin, but it is a standalone developer tool.
- Alternatives Considered: Publish to npm as a plugin (rejected: misrepresents its purpose and serves no runtime need); publish a container image (rejected: not requested; adds a distribution channel to maintain).
- Decision: Distribute via GitHub source and Docker build; document the distinction prominently; do not submit to any plugin registry.
- Rationale: Honest positioning; users clone and run it.
- Consequences: Documentation must state the distinction; release checklist excludes registry publication.
- Supersedes: None
- Date: 2026-10-16
- Status: Accepted
- Context: The builder hardcoded three plugins and assumed a
tscbuild. - Alternatives Considered: Keep a hardcoded builder (rejected: not generic); parse the manifest inside the image (rejected: source paths are host-only and must not be resolved in the container).
- Decision: Host staging emits
staging/_build/plan.json(absolute, resolved);docker/build-plugins.mjsconsumes it and builds each plugin per the manifest, supporting compiled and prebuilt shapes. - Rationale: Separates host path resolution from image build; supports any plugin, both build shapes, and fails loudly on a missing build output.
- Consequences: The Docker context carries
staging/_build/plan.json; the builder has no knowledge of the host filesystem. - Supersedes: None