@aperant/framework 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +226 -29
- 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/SKILL.md +2 -2
- 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 +17 -0
- package/prompts/inbox-triage.md +21 -0
- package/prompts/qa_orchestrator_agentic.md +18 -17
- package/prompts/qa_reviewer.md +17 -15
- package/skills/apt/SKILL.md +2 -2
- 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,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate-predicate.mjs — extracted decision function for the sandbox
|
|
3
|
+
* precondition gate at apt-verify-proof/SKILL.md Step 2c.
|
|
4
|
+
*
|
|
5
|
+
* Refactored out of the SKILL.md prose so the e2e test (subtask 7.3)
|
|
6
|
+
* can call it directly without re-implementing the dispatch table.
|
|
7
|
+
*
|
|
8
|
+
* Inputs:
|
|
9
|
+
* - config: {verification?: {sandbox?: {enabled:bool, ...}}}
|
|
10
|
+
* - stateFile: the v2-shaped envelope read from
|
|
11
|
+
* .aperant/state/verify-proof-sandbox-verified.json
|
|
12
|
+
* OR null when the file is missing.
|
|
13
|
+
* - parallelRequested: bool — true when /apt:verify-proof was invoked
|
|
14
|
+
* with --parallel.
|
|
15
|
+
*
|
|
16
|
+
* Returns:
|
|
17
|
+
* - {action: 'proceed'} — verified + approved, walker may run
|
|
18
|
+
* - {action: 'skip-gate'} — --parallel NOT requested OR
|
|
19
|
+
* verification.sandbox absent;
|
|
20
|
+
* Fast Path Guarantee (ID-07).
|
|
21
|
+
* - {action: 'exit', envelope} — verified:false OR state missing;
|
|
22
|
+
* envelope is the v2 fail-loud shape.
|
|
23
|
+
*
|
|
24
|
+
* The `envelope` shape on exit is:
|
|
25
|
+
* {status:'error', code:'E_SANDBOX_UNVERIFIED', verdict:'needs_human',
|
|
26
|
+
* reason:'sandbox_unverified', remediation:'/apt:verify-proof setup'}
|
|
27
|
+
*
|
|
28
|
+
* No I/O, no process.exit — pure function for testability.
|
|
29
|
+
*/
|
|
30
|
+
const FAIL_ENVELOPE = Object.freeze({
|
|
31
|
+
status: 'error',
|
|
32
|
+
code: 'E_SANDBOX_UNVERIFIED',
|
|
33
|
+
verdict: 'needs_human',
|
|
34
|
+
reason: 'sandbox_unverified',
|
|
35
|
+
remediation: '/apt:verify-proof setup',
|
|
36
|
+
});
|
|
37
|
+
const STALE_VERDICT_ENVELOPE = Object.freeze({
|
|
38
|
+
status: 'error',
|
|
39
|
+
code: 'E_SANDBOX_VERDICT_STALE',
|
|
40
|
+
verdict: 'needs_human',
|
|
41
|
+
reason: 'sandbox_recipe_version_drift',
|
|
42
|
+
remediation: 'apt-tools verify-sandbox <project-dir>',
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} GateInput
|
|
46
|
+
* @property {object|null|undefined} config
|
|
47
|
+
* @property {object|null|undefined} stateFile
|
|
48
|
+
* @property {boolean} parallelRequested
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} GateEnvelope
|
|
52
|
+
* @property {'error'} status
|
|
53
|
+
* @property {string} code
|
|
54
|
+
* @property {string} verdict
|
|
55
|
+
* @property {string} reason
|
|
56
|
+
* @property {string} remediation
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Object} GateOutput
|
|
60
|
+
* @property {'proceed'|'skip-gate'|'exit'} action
|
|
61
|
+
* @property {GateEnvelope} [envelope]
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* @param {GateInput} input
|
|
65
|
+
* @returns {GateOutput}
|
|
66
|
+
*/
|
|
67
|
+
export function gatePredicate({ config, stateFile, parallelRequested }) {
|
|
68
|
+
// (1) --parallel NOT requested → gate is a no-op (Fast Path Guarantee ID-07).
|
|
69
|
+
if (!parallelRequested)
|
|
70
|
+
return { action: 'skip-gate' };
|
|
71
|
+
// (2) verification.sandbox is ABSENT → also skip-gate (the adopter
|
|
72
|
+
// has not opted in; respect the absent-config branch as no-op).
|
|
73
|
+
const sandboxCfg = config?.verification?.sandbox;
|
|
74
|
+
if (!sandboxCfg || sandboxCfg.enabled !== true) {
|
|
75
|
+
// Note: even though --parallel was requested, the absent-config
|
|
76
|
+
// branch returns skip-gate; the SKILL.md Step 2c prose handles
|
|
77
|
+
// the absent-config case via the first-run hook (Step 1) which
|
|
78
|
+
// is a UX surface, not a gate failure.
|
|
79
|
+
return { action: 'skip-gate' };
|
|
80
|
+
}
|
|
81
|
+
// (3) state file present + verified:true + verdict:'approved' → check version drift.
|
|
82
|
+
if (stateFile && stateFile.verified === true && stateFile.verdict === 'approved') {
|
|
83
|
+
// (3a) Version drift check: when both sides have recipeVersion, verify the
|
|
84
|
+
// state file's version is in the active recipe's compatibleWithApplied[].
|
|
85
|
+
// If either side has null/undefined recipeVersion, fall through (compat
|
|
86
|
+
// with pre-LOG-003 state files that predate recipeVersion population).
|
|
87
|
+
const stateVersion = stateFile.recipeVersion ?? null;
|
|
88
|
+
const activeVersion = sandboxCfg.recipeVersion ?? null;
|
|
89
|
+
const compatibleWith = Array.isArray(sandboxCfg.compatibleWithApplied)
|
|
90
|
+
? sandboxCfg.compatibleWithApplied
|
|
91
|
+
: null;
|
|
92
|
+
if (stateVersion && activeVersion && compatibleWith !== null) {
|
|
93
|
+
// Both sides versioned — only stale if the state version is NOT
|
|
94
|
+
// listed in the active recipe's compatibleWithApplied AND the
|
|
95
|
+
// versions differ.
|
|
96
|
+
if (stateVersion !== activeVersion && !compatibleWith.includes(stateVersion)) {
|
|
97
|
+
return { action: 'exit', envelope: { ...STALE_VERDICT_ENVELOPE } };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { action: 'proceed' };
|
|
101
|
+
}
|
|
102
|
+
// (4) Everything else → fail loud with the v2 envelope.
|
|
103
|
+
return { action: 'exit', envelope: { ...FAIL_ENVELOPE } };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The frozen fail envelope — exported so callers can detect-and-match
|
|
107
|
+
* without re-stringifying.
|
|
108
|
+
*/
|
|
109
|
+
export const SANDBOX_UNVERIFIED_ENVELOPE = FAIL_ENVELOPE;
|
|
110
|
+
/**
|
|
111
|
+
* The frozen stale-verdict envelope — emitted when the state file's
|
|
112
|
+
* recipeVersion is no longer in the active recipe's compatibleWithApplied[].
|
|
113
|
+
*/
|
|
114
|
+
export const SANDBOX_VERDICT_STALE_ENVELOPE = STALE_VERDICT_ENVELOPE;
|
|
115
|
+
//# sourceMappingURL=gate-predicate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-predicate.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/gate-predicate.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,oBAAoB;IAC5B,WAAW,EAAE,yBAAyB;CACtC,CAAC,CAAA;AAEF,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,yBAAyB;IAC/B,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,8BAA8B;IACtC,WAAW,EAAE,wCAAwC;CACrD,CAAC,CAAA;AAEF;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE;IACrE,8EAA8E;IAC9E,IAAI,CAAC,iBAAiB;QAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAEtD,mEAAmE;IACnE,oEAAoE;IACpE,MAAM,UAAU,GAAG,MAAM,EAAE,YAAY,EAAE,OAAO,CAAA;IAChD,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAChD,gEAAgE;QAChE,+DAA+D;QAC/D,+DAA+D;QAC/D,uCAAuC;QACvC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAC/B,CAAC;IAED,qFAAqF;IACrF,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,KAAK,IAAI,IAAI,SAAS,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QAClF,2EAA2E;QAC3E,+EAA+E;QAC/E,6EAA6E;QAC7E,4EAA4E;QAC5E,MAAM,YAAY,GAAG,SAAS,CAAC,aAAa,IAAI,IAAI,CAAA;QACpD,MAAM,aAAa,GAAG,UAAU,CAAC,aAAa,IAAI,IAAI,CAAA;QACtD,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,qBAAqB,CAAC;YACrE,CAAC,CAAC,UAAU,CAAC,qBAAqB;YAClC,CAAC,CAAC,IAAI,CAAA;QAEP,IAAI,YAAY,IAAI,aAAa,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC9D,gEAAgE;YAChE,8DAA8D;YAC9D,mBAAmB;YACnB,IAAI,YAAY,KAAK,aAAa,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,sBAAsB,EAAE,EAAE,CAAA;YACnE,CAAC;QACF,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAC7B,CAAC;IAED,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,aAAa,EAAE,EAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAa,CAAA;AAExD;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../../util/runtime-capabilities.mjs').RuntimeCapabilities} RuntimeCapabilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} SandboxRecipe
|
|
6
|
+
* @property {string} recipeId
|
|
7
|
+
* @property {string} recipeVersion
|
|
8
|
+
* @property {string} name
|
|
9
|
+
* @property {string} description
|
|
10
|
+
* @property {{capabilities: string[], deps: string[]}} match
|
|
11
|
+
* @property {{logPattern: string, timeoutMs: number}} readiness
|
|
12
|
+
* @property {Array<object>} changes
|
|
13
|
+
* @property {string[]} compatibleWithApplied
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} MatchResult
|
|
17
|
+
* @property {string|null} recipeId
|
|
18
|
+
* @property {string} reason
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @param {RuntimeCapabilities} runtimeCaps
|
|
22
|
+
* @param {Record<string, unknown> | null} packageJson
|
|
23
|
+
* @param {SandboxRecipe[]} recipes
|
|
24
|
+
* @returns {MatchResult}
|
|
25
|
+
*/
|
|
26
|
+
export function matchPattern(runtimeCaps: RuntimeCapabilities, packageJson: Record<string, unknown> | null, recipes: SandboxRecipe[]): MatchResult;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience overload — load the project's package.json from disk.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} projectDir
|
|
31
|
+
* @param {RuntimeCapabilities} runtimeCaps
|
|
32
|
+
* @param {SandboxRecipe[]} recipes
|
|
33
|
+
* @returns {MatchResult}
|
|
34
|
+
*/
|
|
35
|
+
export function matchPatternForProject(projectDir: string, runtimeCaps: RuntimeCapabilities, recipes: SandboxRecipe[]): MatchResult;
|
|
36
|
+
export type RuntimeCapabilities = import("../../util/runtime-capabilities.mjs").RuntimeCapabilities;
|
|
37
|
+
export type SandboxRecipe = {
|
|
38
|
+
recipeId: string;
|
|
39
|
+
recipeVersion: string;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
match: {
|
|
43
|
+
capabilities: string[];
|
|
44
|
+
deps: string[];
|
|
45
|
+
};
|
|
46
|
+
readiness: {
|
|
47
|
+
logPattern: string;
|
|
48
|
+
timeoutMs: number;
|
|
49
|
+
};
|
|
50
|
+
changes: Array<object>;
|
|
51
|
+
compatibleWithApplied: string[];
|
|
52
|
+
};
|
|
53
|
+
export type MatchResult = {
|
|
54
|
+
recipeId: string | null;
|
|
55
|
+
reason: string;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=pattern-matcher.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-matcher.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/pattern-matcher.mjs"],"names":[],"mappings":"AAuBA;;GAEG;AAEH;;;;;;;;;;GAUG;AAEH;;;;GAIG;AAEH;;;;;GAKG;AACH,0CALW,mBAAmB,eACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,WAC9B,aAAa,EAAE,GACb,WAAW,CA4BvB;AAED;;;;;;;GAOG;AACH,mDALW,MAAM,eACN,mBAAmB,WACnB,aAAa,EAAE,GACb,WAAW,CAavB;kCA1EY,OAAO,qCAAqC,EAAE,mBAAmB;;cAKhE,MAAM;mBACN,MAAM;UACN,MAAM;iBACN,MAAM;WACN;QAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAC;eACxC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC;aACvC,KAAK,CAAC,MAAM,CAAC;2BACb,MAAM,EAAE;;;cAKR,MAAM,GAAC,IAAI;YACX,MAAM"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pattern-matcher.mjs — sandbox-recipe pattern matcher.
|
|
3
|
+
*
|
|
4
|
+
* Walks the registered recipes' `match.capabilities[]` + `match.deps[]`
|
|
5
|
+
* predicates against the loaded runtime capabilities + package.json deps,
|
|
6
|
+
* and returns the FIRST matching recipe (or null with a human reason).
|
|
7
|
+
*
|
|
8
|
+
* Two-arg shape:
|
|
9
|
+
* - `match.capabilities[]` — every entry must be `true` on the
|
|
10
|
+
* RuntimeCapabilities record (AND semantics — all required).
|
|
11
|
+
* - `match.deps[]` — at least one entry must appear in the merged
|
|
12
|
+
* dependencies + devDependencies of the project's root package.json
|
|
13
|
+
* (OR semantics — any-of).
|
|
14
|
+
*
|
|
15
|
+
* This split lets a recipe say "I need is_electron AND one of
|
|
16
|
+
* [@libsql/client, better-sqlite3]" without enumerating every combination.
|
|
17
|
+
*
|
|
18
|
+
* See: spec ID-04 (v1 ships ONE recipe — electron-libsql), AC5.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { join, resolve } from 'node:path';
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {import('../../util/runtime-capabilities.mjs').RuntimeCapabilities} RuntimeCapabilities
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} SandboxRecipe
|
|
27
|
+
* @property {string} recipeId
|
|
28
|
+
* @property {string} recipeVersion
|
|
29
|
+
* @property {string} name
|
|
30
|
+
* @property {string} description
|
|
31
|
+
* @property {{capabilities: string[], deps: string[]}} match
|
|
32
|
+
* @property {{logPattern: string, timeoutMs: number}} readiness
|
|
33
|
+
* @property {Array<object>} changes
|
|
34
|
+
* @property {string[]} compatibleWithApplied
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} MatchResult
|
|
38
|
+
* @property {string|null} recipeId
|
|
39
|
+
* @property {string} reason
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* @param {RuntimeCapabilities} runtimeCaps
|
|
43
|
+
* @param {Record<string, unknown> | null} packageJson
|
|
44
|
+
* @param {SandboxRecipe[]} recipes
|
|
45
|
+
* @returns {MatchResult}
|
|
46
|
+
*/
|
|
47
|
+
export function matchPattern(runtimeCaps, packageJson, recipes) {
|
|
48
|
+
if (!Array.isArray(recipes) || recipes.length === 0) {
|
|
49
|
+
return { recipeId: null, reason: 'no recipes registered' };
|
|
50
|
+
}
|
|
51
|
+
const deps = mergedDeps(packageJson);
|
|
52
|
+
for (const recipe of recipes) {
|
|
53
|
+
const reqCaps = recipe.match?.capabilities ?? [];
|
|
54
|
+
const anyDeps = recipe.match?.deps ?? [];
|
|
55
|
+
const capsOk = reqCaps.every((cap) => runtimeCaps && runtimeCaps[cap] === true);
|
|
56
|
+
if (!capsOk)
|
|
57
|
+
continue;
|
|
58
|
+
// deps[] is any-of — recipe matches if at least one declared dep is in
|
|
59
|
+
// the project. An empty deps[] means "no dep constraint" (cap-only match).
|
|
60
|
+
const depsOk = anyDeps.length === 0 || anyDeps.some((d) => deps.has(d.toLowerCase()));
|
|
61
|
+
if (!depsOk)
|
|
62
|
+
continue;
|
|
63
|
+
return { recipeId: recipe.recipeId, reason: 'matched' };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
recipeId: null,
|
|
67
|
+
reason: `no recipe available for runtime: ${describeRuntime(runtimeCaps)}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convenience overload — load the project's package.json from disk.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} projectDir
|
|
74
|
+
* @param {RuntimeCapabilities} runtimeCaps
|
|
75
|
+
* @param {SandboxRecipe[]} recipes
|
|
76
|
+
* @returns {MatchResult}
|
|
77
|
+
*/
|
|
78
|
+
export function matchPatternForProject(projectDir, runtimeCaps, recipes) {
|
|
79
|
+
const pkgPath = join(resolve(projectDir), 'package.json');
|
|
80
|
+
let pkg = null;
|
|
81
|
+
if (existsSync(pkgPath)) {
|
|
82
|
+
try {
|
|
83
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* fall through with pkg=null */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return matchPattern(runtimeCaps, pkg, recipes);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build a Set of lowercased package names from dependencies + devDependencies.
|
|
93
|
+
*
|
|
94
|
+
* @param {Record<string, unknown> | null} packageJson
|
|
95
|
+
*/
|
|
96
|
+
function mergedDeps(packageJson) {
|
|
97
|
+
const out = new Set();
|
|
98
|
+
if (packageJson === null || typeof packageJson !== 'object')
|
|
99
|
+
return out;
|
|
100
|
+
const d = /** @type {Record<string, unknown>} */ (packageJson.dependencies);
|
|
101
|
+
const dd = /** @type {Record<string, unknown>} */ (packageJson.devDependencies);
|
|
102
|
+
if (d && typeof d === 'object')
|
|
103
|
+
for (const k of Object.keys(d))
|
|
104
|
+
out.add(k.toLowerCase());
|
|
105
|
+
if (dd && typeof dd === 'object')
|
|
106
|
+
for (const k of Object.keys(dd))
|
|
107
|
+
out.add(k.toLowerCase());
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One-line description of the detected runtime shape — used in the
|
|
112
|
+
* no-match reason so an adopter sees what the matcher saw.
|
|
113
|
+
*
|
|
114
|
+
* @param {RuntimeCapabilities} runtimeCaps
|
|
115
|
+
*/
|
|
116
|
+
function describeRuntime(runtimeCaps) {
|
|
117
|
+
if (!runtimeCaps || typeof runtimeCaps !== 'object')
|
|
118
|
+
return 'unknown';
|
|
119
|
+
const active = Object.keys(runtimeCaps).filter((k) => runtimeCaps[k] === true);
|
|
120
|
+
if (active.length === 0)
|
|
121
|
+
return 'no capabilities detected';
|
|
122
|
+
return active.join(', ');
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=pattern-matcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-matcher.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/pattern-matcher.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{projectDir: string, recipeId: string, recipes: object[]}} args
|
|
3
|
+
* @returns {{
|
|
4
|
+
* format: 'markdown',
|
|
5
|
+
* body: string,
|
|
6
|
+
* changes: Array<{path: string, kind: string, summary: string}>,
|
|
7
|
+
* envelope: {status: 'ok'|'error', recipe_version: string|null, compatible: boolean}
|
|
8
|
+
* }}
|
|
9
|
+
*/
|
|
10
|
+
export function generateRecipeDiff({ projectDir, recipeId, recipes }: {
|
|
11
|
+
projectDir: string;
|
|
12
|
+
recipeId: string;
|
|
13
|
+
recipes: object[];
|
|
14
|
+
}): {
|
|
15
|
+
format: "markdown";
|
|
16
|
+
body: string;
|
|
17
|
+
changes: Array<{
|
|
18
|
+
path: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
summary: string;
|
|
21
|
+
}>;
|
|
22
|
+
envelope: {
|
|
23
|
+
status: "ok" | "error";
|
|
24
|
+
recipe_version: string | null;
|
|
25
|
+
compatible: boolean;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=recipe-diff.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-diff.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/recipe-diff.mjs"],"names":[],"mappings":"AA8CA;;;;;;;;GAQG;AACH,sEARW;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC,GACvD;IACR,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IAC9D,QAAQ,EAAE;QAAC,MAAM,EAAE,IAAI,GAAC,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,GAAC,IAAI,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAC,CAAA;CACnF,CAuDH"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recipe-diff.mjs — read-only Markdown diff generator for a recipe.
|
|
3
|
+
*
|
|
4
|
+
* Loads the recipe, walks `changes[]`, and emits a Markdown report
|
|
5
|
+
* enumerating every proposed write WITH `--- before` / `+++ after`
|
|
6
|
+
* blocks. The generator is pure read — it never writes to the adopter's
|
|
7
|
+
* tree (ID-02). The companion `apply-recipe` command is the only
|
|
8
|
+
* writer.
|
|
9
|
+
*
|
|
10
|
+
* Public contract:
|
|
11
|
+
* generateRecipeDiff({projectDir, recipeId, recipes}) → {
|
|
12
|
+
* format: 'markdown',
|
|
13
|
+
* body: string,
|
|
14
|
+
* changes: [{path, kind, summary}],
|
|
15
|
+
* envelope: {status, recipe_version, compatible}
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* The Markdown is user-facing and load-bearing — drift = golden-snapshot
|
|
19
|
+
* test failure by design (subtask 2.1 / TD-03).
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
/**
|
|
26
|
+
* Defense-in-depth path-under-root check. Returns the validated absolute
|
|
27
|
+
* path or null when the candidate escapes the root. The diff generator
|
|
28
|
+
* surfaces a "template not found" envelope rather than throwing (read-
|
|
29
|
+
* only generator stays non-fatal).
|
|
30
|
+
*
|
|
31
|
+
* @param {string} rootAbs
|
|
32
|
+
* @param {string} relPath
|
|
33
|
+
* @returns {string|null}
|
|
34
|
+
*/
|
|
35
|
+
function safeJoinUnderRoot(rootAbs, relPath) {
|
|
36
|
+
if (typeof relPath !== 'string' || relPath.length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
if (isAbsolute(relPath))
|
|
39
|
+
return null;
|
|
40
|
+
const candidate = resolve(rootAbs, relPath);
|
|
41
|
+
const rel = relative(rootAbs, candidate);
|
|
42
|
+
if (rel.startsWith('..') || isAbsolute(rel))
|
|
43
|
+
return null;
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @param {{projectDir: string, recipeId: string, recipes: object[]}} args
|
|
48
|
+
* @returns {{
|
|
49
|
+
* format: 'markdown',
|
|
50
|
+
* body: string,
|
|
51
|
+
* changes: Array<{path: string, kind: string, summary: string}>,
|
|
52
|
+
* envelope: {status: 'ok'|'error', recipe_version: string|null, compatible: boolean}
|
|
53
|
+
* }}
|
|
54
|
+
*/
|
|
55
|
+
export function generateRecipeDiff({ projectDir, recipeId, recipes }) {
|
|
56
|
+
const recipe = (recipes ?? []).find((r) => r && r.recipeId === recipeId);
|
|
57
|
+
if (!recipe) {
|
|
58
|
+
return {
|
|
59
|
+
format: 'markdown',
|
|
60
|
+
body: `# No recipe available\n\nrecipeId \`${recipeId}\` is not in the registered recipes list.\n`,
|
|
61
|
+
changes: [],
|
|
62
|
+
envelope: { status: 'error', recipe_version: null, compatible: false },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const adopterRoot = resolve(projectDir);
|
|
66
|
+
const changeRows = [];
|
|
67
|
+
const sections = [];
|
|
68
|
+
for (const change of recipe.changes ?? []) {
|
|
69
|
+
// Defense-in-depth: a malicious recipe could otherwise read
|
|
70
|
+
// arbitrary host files via `change.path: '../../etc/passwd'`. The
|
|
71
|
+
// diff generator is read-only but the read CONTENT lands in the
|
|
72
|
+
// emitted Markdown — refuse to enumerate paths outside root.
|
|
73
|
+
const targetAbs = safeJoinUnderRoot(adopterRoot, change.path);
|
|
74
|
+
if (targetAbs === null) {
|
|
75
|
+
sections.push(`## ${change.path}\n\n_(rejected: change.path escapes project root — recipe defect)_\n`);
|
|
76
|
+
changeRows.push({
|
|
77
|
+
path: change.path,
|
|
78
|
+
kind: change.kind,
|
|
79
|
+
summary: 'rejected (path escapes root)',
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const exists = existsSync(targetAbs);
|
|
84
|
+
const before = exists ? safeReadFile(targetAbs) : '';
|
|
85
|
+
const after = renderAfter(change, recipe, adopterRoot);
|
|
86
|
+
const summary = renderChangeSummary(change, exists);
|
|
87
|
+
changeRows.push({ path: change.path, kind: change.kind, summary });
|
|
88
|
+
sections.push(renderChangeSection(change, before, after, exists));
|
|
89
|
+
}
|
|
90
|
+
const body = renderBody(recipe, sections);
|
|
91
|
+
return {
|
|
92
|
+
format: 'markdown',
|
|
93
|
+
body,
|
|
94
|
+
changes: changeRows,
|
|
95
|
+
envelope: {
|
|
96
|
+
status: 'ok',
|
|
97
|
+
recipe_version: recipe.recipeVersion ?? null,
|
|
98
|
+
compatible: true,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function renderBody(recipe, sections) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push(`# Recipe diff: ${recipe.name ?? recipe.recipeId}`);
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push(`**Recipe:** \`${recipe.recipeId}\` v${recipe.recipeVersion} ` +
|
|
107
|
+
`(compatibleWithApplied: ${JSON.stringify(recipe.compatibleWithApplied ?? [])})`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
lines.push(recipe.description ?? '');
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push(`This recipe proposes **${(recipe.changes ?? []).length}** changes.`);
|
|
112
|
+
lines.push('No file is written until you run `apt-tools apply-recipe . --confirm` ' +
|
|
113
|
+
'and accept each change individually.');
|
|
114
|
+
lines.push('');
|
|
115
|
+
for (const section of sections) {
|
|
116
|
+
lines.push(section);
|
|
117
|
+
lines.push('');
|
|
118
|
+
}
|
|
119
|
+
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
120
|
+
}
|
|
121
|
+
function renderChangeSummary(change, exists) {
|
|
122
|
+
const verb = change.kind === 'file-create' ? 'create' : change.kind === 'json-merge' ? 'merge into' : 'edit';
|
|
123
|
+
const target = exists ? change.path : `${change.path} (new file)`;
|
|
124
|
+
return `${verb} ${target}`;
|
|
125
|
+
}
|
|
126
|
+
function renderChangeSection(change, before, after, exists) {
|
|
127
|
+
const lines = [];
|
|
128
|
+
lines.push(`## ${change.path}`);
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push(`- kind: \`${change.kind}\``, `- marker: \`${change.marker ?? '(none)'}\``, `- target ${exists ? 'exists' : 'does not exist'} on disk`);
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('```diff');
|
|
133
|
+
const beforeBlock = before === '' ? '(empty / file does not exist)' : before;
|
|
134
|
+
for (const line of beforeBlock.split('\n'))
|
|
135
|
+
lines.push(`--- ${line}`);
|
|
136
|
+
lines.push('');
|
|
137
|
+
for (const line of after.split('\n'))
|
|
138
|
+
lines.push(`+++ ${line}`);
|
|
139
|
+
lines.push('```');
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Render the "after" content per change kind. For json-merge we stringify
|
|
144
|
+
* the patch payload; for text-insert / file-create we load the template
|
|
145
|
+
* from the recipe directory (when present).
|
|
146
|
+
*/
|
|
147
|
+
function renderAfter(change, recipe, adopterRoot) {
|
|
148
|
+
if (change.kind === 'json-merge') {
|
|
149
|
+
return JSON.stringify(change.after ?? {}, null, 2);
|
|
150
|
+
}
|
|
151
|
+
if (change.template) {
|
|
152
|
+
const tplPath = locateRecipeTemplate(recipe, change.template, adopterRoot);
|
|
153
|
+
if (tplPath && existsSync(tplPath)) {
|
|
154
|
+
return safeReadFile(tplPath);
|
|
155
|
+
}
|
|
156
|
+
return `(template not found on disk: ${change.template})`;
|
|
157
|
+
}
|
|
158
|
+
if (typeof change.after === 'string')
|
|
159
|
+
return change.after;
|
|
160
|
+
return '';
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolve a recipe template path relative to the framework's recipes/
|
|
164
|
+
* directory. The framework ships recipes at packages/framework/recipes/
|
|
165
|
+
* <id>/templates/. We compute the directory from this module's location
|
|
166
|
+
* so the resolver works regardless of cwd.
|
|
167
|
+
*/
|
|
168
|
+
function locateRecipeTemplate(recipe, templateName, _adopterRoot) {
|
|
169
|
+
// __dirname → packages/framework/src/cli/verify-proof/sandbox/
|
|
170
|
+
// → up four levels → packages/framework/
|
|
171
|
+
// Defense-in-depth: reject `../` escapes in templateName AND validate
|
|
172
|
+
// recipeId so a crafted id (e.g. '../../evil') cannot shift the anchor
|
|
173
|
+
// outside the framework root. discoverBundledRecipes() skips schema
|
|
174
|
+
// validation, so the ^[a-z][a-z0-9_-]*$ pattern may not have been enforced.
|
|
175
|
+
if (typeof recipe.recipeId !== 'string' || !/^[a-z][a-z0-9_-]*$/.test(recipe.recipeId)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const frameworkRoot = resolve(__dirname, '..', '..', '..', '..');
|
|
179
|
+
const templatesRoot = join(frameworkRoot, 'recipes', recipe.recipeId, 'templates');
|
|
180
|
+
return safeJoinUnderRoot(templatesRoot, templateName);
|
|
181
|
+
}
|
|
182
|
+
function safeReadFile(path) {
|
|
183
|
+
try {
|
|
184
|
+
return readFileSync(path, 'utf-8');
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=recipe-diff.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-diff.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/recipe-diff.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the framework's recipes directory at runtime. Works whether
|
|
3
|
+
* the module is loaded from source (packages/framework/src/...) or from
|
|
4
|
+
* the installed dist (.aperant/deps/node_modules/@aperant/framework/...).
|
|
5
|
+
*
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function resolveRecipesDir(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Walk the recipes/ directory and return every recipe.json that parses.
|
|
11
|
+
* Each entry is the raw recipe object (NOT validated — callers can
|
|
12
|
+
* call validateManifest if they need schema guarantees).
|
|
13
|
+
*
|
|
14
|
+
* @returns {Array<object>}
|
|
15
|
+
*/
|
|
16
|
+
export function discoverBundledRecipes(): Array<object>;
|
|
17
|
+
/**
|
|
18
|
+
* Read .aperant/config.json's verification.sandbox block, or null when
|
|
19
|
+
* absent / unparseable. Pure read — no defaults injected.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectDir
|
|
22
|
+
* @returns {object|null}
|
|
23
|
+
*/
|
|
24
|
+
export function readSandboxConfig(projectDir: string): object | null;
|
|
25
|
+
//# sourceMappingURL=sandbox-config.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-config.d.mts","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/sandbox-config.mjs"],"names":[],"mappings":"AA0BA;;;;;;GAMG;AACH,qCAFa,MAAM,CAMlB;AAED;;;;;;GAMG;AACH,0CAFa,KAAK,CAAC,MAAM,CAAC,CAiBzB;AAED;;;;;;GAMG;AACH,8CAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAavB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sandbox-config.mjs — central loader for sandbox recipes + the
|
|
3
|
+
* adopter's verification.sandbox config block.
|
|
4
|
+
*
|
|
5
|
+
* Two responsibilities:
|
|
6
|
+
*
|
|
7
|
+
* 1. discoverBundledRecipes() — walk packages/framework/recipes/<id>/
|
|
8
|
+
* and return every parseable recipe.json. Mirrors the bundled-
|
|
9
|
+
* drivers discovery pattern in commands/detect-runtime.mjs.
|
|
10
|
+
*
|
|
11
|
+
* 2. readSandboxConfig(projectDir) — read .aperant/config.json's
|
|
12
|
+
* verification.sandbox block (or null if absent). Pure read; no
|
|
13
|
+
* merge with config.local.json — sandbox state is shared, not
|
|
14
|
+
* per-device.
|
|
15
|
+
*
|
|
16
|
+
* Stays in src/cli/verify-proof/sandbox/ so consumers under that tree
|
|
17
|
+
* share one loader. The CLI commands (recipe-diff, apply-recipe,
|
|
18
|
+
* verify-sandbox) all import from here.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { dirname, join, resolve } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the framework's recipes directory at runtime. Works whether
|
|
26
|
+
* the module is loaded from source (packages/framework/src/...) or from
|
|
27
|
+
* the installed dist (.aperant/deps/node_modules/@aperant/framework/...).
|
|
28
|
+
*
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function resolveRecipesDir() {
|
|
32
|
+
// __dirname → packages/framework/src/cli/verify-proof/sandbox/
|
|
33
|
+
// → up four levels → packages/framework/
|
|
34
|
+
return resolve(__dirname, '..', '..', '..', '..', 'recipes');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Walk the recipes/ directory and return every recipe.json that parses.
|
|
38
|
+
* Each entry is the raw recipe object (NOT validated — callers can
|
|
39
|
+
* call validateManifest if they need schema guarantees).
|
|
40
|
+
*
|
|
41
|
+
* @returns {Array<object>}
|
|
42
|
+
*/
|
|
43
|
+
export function discoverBundledRecipes() {
|
|
44
|
+
const dir = resolveRecipesDir();
|
|
45
|
+
if (!existsSync(dir))
|
|
46
|
+
return [];
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
49
|
+
if (!entry.isDirectory())
|
|
50
|
+
continue;
|
|
51
|
+
const recipePath = join(dir, entry.name, 'recipe.json');
|
|
52
|
+
if (!existsSync(recipePath))
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
out.push(JSON.parse(readFileSync(recipePath, 'utf-8')));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* skip malformed recipes */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read .aperant/config.json's verification.sandbox block, or null when
|
|
65
|
+
* absent / unparseable. Pure read — no defaults injected.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} projectDir
|
|
68
|
+
* @returns {object|null}
|
|
69
|
+
*/
|
|
70
|
+
export function readSandboxConfig(projectDir) {
|
|
71
|
+
const path = join(resolve(projectDir), '.aperant', 'config.json');
|
|
72
|
+
if (!existsSync(path))
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
const config = JSON.parse(readFileSync(path, 'utf-8'));
|
|
76
|
+
const v = config?.verification;
|
|
77
|
+
if (v === null || typeof v !== 'object')
|
|
78
|
+
return null;
|
|
79
|
+
return v.sandbox ?? null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=sandbox-config.mjs.map
|