@aperant/framework 0.8.1 → 0.8.6
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 +443 -1
- package/agents/apt-planner.md +39 -7
- package/dist/cli/commands/adr.mjs +1 -1
- package/dist/cli/commands/audit-branch-current.d.mts +25 -0
- package/dist/cli/commands/audit-branch-current.d.mts.map +1 -0
- package/dist/cli/commands/audit-branch-current.mjs +252 -0
- package/dist/cli/commands/audit-branch-current.mjs.map +1 -0
- package/dist/cli/commands/audit.d.mts.map +1 -1
- package/dist/cli/commands/audit.mjs +13 -2
- package/dist/cli/commands/audit.mjs.map +1 -1
- package/dist/cli/commands/check-version.d.mts.map +1 -1
- package/dist/cli/commands/check-version.mjs +19 -1
- package/dist/cli/commands/check-version.mjs.map +1 -1
- package/dist/cli/commands/context.mjs +3 -3
- package/dist/cli/commands/context.mjs.map +1 -1
- package/dist/cli/commands/coverage-check.d.mts.map +1 -1
- package/dist/cli/commands/coverage-check.mjs +74 -7
- package/dist/cli/commands/coverage-check.mjs.map +1 -1
- package/dist/cli/commands/detect-runtime.d.mts +6 -0
- package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
- package/dist/cli/commands/detect-runtime.mjs +142 -0
- package/dist/cli/commands/detect-runtime.mjs.map +1 -0
- package/dist/cli/commands/driver-doctor.d.mts +29 -0
- package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
- package/dist/cli/commands/driver-doctor.mjs +291 -0
- package/dist/cli/commands/driver-doctor.mjs.map +1 -0
- package/dist/cli/commands/gate.mjs +14 -0
- package/dist/cli/commands/gate.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +9 -6
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/install-from-source.d.mts +2 -0
- package/dist/cli/commands/install-from-source.d.mts.map +1 -0
- package/dist/cli/commands/install-from-source.mjs +2 -0
- package/dist/cli/commands/install-from-source.mjs.map +1 -0
- package/dist/cli/commands/modes.mjs +1 -1
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +38 -1
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/triage.mjs +1 -1
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +9 -3
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
- package/dist/cli/coordination/event-schema.d.mts.map +1 -1
- package/dist/cli/coordination/event-schema.mjs +18 -0
- package/dist/cli/coordination/event-schema.mjs.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.d.mts +84 -0
- package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -0
- package/dist/cli/coverage-check/user-outcomes.mjs +265 -0
- package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -0
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +18 -1
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/context.d.mts +1 -0
- package/dist/cli/gate/context.d.mts.map +1 -1
- package/dist/cli/gate/context.mjs +14 -1
- package/dist/cli/gate/context.mjs.map +1 -1
- package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
- package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
- package/dist/cli/gate/gates/consistency-check.mjs +1 -0
- package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts +1 -0
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +1 -0
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +1 -0
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/gate/registry.d.mts.map +1 -1
- package/dist/cli/gate/registry.mjs +9 -1
- package/dist/cli/gate/registry.mjs.map +1 -1
- package/dist/cli/host/detect.mjs +1 -1
- package/dist/cli/host/detect.mjs.map +1 -1
- package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
- package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
- package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
- package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
- package/dist/cli/install/install-from-source.d.mts +10 -0
- package/dist/cli/install/install-from-source.d.mts.map +1 -0
- package/dist/cli/install/install-from-source.mjs +271 -0
- package/dist/cli/install/install-from-source.mjs.map +1 -0
- package/dist/cli/install/install-kernel.d.mts.map +1 -1
- package/dist/cli/install/install-kernel.mjs +24 -0
- package/dist/cli/install/install-kernel.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts +33 -11
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +163 -39
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +1 -1
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/install/runtime-migrate.d.mts +34 -14
- package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
- package/dist/cli/install/runtime-migrate.mjs +63 -30
- package/dist/cli/install/runtime-migrate.mjs.map +1 -1
- package/dist/cli/install/transforms/codex.d.mts.map +1 -1
- package/dist/cli/install/transforms/codex.mjs +28 -17
- package/dist/cli/install/transforms/codex.mjs.map +1 -1
- package/dist/cli/skill-author/contract.mjs +2 -2
- package/dist/cli/skill-author/contract.mjs.map +1 -1
- package/dist/cli/verify-proof/audit.d.mts +34 -0
- package/dist/cli/verify-proof/audit.d.mts.map +1 -0
- package/dist/cli/verify-proof/audit.mjs +53 -0
- package/dist/cli/verify-proof/audit.mjs.map +1 -0
- package/dist/cli/verify-proof/exec.d.mts +20 -0
- package/dist/cli/verify-proof/exec.d.mts.map +1 -0
- package/dist/cli/verify-proof/exec.mjs +74 -0
- package/dist/cli/verify-proof/exec.mjs.map +1 -0
- package/dist/cli/verify-proof/idl/index.d.mts +2 -0
- package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
- package/dist/cli/verify-proof/idl/index.mjs +13 -0
- package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
- package/dist/cli/verify-proof/idl/types.d.ts +10 -0
- package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
- package/dist/cli/verify-proof/idl/types.js +10 -0
- package/dist/cli/verify-proof/idl/types.js.map +1 -0
- package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
- package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
- package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
- package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
- package/dist/cli/verify-proof/resolver.d.mts +105 -0
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
- package/dist/cli/verify-proof/resolver.mjs +320 -0
- package/dist/cli/verify-proof/resolver.mjs.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts +30 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.mjs +209 -0
- package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
- package/dist/cli/verify-proof/trust.d.mts +70 -0
- package/dist/cli/verify-proof/trust.d.mts.map +1 -0
- package/dist/cli/verify-proof/trust.mjs +174 -0
- package/dist/cli/verify-proof/trust.mjs.map +1 -0
- package/dist/driver-sdk/conformance.d.ts +41 -0
- package/dist/driver-sdk/conformance.d.ts.map +1 -0
- package/dist/driver-sdk/conformance.js +123 -0
- package/dist/driver-sdk/conformance.js.map +1 -0
- package/dist/driver-sdk/errors.d.ts +75 -0
- package/dist/driver-sdk/errors.d.ts.map +1 -0
- package/dist/driver-sdk/errors.js +80 -0
- package/dist/driver-sdk/errors.js.map +1 -0
- package/dist/driver-sdk/idl.d.ts +221 -0
- package/dist/driver-sdk/idl.d.ts.map +1 -0
- package/dist/driver-sdk/idl.js +140 -0
- package/dist/driver-sdk/idl.js.map +1 -0
- package/dist/driver-sdk/index.d.ts +28 -0
- package/dist/driver-sdk/index.d.ts.map +1 -0
- package/dist/driver-sdk/index.js +28 -0
- package/dist/driver-sdk/index.js.map +1 -0
- package/dist/driver-sdk/manifest.d.ts +93 -0
- package/dist/driver-sdk/manifest.d.ts.map +1 -0
- package/dist/driver-sdk/manifest.js +12 -0
- package/dist/driver-sdk/manifest.js.map +1 -0
- package/dist/driver-sdk/retry.d.ts +33 -0
- package/dist/driver-sdk/retry.d.ts.map +1 -0
- package/dist/driver-sdk/retry.js +50 -0
- package/dist/driver-sdk/retry.js.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +4 -1
- package/dist/plugin/agents/apt-planner.md +39 -7
- package/dist/plugin/skills/apt/SKILL.md +75 -6
- package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
- package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
- package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
- package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
- package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
- package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
- package/dist/plugin/skills/apt-execute/SKILL.md +27 -4
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
- package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
- package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
- package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
- package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
- package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
- package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
- package/dist/plugin/skills/apt-plan/SKILL.md +73 -5
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
- package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -1
- package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
- package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
- package/dist/plugin/skills/apt-prototype/UI.md +0 -8
- package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
- package/dist/plugin/skills/apt-research/SKILL.md +526 -0
- package/dist/plugin/skills/apt-research/appendices/budget-loop.md +397 -0
- package/dist/plugin/skills/apt-research/appendices/claim-graph-schema.md +206 -0
- package/dist/plugin/skills/apt-research/appendices/domain-tools.md +236 -0
- package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
- package/dist/plugin/skills/apt-run/SKILL.md +114 -16
- package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
- package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
- package/dist/plugin/skills/apt-spar/SKILL.md +84 -23
- package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
- package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
- package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
- package/dist/plugin/skills/apt-update/SKILL.md +74 -17
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +87 -1
- package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
- package/dist/schemas/quick-task.d.ts +17 -17
- package/dist/schemas/quick-task.d.ts.map +1 -1
- package/dist/schemas/quick-task.js +2 -2
- package/dist/schemas/quick-task.js.map +1 -1
- package/dist/types/config.d.ts +33 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/state.d.ts +1 -1
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/task-record.d.ts +1 -1
- package/dist/types/task-record.d.ts.map +1 -1
- package/drivers/.gitkeep +0 -0
- package/drivers/api/README.md +40 -0
- package/drivers/api/driver.mjs +59 -0
- package/drivers/api/manifest.json +26 -0
- package/drivers/browser/README.md +105 -0
- package/drivers/browser/driver.mjs +134 -0
- package/drivers/browser/manifest.json +35 -0
- package/drivers/cli/README.md +44 -0
- package/drivers/cli/driver.mjs +62 -0
- package/drivers/cli/manifest.json +28 -0
- package/drivers/electron/README.md +64 -0
- package/drivers/electron/driver.mjs +87 -0
- package/drivers/electron/manifest.json +37 -0
- package/package.json +138 -133
- package/prompts/planner.md +1 -1
- package/prompts/spec_writer.md +1 -1
- package/skills/apt/SKILL.md +75 -6
- package/skills/apt-caveman/SKILL.md +64 -0
- package/skills/apt-debug/SKILL.md +36 -4
- package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
- package/skills/apt-discuss/SKILL.md +4 -4
- package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
- package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
- package/skills/apt-execute/SKILL.md +27 -4
- package/skills/apt-execute/appendices/tdd-mode.md +2 -10
- package/skills/apt-fan-out/SKILL.md +36 -1
- package/skills/apt-handoff/SKILL.md +228 -0
- package/skills/apt-improve/DEEPENING.md +0 -8
- package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
- package/skills/apt-improve/LANGUAGE.md +0 -8
- package/skills/apt-improve/SKILL.md +4 -4
- package/skills/apt-plan/SKILL.md +73 -5
- package/skills/apt-plan/adapters/conductor.md +1 -1
- package/skills/apt-planner.md +17 -1
- package/skills/apt-pr-review/SKILL.md +1 -1
- package/skills/apt-prototype/LOGIC.md +0 -8
- package/skills/apt-prototype/SKILL.md +4 -4
- package/skills/apt-prototype/UI.md +0 -8
- package/skills/apt-quick/SKILL.md +31 -1
- package/skills/apt-research/SKILL.md +526 -0
- package/skills/apt-research/appendices/budget-loop.md +397 -0
- package/skills/apt-research/appendices/claim-graph-schema.md +206 -0
- package/skills/apt-research/appendices/domain-tools.md +236 -0
- package/skills/apt-resume/SKILL.md +54 -31
- package/skills/apt-run/SKILL.md +114 -16
- package/skills/apt-setup/SKILL.md +97 -8
- package/skills/apt-ship/SKILL.md +165 -9
- package/skills/apt-spar/SKILL.md +84 -23
- package/skills/apt-triage/AGENT-BRIEF.md +0 -8
- package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
- package/skills/apt-triage/SKILL.md +2 -2
- package/skills/apt-update/SKILL.md +74 -17
- package/skills/apt-verify-proof/SKILL.md +87 -1
- package/skills/apt-zoom-out/SKILL.md +3 -3
- package/src/cli/commands/adr.mjs +1 -1
- package/src/cli/commands/audit-branch-current.mjs +250 -0
- package/src/cli/commands/audit.mjs +13 -2
- package/src/cli/commands/check-version.mjs +20 -1
- package/src/cli/commands/context.mjs +3 -3
- package/src/cli/commands/coverage-check.mjs +126 -44
- package/src/cli/commands/detect-runtime.mjs +143 -0
- package/src/cli/commands/driver-doctor.mjs +294 -0
- package/src/cli/commands/gate.mjs +15 -0
- package/src/cli/commands/init.mjs +9 -6
- package/src/cli/commands/install-from-source.mjs +1 -0
- package/src/cli/commands/modes.mjs +1 -1
- package/src/cli/commands/route.mjs +42 -1
- package/src/cli/commands/triage.mjs +1 -1
- package/src/cli/config/load.mjs +9 -3
- package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
- package/src/cli/coordination/event-schema.mjs +19 -0
- package/src/cli/coverage-check/user-outcomes.mjs +273 -0
- package/src/cli/dispatch.mjs +17 -1
- package/src/cli/gate/context.mjs +20 -1
- package/src/cli/gate/gates/consistency-check.mjs +1 -0
- package/src/cli/gate/gates/review-clean.mjs +1 -0
- package/src/cli/gate/gates/verify-approved.mjs +1 -0
- package/src/cli/gate/registry.mjs +11 -1
- package/src/cli/host/detect.mjs +1 -1
- package/src/cli/install/find-workspace-framework-version.mjs +88 -0
- package/src/cli/install/install-from-source.mjs +276 -0
- package/src/cli/install/install-kernel.mjs +24 -0
- package/src/cli/install/legacy-paths.mjs +164 -39
- package/src/cli/install/runtime-detect.mjs +1 -1
- package/src/cli/install/runtime-migrate.mjs +63 -30
- package/src/cli/install/transforms/codex.mjs +26 -17
- package/src/cli/skill-author/contract.mjs +2 -2
- package/src/cli/verify-proof/.gitkeep +0 -0
- package/src/cli/verify-proof/audit.mjs +55 -0
- package/src/cli/verify-proof/exec.mjs +74 -0
- package/src/cli/verify-proof/idl/index.mjs +23 -0
- package/src/cli/verify-proof/idl/types.ts +42 -0
- package/src/cli/verify-proof/manifest-schema.json +212 -0
- package/src/cli/verify-proof/manifest-validator.mjs +184 -0
- package/src/cli/verify-proof/resolver.mjs +330 -0
- package/src/cli/verify-proof/runtime-detect.mjs +211 -0
- package/src/cli/verify-proof/trust.mjs +187 -0
- package/templates/adr-format.md +0 -8
- package/templates/aperant-claude-md-appendix.md +1 -1
- package/templates/context-format.md +1 -9
- package/templates/proof-verification.md +51 -8
- package/workflows/verify-proof.md +130 -19
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* manifest-validator.mjs — hand-rolled JSON-Schema subset validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates driver manifests against manifest-schema.json. Implements
|
|
5
|
+
* exactly the JSON-Schema keywords we need:
|
|
6
|
+
*
|
|
7
|
+
* type / required / properties / items / enum / const / pattern / oneOf
|
|
8
|
+
*
|
|
9
|
+
* Returns { valid: true } or { valid: false, errors: [{ path, message }] }
|
|
10
|
+
* where `path` is a JSON-pointer (`/transport/local_command/binary`).
|
|
11
|
+
*
|
|
12
|
+
* No external deps — keeps the validator installable as `dist/` with the
|
|
13
|
+
* framework package and avoids dragging ajv (or any v5+ JSON-Schema
|
|
14
|
+
* validator) into the runtime surface for ~80 lines of validator code per
|
|
15
|
+
* AC15. We don't need allOf / anyOf / definitions — manifest-schema.json
|
|
16
|
+
* is intentionally a thin contract.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync } from 'node:fs'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
|
+
import { fileURLToPath } from 'node:url'
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
|
|
25
|
+
/** Lazy-loaded JSON-Schema (read once on first call). */
|
|
26
|
+
let cachedSchema = null
|
|
27
|
+
|
|
28
|
+
/** Read the bundled manifest-schema.json. */
|
|
29
|
+
export function loadManifestSchema() {
|
|
30
|
+
if (cachedSchema !== null) return cachedSchema
|
|
31
|
+
const schemaPath = join(__dirname, 'manifest-schema.json')
|
|
32
|
+
cachedSchema = JSON.parse(readFileSync(schemaPath, 'utf-8'))
|
|
33
|
+
return cachedSchema
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate a manifest object against the bundled schema.
|
|
38
|
+
*
|
|
39
|
+
* @param {unknown} manifest — usually JSON.parse(manifest.json contents).
|
|
40
|
+
* @param {object} [schema] — override the bundled schema (used by tests).
|
|
41
|
+
* @returns {{valid: true} | {valid: false, errors: Array<{path: string, message: string}>}}
|
|
42
|
+
*/
|
|
43
|
+
export function validateManifest(manifest, schema) {
|
|
44
|
+
const root = schema ?? loadManifestSchema()
|
|
45
|
+
const errors = []
|
|
46
|
+
validateNode(manifest, root, '', errors)
|
|
47
|
+
if (errors.length === 0) return { valid: true }
|
|
48
|
+
return { valid: false, errors }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Type check — JSON-Schema's union of "string", "number", "object", "array", "boolean", "null". */
|
|
52
|
+
function checkType(value, type) {
|
|
53
|
+
if (type === 'string') return typeof value === 'string'
|
|
54
|
+
if (type === 'number') return typeof value === 'number' && !Number.isNaN(value)
|
|
55
|
+
if (type === 'integer') return typeof value === 'number' && Number.isInteger(value)
|
|
56
|
+
if (type === 'boolean') return typeof value === 'boolean'
|
|
57
|
+
if (type === 'null') return value === null
|
|
58
|
+
if (type === 'array') return Array.isArray(value)
|
|
59
|
+
if (type === 'object') {
|
|
60
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Human-readable name for the actual shape of a value. Distinguishes
|
|
67
|
+
* `null`, `array`, and plain `object` — JS `typeof` reports all three as
|
|
68
|
+
* `'object'`, which makes error messages like "expected type object, got
|
|
69
|
+
* object" misleading.
|
|
70
|
+
*
|
|
71
|
+
* @param {unknown} value
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function describeType(value) {
|
|
75
|
+
if (value === null) return 'null'
|
|
76
|
+
if (Array.isArray(value)) return 'array'
|
|
77
|
+
return typeof value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Recursive validator. `errors` is mutated.
|
|
82
|
+
*
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @param {object} schema
|
|
85
|
+
* @param {string} path — JSON-pointer
|
|
86
|
+
* @param {Array<{path: string, message: string}>} errors
|
|
87
|
+
*/
|
|
88
|
+
function validateNode(value, schema, path, errors) {
|
|
89
|
+
// const
|
|
90
|
+
if ('const' in schema) {
|
|
91
|
+
if (value !== schema.const) {
|
|
92
|
+
errors.push({
|
|
93
|
+
path: path || '/',
|
|
94
|
+
message: `expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`,
|
|
95
|
+
})
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// type
|
|
101
|
+
if (schema.type !== undefined) {
|
|
102
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type]
|
|
103
|
+
if (!types.some((t) => checkType(value, t))) {
|
|
104
|
+
errors.push({
|
|
105
|
+
path: path || '/',
|
|
106
|
+
message: `expected type ${types.join('|')}, got ${describeType(value)}`,
|
|
107
|
+
})
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// enum
|
|
113
|
+
if (Array.isArray(schema.enum)) {
|
|
114
|
+
if (!schema.enum.includes(value)) {
|
|
115
|
+
errors.push({
|
|
116
|
+
path: path || '/',
|
|
117
|
+
message: `value ${JSON.stringify(value)} not in enum [${schema.enum.join(', ')}]`,
|
|
118
|
+
})
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// pattern
|
|
124
|
+
if (typeof schema.pattern === 'string' && typeof value === 'string') {
|
|
125
|
+
const re = new RegExp(schema.pattern)
|
|
126
|
+
if (!re.test(value)) {
|
|
127
|
+
errors.push({
|
|
128
|
+
path: path || '/',
|
|
129
|
+
message: `value ${JSON.stringify(value)} does not match pattern ${schema.pattern}`,
|
|
130
|
+
})
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// oneOf
|
|
136
|
+
if (Array.isArray(schema.oneOf)) {
|
|
137
|
+
let matchCount = 0
|
|
138
|
+
const branchErrors = []
|
|
139
|
+
for (let i = 0; i < schema.oneOf.length; i++) {
|
|
140
|
+
const sub = schema.oneOf[i]
|
|
141
|
+
const subErrors = []
|
|
142
|
+
validateNode(value, sub, path, subErrors)
|
|
143
|
+
if (subErrors.length === 0) matchCount++
|
|
144
|
+
else branchErrors.push({ branch: i, errors: subErrors })
|
|
145
|
+
}
|
|
146
|
+
if (matchCount !== 1) {
|
|
147
|
+
errors.push({
|
|
148
|
+
path: path || '/',
|
|
149
|
+
message:
|
|
150
|
+
matchCount === 0
|
|
151
|
+
? `oneOf: value matched no branch (${branchErrors.length} branches tried)`
|
|
152
|
+
: `oneOf: value matched ${matchCount} branches (must match exactly 1)`,
|
|
153
|
+
})
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// object validation
|
|
159
|
+
if (checkType(value, 'object')) {
|
|
160
|
+
const obj = /** @type {Record<string, unknown>} */ (value)
|
|
161
|
+
if (Array.isArray(schema.required)) {
|
|
162
|
+
for (const key of schema.required) {
|
|
163
|
+
if (!(key in obj)) {
|
|
164
|
+
errors.push({ path: `${path}/${key}`, message: `required property "${key}" missing` })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (schema.properties && typeof schema.properties === 'object') {
|
|
169
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
170
|
+
if (key in obj) {
|
|
171
|
+
validateNode(obj[key], /** @type {object} */ (subSchema), `${path}/${key}`, errors)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// array validation
|
|
178
|
+
if (checkType(value, 'array') && schema.items) {
|
|
179
|
+
const arr = /** @type {unknown[]} */ (value)
|
|
180
|
+
for (let i = 0; i < arr.length; i++) {
|
|
181
|
+
validateNode(arr[i], schema.items, `${path}/${i}`, errors)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolver.mjs — driver selection + fallback chain.
|
|
3
|
+
*
|
|
4
|
+
* Given a project's runtime capabilities, the discovered drivers, and the
|
|
5
|
+
* test's required IDL capabilities, return the best driver — or throw
|
|
6
|
+
* UnsatisfiedRequiredCapabilityError if no driver in the chain satisfies
|
|
7
|
+
* a REQUIRED capability (ID-02 fail-closed).
|
|
8
|
+
*
|
|
9
|
+
* Scoring (ID-02, ordered high → low):
|
|
10
|
+
* 1. capability_coverage — how many required capabilities the driver has
|
|
11
|
+
* 2. compatibility — does the runtime match the manifest.targets?
|
|
12
|
+
* 3. stability — ga > beta > experimental
|
|
13
|
+
* 4. priority — higher wins (manifest.priority)
|
|
14
|
+
* 5. locality — bundled (packages/framework/drivers/) > user-installed
|
|
15
|
+
* (.aperant/drivers/) > registry (.aperant/registry-cache/)
|
|
16
|
+
*
|
|
17
|
+
* Ties are broken by the next dimension; if every dimension is equal the
|
|
18
|
+
* driver with the lexicographically smaller driverId wins (deterministic).
|
|
19
|
+
*
|
|
20
|
+
* v0.8.6 — `resolveDriversForOutcomes(opts)` adds per-outcome driver
|
|
21
|
+
* dispatch for cross-surface specs (per spec §ID-03). It accepts the
|
|
22
|
+
* positional `outcomes[]` produced by `parseUserOutcomes` and returns
|
|
23
|
+
* `Array<{outcome_id, surface, driver}>` — one entry per
|
|
24
|
+
* (outcome, surface) pair. For `[electron+web]` outcomes it emits TWO
|
|
25
|
+
* entries with the same outcome_id and different drivers. The
|
|
26
|
+
* outcome_id field is passed through verbatim from parser → resolver
|
|
27
|
+
* → workflow runner with no name rewriting (the positional-ID
|
|
28
|
+
* contract).
|
|
29
|
+
*
|
|
30
|
+
* Existing `resolveDriver()` and `scoreDriver()` exports are unchanged.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { UnsatisfiedRequiredCapabilityError } from '@aperant/framework/driver-sdk'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {import('@aperant/framework/driver-sdk').DriverManifest} DriverManifest
|
|
37
|
+
* @typedef {import('./runtime-detect.mjs').RuntimeCapabilities} RuntimeCapabilities
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const STABILITY_RANK = { ga: 3, beta: 2, experimental: 1 }
|
|
41
|
+
const LOCALITY_RANK = { bundled: 3, user: 2, registry: 1 }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Score a single driver against the project's runtime + required capabilities.
|
|
45
|
+
*
|
|
46
|
+
* @param {DriverManifest & {locality?: 'bundled'|'user'|'registry'}} manifest
|
|
47
|
+
* @param {RuntimeCapabilities} runtime
|
|
48
|
+
* @param {string[]} required
|
|
49
|
+
* @returns {{score: number[], reason?: string}}
|
|
50
|
+
*/
|
|
51
|
+
export function scoreDriver(manifest, runtime, required) {
|
|
52
|
+
const caps = new Set(manifest.capabilities ?? [])
|
|
53
|
+
const coverage = required.filter((c) => caps.has(c)).length
|
|
54
|
+
const compatibility = matchesRuntime(manifest, runtime) ? 1 : 0
|
|
55
|
+
const stability = STABILITY_RANK[manifest.stability] ?? 0
|
|
56
|
+
const priority = manifest.priority ?? 0
|
|
57
|
+
const locality = LOCALITY_RANK[manifest.locality ?? 'bundled'] ?? 0
|
|
58
|
+
return { score: [coverage, compatibility, stability, priority, locality] }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a manifest's targets[] mentions a runtime the project has.
|
|
63
|
+
*
|
|
64
|
+
* @param {DriverManifest} manifest
|
|
65
|
+
* @param {RuntimeCapabilities} runtime
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function matchesRuntime(manifest, runtime) {
|
|
69
|
+
const targets = manifest.targets ?? []
|
|
70
|
+
for (const t of targets) {
|
|
71
|
+
// Strip any version range suffix (e.g. "electron@>=25" → "electron").
|
|
72
|
+
const stripped = String(t).split('@')[0].toLowerCase()
|
|
73
|
+
if (stripped === 'electron' && runtime.is_electron) return true
|
|
74
|
+
if (stripped === 'tauri' && runtime.is_tauri) return true
|
|
75
|
+
if ((stripped === 'browser' || stripped === 'web') && runtime.is_web_frontend) return true
|
|
76
|
+
if (stripped === 'next' && runtime.is_nextjs) return true
|
|
77
|
+
if (stripped === 'nuxt' && runtime.is_nuxt) return true
|
|
78
|
+
if (stripped === 'expo' && runtime.is_expo) return true
|
|
79
|
+
if (stripped === 'react-native' && runtime.is_react_native) return true
|
|
80
|
+
if (stripped === 'node' && runtime.has_node) return true
|
|
81
|
+
if (stripped === 'cli' && runtime.has_node) return true
|
|
82
|
+
if (stripped === 'api' && runtime.has_node) return true
|
|
83
|
+
if (stripped === 'fake') return true // fixture-only — always matches
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compare two driver-score tuples lexicographically.
|
|
90
|
+
* @param {number[]} a
|
|
91
|
+
* @param {number[]} b
|
|
92
|
+
* @returns {number}
|
|
93
|
+
*/
|
|
94
|
+
function compareScores(a, b) {
|
|
95
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
96
|
+
if (a[i] !== b[i]) return b[i] - a[i] // higher wins
|
|
97
|
+
}
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the best driver for a given (runtime, drivers, override, required)
|
|
103
|
+
* tuple.
|
|
104
|
+
*
|
|
105
|
+
* @param {Object} opts
|
|
106
|
+
* @param {DriverManifest[]} opts.drivers — manifests of every discovered driver.
|
|
107
|
+
* @param {RuntimeCapabilities} opts.runtime — project's runtime capabilities.
|
|
108
|
+
* @param {string[]} opts.required_capabilities — IDL verbs the test needs.
|
|
109
|
+
* @param {string[]} [opts.required_stability] — accepted stability values; default ['ga'].
|
|
110
|
+
* @param {string[]} [opts.override] — config.json:verification.runtimes[]: explicit driverId precedence.
|
|
111
|
+
* @returns {{selected: DriverManifest, fallback_attempts: Array<{driver_id: string, reason: string}>}}
|
|
112
|
+
* @throws {UnsatisfiedRequiredCapabilityError} when no driver satisfies a REQUIRED capability.
|
|
113
|
+
*/
|
|
114
|
+
export function resolveDriver(opts) {
|
|
115
|
+
const drivers = opts.drivers ?? []
|
|
116
|
+
const runtime = opts.runtime
|
|
117
|
+
const required = opts.required_capabilities ?? []
|
|
118
|
+
const stabilityFilter = new Set(opts.required_stability ?? ['ga'])
|
|
119
|
+
const override = opts.override ?? []
|
|
120
|
+
|
|
121
|
+
const fallback = []
|
|
122
|
+
|
|
123
|
+
// Apply override precedence first (config-listed driverIds come first).
|
|
124
|
+
const overrideOrdered = []
|
|
125
|
+
const overrideSet = new Set(override)
|
|
126
|
+
for (const id of override) {
|
|
127
|
+
const d = drivers.find((m) => m.driverId === id)
|
|
128
|
+
if (d) overrideOrdered.push(d)
|
|
129
|
+
}
|
|
130
|
+
const remaining = drivers.filter((m) => !overrideSet.has(m.driverId))
|
|
131
|
+
const ordered = [...overrideOrdered, ...remaining]
|
|
132
|
+
|
|
133
|
+
// Filter by stability; rejected drivers go into fallback_attempts.
|
|
134
|
+
const stableOnly = []
|
|
135
|
+
for (const d of ordered) {
|
|
136
|
+
if (!stabilityFilter.has(d.stability)) {
|
|
137
|
+
fallback.push({
|
|
138
|
+
driver_id: d.driverId,
|
|
139
|
+
reason: `stability ${d.stability} not in allowed set [${[...stabilityFilter].join(', ')}]`,
|
|
140
|
+
})
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
stableOnly.push(d)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Override is hard precedence (per opts.override docstring "explicit
|
|
147
|
+
// driverId precedence"): when set, try override-listed drivers in
|
|
148
|
+
// override order — the first one that satisfies every required
|
|
149
|
+
// capability wins, regardless of compatibility/priority/stability
|
|
150
|
+
// ranking. Non-matching override entries land in fallback. Only when
|
|
151
|
+
// every override entry misses a required cap do we fall through to
|
|
152
|
+
// score-based pick on the remaining drivers.
|
|
153
|
+
const overrideListed = stableOnly.filter((m) => overrideSet.has(m.driverId))
|
|
154
|
+
for (const candidate of overrideListed) {
|
|
155
|
+
const caps = new Set(candidate.capabilities ?? [])
|
|
156
|
+
const missing = required.filter((c) => !caps.has(c))
|
|
157
|
+
if (missing.length === 0) {
|
|
158
|
+
return { selected: candidate, fallback_attempts: fallback }
|
|
159
|
+
}
|
|
160
|
+
fallback.push({
|
|
161
|
+
driver_id: candidate.driverId,
|
|
162
|
+
reason: `override-listed but missing required capabilities: ${missing.join(', ')}`,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Score every non-override survivor against required capabilities.
|
|
167
|
+
const nonOverride = stableOnly.filter((m) => !overrideSet.has(m.driverId))
|
|
168
|
+
const scored = nonOverride.map((m) => ({ manifest: m, ...scoreDriver(m, runtime, required) }))
|
|
169
|
+
|
|
170
|
+
// Sort highest-first, tie-break on driverId for determinism.
|
|
171
|
+
scored.sort((a, b) => {
|
|
172
|
+
const cmp = compareScores(a.score, b.score)
|
|
173
|
+
if (cmp !== 0) return cmp
|
|
174
|
+
return a.manifest.driverId.localeCompare(b.manifest.driverId)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Pick the top scorer that covers EVERY required capability. Others go into fallback.
|
|
178
|
+
for (const candidate of scored) {
|
|
179
|
+
const caps = new Set(candidate.manifest.capabilities ?? [])
|
|
180
|
+
const missing = required.filter((c) => !caps.has(c))
|
|
181
|
+
if (missing.length === 0) {
|
|
182
|
+
return { selected: candidate.manifest, fallback_attempts: fallback }
|
|
183
|
+
}
|
|
184
|
+
fallback.push({
|
|
185
|
+
driver_id: candidate.manifest.driverId,
|
|
186
|
+
reason: `missing required capabilities: ${missing.join(', ')}`,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Exhausted the chain — fail closed (ID-02).
|
|
191
|
+
throw new UnsatisfiedRequiredCapabilityError(
|
|
192
|
+
`No driver in the chain satisfies every required capability: [${required.join(', ')}]`,
|
|
193
|
+
required[0],
|
|
194
|
+
fallback,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Map a User Outcomes surface tag to the runtime-capability flag a driver
|
|
200
|
+
* must carry (via `matchesRuntime`) to be eligible for that surface.
|
|
201
|
+
*
|
|
202
|
+
* Closed surface set: `electron | web | cli | api | electron+web`.
|
|
203
|
+
* `electron+web` is NOT a single flag — it's a cross-surface tag the
|
|
204
|
+
* workflow runner expands into two single-surface dispatches.
|
|
205
|
+
*
|
|
206
|
+
* @param {('electron'|'web'|'cli'|'api')} surface
|
|
207
|
+
* @returns {('is_electron'|'is_web_frontend'|'has_node')}
|
|
208
|
+
*/
|
|
209
|
+
export function surfaceToRequiredFlag(surface) {
|
|
210
|
+
switch (surface) {
|
|
211
|
+
case 'electron':
|
|
212
|
+
return 'is_electron'
|
|
213
|
+
case 'web':
|
|
214
|
+
return 'is_web_frontend'
|
|
215
|
+
case 'cli':
|
|
216
|
+
case 'api':
|
|
217
|
+
return 'has_node'
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`surfaceToRequiredFlag: unknown surface "${surface}"`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Resolve a driver per outcome surface tag, NOT per verify-proof run
|
|
225
|
+
* (per spec §ID-03). For each input outcome the function returns one
|
|
226
|
+
* `{outcome_id, surface, driver}` entry; for `[electron+web]` outcomes
|
|
227
|
+
* it returns TWO entries (electron + web) sharing the same outcome_id.
|
|
228
|
+
*
|
|
229
|
+
* The `outcome_id` field on each returned entry is the positional ID
|
|
230
|
+
* produced by `parseUserOutcomes` (e.g. `O1`, `O2`). The resolver does
|
|
231
|
+
* NOT rewrite or regenerate IDs — they pass through verbatim so the
|
|
232
|
+
* workflow runner can correlate proof artifacts to spec lines.
|
|
233
|
+
*
|
|
234
|
+
* Each per-surface dispatch synthesizes a runtime-capabilities view
|
|
235
|
+
* that ONLY claims the surface's required flag (e.g. `is_electron`
|
|
236
|
+
* alone for an electron-surface outcome, even when the project also
|
|
237
|
+
* carries `is_web_frontend`). This forces the existing scoring to
|
|
238
|
+
* pick the per-surface driver — without it, a project with BOTH
|
|
239
|
+
* electron AND web capabilities would always select the highest-
|
|
240
|
+
* priority bundled driver (currently `browser`) for every outcome.
|
|
241
|
+
*
|
|
242
|
+
* Delegates to the existing `resolveDriver()` for the actual scoring
|
|
243
|
+
* — the scoring code stays in one place.
|
|
244
|
+
*
|
|
245
|
+
* @param {Object} opts
|
|
246
|
+
* @param {DriverManifest[]} opts.drivers
|
|
247
|
+
* @param {RuntimeCapabilities} opts.runtime
|
|
248
|
+
* @param {Array<{id: string, surface: 'electron'|'web'|'cli'|'api'|'electron+web'}>} opts.outcomes
|
|
249
|
+
* @param {string[]} [opts.override]
|
|
250
|
+
* @param {string[]} [opts.required_stability]
|
|
251
|
+
* @param {string[]} [opts.required_capabilities] — defaults to ['screenshot']
|
|
252
|
+
* @returns {Array<{outcome_id: string, surface: string, driver: DriverManifest, fallback_attempts: Array<{driver_id: string, reason: string}>}>}
|
|
253
|
+
*/
|
|
254
|
+
export function resolveDriversForOutcomes(opts) {
|
|
255
|
+
const drivers = opts.drivers ?? []
|
|
256
|
+
const runtime = opts.runtime
|
|
257
|
+
const outcomes = opts.outcomes ?? []
|
|
258
|
+
const required = opts.required_capabilities ?? ['screenshot']
|
|
259
|
+
const override = opts.override
|
|
260
|
+
const stability = opts.required_stability
|
|
261
|
+
|
|
262
|
+
/** @type {Array<{outcome_id: string, surface: string, driver: DriverManifest, fallback_attempts: Array<{driver_id: string, reason: string}>}>} */
|
|
263
|
+
const results = []
|
|
264
|
+
|
|
265
|
+
for (const outcome of outcomes) {
|
|
266
|
+
const surfacesToDispatch =
|
|
267
|
+
outcome.surface === 'electron+web' ? ['electron', 'web'] : [outcome.surface]
|
|
268
|
+
|
|
269
|
+
for (const surface of surfacesToDispatch) {
|
|
270
|
+
const flag = surfaceToRequiredFlag(/** @type {'electron'|'web'|'cli'|'api'} */ (surface))
|
|
271
|
+
// Synthesize a per-surface runtime view: only the surface's
|
|
272
|
+
// required flag is on; every other flag is masked off so the
|
|
273
|
+
// scoring prefers per-surface drivers (otherwise, on a
|
|
274
|
+
// multi-runtime project, the highest-priority bundled driver
|
|
275
|
+
// always wins). `has_node` stays on for cli/api since multiple
|
|
276
|
+
// drivers may target node.
|
|
277
|
+
const surfaceRuntime = maskRuntimeForSurface(runtime, flag)
|
|
278
|
+
const resolved = resolveDriver({
|
|
279
|
+
drivers,
|
|
280
|
+
runtime: surfaceRuntime,
|
|
281
|
+
required_capabilities: required,
|
|
282
|
+
...(override ? { override } : {}),
|
|
283
|
+
...(stability ? { required_stability: stability } : {}),
|
|
284
|
+
})
|
|
285
|
+
results.push({
|
|
286
|
+
outcome_id: outcome.id,
|
|
287
|
+
surface,
|
|
288
|
+
driver: resolved.selected,
|
|
289
|
+
fallback_attempts: resolved.fallback_attempts,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return results
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Return a copy of `runtime` with every flag false EXCEPT the one named
|
|
299
|
+
* by `flag`. Used to scope the resolver's compatibility check to a
|
|
300
|
+
* single surface during per-outcome dispatch.
|
|
301
|
+
*
|
|
302
|
+
* @param {RuntimeCapabilities} runtime
|
|
303
|
+
* @param {string} flag — one of: is_electron | is_web_frontend | has_node
|
|
304
|
+
* @returns {RuntimeCapabilities}
|
|
305
|
+
*/
|
|
306
|
+
function maskRuntimeForSurface(runtime, flag) {
|
|
307
|
+
/** @type {RuntimeCapabilities} */
|
|
308
|
+
const masked = {
|
|
309
|
+
is_electron: false,
|
|
310
|
+
is_tauri: false,
|
|
311
|
+
is_nextjs: false,
|
|
312
|
+
is_nuxt: false,
|
|
313
|
+
is_web_frontend: false,
|
|
314
|
+
is_expo: false,
|
|
315
|
+
is_react_native: false,
|
|
316
|
+
has_node: false,
|
|
317
|
+
}
|
|
318
|
+
// Only flip the surface's flag on if the project actually carries it
|
|
319
|
+
// (otherwise the resolver should genuinely fail — fail-closed).
|
|
320
|
+
if (flag === 'is_electron' && runtime.is_electron) masked.is_electron = true
|
|
321
|
+
if (flag === 'is_web_frontend' && runtime.is_web_frontend) {
|
|
322
|
+
masked.is_web_frontend = true
|
|
323
|
+
// Carry the framework hint too (Next.js / Nuxt) so drivers that
|
|
324
|
+
// target browser via those frameworks still match.
|
|
325
|
+
masked.is_nextjs = runtime.is_nextjs
|
|
326
|
+
masked.is_nuxt = runtime.is_nuxt
|
|
327
|
+
}
|
|
328
|
+
if (flag === 'has_node') masked.has_node = runtime.has_node
|
|
329
|
+
return masked
|
|
330
|
+
}
|