@sleepy_panda_srl/portulan 0.1.1 → 0.1.3
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/README.md +16 -19
- package/cli/README.md +155 -125
- package/cli/compile.mjs +797 -40
- package/cli/doctor.mjs +59 -2
- package/cli/drills.mjs +1541 -0
- package/cli/fuzz-shell.mjs +880 -0
- package/cli/gate.mjs +3 -2
- package/cli/goldens.mjs +469 -0
- package/cli/index.mjs +92 -42
- package/cli/init.mjs +4 -1
- package/cli/inside.mjs +38 -0
- package/cli/mutants.mjs +925 -0
- package/cli/pack-identity.mjs +9 -2
- package/cli/pack-version.mjs +7 -0
- package/cli/recipe-set.mjs +48 -1
- package/cli/release-eval.mjs +1068 -0
- package/cli/review-meter.mjs +741 -0
- package/cli/skill-goldens.mjs +590 -0
- package/cli/skills-set.mjs +166 -4
- package/cli/stop-gate.mjs +301 -28
- package/cli/telemetry.mjs +1047 -0
- package/cli/version-carriers.mjs +224 -0
- package/core/operating/memory.md +7 -8
- package/core/operating/verification.md +4 -12
- package/core/templates/gate-policy.md +27 -8
- package/evals/README.md +910 -0
- package/evals/releases/0.1.3.json +134 -0
- package/evals/releases/0.1.3.md +64 -0
- package/evals/releases/README.md +32 -0
- package/package.json +7 -2
- package/packs/README.md +11 -21
- package/packs/rituals/checkpoints/README.md +8 -1
- package/packs/rituals/checkpoints/pack.json +1 -1
- package/packs/rituals/checkpoints/skills/pre-commit/SKILL.md +32 -2
- package/packs/tools/README.md +1 -4
- package/spec/README.md +19 -22
- package/spec/migrations/README.md +2 -3
- package/spec/slots.md +30 -11
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
{
|
|
2
|
+
"portulan": {
|
|
3
|
+
"releaseEval": "1"
|
|
4
|
+
},
|
|
5
|
+
"version": "0.1.3",
|
|
6
|
+
"captured": "2026-09-16",
|
|
7
|
+
"source": {
|
|
8
|
+
"commit": "fac7b6d36ce3aa53ce93381ef8cce07e503608e4",
|
|
9
|
+
"clean": true
|
|
10
|
+
},
|
|
11
|
+
"host": {
|
|
12
|
+
"node": "v26.8.1",
|
|
13
|
+
"platform": "darwin"
|
|
14
|
+
},
|
|
15
|
+
"recipes": [
|
|
16
|
+
{
|
|
17
|
+
"id": "docs",
|
|
18
|
+
"exit": 0
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "json",
|
|
22
|
+
"exit": 0
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "doctor",
|
|
26
|
+
"exit": 0
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "tests",
|
|
30
|
+
"exit": 0
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "plugin",
|
|
34
|
+
"exit": 0
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "compile",
|
|
38
|
+
"exit": 0
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "workflow-filters",
|
|
42
|
+
"exit": 0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "index",
|
|
46
|
+
"exit": 0
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "control-chars",
|
|
50
|
+
"exit": 0
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "rule-carriers",
|
|
54
|
+
"exit": 0
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "pack-version",
|
|
58
|
+
"exit": 0
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "pack-identity",
|
|
62
|
+
"exit": 0
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "payload",
|
|
66
|
+
"exit": 0
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "eval-bundle",
|
|
70
|
+
"exit": 0
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "goldens",
|
|
74
|
+
"exit": 0
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "mutants",
|
|
78
|
+
"exit": 0
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "fuzz-shell",
|
|
82
|
+
"exit": 0
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "version-carriers",
|
|
86
|
+
"exit": 0
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "skill-goldens",
|
|
90
|
+
"exit": 0
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "review-loop",
|
|
94
|
+
"exit": 0
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "telemetry",
|
|
98
|
+
"exit": 0
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "drills",
|
|
102
|
+
"exit": 0
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "ab",
|
|
106
|
+
"exit": 0
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "ab-grade",
|
|
110
|
+
"exit": 0
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "ab-run",
|
|
114
|
+
"exit": 0
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "tools/github:actions-pinned",
|
|
118
|
+
"exit": 0
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"excluded": [
|
|
122
|
+
{
|
|
123
|
+
"id": "release-eval",
|
|
124
|
+
"why": "a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file."
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"abBaseline": {
|
|
128
|
+
"snapshot": "evals/ab/baseline.json",
|
|
129
|
+
"register": "evals/ab/baseline.md",
|
|
130
|
+
"captured": "2026-08-31T09:05:32.699Z",
|
|
131
|
+
"commit": "a642d551eec6c146c67ef0acfe3f8ba535c5ce6e",
|
|
132
|
+
"clean": false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Eval result — Portulan 0.1.3
|
|
2
|
+
|
|
3
|
+
> **Generated. Do not edit.** Rendered from [`evals/releases/0.1.3.json`](0.1.3.json) by [`cli/release-eval.mjs`](../../cli/release-eval.mjs) and byte-compared by the `release-eval` verify recipe. This is the eval result milestone 8 requires a release to carry — `docs/plan.md`, Protocol → Versioning.
|
|
4
|
+
|
|
5
|
+
## What this release was measured at
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Version | `0.1.3` |
|
|
10
|
+
| Captured | 2026-09-16 |
|
|
11
|
+
| Commit | `fac7b6d36ce3aa53ce93381ef8cce07e503608e4` |
|
|
12
|
+
| Tree at capture | clean |
|
|
13
|
+
| Node | `v26.8.1` |
|
|
14
|
+
| Platform | `darwin` |
|
|
15
|
+
|
|
16
|
+
## The rails, and what each returned
|
|
17
|
+
|
|
18
|
+
26 of the 27 recipes this workspace yielded at capture. The set is derived from [`cli/recipe-set.mjs`](../../cli/recipe-set.mjs) and never listed by hand.
|
|
19
|
+
|
|
20
|
+
| Recipe | Verdict |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `docs` | green |
|
|
23
|
+
| `json` | green |
|
|
24
|
+
| `doctor` | green |
|
|
25
|
+
| `tests` | green |
|
|
26
|
+
| `plugin` | green |
|
|
27
|
+
| `compile` | green |
|
|
28
|
+
| `workflow-filters` | green |
|
|
29
|
+
| `index` | green |
|
|
30
|
+
| `control-chars` | green |
|
|
31
|
+
| `rule-carriers` | green |
|
|
32
|
+
| `pack-version` | green |
|
|
33
|
+
| `pack-identity` | green |
|
|
34
|
+
| `payload` | green |
|
|
35
|
+
| `eval-bundle` | green |
|
|
36
|
+
| `goldens` | green |
|
|
37
|
+
| `mutants` | green |
|
|
38
|
+
| `fuzz-shell` | green |
|
|
39
|
+
| `version-carriers` | green |
|
|
40
|
+
| `skill-goldens` | green |
|
|
41
|
+
| `review-loop` | green |
|
|
42
|
+
| `telemetry` | green |
|
|
43
|
+
| `drills` | green |
|
|
44
|
+
| `ab` | green |
|
|
45
|
+
| `ab-grade` | green |
|
|
46
|
+
| `ab-run` | green |
|
|
47
|
+
| `tools/github:actions-pinned` | green |
|
|
48
|
+
|
|
49
|
+
**`release-eval` is excluded**, and the reason is printed rather than the row silently dropped: a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file.
|
|
50
|
+
|
|
51
|
+
## The A/B baseline this release ships against
|
|
52
|
+
|
|
53
|
+
[`evals/ab/baseline.md`](../../evals/ab/baseline.md), rendered from `evals/ab/baseline.json` — captured 2026-08-31T09:05:32.699Z at `a642d551eec6c146c67ef0acfe3f8ba535c5ce6e`, over a tree that was **not clean**.
|
|
54
|
+
|
|
55
|
+
**Its figures are not repeated here.** They have one carrier and this is not it.
|
|
56
|
+
|
|
57
|
+
## What this record does not establish
|
|
58
|
+
|
|
59
|
+
- **A green says this record agrees with its own capture — never that the release is good.** Every line below is what the recipes returned at one commit, and a recipe's own green establishes only what that recipe's documentation says it establishes.
|
|
60
|
+
- **The recipes were not run at the tag.** They ran at `fac7b6d36ce3aa53ce93381ef8cce07e503608e4`, and this record is committed *in* the cut change — so the tagged tree is this commit plus whatever that change still adds after the capture, the record itself at minimum. A record cannot be captured at a commit that does not exist yet, and printing the one it was captured at is the only honest form.
|
|
61
|
+
- **`release-eval` is excluded from the rows above**, because a capture cannot be accurate about the record it is inside. Its verdict for this release is the rail's own run on the pull request that carries this file.
|
|
62
|
+
- **This record measures the Portulan repository's own build**, at the commit named above — never the workspace, project or package this release is installed into. Nothing here has looked at your tree.
|
|
63
|
+
- **The tree was clean at capture**, so the commit named above is what was measured.
|
|
64
|
+
- **The A/B baseline's figures are NOT restated here.** This record names which baseline the release ships against; the figures and everything that may not be concluded from them live in that register alone.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `evals/releases/`
|
|
2
|
+
|
|
3
|
+
The eval result each release carries, one pair per version — milestone 8's ninth clause. The argument,
|
|
4
|
+
the limits and the two designs a fresh context reversed are in [`../README.md`](../README.md); the
|
|
5
|
+
mechanism is [`../../cli/release-eval.mjs`](../../cli/release-eval.mjs). This file does not restate
|
|
6
|
+
either, because a rule with two carriers is obeyed at the narrower one.
|
|
7
|
+
|
|
8
|
+
- `<version>.json` — the **capture**: the verdict every recipe the workspace yielded returned, at a named
|
|
9
|
+
commit, plus the identity of the A/B baseline that release ships against. Written by
|
|
10
|
+
`node cli/release-eval.mjs --capture`, which runs them. **A hand-edited capture is NOT caught by the
|
|
11
|
+
byte comparison** — that holds the register to the capture, so `--write` re-renders the edit and the
|
|
12
|
+
pair agrees perfectly. What guards a capture is the rail's reading of its *contents*: an exclusion
|
|
13
|
+
list that is anything but the self-exclusion, a recorded non-zero exit, a version not matching the
|
|
14
|
+
release it is filed under, a record for a version never cut or for one the clause does not govern.
|
|
15
|
+
This sentence used to claim the opposite, and a sentence claiming a guard that does not exist is what
|
|
16
|
+
makes the gap it describes look unreachable.
|
|
17
|
+
- `<version>.md` — the **register**, rendered from the capture. Generated; do not edit. `--write`
|
|
18
|
+
re-renders it and the `release-eval` recipe refuses any difference.
|
|
19
|
+
|
|
20
|
+
**Which releases have records is not written here.** `ls` this directory, or run
|
|
21
|
+
`node cli/release-eval.mjs --verify`, which prints the governed set and is the carrier — a sentence
|
|
22
|
+
counting the files beside it would be a hand-maintained figure of a subject that grows once per release,
|
|
23
|
+
which is the defect this repository deletes on sight.
|
|
24
|
+
|
|
25
|
+
**What is fixed, and is the only thing this file states about coverage:** the clause binds *from
|
|
26
|
+
milestone 8* — [`../../docs/plan.md`](../../docs/plan.md), Protocol → Versioning — so `v0.1.0`, `v0.1.1`
|
|
27
|
+
and `v0.1.2` carry none and are never asked to. That is history and cannot change.
|
|
28
|
+
|
|
29
|
+
_(This paragraph read **"This directory is empty of records"** as a standing claim. It would have gone
|
|
30
|
+
false in the very commit that wrote the first record — and, since this directory ships inside the npm
|
|
31
|
+
payload, it would then have been **frozen false beside the record it denied**, in a tarball only another
|
|
32
|
+
release could correct. Caught by a second opinion before the first cut rather than by a reader of it.)_
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleepy_panda_srl/portulan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Portulan — an agentic-engineering framework whose product is its files. The CLI wraps the file-based mechanics for hosts beyond Claude Code.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,13 +15,18 @@
|
|
|
15
15
|
"core/",
|
|
16
16
|
"spec/",
|
|
17
17
|
"packs/",
|
|
18
|
+
"evals/releases/",
|
|
18
19
|
"README.md",
|
|
19
20
|
"LICENSE",
|
|
20
21
|
"NOTICE",
|
|
21
22
|
"!cli/*.test.mjs",
|
|
22
23
|
"!cli/fixtures",
|
|
23
24
|
"!cli/eval-bundle.mjs",
|
|
24
|
-
"!cli/eval-license.template.md"
|
|
25
|
+
"!cli/eval-license.template.md",
|
|
26
|
+
"!cli/ab.mjs",
|
|
27
|
+
"!cli/ab-run.mjs",
|
|
28
|
+
"!cli/ab-grade.mjs",
|
|
29
|
+
"!cli/payload.mjs"
|
|
25
30
|
],
|
|
26
31
|
"repository": {
|
|
27
32
|
"type": "git",
|
package/packs/README.md
CHANGED
|
@@ -15,18 +15,14 @@ array, in the canonical `category/name` form.
|
|
|
15
15
|
|
|
16
16
|
## Why there is a `.claude-plugin/plugin.json` in *this* directory
|
|
17
17
|
|
|
18
|
-
Because **this directory is itself a plugin payload
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
issue's own diagnosis of it was wrong: it attributed the zero to a declared path pointing one level too
|
|
27
|
-
high, which is a real trap and a different one. Measured on Claude Code 2.1.226 by installing this
|
|
28
|
-
directory both ways — **`Skills (0)` without the manifest, `Skills (3)` with it**, the same three
|
|
29
|
-
`SKILL.md` files either way.
|
|
18
|
+
Because **this directory is itself a plugin payload**. The private feed's
|
|
19
|
+
`portulan-checkpoints` entry is a `git-subdir` source rooted here, so what a host installs is the
|
|
20
|
+
contents of `packs/` — and a payload with no plugin manifest registers nothing, silently, reporting
|
|
21
|
+
it only as a count nobody is watching: `claude plugin details` → `Skills (0)`.
|
|
22
|
+
|
|
23
|
+
**Registration is a property of the plugin manifest and of nothing else.** Measured on Claude Code
|
|
24
|
+
2.1.226 by installing this directory both ways — **`Skills (0)` without the manifest, `Skills (3)`
|
|
25
|
+
with it**, the same three `SKILL.md` files either way.
|
|
30
26
|
|
|
31
27
|
The manifest declares `./rituals/checkpoints/skills/` — the directory that actually holds them — because
|
|
32
28
|
**a host expands a declared skills path exactly one level and no further**. Declaring `./rituals/`
|
|
@@ -50,12 +46,6 @@ exists and the feed's pin moves to it. Until then the green is demonstrated on t
|
|
|
50
46
|
for the feed — a distinction this repository would rather write down than round off.
|
|
51
47
|
|
|
52
48
|
> **Two packs exist.** [`rituals/checkpoints`](rituals/checkpoints/README.md), the supervised-build
|
|
53
|
-
> ritual
|
|
54
|
-
>
|
|
55
|
-
|
|
56
|
-
> until 2026-08-13, and the second half went false the day `tools/github` landed beside it.)_
|
|
57
|
-
>
|
|
58
|
-
> This file read "Nothing here yet" for four milestones, and the pack it predicted — a cross-artifact
|
|
59
|
-
> consistency check — is not the one that arrived. Recorded rather than quietly replaced: the first
|
|
60
|
-
> pack was chosen by which ritual this project could show working evidence for, not by which was named
|
|
61
|
-
> first.
|
|
49
|
+
> ritual; and [`tools/github`](tools/github/README.md), one verify recipe. `stacks/` is still empty and
|
|
50
|
+
> says why.
|
|
51
|
+
|
|
@@ -38,7 +38,7 @@ Four verdicts, and which are available depends on the checkpoint.
|
|
|
38
38
|
| Verdict | Means | Available at |
|
|
39
39
|
|---|---|---|
|
|
40
40
|
| **APPROVE** | Proceed as it stands. | session-open · pre-commit |
|
|
41
|
-
| **APPROVE-WITH-ADJUSTMENTS** | Proceed once the numbered adjustments are folded in. Not a soft failure — it is what a working checkpoint usually returns. | session-open · pre-commit |
|
|
41
|
+
| **APPROVE-WITH-ADJUSTMENTS** | Proceed once the numbered adjustments are folded in. Not a soft failure — it is what a working checkpoint usually returns. **At pre-commit the fold itself is then graded** — see that skill. | session-open · pre-commit |
|
|
42
42
|
| **REQUEST-CHANGES** | Does not meet the standard. Name what, and what would meet it. Needs a second pass at the same checkpoint. | all three |
|
|
43
43
|
| **CLOSE** | Every clause of the criterion is demonstrated; the milestone may be marked done. | milestone-close only |
|
|
44
44
|
|
|
@@ -49,6 +49,13 @@ concerns is not a verdict.
|
|
|
49
49
|
proceed on a promise to fold something in, and a close is the one decision with nothing downstream of
|
|
50
50
|
it to catch what was folded in wrongly.
|
|
51
51
|
|
|
52
|
+
**That argument reaches one moment earlier, which is why pre-commit's A-W-A grades its own fold**
|
|
53
|
+
(proposal `0035`, 2026-08-29). Session-open's fold *does* have something downstream — the plan it folds
|
|
54
|
+
into becomes a diff, and pre-commit grades that. **Pre-commit's fold has only the commit.** So the
|
|
55
|
+
verdict that lets work proceed on a promise is, at that moment, a promise nothing checks; measured on a
|
|
56
|
+
change where two of three later review findings were created by the fold and were absent from the graded
|
|
57
|
+
diff.
|
|
58
|
+
|
|
52
59
|
## The fidelity note
|
|
53
60
|
|
|
54
61
|
What a close leaves in the record once the session that wrote it is gone. Four parts, in one clause:
|
|
@@ -65,8 +65,38 @@ whether a status column changed.
|
|
|
65
65
|
## The verdicts this checkpoint may return
|
|
66
66
|
|
|
67
67
|
- **APPROVE** — commit as it stands.
|
|
68
|
-
- **APPROVE-WITH-ADJUSTMENTS** —
|
|
69
|
-
|
|
68
|
+
- **APPROVE-WITH-ADJUSTMENTS** — fold the numbered adjustments, then **grade the fold**: a pass over
|
|
69
|
+
**the fold delta alone**, in a context that did not perform the fold, before the commit.
|
|
70
|
+
- **REQUEST-CHANGES** — the diff does not meet the criterion; it needs work and a second pre-commit pass
|
|
71
|
+
**over the whole diff**.
|
|
72
|
+
|
|
73
|
+
**Grade a fully staged diff.** The whole procedure below is index-shaped, so anything left unstaged was
|
|
74
|
+
not graded and will not appear in the fold delta either — it would pass through both nets. Stage first,
|
|
75
|
+
then grade; this is what practice here already did, and it is stated because the mechanism now depends
|
|
76
|
+
on it rather than merely benefiting from it.
|
|
77
|
+
|
|
78
|
+
**A checkpoint records the tree it graded** — the index it read, as a tree object (`git write-tree`). The
|
|
79
|
+
fold delta is then **`git diff --cached <graded-tree>`**: the recorded tree against the index, which is
|
|
80
|
+
what is about to be committed. Derived, not reconstructed from memory by the person who folded.
|
|
81
|
+
|
|
82
|
+
_Why the fold is graded at all, and why the A-W-A pass is scoped to the delta._ **The tree that ships is
|
|
83
|
+
not the tree that was graded**, and the difference is where a remediation can inject what the checkpoint
|
|
84
|
+
just caught elsewhere. Measured: on one change, folding an adjustment split a table row and left a
|
|
85
|
+
summary count stale three sections above, and folding another **re-measured a question the checkpoint had
|
|
86
|
+
answered correctly** — with a line-based tool against a normalising scanner — and wrote the wrong answer
|
|
87
|
+
over the right one. Neither defect was in the graded diff. A full re-grade after every A-W-A would double
|
|
88
|
+
the cost of the cheapest verdict, which is the ceremony a definition of done should refuse; **the delta is
|
|
89
|
+
the only region a fold-injected defect can be in.**
|
|
90
|
+
|
|
91
|
+
_The delta is **whatever was staged after the grade** — it *should* be the adjustments and nothing else,
|
|
92
|
+
and the mechanism does not guarantee that. **Where it is wider, that is not noise; it is the finding.**
|
|
93
|
+
Post-grade edits nobody proposed are exactly what escapes a checkpoint today, so a pass that sees them is
|
|
94
|
+
worth more than one over a delta that had been trimmed to what the verdict asked for._
|
|
95
|
+
|
|
96
|
+
_What this does not buy, said plainly: whether a fresh context actually read the delta is not checkable —
|
|
97
|
+
a session can claim a pass it did not run. What changes is that the pass now has a **defined subject**,
|
|
98
|
+
derived from the tree rather than recalled by the one who folded._ Provenance: proposal `0035`,
|
|
99
|
+
accepted 2026-08-29.
|
|
70
100
|
|
|
71
101
|
## Why it earns its tokens
|
|
72
102
|
|
package/packs/tools/README.md
CHANGED
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
Tool packs — integrations for the tools and MCP servers an agent drives (issue trackers, cloud
|
|
4
4
|
CLIs, design tools, databases), with the how-to and the gate classification for each.
|
|
5
5
|
|
|
6
|
-
> **One tool pack exists** — [`github/`](github/README.md)
|
|
7
|
-
> "No tool pack exists yet" until 2026-08-13, beside the directory that refutes it.)_ What changed at
|
|
8
|
-
> milestone 6 was the machinery rather
|
|
9
|
-
> than this directory. Milestone 4 built the thing that would read one — a gate policy in a
|
|
6
|
+
> **One tool pack exists** — [`github/`](github/README.md). The machinery that reads one is a gate policy in a
|
|
10
7
|
> host-neutral vocabulary, dispatched on by [`../../cli/compile.mjs`](../../cli/compile.mjs) — and the
|
|
11
8
|
> cascade's missing middle (`core < pack < workspace`) is now bridged: a pack's `contributes.gates`
|
|
12
9
|
> fragments are resolved and merged into the policy, **tighten-only**, by the same compiler.
|
package/spec/README.md
CHANGED
|
@@ -6,8 +6,7 @@ what makes both machine-readable instead of a folder convention.
|
|
|
6
6
|
|
|
7
7
|
- [`workspace.schema.json`](workspace.schema.json) — the **Workspace Definition**: the manifest at a
|
|
8
8
|
workspace root. Normative.
|
|
9
|
-
- [`pack.schema.json`](pack.schema.json) — the **Pack Definition**: the manifest at a pack root,
|
|
10
|
-
milestone 6, declaring what the pack contributes to the cascade. Normative, and on **its own version
|
|
9
|
+
- [`pack.schema.json`](pack.schema.json) — the **Pack Definition**: the manifest at a pack root, declaring what the pack contributes to the cascade. Normative, and on **its own version
|
|
11
10
|
train** — see [Versioning](#versioning-and-migrations).
|
|
12
11
|
- [`slots.md`](slots.md) — every workspace slot: what it holds, why it exists, where it was derived from,
|
|
13
12
|
and what `doctor` checks about it. The rationale lives there; this file is the orientation.
|
|
@@ -64,7 +63,7 @@ read.
|
|
|
64
63
|
The fourth, **`pointer`**, added at 2.7, governs nothing. It names the workspace that governs this
|
|
65
64
|
repository and carries no slots of its own — a repository whose policy layer resides elsewhere says so in
|
|
66
65
|
one thin manifest instead of keeping a second copy nothing holds in agreement. That is the maintainer's
|
|
67
|
-
residence ruling
|
|
66
|
+
residence ruling in the schema
|
|
68
67
|
([proposal 0017](../.portulan/proposals/0017-one-repository-one-governing-workspace.md)):
|
|
69
68
|
|
|
70
69
|
> A repository is governed by exactly one workspace. It carries its own full workspace, or a pointer to
|
|
@@ -76,11 +75,11 @@ makes that a property rather than a promise is that every feature keys to a work
|
|
|
76
75
|
to a residence. Nothing in `doctor`, `compile` or `index` asks where the manifest lives. The proposal
|
|
77
76
|
carries the one place where that was not yet true, and it was a discovery gap rather than a feature gap:
|
|
78
77
|
the boot skill searched the project directory only, so a repository governed from a feed booted to a
|
|
79
|
-
pointer's honest report rather than to a resolved workspace. **Closed
|
|
78
|
+
pointer's honest report rather than to a resolved workspace. **Closed** by
|
|
80
79
|
[`../cli/discover.mjs`](../cli/discover.mjs), which reads the host's installed-plugin record and
|
|
81
80
|
dereferences a pointer's `governed_by` — from disk, never over the network. The other half of the same amendment — the **pack** root
|
|
82
81
|
([#123](https://github.com/sleepy-panda-srl/portulan/issues/123)) — landed beside it: `--pack-root
|
|
83
|
-
auto` resolves one from that same record. It ran **only when asked
|
|
82
|
+
auto` resolves one from that same record. It ran **only when asked**, which was narrower
|
|
84
83
|
than the row's *"optional where discovery finds a root"* and was recorded as a narrowing rather than read
|
|
85
84
|
as compliance. **The narrowing is now lifted and the row's word is met**: discovery runs on the unasked
|
|
86
85
|
path, and `auto` selects the strict degrade rather than unlocking anything. The narrowing's stated reason
|
|
@@ -159,10 +158,8 @@ real cost, stated here rather than discovered. The subset earns its narrowness b
|
|
|
159
158
|
completely and honestly; the price is this list, and the list is the thing to watch if it keeps growing.
|
|
160
159
|
**Four MINORs at +2 each** was the growth rate to hold the next bump against, and **2.3's, 2.5's and
|
|
161
160
|
2.6's** pairs are the *same* pair three times over — a series needs a store and its index must sit outside
|
|
162
|
-
it — which was an argument for generalising it rather than adding it again. _(2.4's pair is `librarian`'s
|
|
163
|
-
|
|
164
|
-
An earlier draft of this sentence read "2.4's, 2.5's and 2.6's", which would have made the repeating pair
|
|
165
|
-
four bumps old instead of three and misattributed `memory`'s contribution to `librarian`.)_
|
|
161
|
+
it — which was an argument for generalising it rather than adding it again. _(2.4's pair is `librarian`'s and is a
|
|
162
|
+
different one: a pass needs a store to age, and a proposal threshold needs a proposal series.)_
|
|
166
163
|
|
|
167
164
|
**2.7 is the first MINOR since 2.2 that adds nothing to this list**, and that is the reason its pointer
|
|
168
165
|
requirement went into the schema's `oneOf` instead. The generalisation the paragraph above kept asking for
|
|
@@ -198,7 +195,7 @@ number governing both would make a bump in either mean a change in the other:
|
|
|
198
195
|
| Schema | Manifest key | Current | What it governs |
|
|
199
196
|
|---|---|---|---|
|
|
200
197
|
| [`workspace.schema.json`](workspace.schema.json) | `portulan.spec` | **2.8** | the Workspace Definition — the manifest at a workspace root |
|
|
201
|
-
| [`pack.schema.json`](pack.schema.json) | `portulan.pack` | **1.0** | the Pack Definition — the manifest at a pack root
|
|
198
|
+
| [`pack.schema.json`](pack.schema.json) | `portulan.pack` | **1.0** | the Pack Definition — the manifest at a pack root. |
|
|
202
199
|
|
|
203
200
|
The rules below apply to each train independently. `portulan.spec` is `MAJOR.MINOR`, and the current
|
|
204
201
|
Workspace Definition version is **2.8**. It did **not** move when the Pack Definition arrived, because
|
|
@@ -300,7 +297,7 @@ better, and not a fix.)_
|
|
|
300
297
|
|
|
301
298
|
This migration is a paragraph and a version bump, and a directory holding a document that says "add one
|
|
302
299
|
line" would be scaffolding pretending to be machinery. This paragraph therefore ended **"There is still
|
|
303
|
-
no `migrations/` directory: one arrives when a migration needs code"** — and
|
|
300
|
+
no `migrations/` directory: one arrives when a migration needs code"** — and
|
|
304
301
|
one did, on that stated condition rather than because a tool wanted a home.
|
|
305
302
|
|
|
306
303
|
**[`migrations/`](migrations/) is now the contract**, and its README is where a step is defined. What
|
|
@@ -328,24 +325,24 @@ workspace with no constitution slot and a green report.
|
|
|
328
325
|
|---|---|---|
|
|
329
326
|
| Every `.json` file parses | [`../.portulan/verify/json.sh`](../.portulan/verify/json.sh) | **Built.** Well-formedness only — it does not read the schema. |
|
|
330
327
|
| Manifest conforms to the schema | [`../cli/doctor.mjs`](../cli/doctor.mjs) | **Built.** Names the violated constraint and its location. |
|
|
331
|
-
| A declared pack **resolves** | `doctor` | **Built
|
|
332
|
-
| A pack manifest conforms to the Pack Definition | `doctor` | **Built
|
|
333
|
-
| A pack's skills and personas are opened and validated | `doctor` | **Built
|
|
334
|
-
| Pack gate fragments merge **tighten-only** | [`../cli/compile.mjs`](../cli/compile.mjs) | **Built
|
|
335
|
-
| A pack declaring a version ahead of `doctor` | `doctor` | **Built
|
|
328
|
+
| A declared pack **resolves** | `doctor` | **Built**. Roots are derived from `tree`, and named roots may be given with `--pack-root`, which **replace** the derived one rather than preceding it, so a from-a-feed demonstration cannot be satisfied by a copy in the local tree at all. A workspace with no `tree` and no named root has its packs reported *unverifiable* rather than failed. Resolution **from a feed is demonstrated**: the checkpoint ritual pack installed from the private `portulan-internal` marketplace and resolved from the install root, which is the same code path the derived one takes. **Discovery is built** ([#123](https://github.com/sleepy-panda-srl/portulan/issues/123)): `--pack-root auto` reads the host's installed-plugin record — in **both** shapes a plugin lands in, packs under `packs/` for a repository-shaped one and categories at the install root for a flat one, which is what this project's own feed ships. Asked-for discovery is **unioned** with the tree-derived root, discovered first, so a workspace composing a cache-installed pack *and* one of its own resolves both. The **named**-root property above is untouched: a named root still replaces the derived one, so a from-a-feed demonstration given one cannot be satisfied by a local copy. What the union trades is the weaker guarantee that `auto` *alone* implied provenance; it no longer does, and each pack's resolution states which root answered instead. Asking for a named root **and** `auto` together is refused (exit 2) rather than silently resolved to one of them. Discovery runs on the **unasked** path too, because `--pack-root` is *optional where discovery finds a root*. `auto` now selects the strict degrade (asked-and-could-not-look is exit 2) rather than unlocking discovery. Two things bound it, and neither makes a bare run host-independent — `doctor` is a per-host capability report and a bare verdict does move with the machine. First, every **required** check **names** its root, which replaces every other source, so it cannot consult the host at all. Second, `doctor`'s note-vs-fail keying is on a root's **origin**, so discovery can turn a note into a **resolution** and never a **miss** into a failure — a miss under a root the workspace or the caller claimed stays a FAIL whatever is installed. It softens nothing: a discovered copy that resolves and is *invalid* still fails, with its origin named. |
|
|
329
|
+
| A pack manifest conforms to the Pack Definition | `doctor` | **Built**. Same validator, same subset, a different schema and version train. |
|
|
330
|
+
| A pack's skills and personas are opened and validated | `doctor` | **Built**, on the maintainer's ruling that row 7's validation reads broad. A skill's frontmatter (`name` kebab-case, `description` non-empty — the two a host reads before loading a body); a persona against the five-part contract, **including the memory scope** `core/operating/memory.md` promised would be validated at this milestone; and the tier no role may claim. Containment is checked **after resolution**, because opening these paths is what made an escaping value stop being inert. Three honest limits, each measured rather than assumed. The skills walk stops **three levels down**, matching `cli/plugin-lint.mjs`'s bound so two walkers over the same key cannot disagree, and it **reports the directories it did not descend into** rather than counting a root it never opened as empty. The Prohibited check is a prose heuristic defeated by any negation **in the same block** — a bullet list counts as one block — and the structural fix is a declared reach *field*, a contract change that is not built. And the five-part check is **presence**, not content: a persona whose sections are all empty passes, so a declared memory scope is read and not honoured. |
|
|
331
|
+
| Pack gate fragments merge **tighten-only** | [`../cli/compile.mjs`](../cli/compile.mjs) | **Built**. Two layers and two axes: `auto` is absent from the Pack Definition's tier enum, so a demotion to unattended is unexpressible; the comparison against the composed base is the compiler's, and both a weakened **tier** and a changed **action** throw rather than being dropped. The action half is the one a tier-only check misses — raising the tier while swapping the matcher removes the gate and still reads as a tightening. |
|
|
332
|
+
| A pack declaring a version ahead of `doctor` | `doctor` | **Built**. Refused rather than graded, symmetric with the workspace train, and on the Pack Definition's own `$id`. |
|
|
336
333
|
| Path slots resolve to real files | `doctor` | **Built.** File-versus-directory too; a slot resolving outside the workspace is reported, never failed. |
|
|
337
334
|
| Workspace claims match the tree | `doctor` | **Built, where a tree is declared** — repo-card build/test/run lines and layout, and the gate map's required-check claim. A workspace with no `tree` has those claims *reported unverifiable*. |
|
|
338
335
|
| A rule's provenance is well-formed | `doctor` | **Built.** On `type: rule` records in the `memory` slot; the form only, never the truth. |
|
|
339
336
|
| Sealed proportion reported | `doctor` | **Built.** Over rules, which is the denominator the mandate uses. |
|
|
340
|
-
| The recipes a workspace declares actually run | Stop-gate runner | **Built
|
|
341
|
-
| The gate policy compiles to host enforcement | `compile` | **Built
|
|
342
|
-
| What each backend cannot enforce | `doctor` · `compile --matrix` | **Built
|
|
337
|
+
| The recipes a workspace declares actually run | Stop-gate runner | **Built**, for the **default** recipe only, and outside `doctor` — which still reads recipes and executes none. A non-default recipe declaring a tool it does not have still passes. |
|
|
338
|
+
| The gate policy compiles to host enforcement | `compile` | **Built**. Two backends — the Claude Code host and the GitHub repository ruleset that is the platform floor. Every rule ends as compiled or refused-with-a-reason, **per backend**, and each emitted artifact is held to the policy by a verify recipe. That the *host* honours it is not checkable here — CI installs nothing. |
|
|
339
|
+
| What each backend cannot enforce | `doctor` · `compile --matrix` | **Built**. Per-backend coverage, the gates no backend compiles, and the floor's declared status checks against the tree's workflow jobs — the last of those a failure, the rest reports. Exported-versus-live drift is **not** checked: `doctor` does not fetch settings, and no recipe here makes a network call. |
|
|
343
340
|
| One repository, one governing workspace | `doctor` | **Built** (2.7). Two refusals need only the manifest in hand: a `pointer` carrying governing slots, and a governing kind carrying a `governed_by`. Both print the ruling's own sentence, which is why they are `doctor`'s and not the schema's — a schema failure returns at *the manifest must conform first* and the sentence would never reach a reader. |
|
|
344
|
-
| A repository named by one workspace and governed by another | `doctor --repo-root` | **Built** (2.7), **where visible**. Roots are named, never discovered — **and
|
|
345
|
-
| A pointer resolves to the workspace it names | `discover` · `doctor` | **Built
|
|
341
|
+
| A repository named by one workspace and governed by another | `doctor --repo-root` | **Built** (2.7), **where visible**. Roots are named, never discovered — **and this is the narrower limit of the two**: `--pack-root` gained `auto` and `--repo-root` did not, because what it would have to find is a *repository checkout*, which no host plugin record lists. The amendment that ordered discovery names the plugin cache in its own title, so this is outside its scope rather than owed by it. Without a root the check **reports that it did not run** rather than passing quietly. Visibility is one-way: the refusal runs from the naming workspace outward, so a repository carrying a full workspace cannot see a portfolio that claims it. A workspace that names its **own** repository finds itself and is not two managers; that identity is compared on the real path. |
|
|
342
|
+
| A pointer resolves to the workspace it names | `discover` · `doctor` | **Built**. `governed_by` is dereferenced against the host's installed-plugin record — `<config>/plugins/installed_plugins.json`, `CLAUDE_CONFIG_DIR` overriding the config directory — and the match is on the **governing manifest's `name`**, never on a plugin's, with `governed_by.feed` constraining the marketplace where it is declared. Four answers, because three of them are not *no*: `resolved` (exactly one), `not-installed`, `ambiguous` (two or more — **refused and both named**, never ranked), `could-not-look` (a record that would not parse, which must never spend as absence). **Nothing is fetched**: the record is read from disk and no path here touches the network, so *discovery of anything not installed* stays out of scope as row 7 states. `doctor` **reports** the answer on a pointer and never grades it — a pointer whose governor is uninstalled is a correct pointer, and no discovery outcome moves this tool's exit code. The candidate locations inside a plugin payload are a **named pair** — `<installPath>/workspace.json` and `<installPath>/.portulan/workspace.json` — rather than a walk, which is what keeps this bundle's demo and drifted fixtures out of reach by construction — neither sits at one; the bundle's own `.portulan/` does, which is the correct answer to a pointer naming `portulan` rather than a hole. `init` **asks** the residence question and writes a pointer; `vendor` **performs the switch in both directions** as of session 3, and both write pointers rather than resolving them. |
|
|
346
343
|
| A feed-side repo card's claims against a repo-side tree | — | **Not built** — the cross-repo claims-lint gap, priced in [proposal 0017](../.portulan/proposals/0017-one-repository-one-governing-workspace.md). A portfolio workspace declares no `tree`, so its cards' build/test/run and layout claims are *reported unverifiable*; repo-side the tree is present and the cards are not. No single CI run sees both halves. In-session validation does work, and `--repo-root` narrows the governance question specifically — neither closes this. **Tracked as [#138](https://github.com/sleepy-panda-srl/portulan/issues/138)**, filed 2026-07-30 at the maintainer's instruction. This row states no *arrives-at*, unlike its neighbour below which says outright that it is not planned as a gate; which milestone owns it — milestone 9 has been proposed, since its criterion is the first that needs both halves green together — is **the maintainer's to decide**, and this row will name it once [`../docs/plan.md`](../docs/plan.md) does. Until then the honest state is *not built, tracked, undispositioned* rather than a milestone claimed here first. |
|
|
347
344
|
| A rule's link resolves | — | Not built, and not planned as a gate: dereferencing needs the network, and a gate that fails for reasons unrelated to the change under test is worse than no gate. |
|
|
348
|
-
| Agent-legibility scored | `doctor` | **Built
|
|
345
|
+
| Agent-legibility scored | `doctor` | **Built**. **Seven** dimensions, each optional in this definition so it can genuinely be absent and none of them a restatement of a check that already fails — *executable verification* was an eighth for one checkpoint until that rule was applied to it, since form 0 of the `oneOf` above requires `verify` of every workspace the score can reach: recipes declaring `requires` · a machine-readable gate policy · a `dod` slot · a memory store with a generated index · a handoff series with a generated index · affordances reachable for every product (its own or the workspace-level default) · each of those documents naming what an agent must **not** assume. The `affordances` slot is the **named input, not the only one** — a score confined to it could not tell two workspaces apart. **It moves no exit code**: a measurement is not a verdict, and one that could fail a workspace would make conformance a function of prose volume. The last dimension is a **form** check over a named heading table, so a limits section with an empty body passes. |
|
|
349
346
|
|
|
350
347
|
As of milestone 2 the schema is a rail rather than only a specification — but read the right-hand column
|
|
351
348
|
rather than the middle one. `doctor` checks that a workspace's claims are **well-formed and resolvable**;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# `spec/migrations/` — what a migration is
|
|
2
2
|
|
|
3
3
|
> Part of the **Workspace Definition**. [`../README.md`](../README.md)'s *Versioning and migrations*
|
|
4
|
-
> section is the contract this directory implements
|
|
5
|
-
>
|
|
6
|
-
> directory arrives on that stated condition rather than because a tool wanted a home. The runner is
|
|
4
|
+
> section is the contract this directory implements, and this directory exists on that section's own
|
|
5
|
+
> stated condition: a migration needed code. The runner is
|
|
7
6
|
> [`../../cli/upgrade.mjs`](../../cli/upgrade.mjs).
|
|
8
7
|
|
|
9
8
|
## A step, and the two kinds
|
package/spec/slots.md
CHANGED
|
@@ -345,9 +345,7 @@ has a product-layer slot, which is what the criterion requires; *instances* may
|
|
|
345
345
|
**What `doctor` checks:** each `product` path resolves; each `affordances` path **that is present**
|
|
346
346
|
resolves; each `repos` entry names a card in the `repos` slot. Per-product `affordances` is optional
|
|
347
347
|
because a product may inherit the workspace-level default — so the check is conditional, and `doctor`
|
|
348
|
-
*reports* a product that has neither its own nor an inherited one rather than failing it.
|
|
349
|
-
draft of this line said `doctor` checks `affordances` unconditionally, which would have made the spec
|
|
350
|
-
demand something the schema declares optional; caught in review of the change that introduced it.) The
|
|
348
|
+
*reports* a product that has neither its own nor an inherited one rather than failing it. The
|
|
351
349
|
claim it cannot check is whether the mission written there is still true.
|
|
352
350
|
|
|
353
351
|
## `affordances` — what the product offers an agent
|
|
@@ -465,12 +463,9 @@ workspace that cannot say which packs it composes leaves an agent to infer the m
|
|
|
465
463
|
resolution order. The portfolio workspace at milestone 6 ships as a private-feed plugin composing a pack
|
|
466
464
|
delivered by that feed — a manifest that cannot express that forces a MAJOR bump one milestone after v1.
|
|
467
465
|
|
|
468
|
-
_(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
built, so it is corrected to what the tree shows rather than left as a promise about a pack nobody wrote.
|
|
472
|
-
Whether a premium pack should exist at all is a commercial question for the maintainer, not a gap this
|
|
473
|
-
file may quietly keep open.)_
|
|
466
|
+
_(No shipped pack is premium: the one the portfolio workspace composes is the universal checkpoint
|
|
467
|
+
ritual, delivered by the private feed and authored in the public engine. Whether a premium pack should
|
|
468
|
+
exist at all is a commercial question for the maintainer, not a gap this file may quietly keep open.)_
|
|
474
469
|
|
|
475
470
|
An array of names in cascade order, empty by default. Each name is the canonical `category/name` form
|
|
476
471
|
the Pack Definition owns ([`pack.schema.json`](pack.schema.json)) — but the items stay **free strings**
|
|
@@ -776,8 +771,32 @@ Each of these was a candidate; none is an oversight.
|
|
|
776
771
|
- **A per-host capability matrix.** The vision's `doctor` includes a per-host capability report, but that
|
|
777
772
|
is generated from the host and the compiled gates, not declared by the team. It belongs with the
|
|
778
773
|
enforcement backends in milestone 4.
|
|
779
|
-
- **`evals`.** Milestone 8 owns the eval harness
|
|
780
|
-
|
|
774
|
+
- **`evals`.** Milestone 8 owns the eval harness. **The original reason for deferring is spent, and the
|
|
775
|
+
deferral is re-argued rather than left standing on it.** It read *"a slot pointing at golden tasks
|
|
776
|
+
before any exist would be the emptiest kind of scaffolding"* — and golden tasks exist: the gate corpus
|
|
777
|
+
landed at clause (a) and the core-skill corpus at the row's original first subject. The premise
|
|
778
|
+
expired the day it stopped being true and nothing noticed, which is the shape this file elsewhere
|
|
779
|
+
calls a claim outliving its measurement.
|
|
780
|
+
|
|
781
|
+
**What the deferral now rests on.** A slot is the Workspace Definition's promise that every conforming
|
|
782
|
+
workspace can be asked one question and answer it. `evals/` today holds **two corpora with two
|
|
783
|
+
different oracles** — gates answered by the compiler's own `matchesRule`, skills answered by
|
|
784
|
+
mandate-to-artifact bindings — plus a metering register, and a slot naming that directory would
|
|
785
|
+
promise adopters a shape this repository has not settled for itself. **Revisit when milestone 8
|
|
786
|
+
closes**, by which point the row's nine clauses will have decided what an eval artifact is here.
|
|
787
|
+
Filling it is a schema change, a spec version bump and a migration, so it is not a thing to do in
|
|
788
|
+
passing.
|
|
789
|
+
|
|
790
|
+
**Milestone 8 closed 2026-09-09, so this deferral is now DUE and is not discharged here.** The
|
|
791
|
+
close names it rather than filling it, which is what the sentence above asks for: the drafting is
|
|
792
|
+
commissioned by proposal
|
|
793
|
+
[`../.portulan/proposals/0034-one-spec-bump-carries-both-evals-and-telemetry.md`](../.portulan/proposals/0034-one-spec-bump-carries-both-evals-and-telemetry.md)
|
|
794
|
+
— accepted 2026-08-28 — which carries `evals` **and** `telemetry` in **one** MINOR and settles none of
|
|
795
|
+
the shape: the key names, either slot's structure, and whether a migration is owed are the drafted
|
|
796
|
+
work. Doing that drafting inside the close would be the *"splitting on speculation"* this entry warns
|
|
797
|
+
against, in the change that closed the row it was waiting for. **What the close settled is the
|
|
798
|
+
precondition, not the slot:** `evals/` now holds two corpora, a metering register, an A/B capture and
|
|
799
|
+
a per-release record layer, which is the shape a slot would have to name.
|
|
781
800
|
|
|
782
801
|
## What v2.0 is not
|
|
783
802
|
|