create-principles-disciple 1.145.30 → 1.145.33

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.
Files changed (92) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/codex-adapter/dist/ingestion/ingestion.js +3 -1
  3. package/codex-adapter/package.json +1 -1
  4. package/console/dist/server/models/ActivationsConsoleModel.d.ts +19 -2
  5. package/console/dist/server/models/ActivationsConsoleModel.js +124 -10
  6. package/console/dist/server/models/OwnerDecisionConsoleModel.d.ts +3 -1
  7. package/console/dist/server/models/OwnerDecisionConsoleModel.js +6 -2
  8. package/console/dist/server/routes/activations.js +7 -2
  9. package/console/dist/server/routes/owner-decisions.js +1 -1
  10. package/core/dist/runtime-v2/activation/index.d.ts +6 -0
  11. package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
  12. package/core/dist/runtime-v2/activation/index.js +3 -0
  13. package/core/dist/runtime-v2/activation/index.js.map +1 -1
  14. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts +75 -0
  15. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.d.ts.map +1 -1
  16. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js +131 -37
  17. package/core/dist/runtime-v2/activation/openclaw-promotion-checks.js.map +1 -1
  18. package/core/dist/runtime-v2/activation/production-gate-deps.d.ts.map +1 -1
  19. package/core/dist/runtime-v2/activation/production-gate-deps.js +7 -3
  20. package/core/dist/runtime-v2/activation/production-gate-deps.js.map +1 -1
  21. package/core/dist/runtime-v2/activation/recovery-composition.d.ts +54 -0
  22. package/core/dist/runtime-v2/activation/recovery-composition.d.ts.map +1 -0
  23. package/core/dist/runtime-v2/activation/recovery-composition.js +129 -0
  24. package/core/dist/runtime-v2/activation/recovery-composition.js.map +1 -0
  25. package/core/dist/runtime-v2/activation/recovery-control-surfaces.d.ts +62 -0
  26. package/core/dist/runtime-v2/activation/recovery-control-surfaces.d.ts.map +1 -0
  27. package/core/dist/runtime-v2/activation/recovery-control-surfaces.js +84 -0
  28. package/core/dist/runtime-v2/activation/recovery-control-surfaces.js.map +1 -0
  29. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts +13 -1
  30. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.d.ts.map +1 -1
  31. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js +24 -1
  32. package/core/dist/runtime-v2/activation/rulecode-shadow-summary.js.map +1 -1
  33. package/core/dist/runtime-v2/index.d.ts +2 -2
  34. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/index.js +1 -1
  36. package/core/dist/runtime-v2/index.js.map +1 -1
  37. package/core/dist/runtime-v2/trajectory-schema.d.ts.map +1 -1
  38. package/core/dist/runtime-v2/trajectory-schema.js +12 -1
  39. package/core/dist/runtime-v2/trajectory-schema.js.map +1 -1
  40. package/core/dist/runtime-v2/types/event-types.d.ts +10 -0
  41. package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
  42. package/core/dist/runtime-v2/types/event-types.js +1 -0
  43. package/core/dist/runtime-v2/types/event-types.js.map +1 -1
  44. package/core/package.json +1 -1
  45. package/dist/installer.d.ts +16 -0
  46. package/dist/installer.d.ts.map +1 -1
  47. package/dist/installer.js +49 -13
  48. package/dist/installer.js.map +1 -1
  49. package/host-runtime/dist/cli-recovery-probe.d.ts +30 -0
  50. package/host-runtime/dist/cli-recovery-probe.js +169 -0
  51. package/host-runtime/dist/governance-signal-admission.js +22 -5
  52. package/host-runtime/dist/host-liveness-contract.d.ts +51 -28
  53. package/host-runtime/dist/host-liveness-contract.js +143 -46
  54. package/host-runtime/dist/index.d.ts +2 -0
  55. package/host-runtime/dist/index.js +2 -0
  56. package/host-runtime/dist/production-pain-evidence.d.ts +2 -0
  57. package/host-runtime/dist/production-pain-evidence.js +46 -8
  58. package/host-runtime/dist/production-rulehost-gate.js +24 -6
  59. package/host-runtime/dist/workbuddy-rule-context-provider.d.ts +33 -0
  60. package/host-runtime/dist/workbuddy-rule-context-provider.js +126 -0
  61. package/host-runtime/dist/workspace-behavior-evidence.d.ts +22 -5
  62. package/host-runtime/dist/workspace-behavior-evidence.js +41 -11
  63. package/host-runtime/package.json +1 -1
  64. package/package.json +2 -2
  65. package/pd-cli/dist/commands/recovery-reachability-probe.d.ts +25 -0
  66. package/pd-cli/dist/commands/recovery-reachability-probe.d.ts.map +1 -0
  67. package/pd-cli/dist/commands/recovery-reachability-probe.js +50 -0
  68. package/pd-cli/dist/commands/recovery-reachability-probe.js.map +1 -0
  69. package/pd-cli/dist/commands/runtime-activation.d.ts +10 -1
  70. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  71. package/pd-cli/dist/commands/runtime-activation.js +81 -3
  72. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  73. package/pd-cli/dist/services/pain-recall.d.ts +79 -0
  74. package/pd-cli/dist/services/pain-recall.d.ts.map +1 -1
  75. package/pd-cli/dist/services/pain-recall.js +295 -12
  76. package/pd-cli/dist/services/pain-recall.js.map +1 -1
  77. package/pd-cli/package.json +1 -1
  78. package/plugin/dist/bundle.js +385 -385
  79. package/plugin/dist/governance-audit.js +1 -1
  80. package/plugin/dist/openclaw.plugin.json +1 -1
  81. package/plugin/dist/rulehost-evidence.js +1 -1
  82. package/plugin/openclaw.plugin.json +1 -1
  83. package/plugin/package.json +3 -24
  84. package/release-manager/dist/installer.d.ts +16 -0
  85. package/release-manager/dist/installer.d.ts.map +1 -1
  86. package/release-manager/dist/installer.js +49 -13
  87. package/release-manager/dist/installer.js.map +1 -1
  88. package/release-manager/package.json +2 -2
  89. package/workbuddy-adapter/dist/codec/input-decoder.d.ts +14 -11
  90. package/workbuddy-adapter/dist/codec/input-decoder.js +28 -14
  91. package/workbuddy-adapter/dist/pd-hook.js +31 -12
  92. 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
