@principles/codex-adapter 0.4.3 → 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 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: redacted,
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
- const stderr = [...annotateContextWarnings(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic(warning, 'Inspect PD Workspace state and retry; the hook failed open.')), ...ingestionDiagnostics];
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",
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",