@principles/codex-adapter 0.4.2 → 0.4.4
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/pd-hook.js +77 -9
- package/dist/worker/workspace-worker.js +12 -0
- package/package.json +1 -1
package/dist/pd-hook.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { appendEventLogLine, redactTelemetryString } from '@principles/core/runtime-v2';
|
|
6
|
+
import { appendEventLogLine, redactTelemetryString, isRuleHostEvaluatedEventData } from '@principles/core/runtime-v2';
|
|
7
7
|
import { createProductionHostRuntime, loadPdConfigForPlugin, resolveNearestPdWorkspace } from '@principles/host-runtime';
|
|
8
8
|
import { CODEX_TOOL_SEMANTICS } from './tool-semantics.js';
|
|
9
9
|
import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
|
|
@@ -11,6 +11,15 @@ import { CodexHooksHostAdapter } from './host-adapter.js';
|
|
|
11
11
|
import { CodexDecoderError, CodexEncoderError } from './codec/index.js';
|
|
12
12
|
import { ingestCodexConversation } from './ingestion/ingestion.js';
|
|
13
13
|
import { runGovernanceAdmission } from './ingestion/admission.js';
|
|
14
|
+
function redactStringFields(data) {
|
|
15
|
+
// rc-8: telemetry is redacted string-field by string field (same policy as
|
|
16
|
+
// the OpenClaw EventLog redactEventData) — shared by every emitter here.
|
|
17
|
+
const redacted = {};
|
|
18
|
+
for (const [key, value] of Object.entries(data)) {
|
|
19
|
+
redacted[key] = typeof value === 'string' ? redactTelemetryString(value) : value;
|
|
20
|
+
}
|
|
21
|
+
return redacted;
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
24
|
* PRI-750: event emitter for the Codex subprocess model. Writes the same
|
|
16
25
|
* `events_<date>.jsonl` line shape as the OpenClaw EventLog via the core
|
|
@@ -29,18 +38,12 @@ function codexEventEmitter(stateDir) {
|
|
|
29
38
|
});
|
|
30
39
|
},
|
|
31
40
|
recordToolCall(sessionId, data) {
|
|
32
|
-
// rc-8: tool events are telemetry — redact every string field before
|
|
33
|
-
// persisting (same policy as the OpenClaw EventLog redactEventData).
|
|
34
|
-
const redacted = {};
|
|
35
|
-
for (const [key, value] of Object.entries(data)) {
|
|
36
|
-
redacted[key] = typeof value === 'string' ? redactTelemetryString(value) : value;
|
|
37
|
-
}
|
|
38
41
|
appendEventLogLine(stateDir, {
|
|
39
42
|
ts: new Date().toISOString(),
|
|
40
43
|
type: 'tool_call',
|
|
41
44
|
category: data.error || (data.exitCode !== undefined && data.exitCode !== 0) ? 'failure' : 'success',
|
|
42
45
|
sessionId,
|
|
43
|
-
data:
|
|
46
|
+
data: redactStringFields(data),
|
|
44
47
|
});
|
|
45
48
|
},
|
|
46
49
|
};
|
|
@@ -54,6 +57,65 @@ function diagnostic(reason, nextAction) {
|
|
|
54
57
|
function errorMessage(error) {
|
|
55
58
|
return error instanceof Error ? error.message.slice(0, MAX_DIAGNOSTIC) : 'unknown_error';
|
|
56
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* PRI-813: admissible evaluation entry = the CANONICAL core schema guard
|
|
62
|
+
* (rc-1/rc-2, P4: no hand-rolled field copy that could drift from the
|
|
63
|
+
* contract) plus one caller policy: a shadow observation without a non-blank
|
|
64
|
+
* activationId is dead evidence — the shadow summary keys on the exact id —
|
|
65
|
+
* so it is rejected here (CodeRabbit CR-6).
|
|
66
|
+
*/
|
|
67
|
+
function isAdmissibleRuleHostEvaluationEntry(value) {
|
|
68
|
+
return isRuleHostEvaluatedEventData(value)
|
|
69
|
+
&& (value.activationMode !== 'shadow' || (typeof value.activationId === 'string' && value.activationId.trim().length > 0));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* PRI-813: persist the shared gate's per-activation evaluation facts
|
|
73
|
+
* (metadata.evaluations — shadow observations plus the live aggregate) as
|
|
74
|
+
* canonical `rulehost_evaluated` events through the same core JSONL writer
|
|
75
|
+
* and the same telemetry-redaction policy the Codex emitter already uses.
|
|
76
|
+
* This is the Codex side of the shadow-evidence reconnection: exact
|
|
77
|
+
* activationId per event, `activationMode: 'shadow'` rows feed the existing
|
|
78
|
+
* rulecode-shadow-summary and promotion evidence unchanged.
|
|
79
|
+
*/
|
|
80
|
+
function recordRuleHostEvaluations(stateDir, sessionId, evaluations) {
|
|
81
|
+
if (!Array.isArray(evaluations))
|
|
82
|
+
return [];
|
|
83
|
+
// One bounded diagnostic per failure CLASS — an early entry-invalid must
|
|
84
|
+
// not swallow a later persist-failure (review S4).
|
|
85
|
+
const diagnostics = [];
|
|
86
|
+
let sawInvalidEntry = false;
|
|
87
|
+
let sawPersistFailure = false;
|
|
88
|
+
for (const entry of evaluations) {
|
|
89
|
+
if (!isAdmissibleRuleHostEvaluationEntry(entry)) {
|
|
90
|
+
// rc-9: a skipped evidence row must be observable, never silent.
|
|
91
|
+
if (!sawInvalidEntry) {
|
|
92
|
+
sawInvalidEntry = true;
|
|
93
|
+
diagnostics.push(diagnostic('rulehost_evaluation_entry_invalid', 'Inspect host-runtime gate metadata contract; the evaluation event was not persisted.'));
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// CodeRabbit CR-1: evidence persistence is telemetry — an fs failure here
|
|
98
|
+
// must never propagate into processHookInvocation's fail-open catch,
|
|
99
|
+
// which would drop an already-computed deny from stdout and let the tool
|
|
100
|
+
// call proceed. Degrade observably instead (rc-9).
|
|
101
|
+
try {
|
|
102
|
+
appendEventLogLine(stateDir, {
|
|
103
|
+
ts: new Date().toISOString(),
|
|
104
|
+
type: 'rulehost_evaluated',
|
|
105
|
+
category: 'evaluated',
|
|
106
|
+
sessionId,
|
|
107
|
+
data: redactStringFields(entry),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (!sawPersistFailure) {
|
|
112
|
+
sawPersistFailure = true;
|
|
113
|
+
diagnostics.push(diagnostic(`rulehost_evaluation_persist_failed:${errorMessage(error)}`, 'Inspect workspace .state/logs writability; the evaluation event was not persisted, the tool decision is unaffected.'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return diagnostics;
|
|
118
|
+
}
|
|
57
119
|
/**
|
|
58
120
|
* PRI-780 Codex capability declaration (structured UNSUPPORTED — suspension
|
|
59
121
|
* semantics, revised after Codex review round 2 P1): the Codex host has no
|
|
@@ -187,7 +249,13 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
187
249
|
// v2 rules stay SUSPENDED on Codex (never loaded context-blind). See
|
|
188
250
|
// annotateContextWarnings for the structured unsupported declaration.
|
|
189
251
|
}).dispatch(event);
|
|
190
|
-
|
|
252
|
+
// PRI-813: the shared gate's evaluation facts leave through metadata —
|
|
253
|
+
// persist them here (telemetry persistence is host-side business; the
|
|
254
|
+
// gate itself never writes). Codex previously recorded NO
|
|
255
|
+
// rulehost_evaluated rows, so shadow evidence never reached the
|
|
256
|
+
// promotion pipeline.
|
|
257
|
+
const evaluationDiagnostics = recordRuleHostEvaluations(path.join(resolution.workspaceDir, '.state'), event.context.sessionId, result.metadata?.evaluations);
|
|
258
|
+
const stderr = [...annotateContextWarnings(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic(warning, 'Inspect PD Workspace state and retry; the hook failed open.')), ...evaluationDiagnostics, ...ingestionDiagnostics];
|
|
191
259
|
return { stdout: adapter.encodeOutput(result, event.kind), exitCode: 0, stderr };
|
|
192
260
|
}
|
|
193
261
|
catch (error) {
|
|
@@ -206,6 +206,18 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
206
206
|
});
|
|
207
207
|
if (!declared.ok) {
|
|
208
208
|
logger.warn?.(`[PD:CodexWorker] Failed to persist Codex tool declaration: ${declared.reason} — host-neutral consumers will not find it (rc-9)`);
|
|
209
|
+
// CodeRabbit CR-2 (PRI-813): creating activations while the Codex
|
|
210
|
+
// declaration is absent would let promotion fall back to the OpenClaw
|
|
211
|
+
// liveness contract (host_tool_declaration_missing → OpenClaw default)
|
|
212
|
+
// and falsely pass host-liveness checks for a Codex activation. Degrade
|
|
213
|
+
// this cycle instead; the next cycle retries the declaration first.
|
|
214
|
+
return {
|
|
215
|
+
...base,
|
|
216
|
+
mode: 'degraded',
|
|
217
|
+
reason: `host_tool_declaration_persist_failed:${declared.reason}`,
|
|
218
|
+
nextAction: 'Repair Codex host declaration persistence (.pd/host-tool-semantics/codex.json) before running the downstream internalization cycle; the worker retries automatically on the next cycle.',
|
|
219
|
+
report: { catchUp, reconcile, diagnostician, downstream: null },
|
|
220
|
+
};
|
|
209
221
|
}
|
|
210
222
|
const downstream = await runInternalizationConsumerCycle(workspaceDir, {
|
|
211
223
|
owner: WORKER_OWNER,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|