- && db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_use_id = ?').get(candidate.rootSessionId, candidate.toolUseId) !== undefined) {
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 (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 ?')
651
- .get(candidate.rootSessionId, candidate.toolName, paramsJson, outcome.outcome, outcome.exitCode, outcome.error ?? null, resultPreview) !== undefined) {
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
- * PRI-813: resolve the promotion host-liveness contract from the workspace's
55
- * REAL host state instead of unconditionally inheriting the OpenClaw
56
- * contract (which made Codex workspaces report runtime_compatibility /
57
- * runtime_shadow_evidence as passed — a false capability claim; the promotion
58
- * then only ever failed on sample count, hiding the unsupported truth).
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
- * Resolution rules (fail closed outside the one host with an approved
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 intentionally creates no Codex contract, no capability
84
- * registry, and no new store — it only routes the existing single authority
85
- * truthfully over state that already exists.
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
- { capabilityId: 'pd_status', hostToolAliases: ['bash', 'exec_command'] },
12
- { capabilityId: 'rulecode_deactivate', hostToolAliases: ['bash', 'exec_command'] },
13
- { capabilityId: 'rulecode_global_pause', hostToolAliases: ['bash', 'exec_command'] },
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
- { probeId: 'probe-pd-status', capabilityId: 'pd_status', toolName: 'bash', params: { command: 'pd status' }, expectedDecision: 'allow' },
18
- { probeId: 'probe-rule-deactivate', capabilityId: 'rulecode_deactivate', toolName: 'bash', params: { command: 'pd activation deactivate --activation-id test' }, expectedDecision: 'allow' },
19
- { probeId: 'probe-global-pause', capabilityId: 'rulecode_global_pause', toolName: 'bash', params: { command: 'pd activation emergency-pause' }, expectedDecision: 'allow' },
20
- { probeId: 'probe-owner-review', capabilityId: 'owner_review_access', toolName: 'owner_review_access', params: {}, expectedDecision: 'allow' },
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
- * PRI-813: resolve the promotion host-liveness contract from the workspace's
111
- * REAL host state instead of unconditionally inheriting the OpenClaw
112
- * contract (which made Codex workspaces report runtime_compatibility /
113
- * runtime_shadow_evidence as passed — a false capability claim; the promotion
114
- * then only ever failed on sample count, hiding the unsupported truth).
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
- * Resolution rules (fail closed outside the one host with an approved
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 intentionally creates no Codex contract, no capability
140
- * registry, and no new store — it only routes the existing single authority
141
- * truthfully over state that already exists.
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, now via the
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
- // The promotion host can only be the OpenClaw contract when nothing on the
156
- // workspace — neither a declaration nor behavioral evidence — says Codex.
157
- if (!effectiveKinds.includes('codex')) {
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
- if (!declaredKinds.includes('codex')) {
167
- // Codex evidence exists (declaration gone, or only openclaw.json
168
- // remains) — the exact state where a silent OpenClaw fallback would
169
- // resurrect the false capability PASS (Owner review round-4).
170
- return promotionHostFailure('host_declaration_missing_for_configured_host', effectiveKinds, 'Codex evidence exists in this workspace but .pd/host-tool-semantics/codex.json is missing; re-run the Codex worker so it re-persists its declaration (or deactivate the Codex activation) before promoting');
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
- if (declaredKinds.length > 1) {
173
- return promotionHostFailure('multi_host_promotion_ambiguous', effectiveKinds, `the workspace declares multiple hosts (${declaredKinds.join(', ')}); promotion requires an unambiguous governing host — decide the promotion target host first`);
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
- return promotionHostFailure('promotion_host_unsupported', effectiveKinds, `host '${declaredKinds[0]}' has no approved promotion capability authority; promotion on this host is explicitly unsupported until an evidence-backed capability decision exists`);
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