@tech-leads-club/harness-toolkit 0.2.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.
- package/CHANGELOG.md +95 -0
- package/LICENSE +84 -0
- package/NOTICE +9 -0
- package/README.md +480 -0
- package/bin/tlc +10 -0
- package/bin/tlc-build +80 -0
- package/bin/tlc-cli.ts +1509 -0
- package/bin/tlc-exec +10 -0
- package/bin/tlc-exec.cmd +4 -0
- package/bin/tlc-exec.d.mts +60 -0
- package/bin/tlc-exec.mjs +248 -0
- package/bin/tlc.cmd +4 -0
- package/bin/tlc.mjs +7 -0
- package/bin/write-user-hooks.d.mts +40 -0
- package/bin/write-user-hooks.mjs +123 -0
- package/capabilities/catalog.json +418 -0
- package/config.example.json +51 -0
- package/dist/compact-before.mjs +7839 -0
- package/dist/doctor.mjs +8263 -0
- package/dist/help-topic.mjs +82 -0
- package/dist/init-project.mjs +993 -0
- package/dist/install-runtime.mjs +969 -0
- package/dist/lessons-cli.mjs +7258 -0
- package/dist/obs-cli.mjs +7025 -0
- package/dist/price-lookup.mjs +221 -0
- package/dist/prompt-submit.mjs +7860 -0
- package/dist/refresh-model-prices.mjs +158 -0
- package/dist/response-after.mjs +7879 -0
- package/dist/run.mjs +7830 -0
- package/dist/session-end.mjs +7937 -0
- package/dist/session-start.mjs +8056 -0
- package/dist/shim.mjs +52 -0
- package/dist/stop.mjs +8494 -0
- package/dist/subagent-start.mjs +7861 -0
- package/dist/subagent-stop.mjs +7851 -0
- package/dist/support.mjs +7045 -0
- package/dist/tlc-cli.mjs +8063 -0
- package/dist/tool-after.mjs +8113 -0
- package/dist/tool-before.mjs +7985 -0
- package/dist/tool-failure.mjs +7850 -0
- package/dist/uninstall-runtime.mjs +1307 -0
- package/docs/architecture.md +270 -0
- package/docs/concepts.md +562 -0
- package/docs/coverage.md +55 -0
- package/docs/decisions/ad-001.md +32 -0
- package/docs/decisions/ad-002.md +51 -0
- package/docs/decisions/ad-003.md +30 -0
- package/docs/decisions/ad-004.md +37 -0
- package/docs/decisions/ad-005.md +36 -0
- package/docs/decisions/ad-006.md +49 -0
- package/docs/decisions/ad-007.md +36 -0
- package/docs/decisions/ad-008.md +54 -0
- package/docs/decisions/ad-009.md +61 -0
- package/docs/decisions/ad-010.md +45 -0
- package/docs/decisions/ad-011.md +59 -0
- package/docs/decisions/ad-012.md +71 -0
- package/docs/decisions/ad-013.md +87 -0
- package/docs/decisions/ad-014.md +56 -0
- package/docs/decisions/ad-015.md +33 -0
- package/docs/decisions/ad-016.md +98 -0
- package/docs/decisions/ad-017.md +65 -0
- package/docs/decisions/ad-018.md +77 -0
- package/docs/decisions/ad-019.md +75 -0
- package/docs/decisions/ad-020.md +88 -0
- package/docs/decisions/ad-021.md +57 -0
- package/docs/decisions/ad-022.md +120 -0
- package/docs/decisions/ad-023.md +87 -0
- package/docs/decisions/ad-024.md +85 -0
- package/docs/decisions/ad-025.md +92 -0
- package/docs/decisions/ad-026.md +131 -0
- package/docs/decisions/ad-027.md +90 -0
- package/docs/decisions/ad-028.md +90 -0
- package/docs/decisions/ad-029.md +73 -0
- package/docs/decisions/ad-030.md +97 -0
- package/docs/decisions/ad-031.md +94 -0
- package/docs/decisions/ad-032.md +83 -0
- package/docs/decisions/ad-033.md +89 -0
- package/docs/decisions/ad-034.md +86 -0
- package/docs/decisions/ad-035.md +86 -0
- package/docs/decisions/ad-036.md +68 -0
- package/docs/decisions/ad-037.md +47 -0
- package/docs/decisions/ad-038.md +52 -0
- package/docs/decisions/ad-039.md +69 -0
- package/docs/decisions/ad-040.md +89 -0
- package/docs/decisions/ad-041.md +98 -0
- package/docs/decisions/ad-042.md +82 -0
- package/docs/decisions/ad-043.md +79 -0
- package/docs/decisions/ad-044.md +61 -0
- package/docs/decisions/ad-045.md +94 -0
- package/docs/decisions/ad-046.md +111 -0
- package/docs/decisions/ad-047.md +96 -0
- package/docs/decisions/ad-048.md +85 -0
- package/docs/decisions/ad-049.md +66 -0
- package/docs/decisions/ad-050.md +94 -0
- package/docs/decisions/ad-051.md +69 -0
- package/docs/decisions/ad-052.md +69 -0
- package/docs/decisions/ad-053.md +78 -0
- package/docs/decisions/ad-054.md +98 -0
- package/docs/decisions/ad-055.md +74 -0
- package/docs/decisions/ad-056.md +85 -0
- package/docs/decisions/ad-057.md +68 -0
- package/docs/decisions/ad-058.md +97 -0
- package/docs/decisions/ad-059.md +82 -0
- package/docs/decisions/ad-060.md +75 -0
- package/docs/decisions/ad-061.md +68 -0
- package/docs/decisions/ad-062.md +72 -0
- package/docs/decisions/ad-063.md +84 -0
- package/docs/decisions/ad-064.md +79 -0
- package/docs/decisions/ad-065.md +81 -0
- package/docs/decisions/ad-066.md +111 -0
- package/docs/decisions/ad-067.md +64 -0
- package/docs/decisions/ad-068.md +79 -0
- package/docs/decisions/ad-069.md +74 -0
- package/docs/decisions/ad-070.md +86 -0
- package/docs/decisions/ad-071.md +93 -0
- package/docs/decisions/ad-072.md +82 -0
- package/docs/decisions/ad-073.md +102 -0
- package/docs/decisions/ad-074.md +91 -0
- package/docs/decisions/ad-075.md +79 -0
- package/docs/decisions/ad-076.md +102 -0
- package/docs/decisions/ad-077.md +94 -0
- package/docs/decisions/ad-078.md +84 -0
- package/docs/decisions/ad-079.md +73 -0
- package/docs/decisions/ad-080.md +86 -0
- package/docs/decisions/ad-081.md +70 -0
- package/docs/decisions/ad-082.md +79 -0
- package/docs/decisions/ad-083.md +88 -0
- package/docs/decisions/index.md +111 -0
- package/docs/diagnose.md +248 -0
- package/docs/index.md +52 -0
- package/docs/init.md +101 -0
- package/docs/lessons.md +294 -0
- package/docs/log.md +139 -0
- package/docs/measure.md +178 -0
- package/docs/providers/claude-code.md +118 -0
- package/docs/providers/cursor.md +110 -0
- package/docs/providers/index.md +71 -0
- package/docs/troubleshooting.md +138 -0
- package/model-aliases.json +12 -0
- package/model-prices.cursor.json +410 -0
- package/model-prices.json +1 -0
- package/package.json +57 -0
- package/skills/harness-init/SKILL.md +241 -0
- package/skills/harness-init/evals/trigger_evals.json +86 -0
- package/skills/harness-init/references/capabilities.md +113 -0
- package/src/contracts/capabilities.ts +30 -0
- package/src/contracts/decision.ts +21 -0
- package/src/contracts/effort.ts +15 -0
- package/src/contracts/harness-event.ts +87 -0
- package/src/contracts/index.ts +6 -0
- package/src/contracts/policy-defaults.ts +14 -0
- package/src/contracts/wiring.ts +20 -0
- package/src/core/attest/attest.service.ts +0 -0
- package/src/core/capability/capability.service.ts +62 -0
- package/src/core/capability/capability.store.ts +51 -0
- package/src/core/capability/capability.types.ts +56 -0
- package/src/core/comment-policy/comment-policy.service.ts +307 -0
- package/src/core/comment-policy/comment-policy.types.ts +6 -0
- package/src/core/comment-policy/comment-resolvability.ts +134 -0
- package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
- package/src/core/comment-policy/comment-syntax.store.ts +65 -0
- package/src/core/comment-policy/comment-syntax.types.ts +24 -0
- package/src/core/core.facade.ts +461 -0
- package/src/core/duplication/duplication.service.ts +219 -0
- package/src/core/floor/floor.catalog.ts +54 -0
- package/src/core/floor/floor.paths.ts +90 -0
- package/src/core/floor/floor.policy-surface.ts +311 -0
- package/src/core/floor/floor.service.ts +306 -0
- package/src/core/floor/floor.tokenize.ts +179 -0
- package/src/core/floor/floor.verb.ts +28 -0
- package/src/core/floor/index.ts +1 -0
- package/src/core/gate/gate.artifact.ts +157 -0
- package/src/core/gate/gate.command.ts +130 -0
- package/src/core/gate/gate.findings.ts +201 -0
- package/src/core/gate/gate.inputs.ts +0 -0
- package/src/core/gate/gate.lock.ts +283 -0
- package/src/core/gate/gate.service.ts +28 -0
- package/src/core/gate/gate.types.ts +67 -0
- package/src/core/handoff/handoff.service.ts +43 -0
- package/src/core/handoff/handoff.store.ts +59 -0
- package/src/core/handoff/handoff.types.ts +89 -0
- package/src/core/index.ts +52 -0
- package/src/core/integrity/state-seal.ts +81 -0
- package/src/core/lesson/lesson.authored.ts +96 -0
- package/src/core/lesson/lesson.credit.ts +51 -0
- package/src/core/lesson/lesson.garden.ts +283 -0
- package/src/core/lesson/lesson.link.ts +93 -0
- package/src/core/lesson/lesson.score.ts +61 -0
- package/src/core/lesson/lesson.select.ts +209 -0
- package/src/core/lesson/lesson.service.ts +110 -0
- package/src/core/lesson/lesson.store.ts +317 -0
- package/src/core/lesson/lesson.sync.ts +70 -0
- package/src/core/lesson/lesson.types.ts +92 -0
- package/src/core/lesson/lesson.validity.ts +44 -0
- package/src/core/observability/observability.report.ts +326 -0
- package/src/core/observability/observability.service.ts +328 -0
- package/src/core/observability/observability.store.ts +269 -0
- package/src/core/observability/observability.types.ts +193 -0
- package/src/core/observability/observability.why.ts +206 -0
- package/src/core/observe/observe.service.ts +76 -0
- package/src/core/plan/plan.detect.ts +47 -0
- package/src/core/plan/plan.service.ts +81 -0
- package/src/core/plan/plan.types.ts +25 -0
- package/src/core/policy/policy.defaults.ts +103 -0
- package/src/core/policy/policy.guard.ts +34 -0
- package/src/core/policy/policy.integrity.ts +238 -0
- package/src/core/policy/policy.loader.ts +118 -0
- package/src/core/policy/policy.operator.ts +82 -0
- package/src/core/policy/policy.posture.ts +79 -0
- package/src/core/policy/policy.rails.ts +45 -0
- package/src/core/policy/policy.types.ts +157 -0
- package/src/core/presence/presence.service.ts +111 -0
- package/src/core/presence/presence.store.ts +59 -0
- package/src/core/presence/presence.types.ts +9 -0
- package/src/core/release/release.decisions.ts +147 -0
- package/src/core/release/release.seen.ts +38 -0
- package/src/core/shell-policy/shell-policy.service.ts +215 -0
- package/src/core/shell-policy/shell-policy.stall.ts +57 -0
- package/src/core/shell-policy/shell-policy.types.ts +26 -0
- package/src/core/ship/ship.ledger.ts +76 -0
- package/src/core/ship/ship.service.ts +137 -0
- package/src/core/ship/ship.types.ts +19 -0
- package/src/core/stagnation/stagnation.resolution.ts +87 -0
- package/src/core/stagnation/stagnation.service.ts +20 -0
- package/src/core/stagnation/stagnation.store.ts +49 -0
- package/src/core/stagnation/stagnation.types.ts +12 -0
- package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
- package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
- package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
- package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
- package/src/core/supply-chain/supply-chain.service.ts +217 -0
- package/src/core/turn/turn.activity.ts +94 -0
- package/src/core/turn/turn.autopilot.ts +131 -0
- package/src/core/turn/turn.failure-signals.ts +193 -0
- package/src/core/turn/turn.loop-counter.ts +71 -0
- package/src/core/turn/turn.types.ts +14 -0
- package/src/core/untrusted/untrusted.detect.ts +53 -0
- package/src/core/untrusted/untrusted.recall.ts +87 -0
- package/src/core/untrusted/untrusted.service.ts +129 -0
- package/src/core/untrusted/untrusted.store.ts +75 -0
- package/src/core/untrusted/untrusted.types.ts +42 -0
- package/src/entrypoints/compact-before.ts +14 -0
- package/src/entrypoints/prompt-submit.ts +25 -0
- package/src/entrypoints/response-after.ts +60 -0
- package/src/entrypoints/run.ts +179 -0
- package/src/entrypoints/session-end.ts +58 -0
- package/src/entrypoints/session-start.ts +172 -0
- package/src/entrypoints/shim.ts +46 -0
- package/src/entrypoints/stop.ts +876 -0
- package/src/entrypoints/subagent-start.ts +16 -0
- package/src/entrypoints/subagent-stop.ts +32 -0
- package/src/entrypoints/support.ts +182 -0
- package/src/entrypoints/tool-after.ts +111 -0
- package/src/entrypoints/tool-before.ts +186 -0
- package/src/entrypoints/tool-failure.ts +26 -0
- package/src/platform/backoff.ts +49 -0
- package/src/platform/cli-output.ts +30 -0
- package/src/platform/env-scope.ts +22 -0
- package/src/platform/fs-atomic.ts +142 -0
- package/src/platform/fs-jsonl.ts +24 -0
- package/src/platform/git.ts +187 -0
- package/src/platform/paths.ts +80 -0
- package/src/platform/pricing.ts +229 -0
- package/src/platform/process.ts +64 -0
- package/src/platform/sanitize.ts +23 -0
- package/src/platform/screen.ts +46 -0
- package/src/platform/style.ts +103 -0
- package/src/providers/claude/claude.capabilities.ts +25 -0
- package/src/providers/claude/claude.detect.ts +14 -0
- package/src/providers/claude/claude.inbound.ts +252 -0
- package/src/providers/claude/claude.lessons-view.ts +33 -0
- package/src/providers/claude/claude.outbound.ts +65 -0
- package/src/providers/claude/claude.policy-defaults.ts +9 -0
- package/src/providers/claude/claude.transcript.ts +62 -0
- package/src/providers/claude/claude.wiring.ts +245 -0
- package/src/providers/claude/index.ts +17 -0
- package/src/providers/cursor/cursor.capabilities.ts +28 -0
- package/src/providers/cursor/cursor.detect.ts +14 -0
- package/src/providers/cursor/cursor.inbound.ts +203 -0
- package/src/providers/cursor/cursor.lessons-view.ts +30 -0
- package/src/providers/cursor/cursor.outbound.ts +43 -0
- package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
- package/src/providers/cursor/cursor.wiring.ts +214 -0
- package/src/providers/cursor/index.ts +17 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider.degrade.ts +152 -0
- package/src/providers/provider.port.ts +20 -0
- package/src/providers/provider.registry.ts +28 -0
- package/tools/doctor.ts +631 -0
- package/tools/help-topic.ts +74 -0
- package/tools/init-project.ts +277 -0
- package/tools/install-runtime.ts +164 -0
- package/tools/lessons-cli.ts +474 -0
- package/tools/obs-cli.ts +175 -0
- package/tools/price-lookup.ts +57 -0
- package/tools/refresh-model-prices.ts +210 -0
- package/tools/test-env.mjs +28 -0
- package/tools/test-env.names.d.mts +1 -0
- package/tools/test-env.names.mjs +14 -0
- package/tools/uninstall-runtime.ts +463 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-080 — Every hand-written list of our own rules is now checked, including the one that drifted while writing this"
|
|
4
|
+
description: "Three lists of this project's own checks drifted in one week. The last two unchecked ones are now gated: the defaults concepts.md states, and the gate step list in CONTRIBUTING — which was wrong the moment a sixteenth step shipped. Plus the orphan tool got a documented caller and the seal is verified where the stop reads."
|
|
5
|
+
tags: [decision, docs, gates, integrity]
|
|
6
|
+
timestamp: "2026-08-17"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-080 — Every hand-written list of our own rules is now checked, including the one that drifted while writing this
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
1. **A default stated in `concepts.md` must match the catalog.** Checked per capability, in a window around its
|
|
16
|
+
config key.
|
|
17
|
+
2. **`CONTRIBUTING.md` must name every gate tool and state the right count**, one table row per step.
|
|
18
|
+
3. **`eval-skill-triggers` gets a documented caller** rather than removal.
|
|
19
|
+
4. **The seal is verified where the stop reads the handoff**, not only where the session start injects it.
|
|
20
|
+
5. **The `CG-03` intermittent failure is recorded as unreproduced, not fixed.**
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
**Three lists drifted in one week, and the fourth drifted while the record about drift was being written.** The
|
|
25
|
+
wizard's narrated half described the comment gate diffing against `HEAD` after a decision replaced it. `concepts.md`
|
|
26
|
+
said the grind lock waits 120 seconds after a decision replaced that too. `secret-access`'s own description in the
|
|
27
|
+
floor catalog still described only files after the metadata-service rule shipped. And then a sixteenth gate step
|
|
28
|
+
landed and `CONTRIBUTING.md` still said fifteen — the contributor-facing list of our own checks, wrong the moment a
|
|
29
|
+
check was added.
|
|
30
|
+
|
|
31
|
+
Every one was hand-written, and every one was found by looking rather than by failing. That is the pattern, and
|
|
32
|
+
patching instances of it is not the fix ([/decisions/ad-065.md](/decisions/ad-065.md)).
|
|
33
|
+
|
|
34
|
+
**`concepts.md` cannot be generated, and its checkable half can.** It is prose an operator reads end to end, which
|
|
35
|
+
a generator writes badly ([/decisions/ad-079.md](/decisions/ad-079.md)). But one fact in it has an exact answer
|
|
36
|
+
elsewhere: whether a rail is on by default. Presence was already gated; the default is now too, in a window around
|
|
37
|
+
the config key so a default stated about one rail is not read as another's. Inverting one fails.
|
|
38
|
+
|
|
39
|
+
**The gate step list is presence, and presence is enough.** The guide names the tool path rather than the internal
|
|
40
|
+
step label, because the path is what a contributor types. So the check is: every `tools/dev/*` the gate runs appears
|
|
41
|
+
in the guide, the stated count matches, and there is one row per step. Deleting a step from the doc fails; writing
|
|
42
|
+
the wrong number fails.
|
|
43
|
+
|
|
44
|
+
**The orphan tool needed a caller, not a funeral.** `eval-skill-triggers` is invoked by nothing —
|
|
45
|
+
[/decisions/ad-068.md](/decisions/ad-068.md) left open whether it should exist. Our own removal doctrine says a
|
|
46
|
+
tool whose only consumer is its test is a candidate. But it evaluates whether a host routes a request to the init
|
|
47
|
+
skill at all, which is a behaviour change with no test attached, and deleting it would remove the only way to
|
|
48
|
+
check that. The problem was that nothing pointed at it. `CONTRIBUTING.md` now names it as the step before changing
|
|
49
|
+
the skill's description, which is a consumer: a human following a documented workflow.
|
|
50
|
+
|
|
51
|
+
**The stop reads the handoff to decide, which is a different risk from telling the model.**
|
|
52
|
+
[/decisions/ad-078.md](/decisions/ad-078.md) left that open. A decision taken from planted text is worth the same
|
|
53
|
+
withholding, and withholding there means deciding from the file's absence — which is what a first turn already
|
|
54
|
+
does, so the path is exercised.
|
|
55
|
+
|
|
56
|
+
## Trade-offs
|
|
57
|
+
|
|
58
|
+
**`CG-03` is recorded as unreproduced.** It failed once under `TLC_HOME` in a full-suite run, and did not recur in
|
|
59
|
+
eight subsequent runs. Measured against the one hypothesis worth testing: the fixture is three days old, the
|
|
60
|
+
default retention is fourteen days and the narrow case is one, so it is not a boundary race. Inventing a fix for a
|
|
61
|
+
failure that cannot be reproduced would be worse than leaving it named — it would add a change nothing justifies
|
|
62
|
+
and a claim nothing supports.
|
|
63
|
+
|
|
64
|
+
**The default check reads a 120-character window.** A section that states its default further from its config key
|
|
65
|
+
than that is skipped rather than failed. Widening it would start reading a neighbouring rail's default as this
|
|
66
|
+
one's, which is a false positive in a check whose whole value is precision.
|
|
67
|
+
|
|
68
|
+
**The count in `CONTRIBUTING` is a word, matched from a closed list.** `Sixteen` is checked; `sixteen point five`
|
|
69
|
+
is not a thing. It is a small vocabulary and the failure names the mismatch, which is cheaper than parsing English
|
|
70
|
+
numbers.
|
|
71
|
+
|
|
72
|
+
**Keeping the orphan tool costs its tests.** Two of them, and a documented workflow that most contributors will
|
|
73
|
+
never run. The alternative was deleting the only check on whether the skill is reachable at all.
|
|
74
|
+
|
|
75
|
+
## Not decided here
|
|
76
|
+
|
|
77
|
+
- **Generating `concepts.md`.** Still the last unrendered view of the rails, and still prose. Two facts in it are
|
|
78
|
+
now checked; the rest is judgement.
|
|
79
|
+
- **What `doctor` should say about a persistently unsealed file.** Every install starts unsealed and seals on the
|
|
80
|
+
first write, so a warning would fire on a healthy install
|
|
81
|
+
([/decisions/ad-034.md](/decisions/ad-034.md)). Whether a file that stays unsealed across sessions is worth
|
|
82
|
+
reporting needs a measurement nobody has.
|
|
83
|
+
- **PHP in the supply-chain table.** Blocked by a name collision with a provider identifier, not by a design
|
|
84
|
+
problem ([/decisions/ad-075.md](/decisions/ad-075.md)). Dodging the word would be worse than the gap.
|
|
85
|
+
- **The release workflow.** It needs an app installed and two secrets added, which nothing in this repository can
|
|
86
|
+
do.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-081 — The manifest npm publishes is checked here, because the release runner was the only thing reading it"
|
|
4
|
+
description: "package.json declared ./bin/tlc.mjs and npm dropped both executables on publish, so the package would have installed no command at all. A gate step now uses npm's own normaliser as the oracle: any field npm would rewrite fails the build, not just the one spelling that was wrong."
|
|
5
|
+
tags: [decision, release, gates, packaging]
|
|
6
|
+
timestamp: "2026-08-19"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-081 — The manifest npm publishes is checked here, because the release runner was the only thing reading it
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
1. **A gate step checks the published manifest**, `tools/dev/check-manifest.ts`.
|
|
16
|
+
2. **The oracle is `npm pkg fix` against a copy**, not a rule of ours about which spellings npm dislikes. Any
|
|
17
|
+
difference fails.
|
|
18
|
+
3. **The gate never edits `package.json`** — the normaliser runs against a copy in a temp directory.
|
|
19
|
+
4. **A `bin` target that does not exist is a separate failure**, because npm checks the shape of the path and never
|
|
20
|
+
the file.
|
|
21
|
+
5. **`bin` values lose the `./` prefix**: `bin/tlc.mjs`, `bin/tlc-exec.mjs`.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
**The package would have installed no command.** `bin` declared `./bin/tlc.mjs` and `./bin/tlc-exec.mjs`, and npm
|
|
26
|
+
removed both entries on publish — `"bin[tlc]" script name bin/tlc.mjs was invalid and removed`. Measured in a
|
|
27
|
+
fixture: `./bin/x.mjs` removed, `bin/x.mjs` accepted, `./bin/x` removed. So the prefix was the whole cause, and the
|
|
28
|
+
effect was total: `npm i -g` would have installed a package with no `tlc` and no `tlc-exec`.
|
|
29
|
+
|
|
30
|
+
**Nothing here could see it, and the one thing that could was a warning on a failing build.** The defect surfaced
|
|
31
|
+
in the release runner's log, above an unrelated `E404` that was what everyone looked at. A `--dry-run` locally
|
|
32
|
+
reproduces it in about a second, which means the only reason it shipped is that nothing ran one. That is the same
|
|
33
|
+
shape as every drift this project has gated: a fact with an exact answer somewhere, and no mechanism that reads it
|
|
34
|
+
([/decisions/ad-080.md](/decisions/ad-080.md)).
|
|
35
|
+
|
|
36
|
+
**The oracle has to be npm's, or the check is a guess about someone else's normaliser.** A rule of the form
|
|
37
|
+
"reject `./` in `bin`" closes the instance. `npm pkg fix` is the command npm's own warning tells you to run, so it
|
|
38
|
+
is the same code that rewrote the manifest — which closes the class. Any field npm would correct fails here, not
|
|
39
|
+
just `bin`, and a future npm that corrects something new fails on the version the contributor is running rather
|
|
40
|
+
than in a release. Measured on the corrected manifest: zero difference, so the check is not noisy.
|
|
41
|
+
|
|
42
|
+
**Measuring must not fix.** The normaliser writes, so it runs against a copy. A gate that repaired what it measured
|
|
43
|
+
would pass on a repository that is still wrong, and the pass would be one it caused.
|
|
44
|
+
|
|
45
|
+
**Existence is ours to check because npm does not.** npm validated the *shape* of the path and said nothing about
|
|
46
|
+
whether a file is there. A `bin` pointing at a missing file publishes a command that fails on first run, which is
|
|
47
|
+
worse than one npm removed — a removed bin fails visibly at install, a dangling one fails in the user's terminal.
|
|
48
|
+
|
|
49
|
+
## Trade-offs
|
|
50
|
+
|
|
51
|
+
**The gate now shells out to `npm`.** It is the first step that depends on a tool outside the repository's own
|
|
52
|
+
dependencies, and its verdict is that npm's verdict. A contributor on an older npm gets an older opinion — which is
|
|
53
|
+
the correct failure mode, because the version that matters is the one CI publishes with, and CI runs `npm@latest`
|
|
54
|
+
before publishing. The alternative was vendoring a normaliser and inheriting the job of keeping it in step.
|
|
55
|
+
|
|
56
|
+
**A temp directory per run.** `mkdtemp` and one file copy, and nothing removes it. That is deliberate: a gate that
|
|
57
|
+
deletes paths it computed is the shape the floor refuses for good reason, and the operating system's temp policy is
|
|
58
|
+
better at this than a `rm` in a checker.
|
|
59
|
+
|
|
60
|
+
**`npm pkg fix` may one day correct something we intended.** Then this fails and the answer is to read the diff and
|
|
61
|
+
decide, which is the point. A field we want that npm rewrites is not a field we can publish.
|
|
62
|
+
|
|
63
|
+
## Not decided here
|
|
64
|
+
|
|
65
|
+
- **Whether the gate should run a full `npm publish --dry-run`.** It would also validate the file list and the
|
|
66
|
+
tarball, and it costs about a second and packs 1.6 MB on every gate run. The manifest was the part that was
|
|
67
|
+
wrong; the file list is already checked by `check-docs-bundle` and the `files` array is read by nothing else.
|
|
68
|
+
- **The `keywords` array's formatting.** The release bot rewrote it from one line to six and the gate accepted
|
|
69
|
+
both, so nothing is enforced. It will keep flipping with each release until something cares.
|
|
70
|
+
- **Publishing the first version.** It cannot come from CI ([/decisions/ad-082.md](/decisions/ad-082.md)).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-082 — npm is the documented install, and the first version cannot come from CI"
|
|
4
|
+
description: "The repository is public, so the credentialled bootstrap of the private phase is gone from every document and npm is the single route a user is told about. The first published version has to be pushed by hand: npm only lets a trusted publisher be configured on a package that already exists."
|
|
5
|
+
tags: [decision, install, release, npm]
|
|
6
|
+
timestamp: "2026-08-19"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-082 — npm is the documented install, and the first version cannot come from CI
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
1. **`npm i -g @tech-leads-club/harness-toolkit` then `tlc harness install` is the documented install**, on every
|
|
16
|
+
platform. It replaces the `curl | bash` and `irm | iex` one-liners in the README, `CONTRIBUTING.md` and
|
|
17
|
+
[/diagnose.md](/diagnose.md).
|
|
18
|
+
2. **The credentialled bootstrap is deleted from every document.** `gh api …/contents/install.sh | base64 -d | bash`
|
|
19
|
+
existed only because an unauthenticated fetch cannot read a private repository, which stopped being true.
|
|
20
|
+
3. **Recovery from a stuck runtime is the package**, not a script fetched from `raw.githubusercontent.com`.
|
|
21
|
+
4. **The first published version is pushed by hand**, once, and every version after it comes from CI.
|
|
22
|
+
5. **The scripts stay in the repository for now** as the contributor install, and the removal is separate work.
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
**This is the switch [/decisions/ad-052.md](/decisions/ad-052.md) said would be a visibility change and nothing
|
|
27
|
+
else.** It carried both install routes so that going public would not need a commit nobody would remember to make.
|
|
28
|
+
The routes were labelled with which applied; the private one now applies to nothing, and a document that offers a
|
|
29
|
+
reader two routes when one is dead is worse than one that offers a single working route. Measured after the
|
|
30
|
+
visibility change: `install.sh` and `install.ps1` both answer `HTTP 200` from `raw.githubusercontent.com`, so the
|
|
31
|
+
public route works — and it is still not the one to document, because the package exists.
|
|
32
|
+
|
|
33
|
+
**The first version cannot come from the pipeline, and this is npm's constraint rather than ours.** The publish
|
|
34
|
+
failed with `404 Not Found - PUT`, which reads as a permissions problem and is not one: npm's UI only offers the
|
|
35
|
+
trusted-publisher setting on a package that already exists, so there is no way to authorise the workflow for a name
|
|
36
|
+
that has never been published ([npm/cli#8544](https://github.com/npm/cli/issues/8544), open). The OIDC exchange
|
|
37
|
+
itself was working — the provenance statement was signed and published to the transparency log before the PUT was
|
|
38
|
+
refused. So the sequence is: publish once by hand, configure the trusted publisher, and every release after that is
|
|
39
|
+
unattended.
|
|
40
|
+
|
|
41
|
+
**Provenance needed the repository to be public, which it now is.** npm's own words: *"Provenance generation is not
|
|
42
|
+
supported for private repositories, even when publishing public packages."* `publishConfig.provenance` stays `true`
|
|
43
|
+
because the condition it needs is now met — and the by-hand first publish has to turn it off for that one run,
|
|
44
|
+
since provenance is generated from a CI identity that a laptop does not have.
|
|
45
|
+
|
|
46
|
+
**The rest of the delivery mechanism was already built.** [/decisions/ad-056.md](/decisions/ad-056.md) made the
|
|
47
|
+
package a delivery vehicle, `tlc harness install` materialise the runtime at `~/.tlc/harness`, and `update` bump the
|
|
48
|
+
package rather than run git against it. `doctor` already reports `installed from npm` as healthy. Documenting npm is
|
|
49
|
+
therefore a docs change plus one message class, not a migration.
|
|
50
|
+
|
|
51
|
+
## Trade-offs
|
|
52
|
+
|
|
53
|
+
**The documented install names something not yet on the registry.** For the window between this commit and the
|
|
54
|
+
by-hand publish, the README's first command fails. The alternative — documenting `curl | bash` for one more release
|
|
55
|
+
— means writing the private-phase paragraph a third time and removing it again, and this record is what the reader
|
|
56
|
+
who hits that window needs.
|
|
57
|
+
|
|
58
|
+
**Two scripts stay in the tree while the docs stop mentioning them.** They are not dead: run from a checkout,
|
|
59
|
+
`install.sh` links the runtime at the clone, which is the contributor install and how live edits reach a hook. And
|
|
60
|
+
on Windows `tlc harness update` shells out to `install.ps1` to redo the CLI shim, the skill link and the hooks —
|
|
61
|
+
the POSIX branch does that inline, the Windows branch does not. Deleting them means porting that branch into the
|
|
62
|
+
CLI, and that only proves out in Windows CI. Doing it in the same commit as a publish would mix a platform
|
|
63
|
+
refactor into a release.
|
|
64
|
+
|
|
65
|
+
**A by-hand publish is a credential on somebody's laptop, once.** That is the thing trusted publishing exists to
|
|
66
|
+
remove, and it cannot be avoided for the first version. Its blast radius is one version of one package, and the
|
|
67
|
+
version published by hand is the one a tag already points at.
|
|
68
|
+
|
|
69
|
+
## Not decided here
|
|
70
|
+
|
|
71
|
+
- **Removing `install.sh` and `install.ps1`.** It needs the Windows branch of `update` ported into the CLI first,
|
|
72
|
+
and a contributor route that replaces "run the script from your checkout".
|
|
73
|
+
- **Whether `dist/` should still be committed.** It is 4.4 MB across 24 tracked files, and it exists for the Node
|
|
74
|
+
fallback in a cloned runtime. A package built at publish time does not need it in git — but `check-dist-fresh`,
|
|
75
|
+
`tlc harness build` and the clone route all read it, so it is its own decision.
|
|
76
|
+
- **The `managed` runtime kind.** Nothing creates one once the scripts are gone, but installs and forks that
|
|
77
|
+
predate this do, and classifying them wrong is worse than a variant that stops being produced.
|
|
78
|
+
- **Publishing from a tag rather than a branch.** The by-hand publish will be of a tag CI created, which is the
|
|
79
|
+
behaviour wanted; nothing enforces it.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Decision
|
|
3
|
+
title: "AD-083 — Going public switched the branch ruleset on, and an unattended release needs the App to bypass it"
|
|
4
|
+
description: "The release PR merged fine while the repository was private and stopped the moment it went public: the ruleset only started applying then. It allows rebase and nothing else, and it asks for an approving review a bot cannot give itself. The App becomes a bypass actor and the merge switches to rebase."
|
|
5
|
+
tags: [decision, release, ci, github]
|
|
6
|
+
timestamp: "2026-08-19"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# AD-083 — Going public switched the branch ruleset on, and an unattended release needs the App to bypass it
|
|
10
|
+
|
|
11
|
+
- **status**: active
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
1. **The release PR is merged with `--rebase`.** The ruleset sets `allowed_merge_methods: ["rebase"]`, and the
|
|
16
|
+
repository has squash and merge-commit off.
|
|
17
|
+
2. **The release App is a bypass actor** on the `Default` branch ruleset, as `Integration` with
|
|
18
|
+
`bypass_mode: always`. The four rules it guards are unchanged.
|
|
19
|
+
3. **A merge that still fails names the review requirement**, rather than exiting on gh's transport-shaped error.
|
|
20
|
+
4. **Auto-merge is enabled on the repository**, and `--auto` is the path. The direct merge is the fallback and it
|
|
21
|
+
cannot succeed under this ruleset.
|
|
22
|
+
5. **The release PR needs one approving review, and no bot can give it.** A release is unattended up to the
|
|
23
|
+
approval and attended at it.
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
**The same workflow merged release 0.2.0 and refused 0.2.1, and nothing about the workflow changed between them.**
|
|
28
|
+
What changed is that the repository went public in between. Measured on the failing run: `Auto merge is not allowed
|
|
29
|
+
for this repository (enablePullRequestAutoMerge)`, then `Pull request #2 is not mergeable: the base branch policy
|
|
30
|
+
prohibits the merge`. Reading the ruleset explains both — `allowed_merge_methods: ["rebase"]`,
|
|
31
|
+
`required_approving_review_count: 1`, `require_last_push_approval: true`,
|
|
32
|
+
`required_review_thread_resolution: true` — and the repository has `allow_squash_merge: false` with
|
|
33
|
+
`allow_rebase_merge: true`. So the workflow had been asking for a merge method the repository does not offer, and
|
|
34
|
+
had been getting away with it only because the rules were not being applied.
|
|
35
|
+
|
|
36
|
+
**Auto-merge was half the fix, and this record first said it was none of it.** That claim was written before it was
|
|
37
|
+
tried, and trying it refuted the reasoning: with `allow_auto_merge: false` the `--auto` attempt is refused outright
|
|
38
|
+
(`Auto merge is not allowed for this repository`) and the fallback direct merge is refused by the policy, so the job
|
|
39
|
+
had no path at all. Turning it on gave `--auto` a path — `release PR #2 set to auto-merge` — and the PR then sat
|
|
40
|
+
armed, waiting. What it was waiting for was the review, so the original conclusion was right about *what* blocks and
|
|
41
|
+
wrong about *whether the setting mattered*. Both were needed.
|
|
42
|
+
|
|
43
|
+
**A bypass actor does not cover an approving review.** The existing bypass list is `OrganizationAdmin` and
|
|
44
|
+
`RepositoryRole: 5` (admin), and an App holds neither — it is not a member and has no repository role, so it
|
|
45
|
+
inherits nothing from the admin entry. Adding it as `Integration` with `bypass_mode: always` was necessary and not
|
|
46
|
+
sufficient: measured with the App on the bypass list, all three CI jobs green and auto-merge armed, the PR still
|
|
47
|
+
read `reviewDecision: REVIEW_REQUIRED` and `mergeStateStatus: BLOCKED`. A bypass lets the bot act; it does not
|
|
48
|
+
manufacture the approval the rule asks for.
|
|
49
|
+
|
|
50
|
+
**The comparison that settled it was the repository this was supposed to copy.** `agent-skills` has the same
|
|
51
|
+
ruleset — one approval, rebase only, `require_last_push_approval` — and the same App on its bypass list with the
|
|
52
|
+
same mode. It releases unattended because **it opens no release PR at all**: its `release` job runs on the push to
|
|
53
|
+
`main`, commits with the App token and publishes, so the bypass is exercised as a direct write to a protected branch
|
|
54
|
+
and no review is ever requested. Our release is PR-shaped by choice
|
|
55
|
+
([/decisions/ad-054.md](/decisions/ad-054.md), [/decisions/ad-057.md](/decisions/ad-057.md)), and a PR under this
|
|
56
|
+
ruleset needs a human. That is the trade, stated rather than discovered again.
|
|
57
|
+
|
|
58
|
+
**The failure message earns its place.** `gh pr merge` reports a policy refusal in the shape of a transport error,
|
|
59
|
+
and the first reading of it cost a wrong diagnosis — the run was called a permissions problem twice before the
|
|
60
|
+
ruleset was read. The workflow now says which of the two causes it is, so the next person reads the answer instead
|
|
61
|
+
of re-deriving it.
|
|
62
|
+
|
|
63
|
+
## Trade-offs
|
|
64
|
+
|
|
65
|
+
**The bot can now write to `main` without review.** That is a real grant and not a formality. What bounds it is
|
|
66
|
+
that the App token is minted inside `release.yml` and nowhere else, and the content of the PR it merges is
|
|
67
|
+
generated — a version bump and a rendered changelog. It is the same trust already given to any workflow holding
|
|
68
|
+
`contents: write`, made explicit at the ruleset instead of implicit in a token.
|
|
69
|
+
|
|
70
|
+
**A rebase rewrites the release commit.** The ruleset also requires linear history, so this is the method that
|
|
71
|
+
agrees with the rest of it; but the commit that lands is not byte-identical to the one on the PR branch, and a
|
|
72
|
+
signature on the original would not survive.
|
|
73
|
+
|
|
74
|
+
**Reading a ruleset is not something the repository can do for itself.** This record is the only place the four
|
|
75
|
+
values are written down, and a change made in the GitHub UI will not update it. That is the same staleness the
|
|
76
|
+
coverage page admits to ([/decisions/ad-079.md](/decisions/ad-079.md)) and there is no mechanism to fix it here.
|
|
77
|
+
|
|
78
|
+
## Not decided here
|
|
79
|
+
|
|
80
|
+
- **Whether the approval should be removed for release PRs.** Setting `required_approving_review_count` to `0` makes
|
|
81
|
+
the release unattended and removes the review requirement from every other pull request too, because the rule is
|
|
82
|
+
per-ruleset and not per-author. Dropping the PR and writing to `main` the way `agent-skills` does is the other
|
|
83
|
+
answer, and it discards the thing the PR was chosen for.
|
|
84
|
+
- **Whether CI should be a required check.** With none, `--auto` waits only on the review. The publish job runs the
|
|
85
|
+
full gate again on the tag before publishing, so nothing unverified reaches the registry — but that ordering is
|
|
86
|
+
incidental rather than designed.
|
|
87
|
+
- **Narrowing the bypass to a ruleset that only covers release commits.** GitHub scopes bypass per ruleset, not per
|
|
88
|
+
actor per path, so this would mean a second ruleset and a way to tell release pushes apart.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Aggregate
|
|
3
|
+
title: "Decisions index"
|
|
4
|
+
description: "Index of every architectural decision made while building the multi-provider harness, by number. See /log.md for the same records by date."
|
|
5
|
+
tags: [decisions, index]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Decisions
|
|
10
|
+
|
|
11
|
+
Each architectural decision (AD) lives in its own file, cross-linked from here. This index replaces the
|
|
12
|
+
`## Decisions` section that used to live in `.specs/STATE.md` — see [/index.md](/index.md) for the rest of
|
|
13
|
+
the documentation bundle. `.specs/STATE.md` (outside this bundle, at the repo root) now keeps only the
|
|
14
|
+
Handoff section and a link back to this index.
|
|
15
|
+
|
|
16
|
+
| # | Title | Status |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| [AD-001](/decisions/ad-001.md) | Optional parent-Fast sticky deny for Task spawns | active |
|
|
19
|
+
| [AD-002](/decisions/ad-002.md) | Provider-neutral naming and layout | active |
|
|
20
|
+
| [AD-003](/decisions/ad-003.md) | No backward compatibility | active |
|
|
21
|
+
| [AD-004](/decisions/ad-004.md) | Ports and adapters with an anti-corruption layer per provider | active |
|
|
22
|
+
| [AD-005](/decisions/ad-005.md) | Local test runner is the gate; CI matrix runs on every push | active |
|
|
23
|
+
| [AD-006](/decisions/ad-006.md) | Windows is covered by CI, minus the installer and the editor end | active |
|
|
24
|
+
| [AD-007](/decisions/ad-007.md) | Vendor check applies to core tests; absence checks do not | active |
|
|
25
|
+
| [AD-008](/decisions/ad-008.md) | Biome + TypeScript in the gate; `@types/node` pinned to the declared floor | active |
|
|
26
|
+
| [AD-009](/decisions/ad-009.md) | Event kinds are provider-agnostic; capabilities are data, not flags | active |
|
|
27
|
+
| [AD-010](/decisions/ad-010.md) | Shared vocabulary moves to `src/contracts/` | active |
|
|
28
|
+
| [AD-011](/decisions/ad-011.md) | Vendor-specific data belongs to the provider, not to core | active |
|
|
29
|
+
| [AD-012](/decisions/ad-012.md) | Prefer Bun at runtime, keep `dist/` for the Node fallback, ship no binary | active |
|
|
30
|
+
| [AD-013](/decisions/ad-013.md) | Documentation follows the Open Knowledge Format (OKF v0.1) | active |
|
|
31
|
+
| [AD-014](/decisions/ad-014.md) | Claude Code hook payload field paths, pinned | active |
|
|
32
|
+
| [AD-015](/decisions/ad-015.md) | Wiring handler names are the entrypoint filenames | active |
|
|
33
|
+
| [AD-016](/decisions/ad-016.md) | Field semantics, state writers, and the core export surface | active |
|
|
34
|
+
| [AD-017](/decisions/ad-017.md) | The docs gate delegates to the project's tool, and the catalog is the only source of capability metadata | active |
|
|
35
|
+
| [AD-018](/decisions/ad-018.md) | Three rails adopted from an external review, each off by default and declared rather than inferred | active |
|
|
36
|
+
| [AD-019](/decisions/ad-019.md) | A resource is identified by what it resolves to, and a declared capability must be read where it matters | active |
|
|
37
|
+
| [AD-020](/decisions/ad-020.md) | One resolution for the install path, one source for posture, and a config that only advertises what it reads | active |
|
|
38
|
+
| [AD-021](/decisions/ad-021.md) | A gate command that never resolved is a config fault, and a recipe runner does not receive file arguments | active |
|
|
39
|
+
| [AD-022](/decisions/ad-022.md) | The policy surface is a floor rule, detection sits behind interception, and the operator/agent line is structural | active |
|
|
40
|
+
| [AD-023](/decisions/ad-023.md) | One finding per failure, and lesson relevance is recurrence rather than exposure | active |
|
|
41
|
+
| [AD-024](/decisions/ad-024.md) | The gate tells the truth about its own environment, its own lock, and the cause of a failure | active |
|
|
42
|
+
| [AD-025](/decisions/ad-025.md) | Posture governs surfacing only, and each posture has exactly one name | active |
|
|
43
|
+
| [AD-026](/decisions/ad-026.md) | An interruption is worth what it costs: narrower asks, a deadline on questions, and a rate the operator can see | active |
|
|
44
|
+
| [AD-027](/decisions/ad-027.md) | Evidence is ordered against the code, every rail's firing is recorded, and a checker can run with its rule off | active |
|
|
45
|
+
| [AD-028](/decisions/ad-028.md) | A resolved failure is kept, a session attests to itself, and provider neutrality becomes a proof | active |
|
|
46
|
+
| [AD-029](/decisions/ad-029.md) | A capability the init skill cannot correctly initialise is not shipped | active |
|
|
47
|
+
| [AD-030](/decisions/ad-030.md) | Clearing a policy divergence is one operator command behind four independent locks, and a refusal never points an agent at a door the floor holds shut | active |
|
|
48
|
+
| [AD-031](/decisions/ad-031.md) | The decisions are the changelog, a breaking change carries its own instruction, and looking never changes anything | active |
|
|
49
|
+
| [AD-032](/decisions/ad-032.md) | A hook is healthy when it can run, not when a marker string is present | active |
|
|
50
|
+
| [AD-033](/decisions/ad-033.md) | The gate says what it costs, appendFiles stops promising what it cannot deliver, and a dead capability leaves | active |
|
|
51
|
+
| [AD-034](/decisions/ad-034.md) | A warning that fires on a healthy install is not a warning, and the author reads the operator's output before anyone else does | active |
|
|
52
|
+
| [AD-035](/decisions/ad-035.md) | A lesson learned by reasoning can be written down, and the harness never learns where lessons come from | active |
|
|
53
|
+
| [AD-036](/decisions/ad-036.md) | A lesson names what makes it true, and stops being injected when that is gone | active |
|
|
54
|
+
| [AD-037](/decisions/ad-037.md) | A lesson can be true for a period, and an unparseable bound withholds it | active |
|
|
55
|
+
| [AD-038](/decisions/ad-038.md) | Promotion counts distinct sessions, because one stuck session is one observation | active |
|
|
56
|
+
| [AD-039](/decisions/ad-039.md) | A lesson is graded by the gate it was injected for, and unproven is not a passing reading | active |
|
|
57
|
+
| [AD-040](/decisions/ad-040.md) | Three lesson tiers, and nothing crosses between products by itself | active |
|
|
58
|
+
| [AD-041](/decisions/ad-041.md) | A member something reads and nothing writes fails the gate | active |
|
|
59
|
+
| [AD-042](/decisions/ad-042.md) | The suite gets an empty runtime home, and one renderer renders a lesson | active |
|
|
60
|
+
| [AD-043](/decisions/ad-043.md) | A standing rule is pinned, not ranked, and the budget says what it dropped | active |
|
|
61
|
+
| [AD-044](/decisions/ad-044.md) | Only an injection a gate could grade can be unproven | active |
|
|
62
|
+
| [AD-045](/decisions/ad-045.md) | A gate verdict is reused when the content hash of its inputs did not change | active |
|
|
63
|
+
| [AD-046](/decisions/ad-046.md) | The runtime path is an artifact, and update never touches what it does not own | active |
|
|
64
|
+
| [AD-047](/decisions/ad-047.md) | An instruction is not an affordance, and a refusal names the route that works | active |
|
|
65
|
+
| [AD-048](/decisions/ad-048.md) | A broken updater cannot deliver its own fix, so the installer is the recovery route | active |
|
|
66
|
+
| [AD-049](/decisions/ad-049.md) | An empty synced file says which of four reasons made it empty | active |
|
|
67
|
+
| [AD-050](/decisions/ad-050.md) | Lesson transport is a provider capability, not an operator preference | active |
|
|
68
|
+
| [AD-051](/decisions/ad-051.md) | A warning fails the gate, and a suppression states what breaks without it | active |
|
|
69
|
+
| [AD-052](/decisions/ad-052.md) | The repository moves to the org and the runtime paths do not | active |
|
|
70
|
+
| [AD-053](/decisions/ad-053.md) | The harness ships no model allowlist, and a list that names nothing enforces nothing | active |
|
|
71
|
+
| [AD-054](/decisions/ad-054.md) | npm is the distribution, and a merged release PR is the only thing that publishes | active |
|
|
72
|
+
| [AD-055](/decisions/ad-055.md) | The changelog is rendered from the decision records, and git says which release each landed in | active |
|
|
73
|
+
| [AD-056](/decisions/ad-056.md) | The package delivers the runtime, and the runtime path stays where hooks already point | active |
|
|
74
|
+
| [AD-057](/decisions/ad-057.md) | The release PR is a mechanism, not a gate, and the bot merges it | active |
|
|
75
|
+
| [AD-058](/decisions/ad-058.md) | Gates diff against the turn's base, and language knowledge is one table | active |
|
|
76
|
+
| [AD-059](/decisions/ad-059.md) | A rail never writes a field it reads, and a counter reads the plane its events land on | active |
|
|
77
|
+
| [AD-060](/decisions/ad-060.md) | The gate records the environment it ran under, and names it only once the cheap explanations are spent | active |
|
|
78
|
+
| [AD-061](/decisions/ad-061.md) | A decision that refuses names its rule, and degrading preserves it | active |
|
|
79
|
+
| [AD-062](/decisions/ad-062.md) | One command answers whether the harness did that, and says so plainly when it did not | active |
|
|
80
|
+
| [AD-063](/decisions/ad-063.md) | One palette for human output, and a checker keeping it out of everything else | active |
|
|
81
|
+
| [AD-064](/decisions/ad-064.md) | A snapshot is assigned, never accumulated, and a table does not list what it cannot count | active |
|
|
82
|
+
| [AD-065](/decisions/ad-065.md) | The obs bus has a contract, and the gate checks both sides of it | active |
|
|
83
|
+
| [AD-066](/decisions/ad-066.md) | Uninstall reads the artefact, and the plan is the confirmation | active |
|
|
84
|
+
| [AD-067](/decisions/ad-067.md) | A reserved file that cannot be retired is rendered, and the gate holds it there | active |
|
|
85
|
+
| [AD-068](/decisions/ad-068.md) | A directory decides what ships, and dist is derived from disk in both directions | active |
|
|
86
|
+
| [AD-069](/decisions/ad-069.md) | A decision record declares its shape, and cites by link so a move cannot break it | active |
|
|
87
|
+
| [AD-070](/decisions/ad-070.md) | A comment has to read for somebody who was not in the session | active |
|
|
88
|
+
| [AD-071](/decisions/ad-071.md) | The turn's added lines are checked against the code the project already has | active |
|
|
89
|
+
| [AD-072](/decisions/ad-072.md) | A record can leave the corpus, and removing is a change worth recording | active |
|
|
90
|
+
| [AD-073](/decisions/ad-073.md) | A neighbour mid-gate is not a reason to block a turn | active |
|
|
91
|
+
| [AD-074](/decisions/ad-074.md) | Code the gate cannot read is refused, and a credential is not always a file | active |
|
|
92
|
+
| [AD-075](/decisions/ad-075.md) | A dependency a turn adds outlives the turn, so two mechanical failures are worth a stop | active |
|
|
93
|
+
| [AD-076](/decisions/ad-076.md) | Goal-hijack enforcement needs the tool output, and the host does not deliver it | active |
|
|
94
|
+
| [AD-077](/decisions/ad-077.md) | A command that appears verbatim in fetched content is put to the operator | active |
|
|
95
|
+
| [AD-078](/decisions/ad-078.md) | The two files the harness reads aloud are sealed on write and verified on injection | active |
|
|
96
|
+
| [AD-079](/decisions/ad-079.md) | The coverage claim is a generated page with its gaps in it, not a badge in the README | active |
|
|
97
|
+
| [AD-080](/decisions/ad-080.md) | Every hand-written list of our own rules is now checked, including the one that drifted while writing this | active |
|
|
98
|
+
| [AD-081](/decisions/ad-081.md) | The manifest npm publishes is checked here, because the release runner was the only thing reading it | active |
|
|
99
|
+
| [AD-082](/decisions/ad-082.md) | npm is the documented install, and the first version cannot come from CI | active |
|
|
100
|
+
| [AD-083](/decisions/ad-083.md) | Going public switched the branch ruleset on, and an unattended release needs the App to bypass it | active |
|
|
101
|
+
|
|
102
|
+
## Archived
|
|
103
|
+
|
|
104
|
+
A record moves here when its decision is complete and its body would no longer guide a future change. It keeps
|
|
105
|
+
its row in `CHANGELOG.md` and in [/log.md](/log.md), because it shipped; this index is the only view that
|
|
106
|
+
separates the two, because it is the only one that claims to say what currently binds. Value decides, never
|
|
107
|
+
volume — see the archive rule in `CONTRIBUTING.md`.
|
|
108
|
+
|
|
109
|
+
None yet.
|
|
110
|
+
|
|
111
|
+
Related: [/architecture.md](/architecture.md), [/concepts.md](/concepts.md), [/providers/index.md](/providers/index.md).
|