@principles/pd-cli 1.149.0 → 1.149.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/pain-record.d.ts +6 -0
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +78 -29
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +29 -2
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +18 -3
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/pain-record.ts +82 -29
- package/src/commands/runtime-internalization-run-once.ts +30 -3
- package/src/index.ts +12 -3
- package/src/services/rulehost-pipeline-runner.ts +18 -2
- package/tests/commands/pain-record-session-parser.test.ts +25 -1
- package/tests/commands/pain-record.test.ts +68 -9
- package/tests/commands/runtime-activation.test.ts +1 -1
- package/tests/commands/runtime-internalization-run-once.test.ts +8 -1
package/src/index.ts
CHANGED
|
@@ -105,7 +105,7 @@ function handleVersionFlag(args: readonly string[]): boolean {
|
|
|
105
105
|
|
|
106
106
|
program
|
|
107
107
|
.name('pd')
|
|
108
|
-
.description('PD CLI — Pain recording, sample management, and evolution
|
|
108
|
+
.description('PD CLI — Pain recording, sample management, and evolution task history')
|
|
109
109
|
.option('-V, --version', 'output the canonical PD product version')
|
|
110
110
|
.enablePositionalOptions();
|
|
111
111
|
|
|
@@ -123,7 +123,10 @@ painCmd
|
|
|
123
123
|
.option('-S, --source <text>', 'Source of the pain signal', 'manual')
|
|
124
124
|
.option('-w, --workspace <path>', 'Workspace directory')
|
|
125
125
|
.option('--session <id>', 'Session ID to bind (validated against this workspace\'s trajectory.db; without it the record is unbound: no trajectory evidence, candidates likely gated by the admission threshold)')
|
|
126
|
-
.option('--host <kind>', 'PRI-743: explicit host attribution (openclaw | codex). openclaw = default attribution without the disclosure warning; codex
|
|
126
|
+
.option('--host <kind>', 'PRI-743: explicit host attribution (openclaw | codex). openclaw = default attribution without the disclosure warning; codex requires the real Codex lineage (--rollout-id, --host-turn-id, and --session as the root session).', undefined)
|
|
127
|
+
.option('--rollout-id <id>', 'PRI-743: Codex rollout identity — required by --host codex', undefined)
|
|
128
|
+
.option('--host-turn-id <id>', 'PRI-743: Codex turn id — required by --host codex', undefined)
|
|
129
|
+
.option('--logical-key <key>', 'PRI-743: Codex logical observation key override for --host codex (default: codex|<rollout-id>|<host-turn-id>|user)', undefined)
|
|
127
130
|
.option('--wait', 'Wait for diagnosis to complete (sync mode, overrides async flag)')
|
|
128
131
|
.option('--json', 'Output raw JSON')
|
|
129
132
|
.action(async (opts) => {
|
|
@@ -179,9 +182,15 @@ samplesCmd
|
|
|
179
182
|
await handleSamplesReview({ sampleId, decision: decision === 'approve' ? 'approved' : 'rejected', note });
|
|
180
183
|
});
|
|
181
184
|
|
|
185
|
+
// ── Evolution task reader (hidden) ────────────────────────────────────────────
|
|
186
|
+
// PRI-752 review: the worker is gone (flag retired to `gone`), but this
|
|
187
|
+
// read-only surface stays for historical `evolution_tasks` rows in already-
|
|
188
|
+
// provisioned workspaces — data-retention disposition is a separate Owner
|
|
189
|
+
// decision (PRI-737 reality audit §7.3). Table schema + quality-scorecard
|
|
190
|
+
// reader are retained accordingly.
|
|
182
191
|
const evolutionCmd = program
|
|
183
192
|
.command('evolution', { hidden: true })
|
|
184
|
-
.description('Evolution task management');
|
|
193
|
+
.description('Evolution task management (historical data reader)');
|
|
185
194
|
|
|
186
195
|
const tasksCmd = evolutionCmd
|
|
187
196
|
.command('tasks')
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
// PRI-714: resolve outputLanguage from the same effective config the
|
|
55
55
|
// runners already receive (EP-07: canonical resolved value, not raw input).
|
|
56
56
|
resolveOutputLanguage,
|
|
57
|
+
buildArtificerHostSemanticContext,
|
|
57
58
|
} from '@principles/core/runtime-v2';
|
|
58
59
|
import type {
|
|
59
60
|
AdversarialLoopResult,
|
|
@@ -411,12 +412,21 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
411
412
|
onProgress('adversarial_loop', 'failed', refusalReason);
|
|
412
413
|
return rejectedResult(opts.painId, stages, `evaluator_runtime_context_unresolvable: ${evaluatorContext.reason} (nextAction: ${evaluatorContext.nextAction})`);
|
|
413
414
|
}
|
|
415
|
+
// PRI-741: artificer generation + evaluator parity BOTH anchor on the ONE
|
|
416
|
+
// registry snapshot evaluatorContext resolved above (review round: a
|
|
417
|
+
// second independent resolveWorkspaceHostToolSemantics read could observe
|
|
418
|
+
// a different declaration snapshot than the replay gate). Unresolvable
|
|
419
|
+
// provenance already failed loud at the evaluatorContext refusal above.
|
|
420
|
+
const hostSemanticContext = buildArtificerHostSemanticContext(evaluatorContext.registry, evaluatorContext.hostKinds ?? []);
|
|
414
421
|
const artificerRunner = new ArtificerRunner(
|
|
415
422
|
{
|
|
416
423
|
stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
|
|
417
424
|
contextMode: opts.contextMode ?? 'v1', behaviorExamplePack: opts.behaviorExamplePack, contentHashFn,
|
|
418
425
|
},
|
|
419
|
-
|
|
426
|
+
{
|
|
427
|
+
...runnerOptsFor(capability.artificerAdapter),
|
|
428
|
+
hostSemanticContext,
|
|
429
|
+
},
|
|
420
430
|
);
|
|
421
431
|
// PRI-510 (DEFECT-004): construct EvaluatorRunnerDeps via the centralized
|
|
422
432
|
// helper so the repair-loop wiring (isRepairLoopEnabled + seeder) is
|
|
@@ -435,7 +445,13 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
435
445
|
// from durable provenance) — replaces the previous sandbox-only gate
|
|
436
446
|
// deps so generation-time replay verdicts match the activation gate on
|
|
437
447
|
// the same workspace. Options argument (2nd), never deps (1st).
|
|
438
|
-
{
|
|
448
|
+
{
|
|
449
|
+
...runnerOptsFor(agentAdapters.evaluator),
|
|
450
|
+
gateDeps: evaluatorContext.gateDeps,
|
|
451
|
+
// PRI-741: host-name parity replay case from the SAME registry
|
|
452
|
+
// snapshot as the gateDeps above (single resolution).
|
|
453
|
+
hostSemanticContext,
|
|
454
|
+
},
|
|
439
455
|
);
|
|
440
456
|
|
|
441
457
|
const loopResult = await runAdversarialLoop({
|
|
@@ -93,7 +93,31 @@ describe('pd pain record --session (real Commander + real trajectory.db)', () =>
|
|
|
93
93
|
// Commander wraps help text; normalize whitespace before asserting content.
|
|
94
94
|
const normalized = result.stdout.replace(/\s+/g, ' ');
|
|
95
95
|
expect(normalized).toContain('openclaw | codex');
|
|
96
|
-
expect(normalized).toContain('codex
|
|
96
|
+
expect(normalized).toContain('codex requires the real Codex lineage');
|
|
97
|
+
}, 15_000);
|
|
98
|
+
|
|
99
|
+
it('PRI-743: --help registers the Codex lineage flags used by --host codex', async () => {
|
|
100
|
+
const result = await runBuiltCli(['pain', 'record', '--help']);
|
|
101
|
+
expect(result.status).toBe(0);
|
|
102
|
+
expect(result.stdout).toContain('--rollout-id <id>');
|
|
103
|
+
expect(result.stdout).toContain('--host-turn-id <id>');
|
|
104
|
+
expect(result.stdout).toContain('--logical-key <key>');
|
|
105
|
+
}, 15_000);
|
|
106
|
+
|
|
107
|
+
it('PRI-743: --host codex without lineage exits non-zero with codex_lineage_required', async () => {
|
|
108
|
+
const result = await runBuiltCli([
|
|
109
|
+
'pain', 'record',
|
|
110
|
+
'--reason', 'parser test pain',
|
|
111
|
+
'--host', 'codex',
|
|
112
|
+
'--workspace', tmpDir,
|
|
113
|
+
'--json',
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
expect(result.status).not.toBe(0);
|
|
117
|
+
const parsed = JSON.parse(result.stdout.trim()) as Record<string, unknown>;
|
|
118
|
+
expect(parsed.status).toBe('failed');
|
|
119
|
+
expect(parsed.reason).toBe('codex_lineage_required');
|
|
120
|
+
expect(typeof parsed.nextAction).toBe('string');
|
|
97
121
|
}, 15_000);
|
|
98
122
|
|
|
99
123
|
it('PRI-743: an invalid --host value exits non-zero with a single structured JSON object', async () => {
|
|
@@ -414,24 +414,63 @@ describe('pd pain record', () => {
|
|
|
414
414
|
|
|
415
415
|
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]) as { hostAttribution?: string; warnings?: string[] };
|
|
416
416
|
expect(jsonOutput.hostAttribution).toBe('openclaw');
|
|
417
|
-
expect((jsonOutput.warnings ?? []).some((w) => w.includes(
|
|
417
|
+
expect((jsonOutput.warnings ?? []).some((w) => w.includes('host kind not specified, defaulting to openclaw'))).toBe(false);
|
|
418
418
|
|
|
419
419
|
logSpy.mockRestore();
|
|
420
420
|
exitSpy.mockRestore();
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
-
it('PRI-743: --host codex
|
|
423
|
+
it('PRI-743: --host codex records host_kind=codex with the caller-supplied Codex lineage', async () => {
|
|
424
424
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
425
425
|
const exitSpy = mockProcessExit();
|
|
426
426
|
|
|
427
|
-
await handlePainRecord({
|
|
427
|
+
await handlePainRecord({
|
|
428
|
+
reason: 'codex mistake',
|
|
429
|
+
session: 'codex-root-sess',
|
|
430
|
+
host: 'codex',
|
|
431
|
+
rolloutId: 'rollout-abc',
|
|
432
|
+
hostTurnId: 'turn-7',
|
|
433
|
+
json: true,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(lastRecordPainInput).toBeTruthy();
|
|
437
|
+
expect(lastRecordPainInput!.hostKind).toBe('codex');
|
|
438
|
+
expect(lastRecordPainInput!.sessionId).toBe('codex-root-sess');
|
|
439
|
+
expect(lastRecordPainInput!.provenance).toBe('host_context_bound');
|
|
440
|
+
expect(lastRecordPainInput!.recordObservability).toBe(true);
|
|
441
|
+
// The CLI must NOT read the OpenClaw trajectory for a Codex attribution.
|
|
442
|
+
expect(acquireTrajectoryEvidenceFromDb).not.toHaveBeenCalled();
|
|
443
|
+
// Codex lineage is retained unflattened in the versioned payload (SPEC §8.1);
|
|
444
|
+
// the derived key mirrors the ingestion rule (codex|<rollout>|<turn>|user).
|
|
445
|
+
expect(lastRecordPainInput!.painIngress.correlation).toEqual({
|
|
446
|
+
status: 'bound',
|
|
447
|
+
hostKind: 'codex',
|
|
448
|
+
rootSessionId: 'codex-root-sess',
|
|
449
|
+
rolloutIdentity: 'rollout-abc',
|
|
450
|
+
logicalObservationKey: 'codex|rollout-abc|turn-7|user',
|
|
451
|
+
hostTurnId: 'turn-7',
|
|
452
|
+
});
|
|
453
|
+
// The degraded-but-honest path still submits (no fabricated evidence).
|
|
454
|
+
expect(lastRecordPainInput!.evidence).toEqual([]);
|
|
455
|
+
expect(exitSpy).not.toHaveBeenCalledWith(1);
|
|
456
|
+
|
|
457
|
+
logSpy.mockRestore();
|
|
458
|
+
exitSpy.mockRestore();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('PRI-743: --host codex without the Codex lineage fails loudly before any mutation', async () => {
|
|
462
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
463
|
+
const exitSpy = mockProcessExit();
|
|
464
|
+
|
|
465
|
+
await handlePainRecord({ reason: 'codex mistake', host: 'codex', json: true });
|
|
428
466
|
|
|
429
467
|
// cli-1: exactly one JSON object on stdout
|
|
430
468
|
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
431
|
-
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]) as { status: string; reason: string; nextAction: string };
|
|
432
|
-
expect(jsonOutput).toMatchObject({ status: 'failed', reason: '
|
|
433
|
-
expect(jsonOutput.
|
|
434
|
-
|
|
469
|
+
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]) as { status: string; reason: string; nextAction: string; message: string };
|
|
470
|
+
expect(jsonOutput).toMatchObject({ status: 'failed', reason: 'codex_lineage_required' });
|
|
471
|
+
expect(jsonOutput.message).toContain('--rollout-id');
|
|
472
|
+
expect(jsonOutput.nextAction).toContain('--host-turn-id');
|
|
473
|
+
// cli-2/cli-5: stopped before any evidence acquisition or service mutation
|
|
435
474
|
expect(lastRecordPainInput).toBeNull();
|
|
436
475
|
expect(acquireTrajectoryEvidenceFromDb).not.toHaveBeenCalled();
|
|
437
476
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
@@ -440,7 +479,27 @@ describe('pd pain record', () => {
|
|
|
440
479
|
exitSpy.mockRestore();
|
|
441
480
|
});
|
|
442
481
|
|
|
443
|
-
it('PRI-743:
|
|
482
|
+
it('PRI-743: --host codex with PARTIAL lineage still fails loudly (only the missing flag is reported)', async () => {
|
|
483
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
484
|
+
const exitSpy = mockProcessExit();
|
|
485
|
+
|
|
486
|
+
await handlePainRecord({ reason: 'codex mistake', session: 'codex-root', host: 'codex', rolloutId: 'rollout-abc', json: true });
|
|
487
|
+
|
|
488
|
+
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]) as { status: string; reason: string; message: string };
|
|
489
|
+
expect(jsonOutput).toMatchObject({ status: 'failed', reason: 'codex_lineage_required' });
|
|
490
|
+
expect(jsonOutput.message).toContain('--host-turn-id');
|
|
491
|
+
expect(jsonOutput.message).not.toContain('--rollout-id');
|
|
492
|
+
expect(lastRecordPainInput).toBeNull();
|
|
493
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
494
|
+
|
|
495
|
+
logSpy.mockRestore();
|
|
496
|
+
exitSpy.mockRestore();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Task test #3/#4: legacy call compatibility — the pre-PRI-743 invocation
|
|
500
|
+
// (`--reason … --session …`, no --host) keeps host_kind=openclaw and only
|
|
501
|
+
// gains the disclosure wording.
|
|
502
|
+
it('PRI-743: legacy call omitting --host keeps the openclaw default but discloses it (rc-9)', async () => {
|
|
444
503
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
445
504
|
const exitSpy = mockProcessExit();
|
|
446
505
|
|
|
@@ -450,7 +509,7 @@ describe('pd pain record', () => {
|
|
|
450
509
|
expect(lastRecordPainInput!.hostKind).toBe('openclaw');
|
|
451
510
|
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]) as { hostAttribution?: string; warnings?: string[] };
|
|
452
511
|
expect(jsonOutput.hostAttribution).toBe('openclaw');
|
|
453
|
-
expect((jsonOutput.warnings ?? []).some((w) => w.includes(
|
|
512
|
+
expect((jsonOutput.warnings ?? []).some((w) => w.includes('host kind not specified, defaulting to openclaw') && w.includes('--host codex'))).toBe(true);
|
|
454
513
|
|
|
455
514
|
logSpy.mockRestore();
|
|
456
515
|
exitSpy.mockRestore();
|
|
@@ -2035,7 +2035,7 @@ describe('handleRuntimeActivationDispatch — PRI-634-F R2 host provenance refus
|
|
|
2035
2035
|
});
|
|
2036
2036
|
|
|
2037
2037
|
it('code_tool_hook dispatch threads the resolved registry into RuleHostWriter and gateDeps', async () => {
|
|
2038
|
-
const registry = { version: 1, hasHostLayer: true, resolve: () => 'write', lookup: () => null, hasHostTool: () => true };
|
|
2038
|
+
const registry = { version: 1, hasHostLayer: true, resolve: () => 'write', lookup: () => null, hasHostTool: () => true, hostMappings: () => [{ rawToolName: 'write', canonicalKind: 'write' as const }] };
|
|
2039
2039
|
vi.mocked(resolveWorkspaceToolSemantics).mockReturnValueOnce({ ok: true, registry, hostKind: 'openclaw' });
|
|
2040
2040
|
await handleRuntimeActivationDispatch({
|
|
2041
2041
|
workspace: WS,
|
|
@@ -86,6 +86,7 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
86
86
|
ArtificerRunner: vi.fn().mockImplementation(function () {
|
|
87
87
|
return { run: mockRun };
|
|
88
88
|
}),
|
|
89
|
+
buildArtificerHostSemanticContext: (registry) => ({ hostKinds: [], tools: registry.hostMappings() }),
|
|
89
90
|
EvaluatorRunner: vi.fn().mockImplementation(function () {
|
|
90
91
|
return { run: mockRun };
|
|
91
92
|
}),
|
|
@@ -197,6 +198,10 @@ const { mockCreateEvaluatorRuntimeContext, mockCreateRolloutGovernanceDeps, mock
|
|
|
197
198
|
const mockCreateEvaluatorRuntimeContext = vi.fn().mockReturnValue({
|
|
198
199
|
ok: true,
|
|
199
200
|
gateDeps: { evaluateInSandbox: vi.fn() },
|
|
201
|
+
// PRI-741: the context resolution also carries the single registry
|
|
202
|
+
// snapshot the parity projection derives from.
|
|
203
|
+
registry: { resolve: vi.fn(), hostMappings: vi.fn().mockReturnValue([{ rawToolName: 'write', canonicalKind: 'write' }]) },
|
|
204
|
+
hostKinds: ['openclaw'],
|
|
200
205
|
});
|
|
201
206
|
const mockCreateRolloutGovernanceDeps = vi.fn().mockReturnValue({
|
|
202
207
|
dispatchActivation: vi.fn(),
|
|
@@ -204,7 +209,9 @@ const { mockCreateEvaluatorRuntimeContext, mockCreateRolloutGovernanceDeps, mock
|
|
|
204
209
|
});
|
|
205
210
|
const mockResolveWorkspaceHostToolSemantics = vi.fn().mockReturnValue({
|
|
206
211
|
ok: true,
|
|
207
|
-
|
|
212
|
+
// PRI-741: the projection reads the host layer via hostMappings() — the
|
|
213
|
+
// mock registry carries it like a real buildToolSemanticRegistry product.
|
|
214
|
+
registry: { resolve: vi.fn(), hostMappings: vi.fn().mockReturnValue([{ rawToolName: 'write', canonicalKind: 'write' }]) },
|
|
208
215
|
hostKinds: ['openclaw'],
|
|
209
216
|
});
|
|
210
217
|
return { mockCreateEvaluatorRuntimeContext, mockCreateRolloutGovernanceDeps, mockResolveWorkspaceHostToolSemantics };
|