create-principles-disciple 1.145.32 → 1.145.34
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/_release/product-identity.json +1 -1
- package/codex-adapter/dist/ingestion/ingestion.js +3 -1
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ActivationsConsoleModel.d.ts +19 -2
- package/console/dist/server/models/ActivationsConsoleModel.js +124 -10
- package/console/dist/server/models/OwnerDecisionConsoleModel.d.ts +3 -1
- package/console/dist/server/models/OwnerDecisionConsoleModel.js +6 -2
- package/console/dist/server/routes/activations.js +7 -2
- package/console/dist/server/routes/owner-decisions.js +1 -1
- package/core/dist/runtime-v2/activation/index.d.ts +6 -0
- package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/index.js +3 -0
- package/core/dist/runtime-v2/activation/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts +75 -0
- package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js +131 -37
- package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js.map +1 -1
- package/core/dist/runtime-v2/activation/production-gate-deps.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/production-gate-deps.js +7 -3
- package/core/dist/runtime-v2/activation/production-gate-deps.js.map +1 -1
- package/core/dist/runtime-v2/activation/recovery-composition.d.ts +54 -0
- package/core/dist/runtime-v2/activation/recovery-composition.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/recovery-composition.js +129 -0
- package/core/dist/runtime-v2/activation/recovery-composition.js.map +1 -0
- package/core/dist/runtime-v2/activation/recovery-control-surfaces.d.ts +62 -0
- package/core/dist/runtime-v2/activation/recovery-control-surfaces.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/recovery-control-surfaces.js +84 -0
- package/core/dist/runtime-v2/activation/recovery-control-surfaces.js.map +1 -0
- package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts +13 -1
- package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js +24 -1
- package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -2
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/trajectory-schema.d.ts.map +1 -1
- package/core/dist/runtime-v2/trajectory-schema.js +12 -1
- package/core/dist/runtime-v2/trajectory-schema.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +10 -0
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js +1 -0
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +111 -46
- package/dist/installer.js.map +1 -1
- package/host-runtime/dist/cli-recovery-probe.d.ts +30 -0
- package/host-runtime/dist/cli-recovery-probe.js +169 -0
- package/host-runtime/dist/governance-signal-admission.js +22 -5
- package/host-runtime/dist/host-liveness-contract.d.ts +51 -28
- package/host-runtime/dist/host-liveness-contract.js +143 -46
- package/host-runtime/dist/index.d.ts +2 -0
- package/host-runtime/dist/index.js +2 -0
- package/host-runtime/dist/production-pain-evidence.d.ts +2 -0
- package/host-runtime/dist/production-pain-evidence.js +46 -8
- package/host-runtime/dist/production-rulehost-gate.js +24 -6
- package/host-runtime/dist/workbuddy-rule-context-provider.d.ts +33 -0
- package/host-runtime/dist/workbuddy-rule-context-provider.js +126 -0
- package/host-runtime/dist/workspace-behavior-evidence.d.ts +22 -5
- package/host-runtime/dist/workspace-behavior-evidence.js +41 -11
- package/host-runtime/package.json +1 -1
- package/package.json +2 -2
- package/pd-cli/dist/commands/recovery-reachability-probe.d.ts +25 -0
- package/pd-cli/dist/commands/recovery-reachability-probe.d.ts.map +1 -0
- package/pd-cli/dist/commands/recovery-reachability-probe.js +50 -0
- package/pd-cli/dist/commands/recovery-reachability-probe.js.map +1 -0
- package/pd-cli/dist/commands/runtime-activation.d.ts +10 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +81 -3
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +385 -385
- package/plugin/dist/governance-audit.js +1 -1
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/dist/rulehost-evidence.js +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/dist/installer.d.ts.map +1 -1
- package/release-manager/dist/installer.js +111 -46
- package/release-manager/dist/installer.js.map +1 -1
- package/release-manager/package.json +2 -2
- package/workbuddy-adapter/dist/codec/input-decoder.d.ts +14 -11
- package/workbuddy-adapter/dist/codec/input-decoder.js +28 -14
- package/workbuddy-adapter/dist/pd-hook.js +31 -12
- package/workbuddy-adapter/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RecoveryCapabilityEvidence } from '@principles/core/runtime-v2';
|
|
2
|
+
export declare const DEACTIVATE_DRY_RUN_PROBE_ACTIVATION_ID = "recovery-probe-nonexistent";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the built pd CLI entry for the CURRENT environment. Both layouts
|
|
5
|
+
* place the sibling packages at the same relative depth: repo checkouts at
|
|
6
|
+
* `packages/host-runtime/dist` and installed runtimes at
|
|
7
|
+
* `~/.pd/runtime/host-runtime/dist`, each with `pd-cli/dist/index.js` two
|
|
8
|
+
* levels up. Returns null when no built entry exists (callers fail closed).
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveCliRecoveryEntry(): string | null;
|
|
11
|
+
export interface CliRecoveryProbeOptions {
|
|
12
|
+
/** Absolute path to the built pd CLI entry (dist/index.js). */
|
|
13
|
+
cliEntry: string;
|
|
14
|
+
workspaceDir: string;
|
|
15
|
+
/** When supplied, the status listing must include this activation (workspace-targeting proof). */
|
|
16
|
+
expectedActivationId?: string;
|
|
17
|
+
/** Probe budget in ms (default 15s). A timeout fails the capability closed — tests on loaded machines may raise it. */
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
/** `pd activation list --json` — the pd_status status surface, read-only. */
|
|
21
|
+
export declare function probeCliActivationStatus(options: CliRecoveryProbeOptions): Promise<RecoveryCapabilityEvidence>;
|
|
22
|
+
/**
|
|
23
|
+
* `pd activation deactivate --dry-run` — the REAL deactivate entry (parsing,
|
|
24
|
+
* workspace binding, target resolution, preconditions) without audit write or
|
|
25
|
+
* store mutation. The probe id does not exist, so the precondition outcome is
|
|
26
|
+
* an honest not-found while the entry itself is proven executable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function probeCliDeactivateDryRun(options: CliRecoveryProbeOptions): Promise<RecoveryCapabilityEvidence>;
|
|
29
|
+
/** Both CLI-channel capabilities plus explicit NOT-VERIFIED entries for the console channel (fail closed, never fabricated). */
|
|
30
|
+
export declare function probeCliRecoveryCapabilities(options: CliRecoveryProbeOptions): Promise<RecoveryCapabilityEvidence[]>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 review round 4 (P1): per-capability
|
|
7
|
+
* CLI-channel reachability probes, shared by the pd-cli readiness path and
|
|
8
|
+
* the pd-console readiness assembly (both packages depend on host-runtime).
|
|
9
|
+
*
|
|
10
|
+
* Every probe executes the REAL pd CLI as a bounded subprocess against the
|
|
11
|
+
* TARGET workspace — static command lists, no shell, output capped, timeout
|
|
12
|
+
* enforced. Evidence is produced fresh on every call; nothing is cached and
|
|
13
|
+
* nothing destructive runs:
|
|
14
|
+
* - `pd_status`: `pd activation list --json` (read-only status surface);
|
|
15
|
+
* - `rulecode_deactivate`: `pd activation deactivate --activation-id
|
|
16
|
+
* recovery-probe-nonexistent --dry-run --json` — the REAL deactivate entry
|
|
17
|
+
* through real parsing, workspace binding, and target-resolution
|
|
18
|
+
* preconditions, with the dry-run guard returning BEFORE any governance
|
|
19
|
+
* audit write or store mutation. A nonexistent probe id keeps the
|
|
20
|
+
* precondition honest without touching any real activation.
|
|
21
|
+
*
|
|
22
|
+
* Console-channel capabilities (`rulecode_global_pause`,
|
|
23
|
+
* `owner_review_access`) can never be verified from a CLI subprocess — the
|
|
24
|
+
* caller receives explicit NOT-VERIFIED entries for them instead of
|
|
25
|
+
* fabricated ok flags (fail closed; SPEC R4 §2.4.7).
|
|
26
|
+
*/
|
|
27
|
+
const PROBE_TIMEOUT_MS = 15_000;
|
|
28
|
+
const MAX_STDOUT_BYTES = 1024 * 1024;
|
|
29
|
+
export const DEACTIVATE_DRY_RUN_PROBE_ACTIVATION_ID = 'recovery-probe-nonexistent';
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the built pd CLI entry for the CURRENT environment. Both layouts
|
|
32
|
+
* place the sibling packages at the same relative depth: repo checkouts at
|
|
33
|
+
* `packages/host-runtime/dist` and installed runtimes at
|
|
34
|
+
* `~/.pd/runtime/host-runtime/dist`, each with `pd-cli/dist/index.js` two
|
|
35
|
+
* levels up. Returns null when no built entry exists (callers fail closed).
|
|
36
|
+
*/
|
|
37
|
+
export function resolveCliRecoveryEntry() {
|
|
38
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const candidates = [
|
|
40
|
+
path.resolve(here, '..', '..', 'pd-cli', 'dist', 'index.js'),
|
|
41
|
+
path.resolve(process.cwd(), 'packages', 'pd-cli', 'dist', 'index.js'),
|
|
42
|
+
path.resolve(process.cwd(), 'dist', 'index.js'),
|
|
43
|
+
];
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
if (existsSync(candidate))
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// probe the next candidate
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
function runCli(cliEntry, args, timeoutMs) {
|
|
59
|
+
return new Promise(resolve => {
|
|
60
|
+
// Static argv list — no shell, no interpolated command string.
|
|
61
|
+
const child = spawn(process.execPath, [cliEntry, ...args], {
|
|
62
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
windowsHide: true,
|
|
64
|
+
});
|
|
65
|
+
let stdout = '';
|
|
66
|
+
let finished = false;
|
|
67
|
+
let timer;
|
|
68
|
+
const finish = (result) => {
|
|
69
|
+
if (finished)
|
|
70
|
+
return;
|
|
71
|
+
finished = true;
|
|
72
|
+
if (timer !== undefined)
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
child.removeAllListeners();
|
|
75
|
+
resolve(result);
|
|
76
|
+
};
|
|
77
|
+
timer = setTimeout(() => {
|
|
78
|
+
child.kill();
|
|
79
|
+
finish({ ok: false, detail: 'cli probe timeout' });
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
child.stdout.on('data', (chunk) => {
|
|
82
|
+
if (stdout.length < MAX_STDOUT_BYTES)
|
|
83
|
+
stdout += chunk.toString('utf8');
|
|
84
|
+
});
|
|
85
|
+
child.stderr.on('data', () => { });
|
|
86
|
+
child.on('error', error => finish({ ok: false, detail: `spawn failed: ${error instanceof Error ? error.message : String(error)}` }));
|
|
87
|
+
child.on('close', code => {
|
|
88
|
+
if (code !== 0) {
|
|
89
|
+
finish({ ok: false, detail: `exited with code ${code}` });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
finish({ ok: true, stdout });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function pathResolved(value) {
|
|
97
|
+
const normalized = value.replace(/\\/g, '/');
|
|
98
|
+
let end = normalized.length;
|
|
99
|
+
while (end > 0 && normalized.charCodeAt(end - 1) === 47)
|
|
100
|
+
end -= 1;
|
|
101
|
+
return normalized.slice(0, end).toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
function capability(capabilityId, evidence) {
|
|
104
|
+
return {
|
|
105
|
+
capabilityId,
|
|
106
|
+
...evidence,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const CONSOLE_UNVERIFIABLE_DETAIL = 'console-channel control: verifying the HTTP-only recovery entry requires the Owner Console process; a CLI subprocess cannot produce this evidence';
|
|
110
|
+
/** `pd activation list --json` — the pd_status status surface, read-only. */
|
|
111
|
+
export async function probeCliActivationStatus(options) {
|
|
112
|
+
const run = await runCli(options.cliEntry, ['activation', 'list', '--json', '--workspace', options.workspaceDir], options.timeoutMs ?? PROBE_TIMEOUT_MS);
|
|
113
|
+
if (!run.ok)
|
|
114
|
+
return capability('pd_status', { channel: 'cli', ok: false, workspaceMatched: false, detail: `pd activation list: ${run.detail}` });
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(run.stdout);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return capability('pd_status', { channel: 'cli', ok: false, workspaceMatched: false, detail: 'pd activation list: stdout is not parseable JSON' });
|
|
121
|
+
}
|
|
122
|
+
const activations = isRecord(parsed) && Array.isArray(parsed.activations) ? parsed.activations : undefined;
|
|
123
|
+
if (activations === undefined) {
|
|
124
|
+
return capability('pd_status', { channel: 'cli', ok: false, workspaceMatched: false, detail: 'pd activation list: output carries no activations listing' });
|
|
125
|
+
}
|
|
126
|
+
if (options.expectedActivationId !== undefined) {
|
|
127
|
+
const listed = activations.some(entry => isRecord(entry) && entry.activationId === options.expectedActivationId);
|
|
128
|
+
if (!listed) {
|
|
129
|
+
return capability('pd_status', { channel: 'cli', ok: false, workspaceMatched: false, detail: `pd activation list: the target workspace listing does not include activation ${options.expectedActivationId}` });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return capability('pd_status', { channel: 'cli', ok: true, workspaceMatched: true });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `pd activation deactivate --dry-run` — the REAL deactivate entry (parsing,
|
|
136
|
+
* workspace binding, target resolution, preconditions) without audit write or
|
|
137
|
+
* store mutation. The probe id does not exist, so the precondition outcome is
|
|
138
|
+
* an honest not-found while the entry itself is proven executable.
|
|
139
|
+
*/
|
|
140
|
+
export async function probeCliDeactivateDryRun(options) {
|
|
141
|
+
const run = await runCli(options.cliEntry, ['activation', 'deactivate', '--activation-id', DEACTIVATE_DRY_RUN_PROBE_ACTIVATION_ID, '--dry-run', '--json', '--workspace', options.workspaceDir], options.timeoutMs ?? PROBE_TIMEOUT_MS);
|
|
142
|
+
if (!run.ok)
|
|
143
|
+
return capability('rulecode_deactivate', { channel: 'cli', ok: false, workspaceMatched: false, detail: `pd activation deactivate --dry-run: ${run.detail}` });
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(run.stdout);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return capability('rulecode_deactivate', { channel: 'cli', ok: false, workspaceMatched: false, detail: 'pd activation deactivate --dry-run: stdout is not parseable JSON' });
|
|
150
|
+
}
|
|
151
|
+
if (!isRecord(parsed) || parsed.dryRun !== true || typeof parsed.workspaceDir !== 'string') {
|
|
152
|
+
return capability('rulecode_deactivate', { channel: 'cli', ok: false, workspaceMatched: false, detail: 'pd activation deactivate --dry-run: output is not a structured dry-run result' });
|
|
153
|
+
}
|
|
154
|
+
if (pathResolved(options.workspaceDir) !== pathResolved(String(parsed.workspaceDir))) {
|
|
155
|
+
return capability('rulecode_deactivate', { channel: 'cli', ok: false, workspaceMatched: false, detail: `pd activation deactivate --dry-run: bound to ${String(parsed.workspaceDir)}, not the governed workspace` });
|
|
156
|
+
}
|
|
157
|
+
return capability('rulecode_deactivate', { channel: 'cli', ok: true, workspaceMatched: true });
|
|
158
|
+
}
|
|
159
|
+
/** Both CLI-channel capabilities plus explicit NOT-VERIFIED entries for the console channel (fail closed, never fabricated). */
|
|
160
|
+
export async function probeCliRecoveryCapabilities(options) {
|
|
161
|
+
const status = await probeCliActivationStatus(options);
|
|
162
|
+
const deactivate = await probeCliDeactivateDryRun(options);
|
|
163
|
+
return [
|
|
164
|
+
status,
|
|
165
|
+
deactivate,
|
|
166
|
+
capability('rulecode_global_pause', { channel: 'console', ok: false, workspaceMatched: false, detail: CONSOLE_UNVERIFIABLE_DETAIL }),
|
|
167
|
+
capability('owner_review_access', { channel: 'console', ok: false, workspaceMatched: false, detail: CONSOLE_UNVERIFIABLE_DETAIL }),
|
|
168
|
+
];
|
|
169
|
+
}
|
|
@@ -319,6 +319,16 @@ function ensureGovernanceAdmissionSchema(db) {
|
|
|
319
319
|
if (!message.includes('duplicate column name'))
|
|
320
320
|
throw err;
|
|
321
321
|
}
|
|
322
|
+
try {
|
|
323
|
+
// SPEC-RULECODE-HOST-PROMOTION-001 R2: additive producer host
|
|
324
|
+
// attribution; legacy rows stay NULL (unprovable, never backfilled).
|
|
325
|
+
db.exec('ALTER TABLE tool_calls ADD COLUMN host_kind TEXT');
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
329
|
+
if (!message.includes('duplicate column name'))
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
322
332
|
db.exec('CREATE INDEX IF NOT EXISTS idx_tool_calls_session_tool_use_id ON tool_calls(session_id, tool_use_id)');
|
|
323
333
|
}
|
|
324
334
|
})();
|
|
@@ -642,13 +652,20 @@ function admitToolFailure({ db, candidate, workspaceDir, nowIso }) {
|
|
|
642
652
|
// Same duplicate probes as the production handler: the host tool-call id
|
|
643
653
|
// (CR-01) is the stable join key; the content-shape probe stays the
|
|
644
654
|
// fallback for hosts/candidates without ids.
|
|
655
|
+
// Review R3/P2: admission dedup is host-scoped — a colliding row from
|
|
656
|
+
// another host is not this candidate's duplicate.
|
|
645
657
|
if (candidate.toolUseId !== undefined
|
|
646
|
-
&&
|
|
658
|
+
&& (candidate.hostKind !== undefined
|
|
659
|
+
? db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_use_id = ? AND host_kind = ?').get(candidate.rootSessionId, candidate.toolUseId, candidate.hostKind)
|
|
660
|
+
: db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_use_id = ?').get(candidate.rootSessionId, candidate.toolUseId)) !== undefined) {
|
|
647
661
|
duplicate = true;
|
|
648
662
|
duplicateWithoutPain = db.prepare('SELECT 1 FROM pain_events WHERE canonical_pain_id = ?').get(painId) === undefined;
|
|
649
663
|
}
|
|
650
|
-
else if (
|
|
651
|
-
.
|
|
664
|
+
else if ((candidate.hostKind !== undefined
|
|
665
|
+
? db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_name = ? AND params_json = ? AND outcome = ? AND exit_code IS ? AND error_message IS ? AND result_preview IS ? AND host_kind = ?')
|
|
666
|
+
.get(candidate.rootSessionId, candidate.toolName, paramsJson, outcome.outcome, outcome.exitCode, outcome.error ?? null, resultPreview, candidate.hostKind)
|
|
667
|
+
: db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_name = ? AND params_json = ? AND outcome = ? AND exit_code IS ? AND error_message IS ? AND result_preview IS ?')
|
|
668
|
+
.get(candidate.rootSessionId, candidate.toolName, paramsJson, outcome.outcome, outcome.exitCode, outcome.error ?? null, resultPreview)) !== undefined) {
|
|
652
669
|
duplicate = true;
|
|
653
670
|
duplicateWithoutPain = db.prepare('SELECT 1 FROM pain_events WHERE canonical_pain_id = ?').get(painId) === undefined;
|
|
654
671
|
}
|
|
@@ -658,8 +675,8 @@ function admitToolFailure({ db, candidate, workspaceDir, nowIso }) {
|
|
|
658
675
|
const projectDir = typeof candidate.projectDir === 'string' && path.isAbsolute(candidate.projectDir)
|
|
659
676
|
? path.resolve(candidate.projectDir)
|
|
660
677
|
: null;
|
|
661
|
-
db.prepare(`INSERT INTO tool_calls (session_id, tool_name, outcome, duration_ms, exit_code, error_type, error_message, gfi_before, gfi_after, params_json, result_preview, created_at, tool_use_id, project_dir) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
662
|
-
.run(candidate.rootSessionId, candidate.toolName, outcome.outcome, outcome.durationMs ?? null, outcome.exitCode, outcome.error ? outcome.error.split(/[\s:]/, 1)[0] : null, outcome.error ?? null, null, null, paramsJson, resultPreview, nowIso, candidate.toolUseId ?? null, projectDir);
|
|
678
|
+
db.prepare(`INSERT INTO tool_calls (session_id, tool_name, outcome, duration_ms, exit_code, error_type, error_message, gfi_before, gfi_after, params_json, result_preview, created_at, tool_use_id, project_dir, host_kind) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
679
|
+
.run(candidate.rootSessionId, candidate.toolName, outcome.outcome, outcome.durationMs ?? null, outcome.exitCode, outcome.error ? outcome.error.split(/[\s:]/, 1)[0] : null, outcome.error ?? null, null, null, paramsJson, resultPreview, nowIso, candidate.toolUseId ?? null, projectDir, candidate.hostKind);
|
|
663
680
|
reason = `tool=${candidate.toolName}; error=${outcome.error ?? `exit=${outcome.exitCode}`}; path=${relativePath}`;
|
|
664
681
|
db.prepare(`INSERT INTO pain_events (session_id, source, score, reason, severity, origin, confidence, text, canonical_pain_id, runtime_task_id, host_kind, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
665
682
|
.run(candidate.rootSessionId, sourceObservation.failureSource ?? 'tool_failure', painScore, reason, painScore >= 70 ? 'severe' : painScore >= 40 ? 'moderate' : 'mild', 'system_infer', null, null, painId, null, candidate.hostKind, nowIso);
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import type { HostLivenessContract } from '@principles/core/runtime-v2';
|
|
2
2
|
/** Adapter-owned facts consumed by the non-bypassable RuleCode promotion gate. */
|
|
3
3
|
export declare const OPENCLAW_HOST_LIVENESS_CONTRACT: HostLivenessContract;
|
|
4
|
+
/**
|
|
5
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 B1: the Codex capability contract. Every
|
|
6
|
+
* tool alias below is the REAL Codex tool surface with in-repo evidence
|
|
7
|
+
* (`Bash` + `apply_patch`, locked by the shipped hooks matcher — see
|
|
8
|
+
* codex-adapter tool-semantics.ts). The out-of-band controls are the
|
|
9
|
+
* host-neutral `pd` CLI and the Web Console: they act on the workspace
|
|
10
|
+
* state directly, never through the Codex hook process, so a broken rule
|
|
11
|
+
* cannot lock them. This contract declares the mapping ONLY; shadow-event
|
|
12
|
+
* attribution, neutral-probe composition and Owner decision authority are
|
|
13
|
+
* enforced by the shared readiness pipeline, not by this constant
|
|
14
|
+
* (SPEC §11.2 — a constant alone must never turn a check green).
|
|
15
|
+
*/
|
|
16
|
+
export declare const CODEX_HOST_LIVENESS_CONTRACT: HostLivenessContract;
|
|
17
|
+
/**
|
|
18
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 C: the WorkBuddy capability contract.
|
|
19
|
+
* Tool aliases use WorkBuddy's REAL capitalized vocabulary (Read/Write/Edit/
|
|
20
|
+
* Bash — frozen Phase 0 fixtures + official hooks reference; see
|
|
21
|
+
* workbuddy-adapter tool-semantics.ts). Out-of-band controls are the same
|
|
22
|
+
* host-neutral `pd` CLI / Web Console pair, invoked outside the WorkBuddy
|
|
23
|
+
* hook channel (the hook failing closed with exit 2 on PreToolUse cannot
|
|
24
|
+
* prevent an Owner terminal or browser from reaching the workspace).
|
|
25
|
+
*/
|
|
26
|
+
export declare const WORKBUDDY_HOST_LIVENESS_CONTRACT: HostLivenessContract;
|
|
4
27
|
export type PromotionHostLivenessResolution = {
|
|
5
28
|
ok: true;
|
|
6
|
-
hostKind: 'openclaw';
|
|
29
|
+
hostKind: 'openclaw' | 'codex' | 'workbuddy';
|
|
7
30
|
/**
|
|
8
31
|
* The workspace host DECLARATIONS this resolution is based on.
|
|
9
32
|
* Empty array = nothing declared yet (the OpenClaw contract is the
|
|
@@ -51,37 +74,37 @@ export type WorkspaceHostKindFacts = {
|
|
|
51
74
|
};
|
|
52
75
|
export declare function readWorkspaceHostKindFacts(workspaceDir: string): WorkspaceHostKindFacts;
|
|
53
76
|
/**
|
|
54
|
-
*
|
|
55
|
-
* REAL host state
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
77
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 §4 (A): resolve the promotion host-liveness
|
|
78
|
+
* contract from the workspace's REAL host state. The historical "anything
|
|
79
|
+
* that is not Codex inherits OpenClaw" fork is REPLACED by an explicit
|
|
80
|
+
* five-way routing over openclaw / codex / workbuddy / unknown / multi-host:
|
|
81
|
+
*
|
|
82
|
+
* - nothing declared, no evidence, or OpenClaw-only traces → OpenClaw
|
|
83
|
+
* contract (the documented historical default; SPEC §4.2 keeps this
|
|
84
|
+
* compatibility semantics ONLY for workspaces with no non-OpenClaw
|
|
85
|
+
* declaration or evidence — it must not extend to workspaces where
|
|
86
|
+
* WorkBuddy has appeared);
|
|
87
|
+
* - durable evidence for a known non-OpenClaw host whose declaration file is
|
|
88
|
+
* missing/corrupted → host_declaration_missing_for_configured_host
|
|
89
|
+
* (generalized from Codex-only to every known host — SPEC §4.3: never
|
|
90
|
+
* silently fall back to OpenClaw when the governing host's declaration
|
|
91
|
+
* cannot be read);
|
|
92
|
+
* - more than one effective host kind → multi_host_promotion_ambiguous
|
|
93
|
+
* (never guess a target host; SPEC §4.4);
|
|
94
|
+
* - exactly one known non-OpenClaw host (declared) → that host's contract;
|
|
95
|
+
* - an unknown declared host kind → promotion_host_unsupported (fail closed;
|
|
96
|
+
* the declaration proves tool mapping only, never promotion capability —
|
|
97
|
+
* SPEC §4.5).
|
|
59
98
|
*
|
|
60
99
|
* Two durable provenance sources, cross-checked (Owner review round-4):
|
|
61
100
|
* 1. host declarations — `.pd/host-tool-semantics/<hostKind>.json`;
|
|
62
101
|
* 2. trajectory.db `pain_events.host_kind` — which hosts actually produced
|
|
63
|
-
* governance evidence here (survives declaration deletion).
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* capability authority, and fail closed whenever Codex evidence exists but
|
|
67
|
-
* its declaration is missing — never silently fall back to OpenClaw):
|
|
68
|
-
* - no declaration AND no Codex evidence → OpenClaw contract (the historical
|
|
69
|
-
* governance default; a workspace where only OpenClaw ever ran — or no
|
|
70
|
-
* host ran — is exactly the legacy state, and existing workspaces rely
|
|
71
|
-
* on it);
|
|
72
|
-
* - only openclaw declared and no Codex evidence → OPENCLAW_HOST_LIVENESS_CONTRACT;
|
|
73
|
-
* - codex declared (alone) → promotion_host_unsupported (Codex promotion
|
|
74
|
-
* stays explicitly unsupported until an evidence-backed capability
|
|
75
|
-
* authority decision exists — no second host contract here);
|
|
76
|
-
* - multiple hosts declared → multi_host_promotion_ambiguous (never guess a
|
|
77
|
-
* target host; the Owner decides host-selection policy separately);
|
|
78
|
-
* - Codex evidence present but codex.json missing (deleted/corrupted —
|
|
79
|
-
* alone or alongside openclaw.json) → host_declaration_missing_for_configured_host
|
|
80
|
-
* (re-persist the declaration before promoting);
|
|
81
|
-
* - declarations or provenance unreadable → fail closed with the reason.
|
|
102
|
+
* governance evidence here (survives declaration deletion). Unknown-host
|
|
103
|
+
* evidence rows are filtered by the facts reader (known-host filter) and
|
|
104
|
+
* can never reach this resolver (SPEC §11.4).
|
|
82
105
|
*
|
|
83
|
-
* This function
|
|
84
|
-
*
|
|
85
|
-
*
|
|
106
|
+
* This function creates no capability registry and no new store — it routes
|
|
107
|
+
* the existing per-host contract authorities truthfully over state that
|
|
108
|
+
* already exists.
|
|
86
109
|
*/
|
|
87
110
|
export declare function resolvePromotionHostLiveness(workspaceDir: string): PromotionHostLivenessResolution;
|
|
@@ -8,18 +8,98 @@ export const OPENCLAW_HOST_LIVENESS_CONTRACT = {
|
|
|
8
8
|
supportsShadowEvidence: true,
|
|
9
9
|
outOfBandControls: ['activation_deactivate', 'global_rulecode_pause', 'owner_review_console'],
|
|
10
10
|
protectedCapabilities: [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// Review R1: `exec` is the ONLY shell tool name that reaches the OpenClaw
|
|
12
|
+
// hook face (constants/tools.ts PRI-741: `bash`/`exec_command` are
|
|
13
|
+
// config-level aliases that normalize away). Aliases and probes must use
|
|
14
|
+
// the real dispatched name or the neutral-probe replay rejects them.
|
|
15
|
+
{ capabilityId: 'pd_status', hostToolAliases: ['exec'] },
|
|
16
|
+
{ capabilityId: 'rulecode_deactivate', hostToolAliases: ['exec'] },
|
|
17
|
+
// Review R1: the global pause has NO pd CLI command — its real surface is
|
|
18
|
+
// the authenticated Owner Console route. Declaring a shell alias here
|
|
19
|
+
// would claim a recovery entry that does not exist.
|
|
20
|
+
{ capabilityId: 'rulecode_global_pause', hostToolAliases: ['rulecode_global_pause'] },
|
|
14
21
|
{ capabilityId: 'owner_review_access', hostToolAliases: ['owner_review_access'] },
|
|
15
22
|
],
|
|
16
23
|
neutralProbes: [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{ probeId: 'probe-
|
|
24
|
+
// Review R1: probe commands are the REAL registered pd commands
|
|
25
|
+
// (verified against the recovery-control registry; `pd status` and
|
|
26
|
+
// `pd activation emergency-pause` do not exist and were removed).
|
|
27
|
+
{ probeId: 'probe-pd-status', capabilityId: 'pd_status', toolName: 'exec', params: { command: 'pd activation list --json' }, expectedDecision: 'allow' },
|
|
28
|
+
{ probeId: 'probe-rule-deactivate', capabilityId: 'rulecode_deactivate', toolName: 'exec', params: { command: 'pd activation deactivate --activation-id recovery-probe' }, expectedDecision: 'allow' },
|
|
29
|
+
// Review R3/P1: console-channel capabilities (global pause, owner review)
|
|
30
|
+
// carry NO probe — their recovery path never passes through the RuleHost
|
|
31
|
+
// gate, so a rule replay would misrepresent their verification. Their
|
|
32
|
+
// entry surfaces live in the recovery-control registry and their
|
|
33
|
+
// target-environment reachability is supplied by the readiness caller.
|
|
21
34
|
],
|
|
22
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 B1: the Codex capability contract. Every
|
|
38
|
+
* tool alias below is the REAL Codex tool surface with in-repo evidence
|
|
39
|
+
* (`Bash` + `apply_patch`, locked by the shipped hooks matcher — see
|
|
40
|
+
* codex-adapter tool-semantics.ts). The out-of-band controls are the
|
|
41
|
+
* host-neutral `pd` CLI and the Web Console: they act on the workspace
|
|
42
|
+
* state directly, never through the Codex hook process, so a broken rule
|
|
43
|
+
* cannot lock them. This contract declares the mapping ONLY; shadow-event
|
|
44
|
+
* attribution, neutral-probe composition and Owner decision authority are
|
|
45
|
+
* enforced by the shared readiness pipeline, not by this constant
|
|
46
|
+
* (SPEC §11.2 — a constant alone must never turn a check green).
|
|
47
|
+
*/
|
|
48
|
+
export const CODEX_HOST_LIVENESS_CONTRACT = {
|
|
49
|
+
version: 'codex-v2@1',
|
|
50
|
+
supportsShadowEvidence: true,
|
|
51
|
+
outOfBandControls: ['activation_deactivate', 'global_rulecode_pause', 'owner_review_console'],
|
|
52
|
+
protectedCapabilities: [
|
|
53
|
+
{ capabilityId: 'pd_status', hostToolAliases: ['Bash'] },
|
|
54
|
+
{ capabilityId: 'rulecode_deactivate', hostToolAliases: ['Bash'] },
|
|
55
|
+
{ capabilityId: 'rulecode_global_pause', hostToolAliases: ['rulecode_global_pause'] },
|
|
56
|
+
{ capabilityId: 'owner_review_access', hostToolAliases: ['owner_review_access'] },
|
|
57
|
+
],
|
|
58
|
+
neutralProbes: [
|
|
59
|
+
{ probeId: 'probe-pd-status', capabilityId: 'pd_status', toolName: 'Bash', params: { command: 'pd activation list --json' }, expectedDecision: 'allow' },
|
|
60
|
+
{ probeId: 'probe-rule-deactivate', capabilityId: 'rulecode_deactivate', toolName: 'Bash', params: { command: 'pd activation deactivate --activation-id recovery-probe' }, expectedDecision: 'allow' },
|
|
61
|
+
// Review R3/P1: console-channel capabilities (global pause, owner review)
|
|
62
|
+
// carry NO probe — their recovery path never passes through the RuleHost
|
|
63
|
+
// gate, so a rule replay would misrepresent their verification. Their
|
|
64
|
+
// entry surfaces live in the recovery-control registry and their
|
|
65
|
+
// target-environment reachability is supplied by the readiness caller.
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 C: the WorkBuddy capability contract.
|
|
70
|
+
* Tool aliases use WorkBuddy's REAL capitalized vocabulary (Read/Write/Edit/
|
|
71
|
+
* Bash — frozen Phase 0 fixtures + official hooks reference; see
|
|
72
|
+
* workbuddy-adapter tool-semantics.ts). Out-of-band controls are the same
|
|
73
|
+
* host-neutral `pd` CLI / Web Console pair, invoked outside the WorkBuddy
|
|
74
|
+
* hook channel (the hook failing closed with exit 2 on PreToolUse cannot
|
|
75
|
+
* prevent an Owner terminal or browser from reaching the workspace).
|
|
76
|
+
*/
|
|
77
|
+
export const WORKBUDDY_HOST_LIVENESS_CONTRACT = {
|
|
78
|
+
version: 'workbuddy-v1@1',
|
|
79
|
+
supportsShadowEvidence: true,
|
|
80
|
+
outOfBandControls: ['activation_deactivate', 'global_rulecode_pause', 'owner_review_console'],
|
|
81
|
+
protectedCapabilities: [
|
|
82
|
+
{ capabilityId: 'pd_status', hostToolAliases: ['Bash'] },
|
|
83
|
+
{ capabilityId: 'rulecode_deactivate', hostToolAliases: ['Bash'] },
|
|
84
|
+
{ capabilityId: 'rulecode_global_pause', hostToolAliases: ['rulecode_global_pause'] },
|
|
85
|
+
{ capabilityId: 'owner_review_access', hostToolAliases: ['owner_review_access'] },
|
|
86
|
+
],
|
|
87
|
+
neutralProbes: [
|
|
88
|
+
{ probeId: 'probe-pd-status', capabilityId: 'pd_status', toolName: 'Bash', params: { command: 'pd activation list --json' }, expectedDecision: 'allow' },
|
|
89
|
+
{ probeId: 'probe-rule-deactivate', capabilityId: 'rulecode_deactivate', toolName: 'Bash', params: { command: 'pd activation deactivate --activation-id recovery-probe' }, expectedDecision: 'allow' },
|
|
90
|
+
// Review R3/P1: console-channel capabilities (global pause, owner review)
|
|
91
|
+
// carry NO probe — their recovery path never passes through the RuleHost
|
|
92
|
+
// gate, so a rule replay would misrepresent their verification. Their
|
|
93
|
+
// entry surfaces live in the recovery-control registry and their
|
|
94
|
+
// target-environment reachability is supplied by the readiness caller.
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
/** The per-host contracts the resolver may hand out — one authority per host kind. */
|
|
98
|
+
const HOST_LIVENESS_CONTRACTS = {
|
|
99
|
+
openclaw: OPENCLAW_HOST_LIVENESS_CONTRACT,
|
|
100
|
+
codex: CODEX_HOST_LIVENESS_CONTRACT,
|
|
101
|
+
workbuddy: WORKBUDDY_HOST_LIVENESS_CONTRACT,
|
|
102
|
+
};
|
|
23
103
|
/**
|
|
24
104
|
* Existing durable provenance (Owner review round-4): `trajectory.db`
|
|
25
105
|
* `pain_events.host_kind` records which host actually produced governance
|
|
@@ -107,41 +187,41 @@ export function readWorkspaceHostKindFacts(workspaceDir) {
|
|
|
107
187
|
return { ok: true, declaredKinds, evidenceKinds, effectiveKinds };
|
|
108
188
|
}
|
|
109
189
|
/**
|
|
110
|
-
*
|
|
111
|
-
* REAL host state
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
190
|
+
* SPEC-RULECODE-HOST-PROMOTION-001 §4 (A): resolve the promotion host-liveness
|
|
191
|
+
* contract from the workspace's REAL host state. The historical "anything
|
|
192
|
+
* that is not Codex inherits OpenClaw" fork is REPLACED by an explicit
|
|
193
|
+
* five-way routing over openclaw / codex / workbuddy / unknown / multi-host:
|
|
194
|
+
*
|
|
195
|
+
* - nothing declared, no evidence, or OpenClaw-only traces → OpenClaw
|
|
196
|
+
* contract (the documented historical default; SPEC §4.2 keeps this
|
|
197
|
+
* compatibility semantics ONLY for workspaces with no non-OpenClaw
|
|
198
|
+
* declaration or evidence — it must not extend to workspaces where
|
|
199
|
+
* WorkBuddy has appeared);
|
|
200
|
+
* - durable evidence for a known non-OpenClaw host whose declaration file is
|
|
201
|
+
* missing/corrupted → host_declaration_missing_for_configured_host
|
|
202
|
+
* (generalized from Codex-only to every known host — SPEC §4.3: never
|
|
203
|
+
* silently fall back to OpenClaw when the governing host's declaration
|
|
204
|
+
* cannot be read);
|
|
205
|
+
* - more than one effective host kind → multi_host_promotion_ambiguous
|
|
206
|
+
* (never guess a target host; SPEC §4.4);
|
|
207
|
+
* - exactly one known non-OpenClaw host (declared) → that host's contract;
|
|
208
|
+
* - an unknown declared host kind → promotion_host_unsupported (fail closed;
|
|
209
|
+
* the declaration proves tool mapping only, never promotion capability —
|
|
210
|
+
* SPEC §4.5).
|
|
115
211
|
*
|
|
116
212
|
* Two durable provenance sources, cross-checked (Owner review round-4):
|
|
117
213
|
* 1. host declarations — `.pd/host-tool-semantics/<hostKind>.json`;
|
|
118
214
|
* 2. trajectory.db `pain_events.host_kind` — which hosts actually produced
|
|
119
|
-
* governance evidence here (survives declaration deletion).
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* capability authority, and fail closed whenever Codex evidence exists but
|
|
123
|
-
* its declaration is missing — never silently fall back to OpenClaw):
|
|
124
|
-
* - no declaration AND no Codex evidence → OpenClaw contract (the historical
|
|
125
|
-
* governance default; a workspace where only OpenClaw ever ran — or no
|
|
126
|
-
* host ran — is exactly the legacy state, and existing workspaces rely
|
|
127
|
-
* on it);
|
|
128
|
-
* - only openclaw declared and no Codex evidence → OPENCLAW_HOST_LIVENESS_CONTRACT;
|
|
129
|
-
* - codex declared (alone) → promotion_host_unsupported (Codex promotion
|
|
130
|
-
* stays explicitly unsupported until an evidence-backed capability
|
|
131
|
-
* authority decision exists — no second host contract here);
|
|
132
|
-
* - multiple hosts declared → multi_host_promotion_ambiguous (never guess a
|
|
133
|
-
* target host; the Owner decides host-selection policy separately);
|
|
134
|
-
* - Codex evidence present but codex.json missing (deleted/corrupted —
|
|
135
|
-
* alone or alongside openclaw.json) → host_declaration_missing_for_configured_host
|
|
136
|
-
* (re-persist the declaration before promoting);
|
|
137
|
-
* - declarations or provenance unreadable → fail closed with the reason.
|
|
215
|
+
* governance evidence here (survives declaration deletion). Unknown-host
|
|
216
|
+
* evidence rows are filtered by the facts reader (known-host filter) and
|
|
217
|
+
* can never reach this resolver (SPEC §11.4).
|
|
138
218
|
*
|
|
139
|
-
* This function
|
|
140
|
-
*
|
|
141
|
-
*
|
|
219
|
+
* This function creates no capability registry and no new store — it routes
|
|
220
|
+
* the existing per-host contract authorities truthfully over state that
|
|
221
|
+
* already exists.
|
|
142
222
|
*/
|
|
143
223
|
export function resolvePromotionHostLiveness(workspaceDir) {
|
|
144
|
-
// Same two provenance sources and failure semantics as before,
|
|
224
|
+
// Same two provenance sources and failure semantics as before, via the
|
|
145
225
|
// shared facts reader (readWorkspaceHostKindFacts) so prompt-injection
|
|
146
226
|
// route resolution and promotion liveness cannot drift apart.
|
|
147
227
|
const facts = readWorkspaceHostKindFacts(workspaceDir);
|
|
@@ -152,9 +232,11 @@ export function resolvePromotionHostLiveness(workspaceDir) {
|
|
|
152
232
|
}
|
|
153
233
|
const { declaredKinds } = facts;
|
|
154
234
|
const { effectiveKinds } = facts;
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
|
|
235
|
+
const nonOpenClawKinds = effectiveKinds.filter((kind) => kind !== 'openclaw');
|
|
236
|
+
// Historical compatibility default (SPEC §4.2): a workspace where nothing
|
|
237
|
+
// non-OpenClaw ever declared itself or produced evidence is exactly the
|
|
238
|
+
// legacy state, and existing workspaces rely on the OpenClaw contract.
|
|
239
|
+
if (nonOpenClawKinds.length === 0) {
|
|
158
240
|
return {
|
|
159
241
|
ok: true,
|
|
160
242
|
hostKind: 'openclaw',
|
|
@@ -163,14 +245,29 @@ export function resolvePromotionHostLiveness(workspaceDir) {
|
|
|
163
245
|
hostRuntimeVersion: OPENCLAW_HOST_LIVENESS_CONTRACT.version,
|
|
164
246
|
};
|
|
165
247
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
248
|
+
// SPEC §4.3: durable evidence for a known non-OpenClaw host whose own
|
|
249
|
+
// declaration is gone (deleted/corrupted — alone or alongside openclaw.json)
|
|
250
|
+
// must fail closed with the precise reason; a silent OpenClaw fallback here
|
|
251
|
+
// would resurrect a false capability PASS (Owner review round-4, now
|
|
252
|
+
// generalized beyond Codex — a WorkBuddy-only workspace is the exact same
|
|
253
|
+
// misattribution class, SPEC §1.1 item 6).
|
|
254
|
+
const missingDeclarationKinds = facts.evidenceKinds.filter((kind) => kind !== 'openclaw' && !declaredKinds.includes(kind));
|
|
255
|
+
if (missingDeclarationKinds.length > 0) {
|
|
256
|
+
return promotionHostFailure('host_declaration_missing_for_configured_host', effectiveKinds, `durable evidence for host(s) ${missingDeclarationKinds.join(', ')} exists in this workspace but the matching .pd/host-tool-semantics/<hostKind>.json declaration is missing; re-run that host so it re-persists its declaration (or deactivate the activation) before promoting`);
|
|
257
|
+
}
|
|
258
|
+
if (effectiveKinds.length > 1) {
|
|
259
|
+
return promotionHostFailure('multi_host_promotion_ambiguous', effectiveKinds, `the workspace declares multiple hosts (${effectiveKinds.join(', ')}); promotion requires an unambiguous governing host — decide the promotion target host first`);
|
|
171
260
|
}
|
|
172
|
-
|
|
173
|
-
|
|
261
|
+
const [governingKind] = nonOpenClawKinds;
|
|
262
|
+
if (governingKind !== 'codex' && governingKind !== 'workbuddy') {
|
|
263
|
+
return promotionHostFailure('promotion_host_unsupported', effectiveKinds, `host '${governingKind}' has no approved promotion capability authority; promotion on this host is explicitly unsupported until an evidence-backed capability decision exists`);
|
|
174
264
|
}
|
|
175
|
-
|
|
265
|
+
const contract = HOST_LIVENESS_CONTRACTS[governingKind];
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
hostKind: governingKind,
|
|
269
|
+
hostKinds: declaredKinds,
|
|
270
|
+
hostContract: contract,
|
|
271
|
+
hostRuntimeVersion: contract.version,
|
|
272
|
+
};
|
|
176
273
|
}
|
|
@@ -87,6 +87,8 @@ export declare function createProductionHostRuntime(options?: Partial<Pick<HostR
|
|
|
87
87
|
export * from './behavior-example-pack-assembler.js';
|
|
88
88
|
export * from './behavior-history.js';
|
|
89
89
|
export * from './codex-rule-context-provider.js';
|
|
90
|
+
export * from './cli-recovery-probe.js';
|
|
91
|
+
export * from './workbuddy-rule-context-provider.js';
|
|
90
92
|
export { createWorkspaceBehaviorEvidencePort } from './workspace-behavior-evidence.js';
|
|
91
93
|
export * from './explicit-owner-statement-prefix.js';
|
|
92
94
|
export { listObservedDesignGateSessions } from './workspace-behavior-evidence.js';
|
|
@@ -317,6 +317,8 @@ export function createProductionHostRuntime(options = {}) {
|
|
|
317
317
|
export * from './behavior-example-pack-assembler.js';
|
|
318
318
|
export * from './behavior-history.js';
|
|
319
319
|
export * from './codex-rule-context-provider.js';
|
|
320
|
+
export * from './cli-recovery-probe.js';
|
|
321
|
+
export * from './workbuddy-rule-context-provider.js';
|
|
320
322
|
export { createWorkspaceBehaviorEvidencePort } from './workspace-behavior-evidence.js';
|
|
321
323
|
export * from './explicit-owner-statement-prefix.js';
|
|
322
324
|
export { listObservedDesignGateSessions } from './workspace-behavior-evidence.js';
|
|
@@ -133,6 +133,8 @@ export declare function recordVerifiedToolOutcomes(args: {
|
|
|
133
133
|
sessionId: string;
|
|
134
134
|
outcomes: readonly VerifiedToolOutcomeInput[];
|
|
135
135
|
painDatabaseFactory?: PainDatabaseFactory;
|
|
136
|
+
/** SPEC R2: producer host attribution stamped onto inserted rows (upgraded rows keep their original stamp). */
|
|
137
|
+
hostKind?: string;
|
|
136
138
|
}): VerifiedToolOutcomeStatus[];
|
|
137
139
|
/**
|
|
138
140
|
* PRI-640: host attribution is supplied by the host adapter that constructs
|