@aperant/framework 0.11.0 → 0.12.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 +213 -30
- package/agents/apt-planner.md +15 -0
- package/agents/apt-verifier.md +15 -10
- package/bin/apt-proof-video.mjs +256 -76
- package/dist/cli/commands/apply-recipe.d.mts +6 -0
- package/dist/cli/commands/apply-recipe.d.mts.map +1 -0
- package/dist/cli/commands/apply-recipe.mjs +98 -0
- package/dist/cli/commands/apply-recipe.mjs.map +1 -0
- package/dist/cli/commands/mobile-prepare.d.mts +53 -0
- package/dist/cli/commands/mobile-prepare.d.mts.map +1 -0
- package/dist/cli/commands/mobile-prepare.mjs +139 -0
- package/dist/cli/commands/mobile-prepare.mjs.map +1 -0
- package/dist/cli/commands/recipe-diff.d.mts +6 -0
- package/dist/cli/commands/recipe-diff.d.mts.map +1 -0
- package/dist/cli/commands/recipe-diff.mjs +75 -0
- package/dist/cli/commands/recipe-diff.mjs.map +1 -0
- package/dist/cli/commands/roadmap.d.mts.map +1 -1
- package/dist/cli/commands/roadmap.mjs +53 -0
- package/dist/cli/commands/roadmap.mjs.map +1 -1
- package/dist/cli/commands/validate-evidence.d.mts +1 -1
- package/dist/cli/commands/verify-sandbox.d.mts +6 -0
- package/dist/cli/commands/verify-sandbox.d.mts.map +1 -0
- package/dist/cli/commands/verify-sandbox.mjs +200 -0
- package/dist/cli/commands/verify-sandbox.mjs.map +1 -0
- package/dist/cli/consistency/parse-qa.d.mts.map +1 -1
- package/dist/cli/consistency/parse-qa.mjs +18 -4
- package/dist/cli/consistency/parse-qa.mjs.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +11 -10
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.d.mts +7 -2
- package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.mjs +50 -17
- package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -1
- package/dist/cli/design/frontmatter-schema.d.mts +8 -8
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +28 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +0 -48
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +23 -80
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/install/mcp-server-specs.d.mts +6 -5
- package/dist/cli/install/mcp-server-specs.d.mts.map +1 -1
- package/dist/cli/install/mcp-server-specs.mjs +22 -4
- package/dist/cli/install/mcp-server-specs.mjs.map +1 -1
- package/dist/cli/personas/sidecar.d.mts +1 -1
- package/dist/cli/roadmap/backend-adapter.d.mts +43 -0
- package/dist/cli/roadmap/backend-adapter.d.mts.map +1 -0
- package/dist/cli/roadmap/backend-adapter.mjs +222 -0
- package/dist/cli/roadmap/backend-adapter.mjs.map +1 -0
- package/dist/cli/roadmap/lifecycle.d.mts +1 -1
- package/dist/cli/roadmap/rollup.d.mts +2 -2
- package/dist/cli/util/runtime-capabilities.d.mts +7 -0
- package/dist/cli/util/runtime-capabilities.d.mts.map +1 -1
- package/dist/cli/util/runtime-capabilities.mjs +65 -0
- package/dist/cli/util/runtime-capabilities.mjs.map +1 -1
- package/dist/cli/verify-proof/manifest-validator.mjs +13 -0
- package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -1
- package/dist/cli/verify-proof/resolver.d.mts +10 -7
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
- package/dist/cli/verify-proof/resolver.mjs +28 -7
- package/dist/cli/verify-proof/resolver.mjs.map +1 -1
- package/dist/cli/verify-proof/sandbox/apply.d.mts +64 -0
- package/dist/cli/verify-proof/sandbox/apply.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs +379 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts +63 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs +115 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts +57 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs +124 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts +28 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs +190 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts +25 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs +85 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts +39 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs +248 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/apt-planner.md +15 -0
- package/dist/plugin/agents/apt-verifier.md +15 -10
- package/dist/plugin/skills/apt-plan/SKILL.md +23 -0
- package/dist/plugin/skills/apt-run/SKILL.md +1 -1
- package/dist/plugin/skills/apt-setup/SKILL.md +56 -0
- package/dist/plugin/skills/apt-verify/SKILL.md +20 -15
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +146 -11
- package/dist/proof-report.d.ts.map +1 -1
- package/dist/proof-report.js +4 -1
- package/dist/proof-report.js.map +1 -1
- package/dist/types/config.d.ts +42 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/qa-scoring.d.ts +45 -9
- package/dist/types/qa-scoring.d.ts.map +1 -1
- package/dist/types/qa-scoring.js +34 -13
- package/dist/types/qa-scoring.js.map +1 -1
- package/dist/types/state.d.ts +7 -2
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/task-record.d.ts +10 -2
- package/dist/types/task-record.d.ts.map +1 -1
- package/drivers/mobile/README.md +40 -0
- package/drivers/mobile/driver.mjs +106 -0
- package/drivers/mobile/manifest.json +49 -0
- package/package.json +138 -138
- package/prompts/inbox-clarification.md +11 -0
- package/prompts/inbox-triage.md +20 -0
- package/prompts/qa_orchestrator_agentic.md +18 -17
- package/prompts/qa_reviewer.md +17 -15
- package/skills/apt-plan/SKILL.md +23 -0
- package/skills/apt-run/SKILL.md +1 -1
- package/skills/apt-setup/SKILL.md +56 -0
- package/skills/apt-verifier.md +7 -4
- package/skills/apt-verify/SKILL.md +20 -15
- package/skills/apt-verify-proof/SKILL.md +146 -11
- package/src/cli/commands/apply-recipe.mjs +105 -0
- package/src/cli/commands/mobile-prepare.mjs +151 -0
- package/src/cli/commands/recipe-diff.mjs +81 -0
- package/src/cli/commands/roadmap.mjs +54 -0
- package/src/cli/commands/verify-sandbox.mjs +231 -0
- package/src/cli/consistency/parse-qa.mjs +20 -4
- package/src/cli/consistency/rules/r5-verdict-consistency.mjs +11 -13
- package/src/cli/coverage-check/user-outcomes.mjs +52 -17
- package/src/cli/dispatch.mjs +27 -0
- package/src/cli/gate/gates/verify-approved.mjs +22 -81
- package/src/cli/install/mcp-server-specs.mjs +24 -4
- package/src/cli/roadmap/backend-adapter.mjs +231 -0
- package/src/cli/util/runtime-capabilities.mjs +67 -0
- package/src/cli/verify-proof/manifest-validator.mjs +15 -0
- package/src/cli/verify-proof/resolver.mjs +27 -7
- package/src/cli/verify-proof/sandbox/apply.mjs +401 -0
- package/src/cli/verify-proof/sandbox/gate-predicate.mjs +126 -0
- package/src/cli/verify-proof/sandbox/pattern-matcher.mjs +127 -0
- package/src/cli/verify-proof/sandbox/recipe-diff.mjs +208 -0
- package/src/cli/verify-proof/sandbox/sandbox-config.mjs +82 -0
- package/src/cli/verify-proof/sandbox/sandbox-schema.json +33 -0
- package/src/cli/verify-proof/sandbox/self-test.mjs +265 -0
- package/templates/config.json +10 -1
- package/templates/proof-verification.md +27 -8
- package/workflows/verify-proof.md +376 -302
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-config.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/sandbox-config.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB;IAChC,+DAA+D;IAC/D,yCAAyC;IACzC,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB;IACrC,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAA;IAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAQ;QAClC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,CAAA;QACvD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAQ;QACrC,IAAI,CAAC;YACJ,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;QACxD,CAAC;QAAC,MAAM,CAAC;YACR,4BAA4B;QAC7B,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAU;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,aAAa,CAAC,CAAA;IACjE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;QACtD,MAAM,CAAC,GAAG,MAAM,EAAE,YAAY,CAAA;QAC9B,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QACpD,OAAO,CAAC,CAAC,OAAO,IAAI,IAAI,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} SelfTestVerdict
|
|
3
|
+
* @property {boolean} verified
|
|
4
|
+
* @property {'approved'|'needs_human'} verdict
|
|
5
|
+
* @property {null|'self_test_failed_sqlite_busy'|'self_test_failed_port_conflict'|'self_test_failed_app_init_timeout'} reason
|
|
6
|
+
* @property {number} durationMs
|
|
7
|
+
* @property {{A: ChildOutcome, B: ChildOutcome}} children
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} ChildOutcome
|
|
11
|
+
* @property {boolean} ready
|
|
12
|
+
* @property {string|null} failureReason
|
|
13
|
+
* @property {string} stderrTail
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @param {{projectDir: string, recipeId: string, recipes: object[]}} args
|
|
17
|
+
* @returns {Promise<SelfTestVerdict>}
|
|
18
|
+
*/
|
|
19
|
+
export function runSelfTest({ projectDir, recipeId, recipes }: {
|
|
20
|
+
projectDir: string;
|
|
21
|
+
recipeId: string;
|
|
22
|
+
recipes: object[];
|
|
23
|
+
}): Promise<SelfTestVerdict>;
|
|
24
|
+
export type SelfTestVerdict = {
|
|
25
|
+
verified: boolean;
|
|
26
|
+
verdict: "approved" | "needs_human";
|
|
27
|
+
reason: null | "self_test_failed_sqlite_busy" | "self_test_failed_port_conflict" | "self_test_failed_app_init_timeout";
|
|
28
|
+
durationMs: number;
|
|
29
|
+
children: {
|
|
30
|
+
A: ChildOutcome;
|
|
31
|
+
B: ChildOutcome;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export type ChildOutcome = {
|
|
35
|
+
ready: boolean;
|
|
36
|
+
failureReason: string | null;
|
|
37
|
+
stderrTail: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=self-test.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-test.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/self-test.mjs"],"names":[],"mappings":"AAgCA;;;;;;;GAOG;AAEH;;;;;GAKG;AAEH;;;GAGG;AACH,+DAHW;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC,GACvD,OAAO,CAAC,eAAe,CAAC,CA4CpC;;cA5Da,OAAO;aACP,UAAU,GAAC,aAAa;YACxB,IAAI,GAAC,8BAA8B,GAAC,gCAAgC,GAAC,mCAAmC;gBACxG,MAAM;cACN;QAAC,CAAC,EAAE,YAAY,CAAC;QAAC,CAAC,EAAE,YAAY,CAAA;KAAC;;;WAKlC,OAAO;mBACP,MAAM,GAAC,IAAI;gBACX,MAAM"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* self-test.mjs — two-instance concurrent self-test for parallel
|
|
3
|
+
* /apt:verify-proof.
|
|
4
|
+
*
|
|
5
|
+
* Spawns TWO instances of the recipe's launch command concurrently
|
|
6
|
+
* (Promise.all with B jittered 50-150 ms) against per-instance sandbox
|
|
7
|
+
* dirs. Asserts BOTH instances reach the recipe's `readiness.logPattern`
|
|
8
|
+
* within `readiness.timeoutMs` AND neither emits `SQLITE_BUSY` or
|
|
9
|
+
* `EADDRINUSE`/`bind` errors in stderr.
|
|
10
|
+
*
|
|
11
|
+
* The verdict envelope shape mirrors verify-proof v2's
|
|
12
|
+
* `verification.json :: meta.verdict`:
|
|
13
|
+
* {verified: bool, verdict: 'approved'|'needs_human',
|
|
14
|
+
* reason: null|'self_test_failed_sqlite_busy'|
|
|
15
|
+
* 'self_test_failed_port_conflict'|
|
|
16
|
+
* 'self_test_failed_app_init_timeout',
|
|
17
|
+
* durationMs: number}
|
|
18
|
+
*
|
|
19
|
+
* Cross-platform via `node:child_process.spawn` (NO shell). win32 short-
|
|
20
|
+
* circuits at the verify-sandbox command layer (subtask 4.2) — this
|
|
21
|
+
* module assumes a POSIX-ish host.
|
|
22
|
+
*
|
|
23
|
+
* No state-file writing here (4.2's job). Pure functional contract so
|
|
24
|
+
* the integration tests can drive it via `vi.spyOn(child_process,
|
|
25
|
+
* 'spawn')`.
|
|
26
|
+
*/
|
|
27
|
+
import child_process from 'node:child_process';
|
|
28
|
+
import { mkdirSync } from 'node:fs';
|
|
29
|
+
import { tmpdir } from 'node:os';
|
|
30
|
+
import { join, resolve } from 'node:path';
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} SelfTestVerdict
|
|
33
|
+
* @property {boolean} verified
|
|
34
|
+
* @property {'approved'|'needs_human'} verdict
|
|
35
|
+
* @property {null|'self_test_failed_sqlite_busy'|'self_test_failed_port_conflict'|'self_test_failed_app_init_timeout'} reason
|
|
36
|
+
* @property {number} durationMs
|
|
37
|
+
* @property {{A: ChildOutcome, B: ChildOutcome}} children
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} ChildOutcome
|
|
41
|
+
* @property {boolean} ready
|
|
42
|
+
* @property {string|null} failureReason
|
|
43
|
+
* @property {string} stderrTail
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* @param {{projectDir: string, recipeId: string, recipes: object[]}} args
|
|
47
|
+
* @returns {Promise<SelfTestVerdict>}
|
|
48
|
+
*/
|
|
49
|
+
export async function runSelfTest({ projectDir, recipeId, recipes }) {
|
|
50
|
+
const recipe = (recipes ?? []).find((r) => r && r.recipeId === recipeId);
|
|
51
|
+
if (!recipe) {
|
|
52
|
+
throw new Error(`runSelfTest: recipe not found: ${recipeId}`);
|
|
53
|
+
}
|
|
54
|
+
const launch = recipe.launch;
|
|
55
|
+
if (!launch?.binary) {
|
|
56
|
+
throw new Error(`runSelfTest: recipe ${recipeId} missing launch.{binary, args}`);
|
|
57
|
+
}
|
|
58
|
+
const readiness = recipe.readiness;
|
|
59
|
+
if (!readiness || typeof readiness.logPattern !== 'string') {
|
|
60
|
+
throw new Error(`runSelfTest: recipe ${recipeId} missing readiness.logPattern`);
|
|
61
|
+
}
|
|
62
|
+
const timeoutMs = readiness.timeoutMs ?? 30000;
|
|
63
|
+
const pid = process.pid;
|
|
64
|
+
const sandboxRoot = join(tmpdir(), `aperant-sandbox-${pid}-${Date.now()}`);
|
|
65
|
+
const dirs = {
|
|
66
|
+
A: join(sandboxRoot, 'A'),
|
|
67
|
+
B: join(sandboxRoot, 'B'),
|
|
68
|
+
};
|
|
69
|
+
mkdirSync(dirs.A, { recursive: true });
|
|
70
|
+
mkdirSync(dirs.B, { recursive: true });
|
|
71
|
+
const startedAt = Date.now();
|
|
72
|
+
const jitter = 50 + Math.random() * 100;
|
|
73
|
+
// Concurrency by construction: A starts immediately, B's start is
|
|
74
|
+
// awaited via a microtask + jitter delay. Promise.all races them.
|
|
75
|
+
const [outcomeA, outcomeB] = await Promise.all([
|
|
76
|
+
spawnInstance({ index: 'A', launch, readiness, timeoutMs, sandboxDir: dirs.A, projectDir }),
|
|
77
|
+
delay(jitter).then(() => spawnInstance({ index: 'B', launch, readiness, timeoutMs, sandboxDir: dirs.B, projectDir })),
|
|
78
|
+
]);
|
|
79
|
+
const durationMs = Date.now() - startedAt;
|
|
80
|
+
const verdict = decideVerdict(outcomeA, outcomeB);
|
|
81
|
+
return {
|
|
82
|
+
...verdict,
|
|
83
|
+
durationMs,
|
|
84
|
+
children: { A: outcomeA, B: outcomeB },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Race a readiness signal against the timeout, scanning stderr for the
|
|
89
|
+
* three known-bad patterns. Resolves to a ChildOutcome regardless of
|
|
90
|
+
* which arm wins. Always kills the child before resolving.
|
|
91
|
+
*/
|
|
92
|
+
function spawnInstance({ index: _index, launch, readiness, timeoutMs, sandboxDir, projectDir }) {
|
|
93
|
+
return new Promise((resolveOutcome) => {
|
|
94
|
+
const env = renderLaunchEnv(launch.envTemplate ?? {}, sandboxDir);
|
|
95
|
+
const child = child_process.spawn(launch.binary, launch.args ?? [], {
|
|
96
|
+
cwd: resolve(projectDir),
|
|
97
|
+
env: { ...process.env, ...env, SANDBOX_DIR: sandboxDir },
|
|
98
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
99
|
+
});
|
|
100
|
+
let resolved = false;
|
|
101
|
+
let stderrBuf = '';
|
|
102
|
+
const stdoutBuf = [];
|
|
103
|
+
const MAX_STDERR_BYTES = 4096;
|
|
104
|
+
const finish = (outcome) => {
|
|
105
|
+
if (resolved)
|
|
106
|
+
return;
|
|
107
|
+
resolved = true;
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
try {
|
|
110
|
+
child.kill('SIGTERM');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* already gone */
|
|
114
|
+
}
|
|
115
|
+
// SIGKILL grace
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
try {
|
|
118
|
+
child.kill('SIGKILL');
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* gone */
|
|
122
|
+
}
|
|
123
|
+
}, 5000).unref?.();
|
|
124
|
+
resolveOutcome(outcome);
|
|
125
|
+
};
|
|
126
|
+
// onStderrText: appends to stderrBuf and checks failure-reason patterns.
|
|
127
|
+
// Must NOT be called with stdout data — informational app logs that mention
|
|
128
|
+
// "SQLITE_BUSY" or "EADDRINUSE" must not trigger failure verdicts (LOG-001).
|
|
129
|
+
const onStderrText = (text) => {
|
|
130
|
+
stderrBuf = (stderrBuf + text).slice(-MAX_STDERR_BYTES);
|
|
131
|
+
if (/SQLITE_BUSY/i.test(text)) {
|
|
132
|
+
finish({
|
|
133
|
+
ready: false,
|
|
134
|
+
failureReason: 'self_test_failed_sqlite_busy',
|
|
135
|
+
stderrTail: stderrBuf.slice(-500),
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (/EADDRINUSE|bind\s+failed|address already in use|EACCES|EPERM/i.test(text)) {
|
|
140
|
+
finish({
|
|
141
|
+
ready: false,
|
|
142
|
+
failureReason: 'self_test_failed_port_conflict',
|
|
143
|
+
stderrTail: stderrBuf.slice(-500),
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
// onStdoutText: checks readiness signal only. Never touches stderrBuf and
|
|
149
|
+
// never triggers failure-reason heuristics — stdout is not an error stream.
|
|
150
|
+
const onStdoutText = (text) => {
|
|
151
|
+
if (text.includes(readiness.logPattern)) {
|
|
152
|
+
finish({ ready: true, failureReason: null, stderrTail: stderrBuf.slice(-500) });
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
child.stdout?.on?.('data', (chunk) => {
|
|
156
|
+
const text = String(chunk);
|
|
157
|
+
stdoutBuf.push(text);
|
|
158
|
+
onStdoutText(text);
|
|
159
|
+
});
|
|
160
|
+
child.stderr?.on?.('data', (chunk) => {
|
|
161
|
+
const text = String(chunk);
|
|
162
|
+
onStderrText(text);
|
|
163
|
+
// Readiness may also appear on stderr (some apps log to stderr only).
|
|
164
|
+
if (text.includes(readiness.logPattern)) {
|
|
165
|
+
finish({ ready: true, failureReason: null, stderrTail: stderrBuf.slice(-500) });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
child.on?.('error', (err) => {
|
|
169
|
+
finish({
|
|
170
|
+
ready: false,
|
|
171
|
+
failureReason: 'self_test_failed_app_init_timeout',
|
|
172
|
+
stderrTail: String(err.message ?? err),
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
child.on?.('exit', () => {
|
|
176
|
+
if (!resolved) {
|
|
177
|
+
// Process exited before signalling readiness. Treat as init timeout
|
|
178
|
+
// unless the stderr already revealed a known bad pattern (handled
|
|
179
|
+
// above before exit).
|
|
180
|
+
finish({
|
|
181
|
+
ready: false,
|
|
182
|
+
failureReason: 'self_test_failed_app_init_timeout',
|
|
183
|
+
stderrTail: stderrBuf.slice(-500),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const timer = setTimeout(() => {
|
|
188
|
+
finish({
|
|
189
|
+
ready: false,
|
|
190
|
+
failureReason: 'self_test_failed_app_init_timeout',
|
|
191
|
+
stderrTail: stderrBuf.slice(-500),
|
|
192
|
+
});
|
|
193
|
+
}, timeoutMs);
|
|
194
|
+
// Don't keep the event loop alive on the timer.
|
|
195
|
+
timer.unref?.();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Combine two outcomes into the v2-shaped verdict envelope.
|
|
200
|
+
* Failure precedence: sqlite_busy > port_conflict > app_init_timeout.
|
|
201
|
+
*
|
|
202
|
+
* Every spawnInstance() resolution sets failureReason explicitly on the
|
|
203
|
+
* !ready path (the four finish() call sites cover SQLITE_BUSY, port
|
|
204
|
+
* conflict, app_init_timeout, and the child-error path which also maps
|
|
205
|
+
* to app_init_timeout). The priority loop therefore always finds a
|
|
206
|
+
* match; the unreachable fallthrough that used to live here was
|
|
207
|
+
* deleted per CLAUDE.md guideline 2 (no handling for impossible
|
|
208
|
+
* scenarios).
|
|
209
|
+
*/
|
|
210
|
+
function decideVerdict(a, b) {
|
|
211
|
+
if (a.ready && b.ready) {
|
|
212
|
+
return { verified: true, verdict: /** @type {const} */ ('approved'), reason: null };
|
|
213
|
+
}
|
|
214
|
+
const reasons = [a.failureReason, b.failureReason].filter(Boolean);
|
|
215
|
+
const priority = [
|
|
216
|
+
'self_test_failed_sqlite_busy',
|
|
217
|
+
'self_test_failed_port_conflict',
|
|
218
|
+
'self_test_failed_app_init_timeout',
|
|
219
|
+
];
|
|
220
|
+
for (const cand of priority) {
|
|
221
|
+
if (reasons.includes(cand)) {
|
|
222
|
+
return {
|
|
223
|
+
verified: false,
|
|
224
|
+
verdict: /** @type {const} */ ('needs_human'),
|
|
225
|
+
reason: /** @type {any} */ (cand),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
throw new Error(`self-test decideVerdict: unreachable — both outcomes failed without a known reason: ${JSON.stringify({ a, b })}`);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Substitute ${SANDBOX_DIR} placeholders in the recipe's envTemplate.
|
|
233
|
+
*/
|
|
234
|
+
function renderLaunchEnv(envTemplate, sandboxDir) {
|
|
235
|
+
const out = {};
|
|
236
|
+
for (const [k, v] of Object.entries(envTemplate)) {
|
|
237
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: config template marker, not a typo
|
|
238
|
+
out[k] = typeof v === 'string' ? v.replaceAll('${SANDBOX_DIR}', sandboxDir) : v;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function delay(ms) {
|
|
243
|
+
return new Promise((res) => {
|
|
244
|
+
const t = setTimeout(res, ms);
|
|
245
|
+
t.unref?.();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=self-test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-test.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/self-test.mjs"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aperant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "AI coding framework — composable skills for planning, executing, verifying, and reviewing code with any LLM provider. Works as Claude Code commands, Codex tasks, or programmatically.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mikalsen AI",
|
|
@@ -217,6 +217,21 @@ Fast Path Guarantee). QUICK-routed tasks keep the existing 4-section
|
|
|
217
217
|
shape; STANDARD/DEEP/COMPLEX tasks get a PRD-shaped artifact that
|
|
218
218
|
survives the task as a second-consumer document.
|
|
219
219
|
|
|
220
|
+
**Sandbox-gated parallel verify-proof — cite the v2 envelope verbatim.**
|
|
221
|
+
When the plan calls for parallel `/apt:verify-proof` (e.g. fan-out
|
|
222
|
+
worker spawned a verify-proof child, or `--parallel` is passed to
|
|
223
|
+
`/apt:verify-proof`), the spec's User Outcomes section MUST cite the
|
|
224
|
+
sandbox gate's v2 failure envelope verbatim:
|
|
225
|
+
`{verdict: "needs_human", reason: "sandbox_unverified"}` (plus the
|
|
226
|
+
structured error code `E_SANDBOX_UNVERIFIED` and the remediation
|
|
227
|
+
`/apt:verify-proof setup`). Downstream agents reading the spec
|
|
228
|
+
dispatch on these exact strings; paraphrasing them breaks the
|
|
229
|
+
contract. The gate runs BEFORE Step 2.5 (Deferred-tool-load) and
|
|
230
|
+
BEFORE the outcome walker — sandbox-unverified is a precondition
|
|
231
|
+
failure, not a per-outcome transport failure. See
|
|
232
|
+
`apt-verify-proof/SKILL.md` Step 2c for the gate definition and
|
|
233
|
+
`docs/verify-proof-sandbox-patterns.md` for the worked adopter flow.
|
|
234
|
+
|
|
220
235
|
### 4.A. QUICK track / SIMPLE complexity — legacy 4-section spec
|
|
221
236
|
|
|
222
237
|
```markdown
|
|
@@ -80,10 +80,13 @@ If the planner claims a dimension is covered but the implementation does not act
|
|
|
80
80
|
| **Code Quality** | Clean, idiomatic, lint-clean | Good, minor style issues | Acceptable, needs cleanup | Poor quality | Unacceptable |
|
|
81
81
|
| **Test Coverage** | Comprehensive, edge cases | Good main-path coverage | Basic tests exist | Minimal tests | No coverage |
|
|
82
82
|
|
|
83
|
-
## 4. Derive Verdict
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
## 4. Derive Verdict (v0.9.0 two-gate envelope)
|
|
84
|
+
|
|
85
|
+
Verdict is the closed set `{approved, rejected, needs_human}`. Derived from the two-gate envelope (`automation_gate` AND `proof_gate`); dimensions are informational rationale only.
|
|
86
|
+
|
|
87
|
+
- **approved:** `automation_gate: pass` AND `proof_gate: pass`.
|
|
88
|
+
- **needs_human:** `automation_gate: pass` AND `proof_gate: fail` (typically: a UI-surface outcome lacked driver transport — see `/apt:verify-proof`).
|
|
89
|
+
- **rejected:** `automation_gate: fail` (any of lint/typecheck/tests red) OR critical findings unresolved.
|
|
87
90
|
|
|
88
91
|
## 5. Write QA Sign-off
|
|
89
92
|
Write `{task_dir}/qa_signoff.json` with scores, checks, issues, and verdict.
|
|
@@ -105,14 +108,16 @@ Either shape is accepted:
|
|
|
105
108
|
|
|
106
109
|
Gate G8 (`consistency-check`) enforces this on ship via rules R4
|
|
107
110
|
(finding-propagation) and R5 (verdict-consistency):
|
|
108
|
-
- `verdict: "approved"`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`findings.notes` and use `approved-with-notes`.
|
|
111
|
+
- `verdict: "approved"` is incompatible with any unresolved `critical` or
|
|
112
|
+
`major` review finding (G42 Phase 1). The sole bypass is `--force --reason
|
|
113
|
+
"<why>"` on the gate CLI (audit-logged). For non-blocking findings
|
|
114
|
+
(suggestion/note/info/nitpick), cite them in `findings.notes` and keep
|
|
115
|
+
the verdict `approved`.
|
|
114
116
|
- `verdict: "rejected"` requires at least one cited finding (rejected
|
|
115
117
|
without evidence is treated as drift).
|
|
118
|
+
- `verdict: "needs_human"` signals a transport gap (e.g. UI-surface outcome
|
|
119
|
+
with no MCP driver). The R5 rule does not gate on it; re-run verify-proof
|
|
120
|
+
after installing the missing transport.
|
|
116
121
|
|
|
117
122
|
## 6. Report
|
|
118
123
|
Output verdict with dimension scores and next step recommendation.
|
|
@@ -209,6 +209,8 @@ see the gate table below.}
|
|
|
209
209
|
**Epic:** {one-sentence user-facing capability headline}
|
|
210
210
|
|
|
211
211
|
- **ON** [surface]: {user does X; system shows/does Y}
|
|
212
|
+
- {indented inferred sub-check derived by the planning LLM}
|
|
213
|
+
- {another inferred sub-check}
|
|
212
214
|
- **ON** [surface]: {user does X; system shows/does Y} *(requires ON 1)*
|
|
213
215
|
- **ON** [electron+web]: {cross-surface outcome — runs twice, once per surface}
|
|
214
216
|
|
|
@@ -246,6 +248,27 @@ ID. Subtasks reference these IDs via their per-subtask
|
|
|
246
248
|
plan-coverage-check input.}
|
|
247
249
|
```
|
|
248
250
|
|
|
251
|
+
### Inferred sub-checks (v0.9.0 — verify-proof-v2-outcome-walker)
|
|
252
|
+
|
|
253
|
+
Under each `**ON**` parent bullet, write indented child bullets (2-space
|
|
254
|
+
prefix + `- `) representing inferred sub-checks the planning LLM derives
|
|
255
|
+
from the outcome prose + surface. These are NOT user-written invariants;
|
|
256
|
+
they are the planner's judgment of "what else must be true for this
|
|
257
|
+
outcome to be approved." The verify-proof runner walks each inferred
|
|
258
|
+
child the same way it walks the parent.
|
|
259
|
+
|
|
260
|
+
Worked example (copy this shape verbatim into spec.md):
|
|
261
|
+
|
|
262
|
+
- **ON** [electron]: Clicking 'Settings' opens the settings modal.
|
|
263
|
+
- The modal traps focus on the close button.
|
|
264
|
+
- Clicking outside the modal closes it.
|
|
265
|
+
- Pressing Esc closes it and returns focus to the trigger.
|
|
266
|
+
|
|
267
|
+
**NO `patterns/` library, NO `surfaces/<name>/manifest.json` tree, NO
|
|
268
|
+
`OUTCOMES.md` file** — inferred bullets live IN spec.md. The author
|
|
269
|
+
edits spec.md once if the inferred list is wrong; the runner does not
|
|
270
|
+
re-derive them at proof time.
|
|
271
|
+
|
|
249
272
|
**`## User Outcomes` is the verify-proof input contract (STANDARD/DEEP
|
|
250
273
|
only — QUICK is hard-exempt per ID-05 / Fast Path Guarantee).** The
|
|
251
274
|
section sits between `## User Stories` (product motivation prose) and
|
|
@@ -281,7 +281,7 @@ Execute the apt:verify workflow inline:
|
|
|
281
281
|
6. Score on 4 dimensions (completeness, correctness, quality, coverage)
|
|
282
282
|
7. Fix all issues found — every severity, not just critical (up to 4 iterations: first 2 discovery, last 2 verification-only)
|
|
283
283
|
8. Write qa_signoff.json
|
|
284
|
-
9. If `status` is `approved
|
|
284
|
+
9. If `status` is `approved`, flip lifecycle to `reviewing` so Stage 4 (or `/apt:ship` when review is skipped) finds the task in the correct from-state. Skip this step entirely on `rejected` (fix the code and re-run) or `needs_human` (install the missing driver transport and re-run /apt:verify-proof):
|
|
285
285
|
```bash
|
|
286
286
|
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase reviewing
|
|
287
287
|
```
|
|
@@ -307,6 +307,62 @@ AskUserQuestion([
|
|
|
307
307
|
])
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
+
### Sandbox-setup hook — parallel /apt:verify-proof (FRAMEWORK milestone fw-sandbox-parallel-verify-proof)
|
|
311
|
+
|
|
312
|
+
Gated on THREE conditions ALL true (ID-08):
|
|
313
|
+
|
|
314
|
+
1. `verification.sandbox` is ABSENT from `.aperant/config.json` (the
|
|
315
|
+
adopter has not opted in yet — once configured this hook never
|
|
316
|
+
fires again).
|
|
317
|
+
2. `host.capabilities.structured_prompts === true` (the host supports
|
|
318
|
+
native question dialogs — Claude Code, Gemini CLI, OpenCode).
|
|
319
|
+
3. `process.stdout.isTTY === true` (interactive shell — never block CI
|
|
320
|
+
or fan-out child terminals on a setup prompt).
|
|
321
|
+
|
|
322
|
+
When all three hold, ask one yes/no question:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
AskUserQuestion([
|
|
326
|
+
{
|
|
327
|
+
question: "Parallel /apt:verify-proof is not configured. Run setup now?",
|
|
328
|
+
header: "Sandbox Setup",
|
|
329
|
+
multiSelect: false,
|
|
330
|
+
options: [
|
|
331
|
+
{ label: "Yes — set up parallel verify-proof", description: "Walk recipe-diff one change at a time, then apply-recipe with --confirm --accept-all once you've reviewed all four, then verify-sandbox" },
|
|
332
|
+
{ label: "Not now", description: "Skip; re-prompted on next /apt:setup if still unconfigured" }
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
])
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
On `Yes`, dispatch the three sandbox commands in order, surfacing the
|
|
339
|
+
output of each to the user before moving to the next. The CLI defaults
|
|
340
|
+
to skipping every change when `--confirm` is passed without
|
|
341
|
+
`--accept-all` (ID-02 fail-safe), so the interactive flow walks the
|
|
342
|
+
adopter through each change in the diff first, then applies once they
|
|
343
|
+
confirm:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
apt-tools recipe-diff . # preview, no writes
|
|
347
|
+
# (host CLI shows each change in the Markdown diff to the user)
|
|
348
|
+
# (host CLI asks: "All four changes accepted? [y/N]")
|
|
349
|
+
apt-tools apply-recipe . --confirm --accept-all
|
|
350
|
+
apt-tools verify-sandbox .
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
On `Not now`, continue with the rest of /apt:setup unchanged.
|
|
354
|
+
|
|
355
|
+
**Non-interactive surfaces** (CI runs, fan-out child terminals, headless
|
|
356
|
+
agents — `host.capabilities.structured_prompts === false` OR
|
|
357
|
+
`process.stdout.isTTY === false`) SKIP this hook entirely. Print a
|
|
358
|
+
one-line stderr tip:
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
[apt] tip: run `/apt:verify-proof setup` from an interactive terminal to enable parallel verification
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
and continue. NEVER block on a setup prompt in a non-interactive context.
|
|
365
|
+
|
|
310
366
|
## 3. Present Settings (Batch 2: Pipeline)
|
|
311
367
|
|
|
312
368
|
```
|
|
@@ -211,15 +211,15 @@ Rate each of the 4 dimensions on a 1-10 scale using the rubrics below. Provide a
|
|
|
211
211
|
- **3-4:** Minimal tests, many untested paths
|
|
212
212
|
- **1-2:** No meaningful test coverage
|
|
213
213
|
|
|
214
|
-
### Verdict Derivation
|
|
214
|
+
### Verdict Derivation (v0.9.0 two-gate envelope)
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
Verdict is the closed set `{approved, rejected, needs_human}`. It derives from the two-gate envelope (`automation_gate` AND `proof_gate`); dimensions are informational rationale only.
|
|
217
217
|
|
|
218
|
-
- **approved** --
|
|
219
|
-
- **
|
|
220
|
-
- **rejected** --
|
|
218
|
+
- **approved** -- `automation_gate: pass` AND `proof_gate: pass` AND no critical issues
|
|
219
|
+
- **needs_human** -- `automation_gate: pass` AND `proof_gate: fail` (typically: UI-surface outcome lacked driver transport; the human must install it and re-run /apt:verify-proof)
|
|
220
|
+
- **rejected** -- `automation_gate: fail` (any of lint/typecheck/tests red) OR critical issues unresolved
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
The `deriveVerdict({automation_gate, proof_gate})` helper in `packages/framework/src/types/qa-scoring.ts` is the canonical mapping. The legacy `approved_with_notes` middle band is deleted in v0.9.0+; reading the legacy value from a stale `qa_signoff.json` crashes the consumer with a clear migration error.
|
|
223
223
|
|
|
224
224
|
## 6. Fix-Verify Loop (max 4 iterations)
|
|
225
225
|
|
|
@@ -272,7 +272,7 @@ Create `qa_signoff.json` using the `ScoredQASignoff` format (see `packages/frame
|
|
|
272
272
|
|
|
273
273
|
```json
|
|
274
274
|
{
|
|
275
|
-
"status": "approved|
|
|
275
|
+
"status": "approved|rejected|needs_human",
|
|
276
276
|
"timestamp": "{ISO date}",
|
|
277
277
|
"iterations": 1,
|
|
278
278
|
"dimensions": [
|
|
@@ -281,6 +281,10 @@ Create `qa_signoff.json` using the `ScoredQASignoff` format (see `packages/frame
|
|
|
281
281
|
{ "dimension": "code_quality", "score": 7, "rationale": "Clean code, follows conventions" },
|
|
282
282
|
{ "dimension": "test_coverage", "score": 7, "rationale": "Good coverage of main paths" }
|
|
283
283
|
],
|
|
284
|
+
"gates": {
|
|
285
|
+
"automation_gate": "pass",
|
|
286
|
+
"proof_gate": "pass"
|
|
287
|
+
},
|
|
284
288
|
"checks": {
|
|
285
289
|
"tests": { "status": "pass", "count": 42, "failures": [] },
|
|
286
290
|
"typecheck": { "status": "pass", "errors": [] },
|
|
@@ -293,10 +297,10 @@ Create `qa_signoff.json` using the `ScoredQASignoff` format (see `packages/frame
|
|
|
293
297
|
|
|
294
298
|
The `dimensions` array must contain exactly the 4 dimensions defined in `QA_DIMENSIONS`: `completeness`, `correctness`, `code_quality`, `test_coverage`. Each entry includes a numeric `score` (1-10) and a `rationale` string explaining the score.
|
|
295
299
|
|
|
296
|
-
The `status` field
|
|
297
|
-
-
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
+
The `status` field derives from the two gates:
|
|
301
|
+
- `automation_gate: pass` AND `proof_gate: pass`: `"approved"`
|
|
302
|
+
- `automation_gate: pass` AND `proof_gate: fail`: `"needs_human"`
|
|
303
|
+
- `automation_gate: fail` OR critical issues unresolved: `"rejected"`
|
|
300
304
|
|
|
301
305
|
```bash
|
|
302
306
|
node packages/framework/bin/apt-tools.mjs commit "qa: verification {status}" --files qa_signoff.json
|
|
@@ -304,10 +308,11 @@ node packages/framework/bin/apt-tools.mjs commit "qa: verification {status}" --f
|
|
|
304
308
|
|
|
305
309
|
### Mark lifecycle phase as `reviewing` (approved path only)
|
|
306
310
|
|
|
307
|
-
If `status` is `approved
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
`
|
|
311
|
+
If `status` is `approved`, flip the task's lifecycle phase so the next stage
|
|
312
|
+
(`/apt:review`, or `/apt:ship` if review is skipped) finds the task in the
|
|
313
|
+
correct from-state. Skip this transition on `rejected` (the task stays in
|
|
314
|
+
`verifying` until the user re-runs after fixes) or `needs_human` (re-run
|
|
315
|
+
/apt:verify-proof after installing the missing driver transport).
|
|
311
316
|
|
|
312
317
|
```bash
|
|
313
318
|
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase reviewing
|