@antelopejs/dms 0.16.1 → 0.17.0
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 +43 -0
- package/README.md +1 -1
- package/bin/control/base.mjs +3 -3
- package/bin/control/commands-verify.mjs +1 -29
- package/bin/control/renderer.test.mjs +0 -57
- package/bin/control/verify/decode.mjs +0 -2
- package/bin/control/verify/decode.test.mjs +1 -1
- package/bin/control/verify/doors.test.mjs +78 -33
- package/bin/control/verify/judgement.mjs +0 -1
- package/bin/control/verify/map.mjs +4 -51
- package/bin/control/verify/skill.mjs +4 -6
- package/bin/control/verify/types.mjs +3 -4
- package/bin/control/verify/verdict.mjs +8 -51
- package/bin/dms-control.mjs +0 -1
- package/dist/store/cache.d.ts +5 -1
- package/dist/store/cache.js +23 -14
- package/dist/store/cache.js.map +1 -1
- package/dist/store/frontend-compatibility.d.ts +3 -1
- package/dist/store/frontend-compatibility.js +6 -8
- package/dist/store/frontend-compatibility.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -14
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +6 -4
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +1 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/registry.d.ts +2 -1
- package/dist/store/registry.js +7 -26
- package/dist/store/registry.js.map +1 -1
- package/dist/store/requirements.js +3 -3
- package/dist/store/requirements.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/build/utils/storedAccounts.ts +62 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +12 -9
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +30 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/HeaderActionIcon.vue +75 -0
- package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/plugins/seo.ts +7 -0
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +10 -0
- package/frontend-vue/package.json +1 -1
- package/package.json +3 -3
- package/skills/dms-project/DEV-SERVERS.md +30 -4
- package/skills/dms-project/SKILL.md +8 -1
- package/skills/dms-project/scripts/dms-dev-usage.sh +5 -2
- package/skills/dms-project/scripts/dms-dev.sh +82 -69
- package/bin/control/verify/seeded.mjs +0 -217
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.17.0
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.16.2...v0.17.0)
|
|
6
|
+
|
|
7
|
+
### 🏡 Chore
|
|
8
|
+
|
|
9
|
+
- **dms-control:** Remove seeded runs and containment journeys from verification ([#774](https://github.com/AntelopeJS/dms/pull/774))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
|
|
14
|
+
|
|
15
|
+
## v0.16.2
|
|
16
|
+
|
|
17
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.16.1...v0.16.2)
|
|
18
|
+
|
|
19
|
+
### 🚀 Enhancements
|
|
20
|
+
|
|
21
|
+
- **header:** Let a header action draw a module component as its icon ([#772](https://github.com/AntelopeJS/dms/pull/772))
|
|
22
|
+
|
|
23
|
+
### 🔥 Performance
|
|
24
|
+
|
|
25
|
+
- **store:** Let the modules listing read stale registry answers ([#768](https://github.com/AntelopeJS/dms/pull/768))
|
|
26
|
+
|
|
27
|
+
### 🩹 Fixes
|
|
28
|
+
|
|
29
|
+
- **layout:** Keep the platform title in the document title after sign-in ([#767](https://github.com/AntelopeJS/dms/pull/767))
|
|
30
|
+
- **layout:** Keep a closing toast inside the collapsed stack ([#771](https://github.com/AntelopeJS/dms/pull/771))
|
|
31
|
+
- **auth:** List each person once in the account switcher ([#770](https://github.com/AntelopeJS/dms/pull/770))
|
|
32
|
+
- **dms-dev:** Run dev servers side by side across worktrees ([#769](https://github.com/AntelopeJS/dms/pull/769))
|
|
33
|
+
|
|
34
|
+
### 📦 Build
|
|
35
|
+
|
|
36
|
+
- **deps:** Move to @antelopejs/core 1.20 and drop the interface-core override ([#765](https://github.com/AntelopeJS/dms/pull/765))
|
|
37
|
+
|
|
38
|
+
### 🏡 Chore
|
|
39
|
+
|
|
40
|
+
- Remove the .amp directory ([#766](https://github.com/AntelopeJS/dms/pull/766))
|
|
41
|
+
|
|
42
|
+
### ❤️ Contributors
|
|
43
|
+
|
|
44
|
+
- Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
|
|
45
|
+
|
|
3
46
|
## v0.16.1
|
|
4
47
|
|
|
5
48
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.16.0...v0.16.1)
|
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ export class SalesPage extends PageController("sales", {
|
|
|
79
79
|
|
|
80
80
|
| Package | Supported | Tested with |
|
|
81
81
|
| ------------------------------------------- | ------------------ | ----------- |
|
|
82
|
-
| `@antelopejs/core` | `>=1.
|
|
82
|
+
| `@antelopejs/core` | `>=1.20.0 <2` | `1.20.0` |
|
|
83
83
|
| `@antelopejs/interface-api` | `>=0.2.0 <1.0.0` | `0.2.0` |
|
|
84
84
|
| `@antelopejs/interface-api-util` | `>=0.3.0 <1.0.0` | `0.3.0` |
|
|
85
85
|
| `@antelopejs/interface-core` | `>=0.4.0 <1.0.0` | `0.4.0` |
|
package/bin/control/base.mjs
CHANGED
|
@@ -43,9 +43,9 @@ function baseCheckout(state, skill) {
|
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Runs `action` while the backend is the base version's, on the run's
|
|
46
|
-
* current database, then starts the run's own backend again.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* current database, then starts the run's own backend again. An upgrade
|
|
47
|
+
* journey writes data with the base version inside `action` and reads it
|
|
48
|
+
* back with the head version after it.
|
|
49
49
|
*
|
|
50
50
|
* @param {import("./run-state.mjs").RunState} state The run
|
|
51
51
|
* @param {import("./verify/skill.mjs").Skill} skill The skill, whose `prepare` builds the base
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
readStatus,
|
|
17
17
|
setStatus,
|
|
18
18
|
} from "./verify/publish.mjs";
|
|
19
|
-
import { runSeededBreaks } from "./verify/seeded.mjs";
|
|
20
19
|
import { journeysFile, loadSkill } from "./verify/skill.mjs";
|
|
21
20
|
import { renderVerdict } from "./verify/verdict.mjs";
|
|
22
21
|
|
|
@@ -86,31 +85,6 @@ async function commandJourney(dir, feature, ids, values) {
|
|
|
86
85
|
return results.every((result) => result.status === "PASS") ? 0 : 1;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
/**
|
|
90
|
-
* @param {string[]} features The features whose seeded breaks run
|
|
91
|
-
* @param {Values} values The options of the command line
|
|
92
|
-
* @returns {Promise<number>} The exit code
|
|
93
|
-
*/
|
|
94
|
-
async function commandSeeded(features, values) {
|
|
95
|
-
const skill = await skillOf(values);
|
|
96
|
-
const outcomes = runSeededBreaks(skill, features);
|
|
97
|
-
for (const outcome of outcomes) {
|
|
98
|
-
print(
|
|
99
|
-
`${outcome.caught ? "CAUGHT" : "MISSED"} ${outcome.feature} by ${outcome.caughtBy.join(", ") || "no journey"} (evidence ${outcome.run}/evidence/${outcome.feature}, control ${outcome.controlRun}/evidence/${outcome.feature})`,
|
|
100
|
-
);
|
|
101
|
-
if (outcome.failingInControl.length > 0)
|
|
102
|
-
print(
|
|
103
|
-
` already failing without the patch, not counted: ${outcome.failingInControl.join(", ")}`,
|
|
104
|
-
);
|
|
105
|
-
if (outcome.harnessErrors.length > 0)
|
|
106
|
-
print(
|
|
107
|
-
` failed without a failing check (harness error), not counted: ${outcome.harnessErrors.join(", ")}`,
|
|
108
|
-
);
|
|
109
|
-
if (!outcome.caught) print(outcome.output.replace(/^/gm, " "));
|
|
110
|
-
}
|
|
111
|
-
return outcomes.every((outcome) => outcome.caught) ? 0 : 1;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
88
|
/**
|
|
115
89
|
* @param {string | undefined} patch A patch file, or none for the diff
|
|
116
90
|
* @param {Values} values The options of the command line
|
|
@@ -201,7 +175,7 @@ async function commandJudge(command, dir, values) {
|
|
|
201
175
|
|
|
202
176
|
/**
|
|
203
177
|
* Runs a command of the verification harness: `map`, `plan`, `journey`,
|
|
204
|
-
* `
|
|
178
|
+
* `doors`, `formal`, `verdict` and `publish`.
|
|
205
179
|
*
|
|
206
180
|
* @param {string} command The command
|
|
207
181
|
* @param {string[]} rest Its positional arguments
|
|
@@ -216,8 +190,6 @@ export function runVerifyCommand(command, rest, values) {
|
|
|
216
190
|
return commandPlan(values);
|
|
217
191
|
case "journey":
|
|
218
192
|
return commandJourney(rest[0], rest[1], rest.slice(2), values);
|
|
219
|
-
case "seeded":
|
|
220
|
-
return commandSeeded(rest, values);
|
|
221
193
|
case "doors":
|
|
222
194
|
return commandDoors(rest[0], values);
|
|
223
195
|
case "formal":
|
|
@@ -15,7 +15,6 @@ import { targetOf } from "./commands-stack.mjs";
|
|
|
15
15
|
import { git } from "./git.mjs";
|
|
16
16
|
import { resolveRenderer } from "./renderer.mjs";
|
|
17
17
|
import { decodeSkillConfig } from "./verify/decode.mjs";
|
|
18
|
-
import { upArguments } from "./verify/seeded.mjs";
|
|
19
18
|
import { cliPaths, layOutCopy } from "./stack.mjs";
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -266,59 +265,3 @@ await test("a run with a renderer has no base backend", async () => {
|
|
|
266
265
|
/no base backend/,
|
|
267
266
|
);
|
|
268
267
|
});
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* @param {Partial<import("./verify/skill.mjs").SkillConfig>} config What differs from a skill with a playground in its repository
|
|
272
|
-
* @returns {import("./verify/skill.mjs").Skill} The skill
|
|
273
|
-
*/
|
|
274
|
-
function skillWith(config) {
|
|
275
|
-
return {
|
|
276
|
-
dir: "/repo/.agents/skills/verify-x",
|
|
277
|
-
name: "verify-x",
|
|
278
|
-
repo: "/repo",
|
|
279
|
-
config: { ...decodeSkillConfig(CONFIG, "verify.config.ts"), ...config },
|
|
280
|
-
featuresDir: "",
|
|
281
|
-
journeysDir: "",
|
|
282
|
-
seededBreaksDir: "",
|
|
283
|
-
seededDoorsDir: "",
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
await test("seeded starts each run on the worktree's renderer and an external playground as it is", () => {
|
|
288
|
-
const prepared = {
|
|
289
|
-
worktree: "/work/seeded-auth-1",
|
|
290
|
-
skill: "/work/seeded-auth-1/skill",
|
|
291
|
-
};
|
|
292
|
-
assert.deepEqual(
|
|
293
|
-
upArguments(
|
|
294
|
-
skillWith({ playground: "/dms/playground", renderer: "." }),
|
|
295
|
-
prepared,
|
|
296
|
-
),
|
|
297
|
-
[
|
|
298
|
-
"up",
|
|
299
|
-
"--playground",
|
|
300
|
-
"/dms/playground",
|
|
301
|
-
"--renderer",
|
|
302
|
-
"/work/seeded-auth-1",
|
|
303
|
-
"--skill",
|
|
304
|
-
"/work/seeded-auth-1/skill",
|
|
305
|
-
],
|
|
306
|
-
);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
await test("seeded without a renderer starts the worktree's own playground", () => {
|
|
310
|
-
const prepared = {
|
|
311
|
-
worktree: "/work/seeded-auth-1",
|
|
312
|
-
skill: "/work/seeded-auth-1/skill",
|
|
313
|
-
};
|
|
314
|
-
assert.deepEqual(
|
|
315
|
-
upArguments(skillWith({ playground: "packages/dms/playground" }), prepared),
|
|
316
|
-
[
|
|
317
|
-
"up",
|
|
318
|
-
"--playground",
|
|
319
|
-
"/work/seeded-auth-1/packages/dms/playground",
|
|
320
|
-
"--skill",
|
|
321
|
-
"/work/seeded-auth-1/skill",
|
|
322
|
-
],
|
|
323
|
-
);
|
|
324
|
-
});
|
|
@@ -98,7 +98,6 @@ export const isRawJudgement = objectOf(
|
|
|
98
98
|
location: isString,
|
|
99
99
|
irreversible: isString,
|
|
100
100
|
containment: isString,
|
|
101
|
-
containmentJourney: isString,
|
|
102
101
|
},
|
|
103
102
|
),
|
|
104
103
|
),
|
|
@@ -141,7 +140,6 @@ const isFormalCheck = objectOf(
|
|
|
141
140
|
const isDoorRule = objectOf({
|
|
142
141
|
globs: strings,
|
|
143
142
|
funnels: strings,
|
|
144
|
-
containments: strings,
|
|
145
143
|
why: isString,
|
|
146
144
|
});
|
|
147
145
|
|
|
@@ -124,7 +124,7 @@ const CONFIG = `export default {
|
|
|
124
124
|
userFacing: ["src/**"],
|
|
125
125
|
smoke: "smoke",
|
|
126
126
|
smokeJourney: "onboard",
|
|
127
|
-
doors: { data: { globs: ["src/**"], funnels: [],
|
|
127
|
+
doors: { data: { globs: ["src/**"], funnels: [], why: "rows" }, format: null },
|
|
128
128
|
formal: [],
|
|
129
129
|
};
|
|
130
130
|
`;
|
|
@@ -1,40 +1,85 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
7
|
-
import { loadSkill } from "./skill.mjs";
|
|
6
|
+
import { doorLine, flaggedDoors, patchFiles } from "./doors.mjs";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
/** @type {import("./skill.mjs").Skill} */
|
|
9
|
+
const skill = {
|
|
10
|
+
dir: "/repo/.agents/skills/verify-x",
|
|
11
|
+
name: "verify-x",
|
|
12
|
+
repo: "/repo",
|
|
13
|
+
featuresDir: "",
|
|
14
|
+
journeysDir: "",
|
|
15
|
+
config: {
|
|
16
|
+
playground: "playground",
|
|
17
|
+
prepare: [],
|
|
18
|
+
userFacing: [],
|
|
19
|
+
notUserFacing: [],
|
|
20
|
+
smoke: "smoke",
|
|
21
|
+
smokeJourney: "",
|
|
22
|
+
formal: [],
|
|
23
|
+
doors: {
|
|
24
|
+
data: { globs: ["src/crons/**"], funnels: [], why: "deletes rows" },
|
|
25
|
+
outbound: {
|
|
26
|
+
globs: ["src/utils/*-mail.ts"],
|
|
27
|
+
funnels: ["src/emails.ts"],
|
|
28
|
+
why: "sends mail",
|
|
29
|
+
},
|
|
30
|
+
publish: null,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
await test("flaggedDoors names each kind a file hits, with its files, through globs and funnels", () => {
|
|
36
|
+
assert.deepEqual(
|
|
37
|
+
flaggedDoors(skill, [
|
|
38
|
+
"src/crons/purge.ts",
|
|
39
|
+
"src/emails.ts",
|
|
40
|
+
"src/utils/invite-mail.ts",
|
|
41
|
+
"src/pages/home.ts",
|
|
42
|
+
]),
|
|
43
|
+
[
|
|
44
|
+
{ kind: "data", files: ["src/crons/purge.ts"] },
|
|
45
|
+
{
|
|
46
|
+
kind: "outbound",
|
|
47
|
+
files: ["src/emails.ts", "src/utils/invite-mail.ts"],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
);
|
|
51
|
+
});
|
|
19
52
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}),
|
|
27
|
-
`${kind}.patch no longer applies; refresh it against the current source`,
|
|
28
|
-
);
|
|
29
|
-
});
|
|
53
|
+
await test("flaggedDoors flags nothing for files outside every rule, a null rule included", () => {
|
|
54
|
+
assert.deepEqual(
|
|
55
|
+
flaggedDoors(skill, ["package.json", "src/pages/home.ts"]),
|
|
56
|
+
[],
|
|
57
|
+
);
|
|
58
|
+
});
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
await test("patchFiles reads the new paths of a unified diff, not the old ones", (t) => {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "dms-control-doors-test-"));
|
|
62
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
63
|
+
const patch = join(dir, "change.patch");
|
|
64
|
+
writeFileSync(
|
|
65
|
+
patch,
|
|
66
|
+
[
|
|
67
|
+
"diff --git a/src/old.ts b/src/crons/purge.ts",
|
|
68
|
+
"--- a/src/old.ts",
|
|
69
|
+
"+++ b/src/crons/purge.ts",
|
|
70
|
+
"@@ -1 +1 @@",
|
|
71
|
+
"-a",
|
|
72
|
+
"+b",
|
|
73
|
+
"",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
);
|
|
76
|
+
assert.deepEqual(patchFiles(patch), ["src/crons/purge.ts"]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await test("doorLine lists the flagged kinds, and is empty when none is", () => {
|
|
80
|
+
assert.equal(
|
|
81
|
+
doorLine([{ kind: "data" }, { kind: "outbound" }]),
|
|
82
|
+
"One-way-door: data, outbound",
|
|
83
|
+
);
|
|
84
|
+
assert.equal(doorLine([]), "");
|
|
85
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join, matchesGlob } from "node:path";
|
|
3
3
|
import { git } from "../git.mjs";
|
|
4
|
-
import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
5
4
|
import { loadJourneys } from "../journey.mjs";
|
|
6
5
|
import { journeysFile, readFeatures } from "./skill.mjs";
|
|
7
6
|
|
|
@@ -82,20 +81,6 @@ async function featureProblems(skill, feature, tracked) {
|
|
|
82
81
|
problems.push(`${feature.file}: docs path ${doc} does not exist`);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
|
-
const patch = join(skill.seededBreaksDir, `${feature.name}.patch`);
|
|
86
|
-
if (!existsSync(patch)) {
|
|
87
|
-
problems.push(
|
|
88
|
-
`${feature.file}: seeded-breaks/${feature.name}.patch is missing`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
const always = feature.name === skill.config.smoke;
|
|
92
|
-
for (const file of existsSync(patch) && !always ? patchFiles(patch) : []) {
|
|
93
|
-
if (!matches(file, feature.covers)) {
|
|
94
|
-
problems.push(
|
|
95
|
-
`${feature.file}: its seeded break edits ${file}, which its covers globs miss, so plan would not run it`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
84
|
return problems;
|
|
100
85
|
}
|
|
101
86
|
|
|
@@ -108,11 +93,6 @@ function doorProblems(skill, tracked) {
|
|
|
108
93
|
const problems = [];
|
|
109
94
|
for (const [kind, rule] of Object.entries(skill.config.doors)) {
|
|
110
95
|
if (!rule) continue;
|
|
111
|
-
if (!existsSync(join(skill.seededDoorsDir, `${kind}.patch`))) {
|
|
112
|
-
problems.push(
|
|
113
|
-
`seeded-doors/${kind}.patch is missing for the ${kind} door rule`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
96
|
for (const glob of [...rule.globs, ...rule.funnels]) {
|
|
117
97
|
if (!tracked.some((path) => matchesGlob(path, glob))) {
|
|
118
98
|
problems.push(
|
|
@@ -126,11 +106,9 @@ function doorProblems(skill, tracked) {
|
|
|
126
106
|
|
|
127
107
|
/**
|
|
128
108
|
* Checks the map against the repository: every user-facing file is
|
|
129
|
-
* covered, every feature has journeys, each described in its file,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* pages, every feature and door glob
|
|
133
|
-
* matches a tracked file, and every door rule has a seeded door.
|
|
109
|
+
* covered, every feature has journeys, each described in its file,
|
|
110
|
+
* existing docs pages, and every feature and door glob matches a tracked
|
|
111
|
+
* file.
|
|
134
112
|
*
|
|
135
113
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
136
114
|
* @returns {Promise<string[]>} One line per problem
|
|
@@ -159,33 +137,10 @@ export async function checkMap(skill) {
|
|
|
159
137
|
return [...problems, ...doorProblems(skill, tracked)];
|
|
160
138
|
}
|
|
161
139
|
|
|
162
|
-
const CONTAINED_KINDS = ["data", "format"];
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* The containment journeys, as `feature/journey`, of the Data and Format
|
|
166
|
-
* doors a set of changed files opens: a door row must name one of them,
|
|
167
|
-
* passing in the run.
|
|
168
|
-
*
|
|
169
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
170
|
-
* @param {string[]} changed Repository paths
|
|
171
|
-
* @returns {string[]} The journeys, each once
|
|
172
|
-
*/
|
|
173
|
-
function containmentJourneys(skill, changed) {
|
|
174
|
-
const kinds = flaggedDoors(skill, changed)
|
|
175
|
-
.map((door) => door.kind)
|
|
176
|
-
.filter((kind) => CONTAINED_KINDS.includes(kind));
|
|
177
|
-
return [
|
|
178
|
-
...new Set(
|
|
179
|
-
kinds.flatMap((kind) => skill.config.doors[kind]?.containments ?? []),
|
|
180
|
-
),
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
140
|
/**
|
|
185
141
|
* Maps the diff from the merge base of `baseRef` to HEAD onto features:
|
|
186
142
|
* every touched feature plus the smoke feature runs, a user-facing file
|
|
187
|
-
* outside every glob is an unmapped surface
|
|
188
|
-
* journeys that can contain each Data or Format door the diff opens.
|
|
143
|
+
* outside every glob is an unmapped surface.
|
|
189
144
|
*
|
|
190
145
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
191
146
|
* @param {string} baseRef The base, such as `origin/main`
|
|
@@ -216,7 +171,6 @@ export function planFor(skill, baseRef) {
|
|
|
216
171
|
),
|
|
217
172
|
];
|
|
218
173
|
const smoke = skill.config.smoke;
|
|
219
|
-
const containments = containmentJourneys(skill, changed);
|
|
220
174
|
return {
|
|
221
175
|
baseRef,
|
|
222
176
|
baseSha,
|
|
@@ -225,7 +179,6 @@ export function planFor(skill, baseRef) {
|
|
|
225
179
|
touched,
|
|
226
180
|
unmapped,
|
|
227
181
|
docs,
|
|
228
|
-
containments,
|
|
229
182
|
run: [smoke, ...[...touchedNames].filter((name) => name !== smoke)],
|
|
230
183
|
};
|
|
231
184
|
}
|
|
@@ -10,8 +10,8 @@ const CONFIG_FILE = "verify.config.ts";
|
|
|
10
10
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @typedef {{ dir: string, name: string, repo: string, config: SkillConfig, featuresDir: string, journeysDir: string
|
|
14
|
-
* @typedef {{ playground: string, renderer?: string, prepare: string[][], userFacing: string[], notUserFacing: string[], smoke: string, smokeJourney: string, doors: Record<string, { globs: string[], funnels: string[],
|
|
13
|
+
* @typedef {{ dir: string, name: string, repo: string, config: SkillConfig, featuresDir: string, journeysDir: string }} Skill
|
|
14
|
+
* @typedef {{ playground: string, renderer?: string, prepare: string[][], userFacing: string[], notUserFacing: string[], smoke: string, smokeJourney: string, doors: Record<string, { globs: string[], funnels: string[], why: string } | null>, formal: import("./types.mjs").FormalCheck[] }} SkillConfig
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -60,8 +60,8 @@ function discover(cwd) {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Loads a verification skill: its `verify.config.ts` (imported with Node's
|
|
63
|
-
* TypeScript type stripping), its feature files and where its journeys
|
|
64
|
-
*
|
|
63
|
+
* TypeScript type stripping), its feature files and where its journeys
|
|
64
|
+
* live. Without `--skill`, the one
|
|
65
65
|
* `.agents/skills/verify-*` folder of the checkout holding `cwd`.
|
|
66
66
|
*
|
|
67
67
|
* @param {string | undefined} option The `--skill` folder
|
|
@@ -86,8 +86,6 @@ export async function loadSkill(option, cwd) {
|
|
|
86
86
|
config,
|
|
87
87
|
featuresDir: join(dir, "features"),
|
|
88
88
|
journeysDir: join(dir, "journeys"),
|
|
89
|
-
seededBreaksDir: join(dir, "seeded-breaks"),
|
|
90
|
-
seededDoorsDir: join(dir, "seeded-doors"),
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* @typedef {{ exitCode: number | null, violated: string[], passing: string[] }} Rules
|
|
11
11
|
* @typedef {{ name: string, exitCode: number | null, violated: string[], passing: string[], folder: string, commits?: Commits, behaviour?: BehaviourChange, checkChanged?: boolean, base?: Rules | null, fixed?: string[], missing?: string[] }} FormalResult
|
|
12
12
|
* @typedef {{ feature: string, files: string[] }} TouchedFeature
|
|
13
|
-
* @typedef {{ baseRef: string, baseSha: string, headSha: string, changed: string[], touched: TouchedFeature[], unmapped: string[], docs: string[],
|
|
14
|
-
* @typedef {{ kind?: string, location?: string, irreversible?: string, containment?: string
|
|
13
|
+
* @typedef {{ baseRef: string, baseSha: string, headSha: string, changed: string[], touched: TouchedFeature[], unmapped: string[], docs: string[], run: string[] }} Plan
|
|
14
|
+
* @typedef {{ kind?: string, location?: string, irreversible?: string, containment?: string }} RawDoorRow
|
|
15
15
|
* @typedef {{ kind?: string, location?: string, reason?: string }} RawFalsePositive
|
|
16
16
|
* @typedef {{ page: string, matches?: boolean, note?: string }} RawDocsJudgement
|
|
17
17
|
* @typedef {{ verifier?: string, notVerified?: string[], attention?: string[], docs?: RawDocsJudgement[], doors?: { none?: string, rows?: RawDoorRow[], falsePositives?: RawFalsePositive[] } }} RawJudgement
|
|
18
|
-
* @typedef {{ kind: string, location: string, irreversible: string, containment: string
|
|
18
|
+
* @typedef {{ kind: string, location: string, irreversible: string, containment: string }} DoorRow
|
|
19
19
|
* @typedef {{ kind: string, location: string, reason: string }} FalsePositive
|
|
20
20
|
* @typedef {{ page: string, matches: boolean, note: string }} DocsJudgement
|
|
21
21
|
* @typedef {{ notVerified: string[], attention: string[], docs: DocsJudgement[], doorsNone: string | null, doors: DoorRow[], falsePositives: FalsePositive[], verifier: string }} Judgement
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* @typedef {{ feature: string, journey: string, title: string, entryPoint: string, status: "PASS" | "FAIL", database: string, steps: { name: string, checks: JourneyCheck[] }[], error: string | null, folder: string }} FeatureResult
|
|
24
24
|
* @typedef {{ result: string, headSha: string, skillSha: string, pr: number, reasons: string[] }} VerdictCore
|
|
25
25
|
* @typedef {VerdictCore & { markdown: string }} Verdict
|
|
26
|
-
* @typedef {{ feature: string, caught: boolean, run: string, controlRun: string, caughtBy: string[], failingInControl: string[], harnessErrors: string[], output: string }} SeededOutcome
|
|
27
26
|
*/
|
|
28
27
|
|
|
29
28
|
export {};
|
|
@@ -12,8 +12,6 @@ import {
|
|
|
12
12
|
import { planFor } from "./map.mjs";
|
|
13
13
|
import { journeysFile } from "./skill.mjs";
|
|
14
14
|
|
|
15
|
-
const CONTAINED_KINDS = new Set(["data", "format"]);
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* The GitHub `owner/repo` of the checkout's origin remote.
|
|
19
17
|
*
|
|
@@ -58,15 +56,13 @@ function readResults(state, feature) {
|
|
|
58
56
|
* @param {import("../run-state.mjs").RunState} state The run
|
|
59
57
|
* @param {import("./types.mjs").Plan} plan The plan
|
|
60
58
|
* @param {(path: string) => string} link The evidence link of a path
|
|
61
|
-
* @returns {Promise<{ rows: string[], reasons: string[]
|
|
59
|
+
* @returns {Promise<{ rows: string[], reasons: string[] }>} The table rows and why the verdict fails
|
|
62
60
|
*/
|
|
63
61
|
async function journeyRows(skill, state, plan, link) {
|
|
64
62
|
/** @type {string[]} */
|
|
65
63
|
const rows = [];
|
|
66
64
|
/** @type {string[]} */
|
|
67
65
|
const reasons = [];
|
|
68
|
-
/** @type {Map<string, import("./types.mjs").FeatureResult>} */
|
|
69
|
-
const byJourney = new Map();
|
|
70
66
|
for (const feature of plan.run) {
|
|
71
67
|
const results = readResults(state, feature);
|
|
72
68
|
const file = journeysFile(skill, feature);
|
|
@@ -85,56 +81,24 @@ async function journeyRows(skill, state, plan, link) {
|
|
|
85
81
|
);
|
|
86
82
|
if (result.status === "FAIL")
|
|
87
83
|
reasons.push(`${feature}/${result.journey} failed`);
|
|
88
|
-
byJourney.set(`${feature}/${result.journey}`, result);
|
|
89
84
|
const outcome = result.status === "PASS" ? "pass" : "**fail**";
|
|
90
85
|
rows.push(
|
|
91
86
|
`| ${feature} | \`${result.journey}\`: ${result.entryPoint} | ${outcome}: ${result.title} | [${result.folder}](${link(result.folder)}) |`,
|
|
92
87
|
);
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
|
-
return { rows, reasons
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @param {import("../run-state.mjs").RunState} state The run
|
|
100
|
-
* @param {Map<string, import("./types.mjs").FeatureResult>} byJourney The results of this verdict's journeys
|
|
101
|
-
* @param {string} id The containment journey, as `feature/journey`
|
|
102
|
-
* @returns {import("./types.mjs").FeatureResult | undefined} Its result
|
|
103
|
-
*/
|
|
104
|
-
function containmentResult(state, byJourney, id) {
|
|
105
|
-
const [feature, journey] = id.split("/");
|
|
106
|
-
return (
|
|
107
|
-
byJourney.get(id) ??
|
|
108
|
-
readResults(state, feature).find((result) => result.journey === journey)
|
|
109
|
-
);
|
|
90
|
+
return { rows, reasons };
|
|
110
91
|
}
|
|
111
92
|
|
|
112
93
|
/**
|
|
113
94
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
114
|
-
* @param {import("../run-state.mjs").RunState} state The run
|
|
115
95
|
* @param {import("./types.mjs").Judgement} judgement The verifier's judgement
|
|
116
|
-
* @param {Map<string, import("./types.mjs").FeatureResult>} byJourney The results of this verdict's journeys
|
|
117
96
|
* @param {import("./types.mjs").Plan} plan The plan
|
|
118
97
|
* @returns {string[]} Why the doors fail the verdict
|
|
119
98
|
*/
|
|
120
|
-
function doorReasons(skill,
|
|
99
|
+
function doorReasons(skill, judgement, plan) {
|
|
121
100
|
/** @type {string[]} */
|
|
122
101
|
const reasons = [];
|
|
123
|
-
for (const row of judgement.doors.filter((door) =>
|
|
124
|
-
CONTAINED_KINDS.has(door.kind),
|
|
125
|
-
)) {
|
|
126
|
-
const allowed = skill.config.doors[row.kind]?.containments ?? [];
|
|
127
|
-
const journey = containmentResult(state, byJourney, row.containmentJourney);
|
|
128
|
-
if (!allowed.includes(row.containmentJourney)) {
|
|
129
|
-
reasons.push(
|
|
130
|
-
`${row.kind} door at ${row.location} names ${row.containmentJourney || "no journey"}; its containment must be one of ${allowed.join(", ")}`,
|
|
131
|
-
);
|
|
132
|
-
} else if (journey?.status !== "PASS") {
|
|
133
|
-
reasons.push(
|
|
134
|
-
`${row.kind} door at ${row.location}: containment journey ${row.containmentJourney} did not pass in this run`,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
102
|
for (const door of flaggedDoors(skill, plan.changed)) {
|
|
139
103
|
for (const file of door.files) {
|
|
140
104
|
const listed = judgement.doors.some(
|
|
@@ -168,12 +132,10 @@ function doorsSection(judgement) {
|
|
|
168
132
|
...dismissed,
|
|
169
133
|
"| Kind | Location | What cannot be undone | Containment |",
|
|
170
134
|
"|---|---|---|---|",
|
|
171
|
-
...judgement.doors.map(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return `| ${door.kind} | \`${door.location}\` | ${door.irreversible} | ${door.containment}${journey} |`;
|
|
176
|
-
}),
|
|
135
|
+
...judgement.doors.map(
|
|
136
|
+
(door) =>
|
|
137
|
+
`| ${door.kind} | \`${door.location}\` | ${door.irreversible} | ${door.containment} |`,
|
|
138
|
+
),
|
|
177
139
|
];
|
|
178
140
|
}
|
|
179
141
|
|
|
@@ -266,11 +228,6 @@ function markdown(skill, verdict, parts) {
|
|
|
266
228
|
"### Touched features",
|
|
267
229
|
...bullets(touched, "none"),
|
|
268
230
|
`- ${skill.config.smoke}: ${skill.config.smokeJourney}`,
|
|
269
|
-
...(plan.containments.length > 0
|
|
270
|
-
? [
|
|
271
|
-
`- containment journeys of the Data and Format doors: ${plan.containments.map((feature) => `\`${feature}\``).join(", ")}`,
|
|
272
|
-
]
|
|
273
|
-
: []),
|
|
274
231
|
"",
|
|
275
232
|
...(plan.unmapped.length > 0
|
|
276
233
|
? [
|
|
@@ -344,7 +301,7 @@ export async function renderVerdict(skill, state, pr, judgement) {
|
|
|
344
301
|
...journeys.reasons,
|
|
345
302
|
...plan.unmapped.map((file) => `unmapped surface ${file}`),
|
|
346
303
|
...docsReasons(judgement, plan),
|
|
347
|
-
...doorReasons(skill,
|
|
304
|
+
...doorReasons(skill, judgement, plan),
|
|
348
305
|
...formal.reasons,
|
|
349
306
|
);
|
|
350
307
|
const attention = judgement.attention;
|
package/bin/dms-control.mjs
CHANGED
|
@@ -28,7 +28,6 @@ Verify a change (the .agents/skills/verify-* folder of the checkout, or --skill
|
|
|
28
28
|
map check the feature map covers every user-facing file
|
|
29
29
|
plan [--base <ref>] map the diff to features; list unmapped surfaces
|
|
30
30
|
journey <run> <feature> [ids...] run a feature's journeys and record evidence
|
|
31
|
-
seeded <feature...> run each feature unpatched and with its seeded break
|
|
32
31
|
doors [patch] [--base <ref>] print the One-way-door line for a patch, or for the diff
|
|
33
32
|
formal <run> [--all] run the formal checks that cover the diff (or every one)
|
|
34
33
|
verdict <run> --pr <n> render evidence/verdict.md from the results and evidence/judgement.json
|
package/dist/store/cache.d.ts
CHANGED
|
@@ -2,7 +2,11 @@ export interface CacheLifetimes {
|
|
|
2
2
|
found: number;
|
|
3
3
|
failed: number;
|
|
4
4
|
}
|
|
5
|
+
export type Freshness = "cached" | "fresh" | "stale";
|
|
6
|
+
export interface LookupOptions {
|
|
7
|
+
freshness?: Freshness;
|
|
8
|
+
}
|
|
5
9
|
export interface LookupCache<T> {
|
|
6
|
-
get(key: string, lookup: () => Promise<T | undefined
|
|
10
|
+
get(key: string, lookup: () => Promise<T | undefined>, options?: LookupOptions): Promise<T | undefined>;
|
|
7
11
|
}
|
|
8
12
|
export declare function createLookupCache<T>(lifetimes: CacheLifetimes): LookupCache<T>;
|