fix(dsh): самопроверка гейтит bootstrap/commands — бандл не падает без ctx.on/ctx.inject (I-2, #69)
This commit is contained in:
parent
f7b616d32e
commit
6d366ca177
@ -361,8 +361,9 @@ profile was removed; the real `desktop` profile was not touched.
|
||||
(`ctx.tools.guard`, `ctx.on` for `agent/pre-step`, `ctx.inject` for commands,
|
||||
`workingDirectory.get`), logs
|
||||
`[dsh-harness] self-check: <feature> not-ok: <detail>` for each missing
|
||||
service, and skips only the guard wiring when `tools.guard` is missing
|
||||
(bootstrap and `/role` keep working when their own services exist).
|
||||
service, and skips the feature whose service is missing (guards, bootstrap,
|
||||
or commands — each gated by its own check), so a DSH version without one of
|
||||
the services does not crash the bundle.
|
||||
`/dsh-harness-check` re-runs the same check on demand. This is the in-DSH
|
||||
compat check that closes RV-11 (D-6) — the terminal `--check` cannot see the
|
||||
SDK inside `app.asar`.
|
||||
|
||||
@ -222,8 +222,10 @@ repository layout:
|
||||
- The bundle self-checks the DSH contract it depends on at activation:
|
||||
`ctx.tools.guard`, `ctx.on` for `agent/pre-step`, `ctx.inject` for commands,
|
||||
`workingDirectory.get`. Each missing service is logged as
|
||||
`[dsh-harness] self-check: <feature> not-ok: …`; the guard wiring is the only
|
||||
feature skipped when its service is missing — bootstrap and `/role` keep
|
||||
working when their own services exist. The `/dsh-harness-check` command
|
||||
re-runs the same check on demand (one `ok` / `not-ok` line per feature).
|
||||
Disable with `selfCheck: false` / `checkCommand: false`.
|
||||
`[dsh-harness] self-check: <feature> not-ok: …`, and the feature whose
|
||||
service is missing is skipped (guards, bootstrap, or commands — each gated
|
||||
by its own check), so a DSH version without one of the services does not
|
||||
crash the bundle; the remaining features keep working. The
|
||||
`/dsh-harness-check` command re-runs the same check on demand (one
|
||||
`ok` / `not-ok` line per feature). Disable with `selfCheck: false` /
|
||||
`checkCommand: false`.
|
||||
|
||||
@ -105,13 +105,18 @@ export function apply(ctx, rawConfig = {}) {
|
||||
|
||||
const selfCheckReport =
|
||||
config.enabled && config.selfCheck ? collectSelfCheck(ctx, config) : null
|
||||
const guardsOk = selfCheckReport
|
||||
? selfCheckReport.find((entry) => entry.feature === 'guards')?.ok === true
|
||||
: true
|
||||
// A feature is wired only when its own service passed the self-check (or
|
||||
// the self-check itself is off): a missing service must disable its feature
|
||||
// instead of crashing the whole bundle.
|
||||
const serviceOk = (feature) =>
|
||||
!selfCheckReport ||
|
||||
selfCheckReport.find((entry) => entry.feature === feature)?.ok === true
|
||||
const guardsOk = serviceOk('guards')
|
||||
const bootstrapOk = serviceOk('bootstrap')
|
||||
const commandsOk = serviceOk('commands')
|
||||
|
||||
// Log every not-ok feature before any wiring runs, so a service that is
|
||||
// missing from this DSH version leaves a diagnostic line even when a later
|
||||
// wiring block throws on first use.
|
||||
// Log every not-ok feature before any wiring runs, so a missing service
|
||||
// always leaves a diagnostic line for the feature being skipped.
|
||||
if (selfCheckReport) {
|
||||
for (const entry of selfCheckReport) {
|
||||
if (!entry.ok) {
|
||||
@ -133,7 +138,7 @@ export function apply(ctx, rawConfig = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
if (config.enabled && config.bootstrap) {
|
||||
if (config.enabled && config.bootstrap && bootstrapOk) {
|
||||
ctx.on('agent/pre-step', async ({ agent, signal }, next) => {
|
||||
const decision = await next()
|
||||
if (decision.kind === 'reject' || signal?.aborted) return decision
|
||||
@ -161,7 +166,7 @@ export function apply(ctx, rawConfig = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
if (config.enabled && (config.roleCommand || config.checkCommand)) {
|
||||
if (config.enabled && (config.roleCommand || config.checkCommand) && commandsOk) {
|
||||
ctx.inject(['commands'], (commandCtx) => {
|
||||
if (config.roleCommand) {
|
||||
registerRoleCommand({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { apply } from '../bundle/index.js'
|
||||
import { collectSelfCheck } from '../bundle/lib/selfcheck.js'
|
||||
import { registerCheckCommand } from '../bundle/command-check.js'
|
||||
|
||||
@ -117,3 +118,52 @@ test('registerCheckCommand builds report text', async () => {
|
||||
assert.match(badResult.text, /guards: not-ok/)
|
||||
assert.match(badResult.text, /problem/)
|
||||
})
|
||||
|
||||
/** Logger fake that interpolates `%s` and records error lines. */
|
||||
function recordingLogger(errors) {
|
||||
return {
|
||||
info() {},
|
||||
warn() {},
|
||||
error(fmt, ...args) {
|
||||
const line = fmt.replace(/%s/g, () => String(args.shift() ?? ''))
|
||||
errors.push(line)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('apply does not throw when ctx.on and ctx.inject are missing', () => {
|
||||
const calls = { guard: 0 }
|
||||
const errors = []
|
||||
const ctx = {
|
||||
tools: { guard() { calls.guard += 1 } },
|
||||
workingDirectory: { get() {} },
|
||||
logger: recordingLogger(errors),
|
||||
}
|
||||
|
||||
assert.doesNotThrow(() => apply(ctx))
|
||||
assert.equal(calls.guard, 1, 'guards feature stays wired')
|
||||
assert.ok(
|
||||
errors.some((line) => line.includes('self-check: bootstrap not-ok')),
|
||||
'bootstrap not-ok is logged',
|
||||
)
|
||||
assert.ok(
|
||||
errors.some((line) => line.includes('self-check: commands not-ok')),
|
||||
'commands not-ok is logged',
|
||||
)
|
||||
})
|
||||
|
||||
test('apply wires guards, bootstrap, and commands with a full ctx', () => {
|
||||
const calls = { guard: 0, on: 0, inject: 0 }
|
||||
const ctx = {
|
||||
tools: { guard() { calls.guard += 1 } },
|
||||
on() { calls.on += 1 },
|
||||
inject() { calls.inject += 1 },
|
||||
workingDirectory: { get() {} },
|
||||
logger: { info() {}, warn() {}, error() {} },
|
||||
}
|
||||
|
||||
assert.doesNotThrow(() => apply(ctx))
|
||||
assert.equal(calls.guard, 1, 'guards wired')
|
||||
assert.equal(calls.on, 1, 'bootstrap wired')
|
||||
assert.equal(calls.inject, 1, 'commands wired')
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user