create-principles-disciple 1.147.0 → 1.147.2
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/admission.d.ts +5 -10
- package/codex-adapter/dist/ingestion/admission.js +19 -77
- package/codex-adapter/dist/ingestion/explicit-owner-statement.js +1 -1
- package/codex-adapter/dist/ingestion/ingestion.d.ts +0 -2
- package/codex-adapter/dist/ingestion/ingestion.js +6 -4
- package/codex-adapter/dist/pd-hook.js +5 -7
- package/codex-adapter/package.json +1 -1
- package/console/dist/shared/prompt-injection-contract.d.ts +1 -1
- package/console/dist/web/assets/app.js +31 -31
- package/core/dist/prompt-builder/size-guard.d.ts.map +1 -1
- package/core/dist/prompt-builder/size-guard.js +6 -1
- package/core/dist/prompt-builder/size-guard.js.map +1 -1
- package/core/dist/prompt-builder/types.d.ts +2 -1
- package/core/dist/prompt-builder/types.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +12 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js +12 -1
- package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts +42 -11
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js +82 -30
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js +17 -5
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/index.d.ts +0 -1
- package/core/dist/runtime-v2/evidence-triage/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/index.js +0 -2
- package/core/dist/runtime-v2/evidence-triage/index.js.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/source-descriptors.d.ts +3 -8
- package/core/dist/runtime-v2/evidence-triage/source-descriptors.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/source-descriptors.js +5 -17
- package/core/dist/runtime-v2/evidence-triage/source-descriptors.js.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/triage-policy.d.ts +11 -9
- package/core/dist/runtime-v2/evidence-triage/triage-policy.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/triage-policy.js +10 -103
- package/core/dist/runtime-v2/evidence-triage/triage-policy.js.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/trigger-controller.js +1 -1
- package/core/dist/runtime-v2/evidence-triage/trigger-controller.js.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/types.d.ts +4 -16
- package/core/dist/runtime-v2/evidence-triage/types.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-triage/types.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +1 -1
- 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/intent/intent-friction-block.d.ts +2 -2
- package/core/dist/runtime-v2/intent/intent-friction-block.js +2 -2
- package/core/dist/runtime-v2/types/event-types.d.ts +2 -2
- 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/host-runtime/dist/governance-signal-admission.d.ts +1 -77
- package/host-runtime/dist/governance-signal-admission.js +34 -294
- package/host-runtime/dist/production-pain-evidence.d.ts +8 -69
- package/host-runtime/dist/production-pain-evidence.js +19 -157
- package/host-runtime/dist/prompt-injection-budget-observation.js +4 -2
- package/host-runtime/package.json +1 -1
- package/package.json +3 -3
- package/pd-cli/package.json +1 -1
- package/pd-mcp-server/dist/version.d.ts +1 -1
- package/pd-mcp-server/dist/version.js +1 -1
- package/pd-mcp-server/package.json +1 -1
- package/plugin/dist/bundle.js +411 -412
- package/plugin/dist/governance-audit.js +2 -2
- package/plugin/dist/hooks/after-tool-call-helpers.d.ts +2 -35
- package/plugin/dist/hooks/after-tool-call-types.d.ts +1 -30
- package/plugin/dist/hooks/pain.d.ts +6 -19
- package/plugin/dist/hooks/trajectory-evidence.d.ts +3 -10
- package/plugin/dist/hooks/triage-adapter.d.ts +7 -20
- package/plugin/dist/host-runtime/openclaw-host-runtime.d.ts +0 -1
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/package.json +1 -1
- package/workbuddy-adapter/dist/codec/input-decoder.js +3 -1
- package/workbuddy-adapter/dist/pd-hook.js +0 -26
- package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +1 -7
- package/workbuddy-adapter/dist/worker/workspace-worker.js +11 -21
- package/workbuddy-adapter/package.json +1 -1
|
@@ -35,8 +35,8 @@ import fs from 'node:fs';
|
|
|
35
35
|
import path from 'node:path';
|
|
36
36
|
import { randomUUID } from 'node:crypto';
|
|
37
37
|
import Database from 'better-sqlite3';
|
|
38
|
-
import { collectSync, computeFeatureFlagsFromConfig, CORRECTION_SEED_KEYWORDS,
|
|
39
|
-
import { deriveProductionCorrectionPainIdentity,
|
|
38
|
+
import { collectSync, computeFeatureFlagsFromConfig, CORRECTION_SEED_KEYWORDS, PainToPrincipleService, PrincipleTreeLedgerAdapter, RuntimeStateManager, createDiagnosticianTaskId, disposePainSignalBridgesForWorkspace, sanitizeString, boundCorrectionEvidenceText, MAX_EVIDENCE_VALUE_CHARS, evaluateStage2ConfidenceGate, evaluatePainIngress, STAGE2_MAX_ATTEMPTS, PENDING_CANDIDATE_TTL_MS, } from '@principles/core/runtime-v2';
|
|
39
|
+
import { deriveProductionCorrectionPainIdentity, hasProductionPainSchema, } from './production-pain-evidence.js';
|
|
40
40
|
import { promoteGovernanceEvidence, ensureGovernanceSchema } from './governance-observation-store.js';
|
|
41
41
|
import { loadPdConfigForPlugin } from './pd-config.js';
|
|
42
42
|
// ─── Shared detector constants (one truth for both hosts) ───────────────────
|
|
@@ -305,8 +305,8 @@ function ensureGovernanceAdmissionSchema(db) {
|
|
|
305
305
|
db.transaction(() => {
|
|
306
306
|
for (const statement of ADMISSION_CREATE_STATEMENTS)
|
|
307
307
|
db.exec(statement);
|
|
308
|
-
// SPEC-CODEX-PAIN-CLOSURE-001 CR-01:
|
|
309
|
-
//
|
|
308
|
+
// SPEC-CODEX-PAIN-CLOSURE-001 CR-01: trajectory evidence writers persist
|
|
309
|
+
// tool_calls; make sure the host tool-use-id join column exists on
|
|
310
310
|
// workspaces whose base schema predates it (idempotent additive ALTER —
|
|
311
311
|
// only when the table itself exists: governance-only databases without a
|
|
312
312
|
// tool_calls table are a valid pre-existing state, not an error).
|
|
@@ -439,29 +439,6 @@ function insertCorrectionPain({ db, candidate, workspaceDir, painId, reason, now
|
|
|
439
439
|
.run(candidate.rootSessionId, 'user_correction', GOVERNANCE_STRONG_PAIN_SCORE, reason.slice(0, MAX_REASON_BOUND), 'severe', 'system_infer', null, excerpt, painId, null, candidate.hostKind, nowIso);
|
|
440
440
|
return reason;
|
|
441
441
|
}
|
|
442
|
-
/**
|
|
443
|
-
* ONE builder for the tool_failure task payload shape — the Codex inline
|
|
444
|
-
* admission path and the WorkBuddy bare-pain sweep must produce the SAME
|
|
445
|
-
* shape, differing only in the attributed host and the pain facts.
|
|
446
|
-
*/
|
|
447
|
-
function buildToolFailureTaskPayload(fields) {
|
|
448
|
-
// P1-1 privacy (review round 2): sanitize the FULL text before any
|
|
449
|
-
// truncation — a token crossing the 200-char boundary must be redacted, not
|
|
450
|
-
// split into a fragment that survives verbatim. sanitizeString bounds itself.
|
|
451
|
-
const payload = {
|
|
452
|
-
painType: 'tool_failure',
|
|
453
|
-
source: fields.toolName,
|
|
454
|
-
reason: fields.reason || `tool=${fields.toolName}`,
|
|
455
|
-
score: fields.score,
|
|
456
|
-
sessionId: fields.sessionId,
|
|
457
|
-
hostKind: fields.hostKind,
|
|
458
|
-
evidence: [{ sourceRef: `governance_observation:${fields.logicalObservationKey}`, note: sanitizeString(fields.reason || fields.toolName, fields.workspaceDir) }],
|
|
459
|
-
};
|
|
460
|
-
return payload;
|
|
461
|
-
}
|
|
462
|
-
function toolFailureTaskPayload(fields) {
|
|
463
|
-
return JSON.stringify(buildToolFailureTaskPayload(fields));
|
|
464
|
-
}
|
|
465
442
|
/**
|
|
466
443
|
* P1 (review round 3): a `behavior_trace` may only name a row that EXISTS in
|
|
467
444
|
* trajectory.db with the SAME lineage tuple. WorkBuddy currently has no
|
|
@@ -486,47 +463,36 @@ function persistedBehaviorTraceEvidence(args) {
|
|
|
486
463
|
}
|
|
487
464
|
function buildTaskSubmissionPayload(candidate, workspaceDir, details) {
|
|
488
465
|
const { reason, db } = details;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
score: GOVERNANCE_STRONG_PAIN_SCORE,
|
|
498
|
-
sessionId: candidate.rootSessionId,
|
|
499
|
-
hostKind: candidate.hostKind,
|
|
500
|
-
evidence: persistedBehaviorTraceEvidence({
|
|
501
|
-
db,
|
|
502
|
-
hostKind: candidate.hostKind,
|
|
503
|
-
logicalObservationKey: candidate.logicalObservationKey,
|
|
504
|
-
rolloutIdentity: candidate.rolloutIdentity,
|
|
505
|
-
rootSessionId: candidate.rootSessionId,
|
|
506
|
-
hostTurnId: candidate.hostTurnId,
|
|
507
|
-
text: candidate.text,
|
|
508
|
-
workspaceDir,
|
|
509
|
-
}),
|
|
510
|
-
// SPEC-CODEX-PAIN-CLOSURE-001 CR-02 (merged from main): an explicit
|
|
511
|
-
// Owner statement rides as classified correction evidence — never
|
|
512
|
-
// summarized by an AI, never inside evidence[] pseudo-trace entries.
|
|
513
|
-
...(candidate.explicitOwnerStatement === true
|
|
514
|
-
? { correctionEvidence: candidate.hostKind === 'workbuddy'
|
|
515
|
-
? { text: boundCorrectionEvidenceText({ text: candidate.text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }, workspaceDir).text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }
|
|
516
|
-
: { text: sanitizeString(candidate.text, workspaceDir), classification: 'owner_statement', channel: candidate.explicitChannel === 'codex_explicit_agent' ? 'codex_explicit_agent' : 'codex_explicit_prefix' } }
|
|
517
|
-
: {}),
|
|
518
|
-
};
|
|
519
|
-
return payload;
|
|
520
|
-
}
|
|
521
|
-
return buildToolFailureTaskPayload({
|
|
522
|
-
hostKind: candidate.hostKind,
|
|
523
|
-
logicalObservationKey: candidate.logicalObservationKey,
|
|
466
|
+
// P1-1 privacy (review round 2): sanitize the FULL text before any
|
|
467
|
+
// truncation — a token crossing the 200-char boundary must be redacted, not
|
|
468
|
+
// split into a fragment that survives verbatim. sanitizeString bounds itself.
|
|
469
|
+
const payload = {
|
|
470
|
+
painType: 'user_frustration',
|
|
471
|
+
source: 'user_correction',
|
|
472
|
+
reason: reason || 'User correction detected',
|
|
473
|
+
score: GOVERNANCE_STRONG_PAIN_SCORE,
|
|
524
474
|
sessionId: candidate.rootSessionId,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
475
|
+
hostKind: candidate.hostKind,
|
|
476
|
+
evidence: persistedBehaviorTraceEvidence({
|
|
477
|
+
db,
|
|
478
|
+
hostKind: candidate.hostKind,
|
|
479
|
+
logicalObservationKey: candidate.logicalObservationKey,
|
|
480
|
+
rolloutIdentity: candidate.rolloutIdentity,
|
|
481
|
+
rootSessionId: candidate.rootSessionId,
|
|
482
|
+
hostTurnId: candidate.hostTurnId,
|
|
483
|
+
text: candidate.text,
|
|
484
|
+
workspaceDir,
|
|
485
|
+
}),
|
|
486
|
+
// SPEC-CODEX-PAIN-CLOSURE-001 CR-02 (merged from main): an explicit
|
|
487
|
+
// Owner statement rides as classified correction evidence — never
|
|
488
|
+
// summarized by an AI, never inside evidence[] pseudo-trace entries.
|
|
489
|
+
...(candidate.explicitOwnerStatement === true
|
|
490
|
+
? { correctionEvidence: candidate.hostKind === 'workbuddy'
|
|
491
|
+
? { text: boundCorrectionEvidenceText({ text: candidate.text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }, workspaceDir).text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }
|
|
492
|
+
: { text: sanitizeString(candidate.text, workspaceDir), classification: 'owner_statement', channel: candidate.explicitChannel === 'codex_explicit_agent' ? 'codex_explicit_agent' : 'codex_explicit_prefix' } }
|
|
493
|
+
: {}),
|
|
494
|
+
};
|
|
495
|
+
return payload;
|
|
530
496
|
}
|
|
531
497
|
function taskSubmissionPayload(candidate, workspaceDir, details) {
|
|
532
498
|
return JSON.stringify(buildTaskSubmissionPayload(candidate, workspaceDir, details));
|
|
@@ -1040,106 +1006,6 @@ function admitCorrection({ db, candidate, workspaceDir, now, nowIso, keywordStor
|
|
|
1040
1006
|
}
|
|
1041
1007
|
return { disposition: 'admitted', kind: 'user_correction', logicalObservationKey: candidate.logicalObservationKey, canonicalPainId: painId, duplicate, ruleVersion: detection?.ruleVersion ?? null };
|
|
1042
1008
|
}
|
|
1043
|
-
function admitToolFailure({ db, candidate, workspaceDir, nowIso }) {
|
|
1044
|
-
const identity = deriveProductionToolPainIdentity({
|
|
1045
|
-
workspaceDir,
|
|
1046
|
-
sessionId: candidate.rootSessionId,
|
|
1047
|
-
turnId: candidate.hostTurnId,
|
|
1048
|
-
toolName: candidate.toolName,
|
|
1049
|
-
source: candidate.source,
|
|
1050
|
-
// I1 identity parity: same call id the live handler derived from (the
|
|
1051
|
-
// candidate builder forwards the host `tool_use_id`).
|
|
1052
|
-
...(candidate.toolCallId !== undefined ? { toolCallId: candidate.toolCallId } : {}),
|
|
1053
|
-
...(candidate.toolInput !== undefined ? { toolInput: candidate.toolInput } : {}),
|
|
1054
|
-
...(candidate.toolOutput !== undefined ? { toolOutput: candidate.toolOutput } : {}),
|
|
1055
|
-
});
|
|
1056
|
-
const { outcome, sanitizedParams, paramsJson, resultPreview, painId } = identity;
|
|
1057
|
-
// Admission gate — identical semantics to the live production handler on a
|
|
1058
|
-
// fresh process (ADR-0020 accepts cold in-memory cooldowns for Codex;
|
|
1059
|
-
// canonical idempotency is the guard).
|
|
1060
|
-
const sourceObservation = buildToolFailureObservation({ toolName: candidate.toolName, error: outcome.error, exitCode: outcome.exitCode ?? undefined });
|
|
1061
|
-
const sourceKind = resolveSourceKind({
|
|
1062
|
-
observedAt: nowIso,
|
|
1063
|
-
workspaceId: workspaceDir,
|
|
1064
|
-
sessionId: candidate.rootSessionId,
|
|
1065
|
-
toolName: candidate.toolName,
|
|
1066
|
-
failureSource: sourceObservation.failureSource,
|
|
1067
|
-
toolNotFound: sourceObservation.toolNotFound,
|
|
1068
|
-
nonZeroExit: outcome.exitCode !== null && outcome.exitCode !== 0,
|
|
1069
|
-
});
|
|
1070
|
-
// Risk evaluation mirrors the production handler: it reads the RAW params
|
|
1071
|
-
// (sanitized params replace paths with placeholders and would lose the
|
|
1072
|
-
// absolute-path risk signal). Identity still uses sanitized params.
|
|
1073
|
-
const rawFile = isRecord(outcome.params) ? (own(outcome.params, 'file_path') ?? own(outcome.params, 'path')) : undefined;
|
|
1074
|
-
const relativePath = String(rawFile ?? 'unknown').slice(0, 500);
|
|
1075
|
-
const isRisky = path.isAbsolute(relativePath) && !path.resolve(relativePath).startsWith(`${workspaceDir}${path.sep}`);
|
|
1076
|
-
const painScore = Math.min(100, (outcome.failure ? 70 : 0) + (isRisky ? 20 : 0));
|
|
1077
|
-
const triage = evaluateTriage({ sourceKind, score: painScore, isRisky });
|
|
1078
|
-
const trigger = evaluateTriggerController({ triageResult: triage, isOwnerManual: false, isCooldownActive: false, isValid: true, score: painScore, sessionId: candidate.rootSessionId });
|
|
1079
|
-
const gateAdmitted = outcome.failure && PRODUCTION_WRITE_TOOLS.has(candidate.toolName) && trigger.shouldCreateDiagnosticTask;
|
|
1080
|
-
void sanitizedParams;
|
|
1081
|
-
if (!gateAdmitted) {
|
|
1082
|
-
return { disposition: 'not_admitted', kind: 'tool_failure', logicalObservationKey: candidate.logicalObservationKey, reason: trigger.reason };
|
|
1083
|
-
}
|
|
1084
|
-
let duplicate = false;
|
|
1085
|
-
let duplicateWithoutPain = false;
|
|
1086
|
-
let reason = '';
|
|
1087
|
-
db.transaction(() => {
|
|
1088
|
-
// Same duplicate probes as the production handler: the host tool-call id
|
|
1089
|
-
// (CR-01) is the stable join key; the content-shape probe stays the
|
|
1090
|
-
// fallback for hosts/candidates without ids.
|
|
1091
|
-
// Review R3/P2: admission dedup is host-scoped — a colliding row from
|
|
1092
|
-
// another host is not this candidate's duplicate.
|
|
1093
|
-
if (candidate.toolUseId !== undefined
|
|
1094
|
-
&& (candidate.hostKind !== undefined
|
|
1095
|
-
? db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_use_id = ? AND host_kind = ?').get(candidate.rootSessionId, candidate.toolUseId, candidate.hostKind)
|
|
1096
|
-
: db.prepare('SELECT 1 FROM tool_calls WHERE session_id = ? AND tool_use_id = ?').get(candidate.rootSessionId, candidate.toolUseId)) !== undefined) {
|
|
1097
|
-
duplicate = true;
|
|
1098
|
-
duplicateWithoutPain = db.prepare('SELECT 1 FROM pain_events WHERE canonical_pain_id = ?').get(painId) === undefined;
|
|
1099
|
-
}
|
|
1100
|
-
else if ((candidate.hostKind !== undefined
|
|
1101
|
-
? 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 = ?')
|
|
1102
|
-
.get(candidate.rootSessionId, candidate.toolName, paramsJson, outcome.outcome, outcome.exitCode, outcome.error ?? null, resultPreview, candidate.hostKind)
|
|
1103
|
-
: 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 ?')
|
|
1104
|
-
.get(candidate.rootSessionId, candidate.toolName, paramsJson, outcome.outcome, outcome.exitCode, outcome.error ?? null, resultPreview)) !== undefined) {
|
|
1105
|
-
duplicate = true;
|
|
1106
|
-
duplicateWithoutPain = db.prepare('SELECT 1 FROM pain_events WHERE canonical_pain_id = ?').get(painId) === undefined;
|
|
1107
|
-
}
|
|
1108
|
-
else {
|
|
1109
|
-
db.prepare(`INSERT INTO sessions (session_id, started_at, updated_at) VALUES (?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET updated_at = excluded.updated_at`)
|
|
1110
|
-
.run(candidate.rootSessionId, nowIso, nowIso);
|
|
1111
|
-
const projectDir = typeof candidate.projectDir === 'string' && path.isAbsolute(candidate.projectDir)
|
|
1112
|
-
? path.resolve(candidate.projectDir)
|
|
1113
|
-
: null;
|
|
1114
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1115
|
-
.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);
|
|
1116
|
-
reason = `tool=${candidate.toolName}; error=${outcome.error ?? `exit=${outcome.exitCode}`}; path=${relativePath}`;
|
|
1117
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1118
|
-
.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);
|
|
1119
|
-
}
|
|
1120
|
-
// PRI-640 §17: enrich unknown → codex on the already-admitted duplicate
|
|
1121
|
-
// pain row; the IS NULL guard keeps any durable attribution.
|
|
1122
|
-
db.prepare('UPDATE pain_events SET host_kind = ? WHERE canonical_pain_id = ? AND host_kind IS NULL').run(candidate.hostKind, painId);
|
|
1123
|
-
if (!duplicateWithoutPain) {
|
|
1124
|
-
insertAdmissionMarker({
|
|
1125
|
-
db,
|
|
1126
|
-
identity: { hostKind: candidate.hostKind, logicalObservationKey: candidate.logicalObservationKey, rolloutIdentity: candidate.rolloutIdentity, rootSessionId: candidate.rootSessionId, signalKind: candidate.kind },
|
|
1127
|
-
canonicalPainId: painId,
|
|
1128
|
-
ruleVersion: null,
|
|
1129
|
-
reason: sanitizeString(reason, workspaceDir),
|
|
1130
|
-
payloadJson: taskSubmissionPayload(candidate, workspaceDir, { reason, db }),
|
|
1131
|
-
nowIso,
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
// A duplicate call row without an admitted pain mirrors the production
|
|
1135
|
-
// handler (no pain insert on duplicates) and records no marker, so a later
|
|
1136
|
-
// delivery whose gate reaches threshold can still admit.
|
|
1137
|
-
})();
|
|
1138
|
-
if (duplicateWithoutPain) {
|
|
1139
|
-
return { disposition: 'not_admitted', kind: 'tool_failure', logicalObservationKey: candidate.logicalObservationKey, reason: 'duplicate_tool_call_without_admitted_pain' };
|
|
1140
|
-
}
|
|
1141
|
-
return { disposition: 'admitted', kind: 'tool_failure', logicalObservationKey: candidate.logicalObservationKey, canonicalPainId: painId, duplicate, ruleVersion: null };
|
|
1142
|
-
}
|
|
1143
1009
|
function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDegradation, trustedWorkbuddyLive }) {
|
|
1144
1010
|
if (candidate.hostKind !== 'codex' && !(candidate.hostKind === 'workbuddy' && candidate.kind === 'user_correction' && (candidate.explicitOwnerStatement === true || trustedWorkbuddyLive === true))) {
|
|
1145
1011
|
return { ok: false, reason: 'unsupported_host_kind', nextAction: 'governance signal admission accepts Codex observations or explicit WorkBuddy Owner statements' };
|
|
@@ -1171,10 +1037,7 @@ function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDe
|
|
|
1171
1037
|
};
|
|
1172
1038
|
}
|
|
1173
1039
|
try {
|
|
1174
|
-
|
|
1175
|
-
return admitCorrection({ db, candidate, workspaceDir, now, nowIso, ...(keywordStore ? { keywordStore } : {}), ...(onDegradation ? { onDegradation } : {}) });
|
|
1176
|
-
}
|
|
1177
|
-
return admitToolFailure({ db, candidate, workspaceDir, nowIso });
|
|
1040
|
+
return admitCorrection({ db, candidate, workspaceDir, now, nowIso, ...(keywordStore ? { keywordStore } : {}), ...(onDegradation ? { onDegradation } : {}) });
|
|
1178
1041
|
}
|
|
1179
1042
|
catch (error) {
|
|
1180
1043
|
// RateLimitedSignal never reaches here — admitCorrection converts it to a
|
|
@@ -2117,129 +1980,6 @@ export async function ensureGovernanceContinuation(input) {
|
|
|
2117
1980
|
close();
|
|
2118
1981
|
}
|
|
2119
1982
|
}
|
|
2120
|
-
/**
|
|
2121
|
-
* Admit bare host pains — failure rows the shared production pain handler
|
|
2122
|
-
* already persisted (host_kind set, canonical_pain_id set, runtime_task_id
|
|
2123
|
-
* NULL) but that no inline admission exists for. The WorkBuddy hook is the
|
|
2124
|
-
* only evidence producer on that host (SPEC workbuddy-host-adapter §1: no
|
|
2125
|
-
* transcript reads), so these rows would otherwise stay stranded as
|
|
2126
|
-
* evidence-only forever: reconcile only continues ADMITTED markers, and no
|
|
2127
|
-
* WorkBuddy admission step existed (review R2/P1).
|
|
2128
|
-
*
|
|
2129
|
-
* Policy: only failures scoring at/above the Codex tool_failure admission
|
|
2130
|
-
* floor (GOVERNANCE_STRONG_PAIN_SCORE = 70) are queued for diagnosis — that
|
|
2131
|
-
* floor is the existing signal-quality boundary for write-tool failures, and
|
|
2132
|
-
* lower-scored failures stay evidence-only in pain_events/tool_calls rather
|
|
2133
|
-
* than diluting the diagnostician queue. Idempotency reuses the ONE existing
|
|
2134
|
-
* authorities: the unique admission marker (canonical_pain_id probe +
|
|
2135
|
-
* UNIQUE logical key) and the deterministic task id, so repeated sweeps and
|
|
2136
|
-
* crash restarts never mint a second task. Task creation reuses
|
|
2137
|
-
* `ensureGovernanceDiagnosticianTask` unchanged (including its Case-B link
|
|
2138
|
-
* repair). Evidence promotion is deliberately NOT attempted here: the
|
|
2139
|
-
* promotion substrate (governance_rollouts/observations) is fed by transcript
|
|
2140
|
-
* ingestion, which the WorkBuddy MVP excludes.
|
|
2141
|
-
*/
|
|
2142
|
-
export async function admitBareHostPains(input) {
|
|
2143
|
-
const opened = openAdmissionStore(input.workspaceDir, input.databaseFactory);
|
|
2144
|
-
if (!('db' in opened)) {
|
|
2145
|
-
return { ok: false, reason: opened.reason, nextAction: opened.nextAction, admitted: 0, ensured: 0, skippedLowScore: 0, skippedInvalid: 0, degradations: [opened.reason] };
|
|
2146
|
-
}
|
|
2147
|
-
const { db, close } = opened;
|
|
2148
|
-
if (!hasProductionPainSchema(db)) {
|
|
2149
|
-
close();
|
|
2150
|
-
return { ok: false, reason: 'trajectory_schema_invalid', nextAction: 'run the supported PD workspace migration before enabling the worker', admitted: 0, ensured: 0, skippedLowScore: 0, skippedInvalid: 0, degradations: ['trajectory_schema_invalid'] };
|
|
2151
|
-
}
|
|
2152
|
-
const nowIso = (input.now ?? new Date()).toISOString();
|
|
2153
|
-
const workspaceDir = path.resolve(input.workspaceDir);
|
|
2154
|
-
const limit = Math.min(Math.max(input.limit ?? 5, 1), 50);
|
|
2155
|
-
// Bare pains of this host that no admission marker covers yet. A marker is
|
|
2156
|
-
// the durable "already handled" fact; runtime_task_id additionally excludes
|
|
2157
|
-
// pains that reached a task through another path.
|
|
2158
|
-
const barePainPredicate = `
|
|
2159
|
-
FROM pain_events p
|
|
2160
|
-
WHERE p.host_kind = ?
|
|
2161
|
-
AND p.runtime_task_id IS NULL
|
|
2162
|
-
AND p.canonical_pain_id IS NOT NULL
|
|
2163
|
-
AND NOT EXISTS (SELECT 1 FROM governance_signal_admissions a WHERE a.canonical_pain_id = p.canonical_pain_id)`;
|
|
2164
|
-
try {
|
|
2165
|
-
const lowScoreRow = db.prepare(`SELECT COUNT(*) AS n ${barePainPredicate} AND p.score < ?`).get(input.hostKind, GOVERNANCE_STRONG_PAIN_SCORE);
|
|
2166
|
-
const skippedLowScore = typeof rowField(lowScoreRow, 'n') === 'number' ? rowField(lowScoreRow, 'n') : 0;
|
|
2167
|
-
const rows = db.prepare(`SELECT p.canonical_pain_id, p.session_id, p.source, p.score, p.reason ${barePainPredicate} AND p.score >= ? ORDER BY p.id LIMIT ?`)
|
|
2168
|
-
.all(input.hostKind, GOVERNANCE_STRONG_PAIN_SCORE, limit);
|
|
2169
|
-
let admitted = 0;
|
|
2170
|
-
let ensured = 0;
|
|
2171
|
-
let skippedInvalid = 0;
|
|
2172
|
-
const degradations = [];
|
|
2173
|
-
const freshMarkers = [];
|
|
2174
|
-
for (const row of rows) {
|
|
2175
|
-
const painId = rowField(row, 'canonical_pain_id');
|
|
2176
|
-
const sessionId = rowField(row, 'session_id');
|
|
2177
|
-
const score = rowField(row, 'score');
|
|
2178
|
-
if (typeof painId !== 'string' || painId.length === 0 || typeof sessionId !== 'string' || sessionId.length === 0
|
|
2179
|
-
|| typeof score !== 'number' || !Number.isFinite(score)) {
|
|
2180
|
-
skippedInvalid += 1;
|
|
2181
|
-
continue;
|
|
2182
|
-
}
|
|
2183
|
-
const logicalObservationKey = `${input.hostKind}|${sessionId}|${painId}`;
|
|
2184
|
-
const reason = typeof rowField(row, 'reason') === 'string' ? rowField(row, 'reason') : '';
|
|
2185
|
-
const source = typeof rowField(row, 'source') === 'string' ? rowField(row, 'source') : 'tool_failure';
|
|
2186
|
-
// The pain row carries the tool name only inside its reason prefix
|
|
2187
|
-
// (`tool=<name>; ...` — the production writer's format); the row's
|
|
2188
|
-
// source column is the failure class, used as the fallback.
|
|
2189
|
-
const toolName = /^tool=([^;]{1,120});/.exec(reason)?.[1] ?? source;
|
|
2190
|
-
const inserted = db.transaction(() => {
|
|
2191
|
-
// Re-probe inside the transaction: a concurrent sweep or live admission
|
|
2192
|
-
// may have marked this pain between the scan and this write.
|
|
2193
|
-
if (db.prepare('SELECT 1 FROM governance_signal_admissions WHERE canonical_pain_id = ?').get(painId) !== undefined)
|
|
2194
|
-
return false;
|
|
2195
|
-
insertAdmissionMarker({
|
|
2196
|
-
db,
|
|
2197
|
-
identity: { hostKind: input.hostKind, logicalObservationKey, rolloutIdentity: sessionId, rootSessionId: sessionId, signalKind: 'tool_failure' },
|
|
2198
|
-
canonicalPainId: painId,
|
|
2199
|
-
ruleVersion: null,
|
|
2200
|
-
reason,
|
|
2201
|
-
payloadJson: toolFailureTaskPayload({ hostKind: input.hostKind, logicalObservationKey, sessionId, toolName, reason, score, workspaceDir }),
|
|
2202
|
-
nowIso,
|
|
2203
|
-
});
|
|
2204
|
-
return true;
|
|
2205
|
-
})();
|
|
2206
|
-
if (inserted) {
|
|
2207
|
-
admitted += 1;
|
|
2208
|
-
freshMarkers.push({ logicalObservationKey, canonicalPainId: painId });
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
// Reuse the crash-safe ensure semantics unchanged: deterministic task id,
|
|
2212
|
-
// task-store PK idempotency, Case-B link repair.
|
|
2213
|
-
for (const marker of freshMarkers) {
|
|
2214
|
-
const continuation = await ensureGovernanceDiagnosticianTask({
|
|
2215
|
-
workspaceDir,
|
|
2216
|
-
logicalObservationKey: marker.logicalObservationKey,
|
|
2217
|
-
canonicalPainId: marker.canonicalPainId,
|
|
2218
|
-
...(input.databaseFactory ? { databaseFactory: input.databaseFactory } : {}),
|
|
2219
|
-
});
|
|
2220
|
-
if (continuation.ok) {
|
|
2221
|
-
if (continuation.created)
|
|
2222
|
-
ensured += 1;
|
|
2223
|
-
}
|
|
2224
|
-
else {
|
|
2225
|
-
degradations.push(`${marker.logicalObservationKey}:${continuation.reason}`);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
return {
|
|
2229
|
-
ok: degradations.length === 0,
|
|
2230
|
-
...(degradations.length > 0 ? { reason: 'bare_pain_admission_degradations', nextAction: 'inspect the per-item degradations; the admitted pains and markers remain durable and reconcile retries task creation' } : {}),
|
|
2231
|
-
admitted, ensured, skippedLowScore, skippedInvalid,
|
|
2232
|
-
degradations: degradations.slice(0, 10),
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
catch (error) {
|
|
2236
|
-
const detail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
|
|
2237
|
-
return { ok: false, reason: `bare_pain_admission_failed:${detail}`, nextAction: 'inspect the workspace trajectory database and retry; committed markers remain durable', admitted: 0, ensured: 0, skippedLowScore: 0, skippedInvalid: 0, degradations: [`bare_pain_admission_failed:${detail}`] };
|
|
2238
|
-
}
|
|
2239
|
-
finally {
|
|
2240
|
-
close();
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
1983
|
/**
|
|
2244
1984
|
* Narrow idempotent reconciliation between the trajectory admission markers
|
|
2245
1985
|
* and the Runtime V2 task store (separate SQLite databases — no cross-store
|
|
@@ -1,80 +1,14 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import type { GovernanceHostKind } from '@principles/core/runtime-v2';
|
|
3
3
|
import type { HostEvent, HostEventEmitter, HostEventResult } from '@principles/core/host';
|
|
4
|
-
/** Shared with the governance admission path so both gate tool failures identically. */
|
|
5
|
-
export declare const PRODUCTION_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
|
-
export interface PainEvidenceEntry {
|
|
7
|
-
sourceRef: string;
|
|
8
|
-
note: string;
|
|
9
|
-
}
|
|
10
4
|
export interface ProductionPainEnrichment {
|
|
5
|
+
/** Stable host-supplied occurrence identity for the tool call. */
|
|
11
6
|
eventId?: string;
|
|
12
|
-
painScore?: number;
|
|
13
|
-
isRisky?: boolean;
|
|
14
|
-
consecutiveErrors?: number;
|
|
15
|
-
relativePath?: string;
|
|
16
|
-
agentId?: string;
|
|
17
|
-
errorHash?: string;
|
|
18
|
-
evidence?: readonly PainEvidenceEntry[];
|
|
19
7
|
}
|
|
20
8
|
export type PainEnrichmentProvider = (event: HostEvent) => unknown | Promise<unknown>;
|
|
21
9
|
export type PainDatabaseFactory = (databasePath: string) => Database.Database;
|
|
22
10
|
/** SPEC-CODEX-PAIN-CLOSURE-001 CR-01: a tool result is success, failure or unknown. */
|
|
23
11
|
export type ToolOutcomeClassification = 'success' | 'failure' | 'unknown';
|
|
24
|
-
interface NormalizedOutcome {
|
|
25
|
-
outcome: ToolOutcomeClassification;
|
|
26
|
-
failure: boolean;
|
|
27
|
-
exitCode: number | null;
|
|
28
|
-
/** Bounded structured reason present when the outcome is unknown (rc-9). */
|
|
29
|
-
unknownReason?: string;
|
|
30
|
-
error?: string;
|
|
31
|
-
durationMs?: number;
|
|
32
|
-
params: unknown;
|
|
33
|
-
result: unknown;
|
|
34
|
-
}
|
|
35
|
-
/** Structural input accepted by the canonical derivations (HostEvent satisfies it without a cast). */
|
|
36
|
-
export interface ProductionToolEventFields {
|
|
37
|
-
workspaceDir: string;
|
|
38
|
-
sessionId: string;
|
|
39
|
-
turnId?: string;
|
|
40
|
-
/**
|
|
41
|
-
* I1: the host's real per-call identity (Codex `tool_use_id`, WorkBuddy
|
|
42
|
-
* `call_id`). Two calls with the same session/generation/tool/params/result
|
|
43
|
-
* but DIFFERENT call ids are two real occurrences and must derive two
|
|
44
|
-
* identities; a redelivery of the SAME call id is one occurrence. Optional:
|
|
45
|
-
* hosts that supply no call id keep the historical content-derived identity
|
|
46
|
-
* (identical-content calls then cannot be distinguished — a documented
|
|
47
|
-
* limitation, never fabricated precision).
|
|
48
|
-
*/
|
|
49
|
-
toolCallId?: string;
|
|
50
|
-
toolName?: string;
|
|
51
|
-
toolInput?: unknown;
|
|
52
|
-
toolOutput?: unknown;
|
|
53
|
-
source: string;
|
|
54
|
-
}
|
|
55
|
-
export interface DerivedToolPainIdentity {
|
|
56
|
-
eventId: string;
|
|
57
|
-
painId: string;
|
|
58
|
-
outcome: NormalizedOutcome;
|
|
59
|
-
sanitizedParams: Record<string, unknown>;
|
|
60
|
-
paramsJson: string;
|
|
61
|
-
resultPreview: string | null;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* The canonical tool-pain identity derivation, exposed for the governance
|
|
65
|
-
* admission path (Codex Governance Closure SPEC §10): the same normalized
|
|
66
|
-
* fields fed to the live production handler derive the same deterministic
|
|
67
|
-
* `pain_host_<sha256>` id, so live and observation-delivered admissions of one
|
|
68
|
-
* tool call converge on one canonical pain.
|
|
69
|
-
*
|
|
70
|
-
* I1 parity obligation: when the host supplies a call id, BOTH paths must pass
|
|
71
|
-
* the same `toolCallId` (Codex admission candidates carry it from the same
|
|
72
|
-
* `tool_use_id` the live decoder required) — otherwise one real call would
|
|
73
|
-
* derive two canonical pains.
|
|
74
|
-
*/
|
|
75
|
-
export declare function deriveProductionToolPainIdentity(fields: ProductionToolEventFields & {
|
|
76
|
-
canonicalEventId?: string;
|
|
77
|
-
}): DerivedToolPainIdentity;
|
|
78
12
|
/**
|
|
79
13
|
* The canonical correction-pain identity derivation (SPEC §10/§12): deterministic,
|
|
80
14
|
* content-derived, retry-safe — replacing the legacy random `correction_<traceId>`
|
|
@@ -137,6 +71,13 @@ export declare function recordVerifiedToolOutcomes(args: {
|
|
|
137
71
|
hostKind?: string;
|
|
138
72
|
}): VerifiedToolOutcomeStatus[];
|
|
139
73
|
/**
|
|
74
|
+
* Shared production tool-call evidence handler.
|
|
75
|
+
*
|
|
76
|
+
* Records the real host tool call in `trajectory.db` (tool_calls) and emits the
|
|
77
|
+
* receipt event. Phase A retired the legacy automatic Pain admission that used
|
|
78
|
+
* to ride on this path: a technical tool failure is trajectory + health
|
|
79
|
+
* evidence, never an Owner-pain by itself.
|
|
80
|
+
*
|
|
140
81
|
* PRI-640: host attribution is supplied by the host adapter that constructs
|
|
141
82
|
* this shared handler (OpenClaw / Codex adapters) — never guessed inside the
|
|
142
83
|
* writer. Omitted -> NULL (unknown).
|
|
@@ -148,6 +89,4 @@ export declare function createProductionPainEvidenceHandler(options?: {
|
|
|
148
89
|
projectDir?: string;
|
|
149
90
|
events?: HostEventEmitter;
|
|
150
91
|
}): (event: HostEvent) => Promise<HostEventResult>;
|
|
151
|
-
export declare function resetProductionPainCooldownForTest(): void;
|
|
152
|
-
export declare function productionPainCooldownEntryCountForTest(): number;
|
|
153
92
|
export { hasCanonicalSchema as hasProductionPainSchema };
|