@principles/pd-cli 1.152.12 → 1.152.14
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/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +34 -7
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +23 -7
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +21 -7
- package/dist/commands/pain-retry.js.map +1 -1
- package/package.json +13 -7
- package/CHANGELOG.md +0 -23
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
- package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
- package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
- package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
- package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
- package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
- package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/version.test.d.ts +0 -2
- package/dist/commands/__tests__/version.test.d.ts.map +0 -1
- package/dist/commands/__tests__/version.test.js +0 -238
- package/dist/commands/__tests__/version.test.js.map +0 -1
- package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
- package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
- package/dist/commands/runtime-uat.guard.test.js +0 -153
- package/dist/commands/runtime-uat.guard.test.js.map +0 -1
- package/dist/services/__tests__/cli-output.test.d.ts +0 -18
- package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
- package/dist/services/__tests__/cli-output.test.js +0 -103
- package/dist/services/__tests__/cli-output.test.js.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
- package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
- package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
- package/dist/utils/production-workspace-guard.test.d.ts +0 -2
- package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
- package/dist/utils/production-workspace-guard.test.js +0 -137
- package/dist/utils/production-workspace-guard.test.js.map +0 -1
- package/scripts/llm-dogfood.ts +0 -456
- package/scripts/migrate-illegal-expected-decision.ts +0 -252
- package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
- package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
- package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
- package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
- package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
- package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
- package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
- package/src/commands/__tests__/version.test.ts +0 -251
- package/src/commands/admission-gate.ts +0 -30
- package/src/commands/artifact.ts +0 -82
- package/src/commands/build-trajectory-evidence.ts +0 -298
- package/src/commands/candidate.ts +0 -1250
- package/src/commands/codex-ingest-catchup.ts +0 -114
- package/src/commands/codex-ingest-quarantine.ts +0 -127
- package/src/commands/codex-reconcile.ts +0 -84
- package/src/commands/codex-setup.ts +0 -476
- package/src/commands/codex-worker.ts +0 -185
- package/src/commands/command-helpers.ts +0 -24
- package/src/commands/config-doctor.ts +0 -193
- package/src/commands/console.ts +0 -797
- package/src/commands/context.ts +0 -72
- package/src/commands/demo-story-a.ts +0 -241
- package/src/commands/diagnose.ts +0 -756
- package/src/commands/errors-list.ts +0 -441
- package/src/commands/evolution-tasks-list.ts +0 -48
- package/src/commands/evolution-tasks-show.ts +0 -62
- package/src/commands/flow.ts +0 -60
- package/src/commands/health-codex.ts +0 -576
- package/src/commands/health.ts +0 -315
- package/src/commands/history.ts +0 -63
- package/src/commands/intent.ts +0 -457
- package/src/commands/legacy-cleanup.ts +0 -531
- package/src/commands/legacy-import.ts +0 -104
- package/src/commands/mvp-smoke.ts +0 -160
- package/src/commands/pain-evidence.ts +0 -226
- package/src/commands/pain-list.ts +0 -239
- package/src/commands/pain-record.ts +0 -612
- package/src/commands/pain-retry.ts +0 -916
- package/src/commands/principles-stats.ts +0 -700
- package/src/commands/proven-channel-baseline.ts +0 -115
- package/src/commands/quality-scorecard.ts +0 -272
- package/src/commands/remediation-output.ts +0 -66
- package/src/commands/rulecode.ts +0 -465
- package/src/commands/run.ts +0 -89
- package/src/commands/runtime-activation.ts +0 -1629
- package/src/commands/runtime-artifact-repair.ts +0 -524
- package/src/commands/runtime-canary.ts +0 -395
- package/src/commands/runtime-compatibility-scan.ts +0 -105
- package/src/commands/runtime-diagnostics-export.ts +0 -229
- package/src/commands/runtime-features.ts +0 -153
- package/src/commands/runtime-gfi-snapshot.ts +0 -135
- package/src/commands/runtime-health-snapshot.ts +0 -108
- package/src/commands/runtime-init.ts +0 -429
- package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
- package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
- package/src/commands/runtime-internalization-integrity.ts +0 -107
- package/src/commands/runtime-internalization-queue.ts +0 -135
- package/src/commands/runtime-internalization-retry.ts +0 -191
- package/src/commands/runtime-internalization-run-once.ts +0 -791
- package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
- package/src/commands/runtime-internalization-wake-once.ts +0 -87
- package/src/commands/runtime-pain-flood-simulation.ts +0 -121
- package/src/commands/runtime-pruning.ts +0 -438
- package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
- package/src/commands/runtime-recovery.ts +0 -109
- package/src/commands/runtime-synthetic-baseline.ts +0 -70
- package/src/commands/runtime-uat.guard.test.ts +0 -175
- package/src/commands/runtime-uat.ts +0 -380
- package/src/commands/runtime.ts +0 -473
- package/src/commands/samples-list.ts +0 -39
- package/src/commands/samples-review.ts +0 -36
- package/src/commands/task.ts +0 -271
- package/src/commands/telemetry.ts +0 -349
- package/src/commands/trace.ts +0 -174
- package/src/commands/trajectory-db-unavailable.ts +0 -19
- package/src/commands/trajectory.ts +0 -64
- package/src/commands/version.ts +0 -49
- package/src/config-reader.ts +0 -122
- package/src/index.ts +0 -1276
- package/src/legacy/legacy-import.ts +0 -179
- package/src/legacy/session-history-import.ts +0 -203
- package/src/resolve-workspace.ts +0 -106
- package/src/services/__tests__/cli-output.test.ts +0 -130
- package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
- package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
- package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
- package/src/services/cli-output.ts +0 -95
- package/src/services/config-doctor.ts +0 -697
- package/src/services/console-launcher.ts +0 -537
- package/src/services/demo-rule-compiler.ts +0 -40
- package/src/services/demo-story-a-runner.ts +0 -471
- package/src/services/mainline-snapshot-assembler.ts +0 -579
- package/src/services/pain-flood-simulation-runner.ts +0 -462
- package/src/services/pd-config-loader.ts +0 -433
- package/src/services/proven-channel-baseline-runner.ts +0 -157
- package/src/services/quality-scorecard/data-extractor.ts +0 -150
- package/src/services/quality-scorecard/local-evaluator.ts +0 -142
- package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
- package/src/services/resolve-runtime-from-pd-config.ts +0 -230
- package/src/services/rulehost-pipeline-runner.ts +0 -1214
- package/src/services/rulehost-readiness.ts +0 -326
- package/src/services/runtime-adapter-resolver.ts +0 -364
- package/src/services/synthetic-baseline-runner.ts +0 -400
- package/src/services/version-report.ts +0 -253
- package/src/services/workspace-tool-semantics.ts +0 -26
- package/src/utils/path-security.ts +0 -96
- package/src/utils/production-workspace-guard.test.ts +0 -157
- package/src/utils/production-workspace-guard.ts +0 -210
- package/tests/bdd/cli-contract.steps.ts +0 -346
- package/tests/bdd/principles-stats.steps.ts +0 -211
- package/tests/bdd/support/gherkin-loader.ts +0 -147
- package/tests/bdd/support/repo-root.ts +0 -62
- package/tests/bdd/support/vitest-bdd.ts +0 -179
- package/tests/commands/admission-gate-coverage.test.ts +0 -79
- package/tests/commands/build-trajectory-evidence.test.ts +0 -508
- package/tests/commands/candidate-audit-repair.test.ts +0 -421
- package/tests/commands/candidate-intake.test.ts +0 -732
- package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
- package/tests/commands/candidate-internalize-lineage.test.ts +0 -628
- package/tests/commands/candidate-internalize.test.ts +0 -299
- package/tests/commands/candidate-route.test.ts +0 -329
- package/tests/commands/candidate-show.test.ts +0 -95
- package/tests/commands/cli-command-tree.test.ts +0 -144
- package/tests/commands/cli-help-snapshot.test.ts +0 -155
- package/tests/commands/cli-skill-contract.test.ts +0 -121
- package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
- package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
- package/tests/commands/codex-setup.test.ts +0 -297
- package/tests/commands/codex-worker-daemon.test.ts +0 -95
- package/tests/commands/codex-worker-registration.test.ts +0 -207
- package/tests/commands/config-doctor.test.ts +0 -336
- package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
- package/tests/commands/console-open.test.ts +0 -1381
- package/tests/commands/context.test.ts +0 -114
- package/tests/commands/demo-story-a.test.ts +0 -318
- package/tests/commands/diagnose.test.ts +0 -1803
- package/tests/commands/errors-list.test.ts +0 -679
- package/tests/commands/evolution-tasks-list.test.ts +0 -41
- package/tests/commands/evolution-tasks-show.test.ts +0 -46
- package/tests/commands/health-codex.test.ts +0 -349
- package/tests/commands/health-host-wiring.test.ts +0 -76
- package/tests/commands/health-reliability.test.ts +0 -99
- package/tests/commands/health.test.ts +0 -413
- package/tests/commands/intent.test.ts +0 -321
- package/tests/commands/legacy-cleanup.test.ts +0 -148
- package/tests/commands/pain-evidence.test.ts +0 -518
- package/tests/commands/pain-list.test.ts +0 -219
- package/tests/commands/pain-record-async.test.ts +0 -335
- package/tests/commands/pain-record-session-parser.test.ts +0 -228
- package/tests/commands/pain-record.test.ts +0 -851
- package/tests/commands/pain-retry.test.ts +0 -1121
- package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
- package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
- package/tests/commands/principles-stats-wiring.test.ts +0 -135
- package/tests/commands/principles-stats.test.ts +0 -274
- package/tests/commands/product-path-regression.test.ts +0 -86
- package/tests/commands/proven-channel-baseline.test.ts +0 -441
- package/tests/commands/rulecode.test.ts +0 -177
- package/tests/commands/run-rulehost-handler.test.ts +0 -908
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
- package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
- package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
- package/tests/commands/runtime-activation.test.ts +0 -2052
- package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
- package/tests/commands/runtime-artifact-repair.test.ts +0 -404
- package/tests/commands/runtime-canary.test.ts +0 -430
- package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
- package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
- package/tests/commands/runtime-features.test.ts +0 -249
- package/tests/commands/runtime-health-snapshot.test.ts +0 -358
- package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
- package/tests/commands/runtime-init.test.ts +0 -420
- package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
- package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
- package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
- package/tests/commands/runtime-internalization-queue.test.ts +0 -380
- package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
- package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
- package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
- package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
- package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
- package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
- package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
- package/tests/commands/runtime-internalization.test.ts +0 -53
- package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
- package/tests/commands/runtime-pruning.test.ts +0 -694
- package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
- package/tests/commands/runtime-recovery.test.ts +0 -120
- package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
- package/tests/commands/runtime-uat.test.ts +0 -397
- package/tests/commands/runtime.test.ts +0 -392
- package/tests/commands/samples-list.test.ts +0 -43
- package/tests/commands/samples-review.test.ts +0 -41
- package/tests/commands/task.test.ts +0 -153
- package/tests/commands/telemetry.test.ts +0 -190
- package/tests/commands/trace.test.ts +0 -316
- package/tests/config-reader.test.ts +0 -143
- package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
- package/tests/e2e/cli-full-flow.test.ts +0 -233
- package/tests/e2e/cross-package-acceptance.test.ts +0 -635
- package/tests/helpers/pd-cli-path.ts +0 -5
- package/tests/pd-cli-smoke.test.ts +0 -13
- package/tests/resolve-workspace.test.ts +0 -196
- package/tests/services/config-doctor-signal-health.test.ts +0 -109
- package/tests/services/console-launcher.test.ts +0 -114
- package/tests/services/demo-rule-compiler.test.ts +0 -242
- package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
- package/tests/services/pd-config-loader.test.ts +0 -505
- package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
- package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
- package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
- package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
- package/tests/services/rulehost-pipeline-runner.test.ts +0 -1242
- package/tests/services/workspace-tool-semantics.test.ts +0 -76
- package/tests/utils/path-security.test.ts +0 -180
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -10
|
@@ -1,1214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RuleHost Pipeline Runner — full-chain internalization driver (PRI-429).
|
|
3
|
-
*
|
|
4
|
-
* Drives a pain signal all the way to a validated rule artifact in ONE call:
|
|
5
|
-
* pain → dreamer → philosopher → scribe → artificer↔evaluator adversarial loop
|
|
6
|
-
*
|
|
7
|
-
* This is the "last mile" wiring that makes `runAdversarialLoop` reachable
|
|
8
|
-
* from a real host entry point (the PRI-428 pr-review finding: the loop had no
|
|
9
|
-
* production caller). Operators invoke it via
|
|
10
|
-
* `pd runtime internalization run-rulehost --pain-id <id>`
|
|
11
|
-
*
|
|
12
|
-
* Design:
|
|
13
|
-
* - Does NOT reuse the lease-based InternalizationOrchestrator. Instead it
|
|
14
|
-
* chains runners directly (mirrors full-chain-real-llm.test.ts), because
|
|
15
|
-
* the lease model is single-step + successor-proposal and cannot host a
|
|
16
|
-
* synchronous multi-round loop.
|
|
17
|
-
* - The artificer↔evaluator stage delegates to `runAdversarialLoop` (PRI-428),
|
|
18
|
-
* which never throws and degrades to { decision: 'rejected' } with a reason.
|
|
19
|
-
* - gateDeps (the sandbox adapter) is built from `compileDemoRule` +
|
|
20
|
-
* `evaluateInRefinerSandbox`. `compileDemoRule` delegates to the canonical
|
|
21
|
-
* hardened replay evaluator in @principles/core (PRI-809), so pre-activation
|
|
22
|
-
* replay crosses the same vm trust boundary as the production gate.
|
|
23
|
-
*
|
|
24
|
-
* @see docs/plans/rulehost-mvp-activation.md
|
|
25
|
-
* @see runAdversarialLoop in @principles/core/runtime-v2
|
|
26
|
-
*/
|
|
27
|
-
import {
|
|
28
|
-
RuntimeStateManager,
|
|
29
|
-
StoreEventEmitter,
|
|
30
|
-
DreamerRunner,
|
|
31
|
-
DefaultDreamerValidator,
|
|
32
|
-
PhilosopherRunner,
|
|
33
|
-
DefaultPhilosopherValidator,
|
|
34
|
-
ScribeRunner,
|
|
35
|
-
DefaultScribeValidator,
|
|
36
|
-
ArtificerRunner,
|
|
37
|
-
DefaultArtificerValidator,
|
|
38
|
-
EvaluatorRunner,
|
|
39
|
-
DefaultEvaluatorValidator,
|
|
40
|
-
createPITaskDiagnosticJson,
|
|
41
|
-
parsePITaskMetadata,
|
|
42
|
-
parseSeedSourcePainId,
|
|
43
|
-
artificerRepairTaskId,
|
|
44
|
-
runAdversarialLoop,
|
|
45
|
-
evaluateInRefinerSandbox,
|
|
46
|
-
DEFAULT_MAX_ROUNDS,
|
|
47
|
-
SqliteApprovalQueueStore,
|
|
48
|
-
getChannelRiskLevel,
|
|
49
|
-
// PRI-510: feature-flag resolvers imported from the core barrel (EP-02:
|
|
50
|
-
// pd-config-loader.ts only re-exports loadPdConfig + computeFlagsFromLoadResult;
|
|
51
|
-
// computeFeatureFlagsFromConfig / isFeatureEnabled live in core).
|
|
52
|
-
computeFeatureFlagsFromConfig,
|
|
53
|
-
isFeatureEnabled,
|
|
54
|
-
// PRI-714: resolve outputLanguage from the same effective config the
|
|
55
|
-
// runners already receive (EP-07: canonical resolved value, not raw input).
|
|
56
|
-
resolveOutputLanguage,
|
|
57
|
-
buildArtificerHostSemanticContext,
|
|
58
|
-
// PRI-804(a): ledger identity lookup for the source_principle_id backfill.
|
|
59
|
-
PrincipleTreeLedgerAdapter,
|
|
60
|
-
} from '@principles/core/runtime-v2';
|
|
61
|
-
import type {
|
|
62
|
-
AdversarialLoopResult,
|
|
63
|
-
PDRuntimeAdapter,
|
|
64
|
-
PeerRunnerResult,
|
|
65
|
-
RefinerRuleHostGateDeps,
|
|
66
|
-
PIArtifactStore,
|
|
67
|
-
ApprovalRecord,
|
|
68
|
-
BehaviorExamplePack,
|
|
69
|
-
// PRI-510: type-only import for the repair-loop deps contract (EP-02 wiring).
|
|
70
|
-
// RuntimeStateManager / StoreEventEmitter are already imported as values
|
|
71
|
-
// above; EvaluatorValidator is type-only.
|
|
72
|
-
EvaluatorRunnerDeps,
|
|
73
|
-
SeedArtificerRepairParams,
|
|
74
|
-
EvaluatorValidator,
|
|
75
|
-
} from '@principles/core/runtime-v2';
|
|
76
|
-
import * as path from 'node:path';
|
|
77
|
-
import { loadPdConfig } from './pd-config-loader.js';
|
|
78
|
-
import { createEvaluatorRuntimeContext } from '@principles/host-runtime';
|
|
79
|
-
/* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after main, matching codebase convention */
|
|
80
|
-
import { compileDemoRule } from './demo-rule-compiler.js';
|
|
81
|
-
|
|
82
|
-
// ── Types ────────────────────────────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Code-rule capability (atomic: ArtificerL2 + Evaluator).
|
|
86
|
-
*
|
|
87
|
-
* Per the user correction (2026-06-18): ArtificerL2 and Evaluator are atomic —
|
|
88
|
-
* both must run or neither runs. When enabled, the adversarial loop runs with
|
|
89
|
-
* the artificerAdapter (L2 write-test-fix). When disabled or not provided, the
|
|
90
|
-
* pipeline degrades to text-principle-only.
|
|
91
|
-
*
|
|
92
|
-
* The caller (CLI handler) resolves per-agent config (enabled/runtimeProfile)
|
|
93
|
-
* and constructs the ArtificerL2Adapter when both artificer + evaluator are
|
|
94
|
-
* enabled. The pipeline runner branches on `enabled` — it does NOT do config
|
|
95
|
-
* resolution itself (separation of concerns: service vs I/O).
|
|
96
|
-
*/
|
|
97
|
-
export interface CodeRuleCapability {
|
|
98
|
-
/** Whether the capability is enabled (atomic: both artificer + evaluator enabled in config). */
|
|
99
|
-
readonly enabled: boolean;
|
|
100
|
-
/**
|
|
101
|
-
* The ArtificerL2Adapter (or test-double) for the artificer stage. Required
|
|
102
|
-
* when enabled. When enabled, this adapter replaces the base runtimeAdapter
|
|
103
|
-
* for the ArtificerRunner only; EvaluatorRunner still uses the base adapter.
|
|
104
|
-
* Ignored when disabled (may be omitted or set to the base adapter).
|
|
105
|
-
*/
|
|
106
|
-
readonly artificerAdapter?: PDRuntimeAdapter;
|
|
107
|
-
/** Structured reason when disabled (for degradation reporting). Required when disabled. */
|
|
108
|
-
readonly disabledReason?: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface RuleHostAgentAdapters {
|
|
112
|
-
readonly dreamer: PDRuntimeAdapter;
|
|
113
|
-
readonly philosopher: PDRuntimeAdapter;
|
|
114
|
-
readonly scribe: PDRuntimeAdapter;
|
|
115
|
-
readonly evaluator: PDRuntimeAdapter;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface RuleHostPipelineOptions {
|
|
119
|
-
/** Workspace directory containing .state/ (SQLite stores). */
|
|
120
|
-
readonly workspaceDir: string;
|
|
121
|
-
/** Pain ID whose internalization chain to drive. */
|
|
122
|
-
readonly painId: string;
|
|
123
|
-
/**
|
|
124
|
-
* Runtime adapter (LLM). Caller constructs it (PiAi / test-double) so the
|
|
125
|
-
* service stays runtime-agnostic. Mirrors full-chain-real-llm.test.ts.
|
|
126
|
-
*
|
|
127
|
-
* Used for: dreamer, philosopher, scribe, and evaluator (when capability ON).
|
|
128
|
-
* The artificer stage uses codeRuleCapability.artificerAdapter when enabled.
|
|
129
|
-
*/
|
|
130
|
-
readonly runtimeAdapter: PDRuntimeAdapter;
|
|
131
|
-
/** Production per-agent adapters. Tests may omit this to use runtimeAdapter for every stage. */
|
|
132
|
-
readonly agentAdapters?: RuleHostAgentAdapters;
|
|
133
|
-
/**
|
|
134
|
-
* Code-rule capability (atomic: ArtificerL2 + Evaluator). When omitted, the
|
|
135
|
-
* capability is treated as OFF with reason 'code_rule_capability not provided'.
|
|
136
|
-
*/
|
|
137
|
-
readonly codeRuleCapability?: CodeRuleCapability;
|
|
138
|
-
/**
|
|
139
|
-
* PRI-780: required whenever code-rule generation runs — v2 is the only
|
|
140
|
-
* Artificer contract (no v1 fallback). Assembled from Owner-labelled
|
|
141
|
-
* production evidence; a missing pack refuses the adversarial loop loud.
|
|
142
|
-
*/
|
|
143
|
-
readonly behaviorExamplePack?: BehaviorExamplePack;
|
|
144
|
-
/** Internalization channel for created tasks (default 'code_tool_hook'). */
|
|
145
|
-
readonly channel?: 'prompt' | 'code_tool_hook' | 'defer_archive';
|
|
146
|
-
/** Max adversarial rounds (PRD cap = 2). */
|
|
147
|
-
readonly maxRounds?: number;
|
|
148
|
-
/** Per-LLM-call timeout (default 300_000). */
|
|
149
|
-
readonly timeoutMs?: number;
|
|
150
|
-
/** Runner poll interval (default 100). */
|
|
151
|
-
readonly pollIntervalMs?: number;
|
|
152
|
-
/**
|
|
153
|
-
* Max stage retry attempts for transient `retried` status (default 2).
|
|
154
|
-
* Each retry gets fresh state from the state manager (Runtime Contract Rule 7).
|
|
155
|
-
* When exhausted, the stage is marked 'degraded' with a structured reason.
|
|
156
|
-
*/
|
|
157
|
-
readonly maxStageRetries?: number;
|
|
158
|
-
/** Correlation prefix for task IDs. */
|
|
159
|
-
readonly correlationId?: string;
|
|
160
|
-
/** Progress callback (stage start/complete). Optional. */
|
|
161
|
-
readonly onProgress?: (stage: string, status: 'start' | 'succeeded' | 'failed' | 'degraded' | 'skipped', detail?: string) => void;
|
|
162
|
-
/**
|
|
163
|
-
* Called once after the internal RuntimeStateManager + artifactStore are
|
|
164
|
-
* constructed, so the caller (e.g. a test-double adapter) can wire the store
|
|
165
|
-
* for artifactId resolution. Optional.
|
|
166
|
-
*/
|
|
167
|
-
readonly onStoreReady?: (store: PIArtifactStore) => void;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export type RuleHostPipelineStageStatus = 'succeeded' | 'failed' | 'skipped' | 'degraded';
|
|
171
|
-
|
|
172
|
-
export interface RuleHostPipelineStage {
|
|
173
|
-
readonly name: 'pain_lookup' | 'dreamer' | 'philosopher' | 'scribe' | 'adversarial_loop';
|
|
174
|
-
readonly status: RuleHostPipelineStageStatus;
|
|
175
|
-
readonly taskId?: string;
|
|
176
|
-
readonly reason?: string;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Final pipeline decision.
|
|
181
|
-
*
|
|
182
|
-
* - `candidate_ready_for_owner_review`: adversarial loop approved. A validated
|
|
183
|
-
* rule artifact exists and is WAITING for owner review. This is NOT owner
|
|
184
|
-
* approval — it means the candidate is ready for the owner to review.
|
|
185
|
-
* - `text_principle_only`: code-rule capability OFF (artificer or evaluator
|
|
186
|
-
* disabled). No rule artifact; a text principle artifact is produced for the
|
|
187
|
-
* prompt channel and enqueued into the existing approval queue (PRI-804).
|
|
188
|
-
* - `generation_rejected`: pipeline failed (no dreamer task, stage failure, or
|
|
189
|
-
* evaluator rejected the candidate). No rule artifact.
|
|
190
|
-
*/
|
|
191
|
-
export interface RuleHostPipelineResult {
|
|
192
|
-
readonly decision: 'candidate_ready_for_owner_review' | 'text_principle_only' | 'generation_rejected';
|
|
193
|
-
readonly painId: string;
|
|
194
|
-
readonly stages: RuleHostPipelineStage[];
|
|
195
|
-
/** Scribe task that fed the adversarial loop (or text-principle path). */
|
|
196
|
-
readonly scribeTaskId: string | null;
|
|
197
|
-
/** From the adversarial loop result, when reached. */
|
|
198
|
-
readonly adversarialLoop?: AdversarialLoopResult;
|
|
199
|
-
/** Rule artifact ID when candidate_ready_for_owner_review; null otherwise. */
|
|
200
|
-
readonly ruleArtifactId: string | null;
|
|
201
|
-
/** Principle artifact ID (always present when scribe ran). */
|
|
202
|
-
readonly principleArtifactId: string | null;
|
|
203
|
-
/**
|
|
204
|
-
* Approval ID when the candidate was auto-enqueued into the ApprovalQueue.
|
|
205
|
-
* Present for decision='candidate_ready_for_owner_review' (rule artifact,
|
|
206
|
-
* code_tool_hook channel) AND for decision='text_principle_only' (principle
|
|
207
|
-
* artifact, prompt channel — PRI-804 wiring). Null when the enqueue itself
|
|
208
|
-
* failed — check degradationReason for the structured reason.
|
|
209
|
-
*/
|
|
210
|
-
readonly approvalId: string | null;
|
|
211
|
-
/** Structured reason when decision is not candidate_ready_for_owner_review. */
|
|
212
|
-
readonly degradationReason?: string;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// ── gateDeps builder ─────────────────────────────────────────────────────────
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Build the sandbox gate deps for adversarial replay.
|
|
219
|
-
*
|
|
220
|
-
* `compileDemoRule` delegates to the canonical hardened replay evaluator in
|
|
221
|
-
* @principles/core (PRI-809) — capability-identical across packages without a
|
|
222
|
-
* duplicate vm implementation. See demo-rule-compiler.ts header comment.
|
|
223
|
-
*/
|
|
224
|
-
export function createSandboxGateDeps(): RefinerRuleHostGateDeps {
|
|
225
|
-
return {
|
|
226
|
-
evaluateInSandbox: (code, goldenTrace, opts) => {
|
|
227
|
-
// compileDemoRule throws on bad code. The throw propagates out of
|
|
228
|
-
// evaluateInSandbox; EvaluatorRunner (and runAdversarialLoop's
|
|
229
|
-
// evaluator_run_threw catch) classifies it as a sandbox failure, so it
|
|
230
|
-
// surfaces as a rejected round rather than an uncaught exception.
|
|
231
|
-
// evaluateInRefinerSandbox separately catches its own runtime throws
|
|
232
|
-
// (from executing the evaluate function) and classifies them as
|
|
233
|
-
// validation_failed / runtime_error.
|
|
234
|
-
const evaluateCode = compileDemoRule(code, 'rulehost-pipeline');
|
|
235
|
-
return evaluateInRefinerSandbox(code, goldenTrace, { evaluateCode, ...opts });
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ── Pipeline ─────────────────────────────────────────────────────────────────
|
|
241
|
-
|
|
242
|
-
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
243
|
-
const DEFAULT_POLL_MS = 100;
|
|
244
|
-
const DEFAULT_MAX_STAGE_RETRIES = 2;
|
|
245
|
-
|
|
246
|
-
export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promise<RuleHostPipelineResult> {
|
|
247
|
-
const channel = opts.channel ?? 'code_tool_hook';
|
|
248
|
-
const maxRounds = opts.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
249
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
250
|
-
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
251
|
-
const maxStageRetries = opts.maxStageRetries ?? DEFAULT_MAX_STAGE_RETRIES;
|
|
252
|
-
const correlation = opts.correlationId ?? `rulehost-${opts.painId}`;
|
|
253
|
-
const onProgress = opts.onProgress ?? (() => { /* noop */ });
|
|
254
|
-
|
|
255
|
-
// ── Resolve code-rule capability (atomic: ArtificerL2 + Evaluator) ──
|
|
256
|
-
// Per user correction (2026-06-18): both must be enabled or neither runs.
|
|
257
|
-
// When OFF, degrade to text-principle-only after scribe. When ON, run the
|
|
258
|
-
// adversarial loop with the artificerAdapter (L2 write-test-fix).
|
|
259
|
-
const capability = opts.codeRuleCapability;
|
|
260
|
-
const capabilityEnabled = capability?.enabled === true;
|
|
261
|
-
const capabilityDisabledReason = capability?.enabled === false
|
|
262
|
-
? (capability.disabledReason ?? 'code_rule_capability is disabled')
|
|
263
|
-
: 'code_rule_capability not provided — default OFF (set codeRuleCapability.enabled=true when both artificer + evaluator agents are enabled)';
|
|
264
|
-
|
|
265
|
-
const stages: RuleHostPipelineStage[] = [];
|
|
266
|
-
const stateManager = new RuntimeStateManager({ workspaceDir: opts.workspaceDir });
|
|
267
|
-
await stateManager.initialize();
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const artifactStore = stateManager.piArtifactStore;
|
|
271
|
-
const eventEmitter = new StoreEventEmitter();
|
|
272
|
-
// Issue 2: load effective config once so every stage runner can resolve
|
|
273
|
-
// feature flags (e.g. `artificer_output_retry`). Mirrors
|
|
274
|
-
// createEvaluatorRunnerDeps (rc-9: malformed config → fallback defaults).
|
|
275
|
-
const configLoad = loadPdConfig(opts.workspaceDir);
|
|
276
|
-
const effectiveConfig = configLoad.ok ? configLoad.effective : configLoad.defaults;
|
|
277
|
-
// PRI-714: resolve outputLanguage once from the effective config so every
|
|
278
|
-
// stage runner (dreamer/philosopher/scribe/artificer/evaluator) receives
|
|
279
|
-
// the same language value. Mirrors pain-signal-runtime-factory (EP-07).
|
|
280
|
-
// Default config also resolves (to zh-CN) — pipeline stages always
|
|
281
|
-
// declare the owner's language instead of leaving review artifacts English.
|
|
282
|
-
const {outputLanguage} = resolveOutputLanguage(effectiveConfig.config.principles?.outputLanguage);
|
|
283
|
-
// Allow the caller's adapter to resolve real artifactIds (needed by
|
|
284
|
-
// test-double adapters whose scripted outputs must match store-assigned IDs).
|
|
285
|
-
opts.onStoreReady?.(artifactStore);
|
|
286
|
-
const owner = 'rulehost-pipeline';
|
|
287
|
-
const agentAdapters = opts.agentAdapters ?? {
|
|
288
|
-
dreamer: opts.runtimeAdapter,
|
|
289
|
-
philosopher: opts.runtimeAdapter,
|
|
290
|
-
scribe: opts.runtimeAdapter,
|
|
291
|
-
evaluator: opts.runtimeAdapter,
|
|
292
|
-
};
|
|
293
|
-
const runnerOptsFor = (adapter: PDRuntimeAdapter) => ({ owner, runtimeKind: adapter.kind(), pollIntervalMs, timeoutMs, effectiveConfig, outputLanguage });
|
|
294
|
-
|
|
295
|
-
// ── Stage: pain lookup ──
|
|
296
|
-
// Find a dreamer task already seeded for this pain (the pain→dreamer bridge
|
|
297
|
-
// runs via `pd pain record` → PainSignalBridge.onPainDetected). We need the
|
|
298
|
-
// dreamer task to start the chain. If none exists, the operator must run
|
|
299
|
-
// `pd pain record` first — fail loud with guidance.
|
|
300
|
-
//
|
|
301
|
-
// D fix (PRI-429): exact sourcePainId match via Object.hasOwn on parsed
|
|
302
|
-
// diagnosticJson. No substring matching (pain-1 must NOT match pain-10).
|
|
303
|
-
onProgress('pain_lookup', 'start', `painId=${opts.painId}`);
|
|
304
|
-
const dreamerLookup = await findDreamerTaskForPain(stateManager, opts.painId, channel);
|
|
305
|
-
if (dreamerLookup.status === 'ambiguous') {
|
|
306
|
-
const reason = `ambiguous_dreamer_tasks_for_pain: ${dreamerLookup.taskIds.join(',')}`;
|
|
307
|
-
stages.push({ name: 'pain_lookup', status: 'failed', reason });
|
|
308
|
-
onProgress('pain_lookup', 'failed', reason);
|
|
309
|
-
return rejectedResult(opts.painId, stages, reason);
|
|
310
|
-
}
|
|
311
|
-
if (dreamerLookup.status === 'not_found') {
|
|
312
|
-
stages.push({ name: 'pain_lookup', status: 'failed', reason: 'no_dreamer_task_seeded_for_pain' });
|
|
313
|
-
onProgress('pain_lookup', 'failed', 'no dreamer task seeded');
|
|
314
|
-
return rejectedResult(opts.painId, stages, 'no_dreamer_task_seeded_for_pain: run `pd pain record` first');
|
|
315
|
-
}
|
|
316
|
-
const dreamerSeedTaskId = dreamerLookup.taskId;
|
|
317
|
-
stages.push({ name: 'pain_lookup', status: 'succeeded', taskId: dreamerSeedTaskId });
|
|
318
|
-
onProgress('pain_lookup', 'succeeded', `dreamerTaskId=${dreamerSeedTaskId}`);
|
|
319
|
-
|
|
320
|
-
// ── Stage: dreamer ──
|
|
321
|
-
onProgress('dreamer', 'start');
|
|
322
|
-
if (dreamerLookup.taskStatus === 'succeeded') {
|
|
323
|
-
stages.push({ name: 'dreamer', taskId: dreamerSeedTaskId, status: 'succeeded' });
|
|
324
|
-
} else {
|
|
325
|
-
const dreamerRunner = new DreamerRunner(
|
|
326
|
-
{ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore },
|
|
327
|
-
runnerOptsFor(agentAdapters.dreamer),
|
|
328
|
-
);
|
|
329
|
-
const dreamerResult = await runStage(dreamerRunner, dreamerSeedTaskId, { maxStageRetries, pollIntervalMs });
|
|
330
|
-
stages.push(stageFromResult('dreamer', dreamerSeedTaskId, dreamerResult));
|
|
331
|
-
if (dreamerResult.status !== 'succeeded') {
|
|
332
|
-
onProgress('dreamer', 'failed', dreamerResult.failureReason);
|
|
333
|
-
return rejectedResult(opts.painId, stages, `dreamer_failed: ${dreamerResult.failureReason ?? dreamerResult.status}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
onProgress('dreamer', 'succeeded');
|
|
337
|
-
|
|
338
|
-
// ── Stage: philosopher ──
|
|
339
|
-
onProgress('philosopher', 'start');
|
|
340
|
-
const philosopherTaskId = `${correlation}-philosopher-${Date.now().toString(36)}`;
|
|
341
|
-
await createInternalizationTask(stateManager, philosopherTaskId, 'philosopher', [dreamerSeedTaskId], channel, timeoutMs);
|
|
342
|
-
const philosopherRunner = new PhilosopherRunner(
|
|
343
|
-
{ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore },
|
|
344
|
-
runnerOptsFor(agentAdapters.philosopher),
|
|
345
|
-
);
|
|
346
|
-
const philosopherResult = await runStage(philosopherRunner, philosopherTaskId, { maxStageRetries, pollIntervalMs });
|
|
347
|
-
stages.push(stageFromResult('philosopher', philosopherTaskId, philosopherResult));
|
|
348
|
-
if (philosopherResult.status !== 'succeeded') {
|
|
349
|
-
onProgress('philosopher', 'failed', philosopherResult.failureReason);
|
|
350
|
-
return rejectedResult(opts.painId, stages, `philosopher_failed: ${philosopherResult.failureReason ?? philosopherResult.status}`);
|
|
351
|
-
}
|
|
352
|
-
onProgress('philosopher', 'succeeded');
|
|
353
|
-
|
|
354
|
-
// ── Stage: scribe ──
|
|
355
|
-
onProgress('scribe', 'start');
|
|
356
|
-
const scribeTaskId = `${correlation}-scribe-${Date.now().toString(36)}`;
|
|
357
|
-
await createInternalizationTask(stateManager, scribeTaskId, 'scribe', [philosopherTaskId], channel, timeoutMs);
|
|
358
|
-
const scribeRunner = new ScribeRunner(
|
|
359
|
-
{ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore },
|
|
360
|
-
runnerOptsFor(agentAdapters.scribe),
|
|
361
|
-
);
|
|
362
|
-
const scribeResult = await runStage(scribeRunner, scribeTaskId, { maxStageRetries, pollIntervalMs });
|
|
363
|
-
stages.push(stageFromResult('scribe', scribeTaskId, scribeResult));
|
|
364
|
-
if (scribeResult.status !== 'succeeded') {
|
|
365
|
-
onProgress('scribe', 'failed', scribeResult.failureReason);
|
|
366
|
-
return rejectedResult(opts.painId, stages, `scribe_failed: ${scribeResult.failureReason ?? scribeResult.status}`);
|
|
367
|
-
}
|
|
368
|
-
onProgress('scribe', 'succeeded');
|
|
369
|
-
|
|
370
|
-
// EP002-R4 follow-up #2 + Owner Decision Experience v1 Phase A: backfill
|
|
371
|
-
// the LEDGER principle UUID on the scribe artifact for BOTH paths (rule +
|
|
372
|
-
// text). Downstream identity resolution (extractPrincipleId's column-first
|
|
373
|
-
// order) then binds the rule artifact, the approval grouping, and the
|
|
374
|
-
// post-approve ledger upgrade to the ledger UUID instead of falling back
|
|
375
|
-
// to principleDraft.title — the title namespace is what left rule-channel
|
|
376
|
-
// approvals stranded as title-keyed groups the Console detail page cannot
|
|
377
|
-
// reach.
|
|
378
|
-
//
|
|
379
|
-
// Phase A (SPEC §9.1): the binding outcome is STRUCTURED. An unverified
|
|
380
|
-
// binding no longer silently continues into Owner approval publication —
|
|
381
|
-
// the enqueue sites below refuse to create an approval subject for an
|
|
382
|
-
// artifact whose stable ledger identity is not established, while the
|
|
383
|
-
// generation chain and its artifacts are preserved for later replay.
|
|
384
|
-
const identityBinding = await backfillScribeIdentity({
|
|
385
|
-
stateManager, artifactStore, scribeTaskId, dreamerTaskId: dreamerSeedTaskId,
|
|
386
|
-
workspaceDir: opts.workspaceDir, now: new Date().toISOString(),
|
|
387
|
-
});
|
|
388
|
-
if (scribeIdentityNote(identityBinding) !== '') {
|
|
389
|
-
const scribeStage = stages.find((s) => s.name === 'scribe');
|
|
390
|
-
if (scribeStage) (scribeStage as { reason?: string }).reason = scribeIdentityNote(identityBinding).replace(/^; /, '');
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// ── Atomic capability branching ──
|
|
394
|
-
// Per user correction (2026-06-18): ArtificerL2 + Evaluator are atomic.
|
|
395
|
-
// When OFF (or not provided), skip the adversarial loop entirely and
|
|
396
|
-
// degrade to text-principle-only. The scribe's principle artifact remains
|
|
397
|
-
// for prompt-channel fallback.
|
|
398
|
-
if (!capabilityEnabled) {
|
|
399
|
-
stages.push({ name: 'adversarial_loop', status: 'skipped', reason: capabilityDisabledReason });
|
|
400
|
-
onProgress('adversarial_loop', 'skipped', capabilityDisabledReason);
|
|
401
|
-
// PRI-804: text principles enter the existing prompt-channel approval
|
|
402
|
-
// queue (Owner governance, same gate as RuleCode) instead of dead-ending.
|
|
403
|
-
const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
|
|
404
|
-
return await textPrincipleOnlyResult({
|
|
405
|
-
painId: opts.painId, stages, scribeTaskId, disabledReason: capabilityDisabledReason,
|
|
406
|
-
artifactStore, approvalStore, now: new Date().toISOString(),
|
|
407
|
-
identityBinding,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// ── Stage: adversarial loop (artificer↔evaluator) ──
|
|
412
|
-
// Capability ON: use the artificerAdapter (L2 write-test-fix) for the
|
|
413
|
-
// artificer stage, and the base runtimeAdapter for the evaluator stage.
|
|
414
|
-
if (!capability?.artificerAdapter) {
|
|
415
|
-
// Contract violation: enabled but no adapter provided. Fail loud.
|
|
416
|
-
stages.push({ name: 'adversarial_loop', status: 'failed', reason: 'artificerAdapter not provided' });
|
|
417
|
-
onProgress('adversarial_loop', 'failed', 'artificerAdapter not provided');
|
|
418
|
-
return rejectedResult(opts.painId, stages, 'code_rule_capability enabled but artificerAdapter not provided');
|
|
419
|
-
}
|
|
420
|
-
// PRI-780: v2-only generation — an enabled code-rule capability without
|
|
421
|
-
// an Owner-labelled BehaviorExamplePack refuses BEFORE any artificer LLM
|
|
422
|
-
// stage runs. Never degrade to an action-only (v1) rule.
|
|
423
|
-
if (!opts.behaviorExamplePack) {
|
|
424
|
-
const reason = 'behavior_example_pack_missing: v2 is the only RuleCode generation contract (PRI-780); provide Owner-labelled behavior examples via --behavior-examples';
|
|
425
|
-
stages.push({ name: 'adversarial_loop', status: 'failed', reason });
|
|
426
|
-
onProgress('adversarial_loop', 'failed', reason);
|
|
427
|
-
return rejectedResult(opts.painId, stages, reason);
|
|
428
|
-
}
|
|
429
|
-
onProgress('adversarial_loop', 'start');
|
|
430
|
-
// PRI-661: the loop's evaluator replay must use the SAME production gate
|
|
431
|
-
// context as the consumer cycle and the activation gate (workspace root +
|
|
432
|
-
// durable host tool registry). The previous sandbox-only gateDeps could
|
|
433
|
-
// pass generation-time replay and fail the production activation gate on
|
|
434
|
-
// the same workspace — the drift class PRI-634-F closed elsewhere.
|
|
435
|
-
// Unresolvable provenance fails the loop LOUD (never a silent baseline
|
|
436
|
-
// fallback — ERR-114), mirroring the artificerAdapter-not-provided failure
|
|
437
|
-
// precedent below.
|
|
438
|
-
const evaluatorContext = createEvaluatorRuntimeContext({ workspaceDir: opts.workspaceDir });
|
|
439
|
-
if (!evaluatorContext.ok) {
|
|
440
|
-
const refusalReason = `${evaluatorContext.reason} — ${evaluatorContext.nextAction}`;
|
|
441
|
-
stages.push({ name: 'adversarial_loop', status: 'failed', reason: refusalReason });
|
|
442
|
-
onProgress('adversarial_loop', 'failed', refusalReason);
|
|
443
|
-
return rejectedResult(opts.painId, stages, `evaluator_runtime_context_unresolvable: ${evaluatorContext.reason} (nextAction: ${evaluatorContext.nextAction})`);
|
|
444
|
-
}
|
|
445
|
-
// PRI-741: artificer generation + evaluator parity BOTH anchor on the ONE
|
|
446
|
-
// registry snapshot evaluatorContext resolved above (review round: a
|
|
447
|
-
// second independent resolveWorkspaceHostToolSemantics read could observe
|
|
448
|
-
// a different declaration snapshot than the replay gate). Unresolvable
|
|
449
|
-
// provenance already failed loud at the evaluatorContext refusal above.
|
|
450
|
-
const hostSemanticContext = buildArtificerHostSemanticContext(evaluatorContext.registry, evaluatorContext.hostKinds ?? []);
|
|
451
|
-
const artificerRunner = new ArtificerRunner(
|
|
452
|
-
{
|
|
453
|
-
stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
|
|
454
|
-
behaviorExamplePack: opts.behaviorExamplePack,
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
...runnerOptsFor(capability.artificerAdapter),
|
|
458
|
-
hostSemanticContext,
|
|
459
|
-
},
|
|
460
|
-
);
|
|
461
|
-
// PRI-510 (DEFECT-004): construct EvaluatorRunnerDeps via the centralized
|
|
462
|
-
// helper so the repair-loop wiring (isRepairLoopEnabled + seeder) is
|
|
463
|
-
// actually invoked in the production CLI path. EP-02: prior code passed
|
|
464
|
-
// only the 5 base deps, leaving the repair loop as dead code at runtime.
|
|
465
|
-
const evaluatorRunner = new EvaluatorRunner(
|
|
466
|
-
createEvaluatorRunnerDeps({
|
|
467
|
-
stateManager,
|
|
468
|
-
runtimeAdapter: agentAdapters.evaluator,
|
|
469
|
-
eventEmitter,
|
|
470
|
-
validator: new DefaultEvaluatorValidator(),
|
|
471
|
-
artifactStore,
|
|
472
|
-
workspaceDir: opts.workspaceDir,
|
|
473
|
-
}),
|
|
474
|
-
// PRI-661: production gate context (workspace root + host tool registry
|
|
475
|
-
// from durable provenance) — replaces the previous sandbox-only gate
|
|
476
|
-
// deps so generation-time replay verdicts match the activation gate on
|
|
477
|
-
// the same workspace. Options argument (2nd), never deps (1st).
|
|
478
|
-
{
|
|
479
|
-
...runnerOptsFor(agentAdapters.evaluator),
|
|
480
|
-
gateDeps: evaluatorContext.gateDeps,
|
|
481
|
-
// PRI-741: host-name parity replay case from the SAME registry
|
|
482
|
-
// snapshot as the gateDeps above (single resolution).
|
|
483
|
-
hostSemanticContext,
|
|
484
|
-
},
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
const loopResult = await runAdversarialLoop({
|
|
488
|
-
artificerRunner,
|
|
489
|
-
evaluatorRunner,
|
|
490
|
-
stateManager,
|
|
491
|
-
artifactStore,
|
|
492
|
-
scribeTaskId,
|
|
493
|
-
maxRounds,
|
|
494
|
-
correlationId: correlation,
|
|
495
|
-
channel,
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
const loopStatus: RuleHostPipelineStageStatus = loopResult.decision === 'approved' ? 'succeeded' : 'degraded';
|
|
499
|
-
stages.push({ name: 'adversarial_loop', status: loopStatus, taskId: loopResult.finalEvaluatorTaskId, reason: loopResult.degradationReason });
|
|
500
|
-
onProgress('adversarial_loop', loopResult.decision === 'approved' ? 'succeeded' : 'degraded', loopResult.degradationReason);
|
|
501
|
-
|
|
502
|
-
// Map adversarial loop decision to pipeline decision:
|
|
503
|
-
// 'approved' → candidate_ready_for_owner_review (NOT owner approval)
|
|
504
|
-
// 'rejected' → generation_rejected
|
|
505
|
-
const pipelineDecision = loopResult.decision === 'approved'
|
|
506
|
-
? 'candidate_ready_for_owner_review' as const
|
|
507
|
-
: 'generation_rejected' as const;
|
|
508
|
-
|
|
509
|
-
// P1 #1 fix: auto-enqueue the candidate into the ApprovalQueue so the
|
|
510
|
-
// owner can review it. Without this, the pipeline produces a candidate
|
|
511
|
-
// artifact but it never enters the approval queue — the production chain
|
|
512
|
-
// is broken at step 2→3. Tests manually called enqueue(); production did not.
|
|
513
|
-
//
|
|
514
|
-
// Owner Decision Experience v1 Phase A (SPEC §9.1.4/§9.1.6): creating an
|
|
515
|
-
// Owner approval subject is a governance publication — it requires a
|
|
516
|
-
// verified stable ledger identity. When the chain's candidate could not be
|
|
517
|
-
// bound to exactly one existing ledger principle, the rule artifact must
|
|
518
|
-
// NOT enter the approvable set, even though the artifact itself (and every
|
|
519
|
-
// intermediate artifact) is preserved. Recovery is replay: after the
|
|
520
|
-
// ledger/identity repair, re-running the pipeline re-attempts the binding
|
|
521
|
-
// idempotently and then publishes.
|
|
522
|
-
let approvalId: string | null = null;
|
|
523
|
-
if (pipelineDecision === 'candidate_ready_for_owner_review' && loopResult.ruleArtifactId) {
|
|
524
|
-
const ruleIdentity = await resolveRuleArtifactIdentity(artifactStore, loopResult.ruleArtifactId, identityBinding);
|
|
525
|
-
if (ruleIdentity === null) {
|
|
526
|
-
const reasonCode = scribeIdentityReasonCode(identityBinding);
|
|
527
|
-
return {
|
|
528
|
-
decision: pipelineDecision,
|
|
529
|
-
painId: opts.painId,
|
|
530
|
-
stages,
|
|
531
|
-
scribeTaskId,
|
|
532
|
-
adversarialLoop: loopResult,
|
|
533
|
-
ruleArtifactId: loopResult.ruleArtifactId,
|
|
534
|
-
principleArtifactId: loopResult.principleArtifactId,
|
|
535
|
-
approvalId: null,
|
|
536
|
-
degradationReason: `identity_binding_unverified: ${reasonCode}. Rule artifact NOT enqueued for Owner approval (governance publication boundary). Artifacts preserved; repair the ledger identity and re-run this pipeline to publish.`,
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
try {
|
|
540
|
-
const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
|
|
541
|
-
const riskLevel = getChannelRiskLevel(channel);
|
|
542
|
-
const enqueuedRecord: ApprovalRecord = await approvalStore.enqueue({
|
|
543
|
-
artifactId: loopResult.ruleArtifactId,
|
|
544
|
-
channel,
|
|
545
|
-
riskLevel,
|
|
546
|
-
summary: `RuleHost pipeline candidate for pain ${opts.painId}`,
|
|
547
|
-
triggerReason: `adversarial_loop_approved: pain=${opts.painId}, rule=${loopResult.ruleArtifactId}`,
|
|
548
|
-
}, new Date().toISOString());
|
|
549
|
-
const { approvalId: enqueuedApprovalId } = enqueuedRecord;
|
|
550
|
-
approvalId = enqueuedApprovalId;
|
|
551
|
-
onProgress('adversarial_loop', 'succeeded', `auto-enqueued as approval ${approvalId}`);
|
|
552
|
-
} catch (err: unknown) {
|
|
553
|
-
// Enqueue failed — the candidate artifact exists but is not in the
|
|
554
|
-
// approval queue. Degrade gracefully with a structured reason (ERR-002).
|
|
555
|
-
const enqueueErr = err instanceof Error ? err.message : String(err);
|
|
556
|
-
const degradeReason = `candidate_approved_but_enqueue_failed: ${enqueueErr}. Manual enqueue required: pd activation dispatch --artifact-id ${loopResult.ruleArtifactId} --channel ${channel}`;
|
|
557
|
-
return {
|
|
558
|
-
decision: pipelineDecision,
|
|
559
|
-
painId: opts.painId,
|
|
560
|
-
stages,
|
|
561
|
-
scribeTaskId,
|
|
562
|
-
adversarialLoop: loopResult,
|
|
563
|
-
ruleArtifactId: loopResult.ruleArtifactId,
|
|
564
|
-
principleArtifactId: loopResult.principleArtifactId,
|
|
565
|
-
approvalId: null,
|
|
566
|
-
degradationReason: degradeReason,
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return {
|
|
572
|
-
decision: pipelineDecision,
|
|
573
|
-
painId: opts.painId,
|
|
574
|
-
stages,
|
|
575
|
-
scribeTaskId,
|
|
576
|
-
adversarialLoop: loopResult,
|
|
577
|
-
ruleArtifactId: loopResult.ruleArtifactId,
|
|
578
|
-
principleArtifactId: loopResult.principleArtifactId,
|
|
579
|
-
approvalId,
|
|
580
|
-
degradationReason: loopResult.degradationReason,
|
|
581
|
-
};
|
|
582
|
-
} finally {
|
|
583
|
-
await stateManager.close();
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Find the dreamer task seeded for a given pain ID.
|
|
591
|
-
*
|
|
592
|
-
* D fix (PRI-429): exact sourcePainId match — no substring matching,
|
|
593
|
-
* 'pain-1' must NOT match 'pain-10'.
|
|
594
|
-
*
|
|
595
|
-
* PRI-866 convergence: the stored side is read through the core canonical
|
|
596
|
-
* reader (parseSeedSourcePainId) and the query side is trimmed at this
|
|
597
|
-
* comparison boundary, so both directions of historical whitespace
|
|
598
|
-
* (' pain-123 ' stored / pain-123 queried, and vice versa) match.
|
|
599
|
-
* parseSeedSourcePainId returns null for malformed JSON — those rows keep
|
|
600
|
-
* the PRI-429 skip behavior (no crash, no match; the caller reports
|
|
601
|
-
* no_dreamer_task_seeded_for_pain, rc-9).
|
|
602
|
-
*
|
|
603
|
-
* The sourcePainId is stored as a top-level key in diagnosticJson (outside the
|
|
604
|
-
* pi_metadata envelope), mirroring the pattern in source-trace-locator.test.ts
|
|
605
|
-
* and PainSignalBridge.
|
|
606
|
-
*
|
|
607
|
-
* ERR refs:
|
|
608
|
-
* - ERR-001: parsed JSON treated as unknown (inside the canonical reader)
|
|
609
|
-
* - ERR-005/007: no `as` bypass; type narrowing lives in the canonical reader
|
|
610
|
-
* - ERR-013: Object.hasOwn for untrusted key checks (canonical reader)
|
|
611
|
-
* - ERR-009: missing sourcePainId = no match (fail loud, not silent skip)
|
|
612
|
-
*/
|
|
613
|
-
type DreamerTaskLookup =
|
|
614
|
-
| { readonly status: 'found'; readonly taskId: string; readonly taskStatus: 'pending' | 'retry_wait' | 'succeeded' }
|
|
615
|
-
| { readonly status: 'not_found' }
|
|
616
|
-
| { readonly status: 'ambiguous'; readonly taskIds: readonly string[] };
|
|
617
|
-
|
|
618
|
-
async function findDreamerTaskForPain(
|
|
619
|
-
stateManager: RuntimeStateManager,
|
|
620
|
-
painId: string,
|
|
621
|
-
channel: 'prompt' | 'code_tool_hook' | 'defer_archive',
|
|
622
|
-
): Promise<DreamerTaskLookup> {
|
|
623
|
-
const tasks = await stateManager.listTasks();
|
|
624
|
-
const dreamerTasks = tasks.filter((t) =>
|
|
625
|
-
t.taskKind === 'dreamer' && (t.status === 'pending' || t.status === 'retry_wait' || t.status === 'succeeded'),
|
|
626
|
-
);
|
|
627
|
-
const normalizedPainId = painId.trim();
|
|
628
|
-
const allMatches: typeof dreamerTasks = [];
|
|
629
|
-
const channelMatches: typeof dreamerTasks = [];
|
|
630
|
-
for (const t of dreamerTasks) {
|
|
631
|
-
if (typeof t.diagnosticJson !== 'string') continue;
|
|
632
|
-
const metadata = parsePITaskMetadata(t.diagnosticJson);
|
|
633
|
-
if (!metadata) continue;
|
|
634
|
-
const sourcePainId = parseSeedSourcePainId(t.diagnosticJson);
|
|
635
|
-
if (sourcePainId !== null && sourcePainId === normalizedPainId) {
|
|
636
|
-
allMatches.push(t);
|
|
637
|
-
if (metadata.channel === channel) channelMatches.push(t);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
const matches = channelMatches.length > 0 ? channelMatches : allMatches;
|
|
641
|
-
matches.sort((left, right) => left.taskId.localeCompare(right.taskId));
|
|
642
|
-
if (matches.length === 0) return { status: 'not_found' };
|
|
643
|
-
if (matches.length > 1) return { status: 'ambiguous', taskIds: matches.map((task) => task.taskId) };
|
|
644
|
-
const [match] = matches;
|
|
645
|
-
if (!match || (match.status !== 'pending' && match.status !== 'retry_wait' && match.status !== 'succeeded')) {
|
|
646
|
-
return { status: 'not_found' };
|
|
647
|
-
}
|
|
648
|
-
return { status: 'found', taskId: match.taskId, taskStatus: match.status };
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// eslint-disable-next-line @typescript-eslint/max-params
|
|
652
|
-
async function createInternalizationTask(
|
|
653
|
-
stateManager: RuntimeStateManager,
|
|
654
|
-
taskId: string,
|
|
655
|
-
taskKind: string,
|
|
656
|
-
dependencyTaskIds: string[],
|
|
657
|
-
channel: 'prompt' | 'code_tool_hook' | 'defer_archive',
|
|
658
|
-
timeoutMs: number,
|
|
659
|
-
): Promise<void> {
|
|
660
|
-
await stateManager.createTask({
|
|
661
|
-
taskId,
|
|
662
|
-
taskKind,
|
|
663
|
-
status: 'pending',
|
|
664
|
-
attemptCount: 0,
|
|
665
|
-
maxAttempts: 3,
|
|
666
|
-
diagnosticJson: createPITaskDiagnosticJson({
|
|
667
|
-
dependencyTaskIds,
|
|
668
|
-
channel,
|
|
669
|
-
timeoutMs,
|
|
670
|
-
inputArtifactRefs: [],
|
|
671
|
-
outputArtifactRefs: [],
|
|
672
|
-
}),
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// ── PRI-510 (DEFECT-004): EvaluatorRunner repair-loop CLI wiring ────────────
|
|
677
|
-
//
|
|
678
|
-
// EP-02 (Production Path Wiring): PRI-509 added the `isRepairLoopEnabled` and
|
|
679
|
-
// `seedArtificerRepairTask` deps to `EvaluatorRunnerDeps` (core), but the two
|
|
680
|
-
// CLI production paths (runtime-internalization-run-once.ts and this file)
|
|
681
|
-
// constructed `EvaluatorRunner` with only the base deps — the repair loop was
|
|
682
|
-
// dead code at runtime (evaluator needs_revision seeded nothing).
|
|
683
|
-
//
|
|
684
|
-
// This helper centralizes the construction of the full deps so both CLI sites
|
|
685
|
-
// stay in sync. The flag resolver reads `.pd/config.yaml` (the unified config
|
|
686
|
-
// — ADR-0016); the seeder writes the repair task via `stateManager.createTask`
|
|
687
|
-
// (the only sanctioned task-creation path; core peer runners are forbidden
|
|
688
|
-
// from calling it directly — architecture-regression.test.ts enforces this).
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Inputs for {@link createEvaluatorRunnerDeps}. Carries the base `PeerRunnerDeps`
|
|
692
|
-
* fields plus `workspaceDir` (used to resolve the feature flag from
|
|
693
|
-
* `.pd/config.yaml`).
|
|
694
|
-
*/
|
|
695
|
-
export interface CreateEvaluatorRunnerDepsInputs {
|
|
696
|
-
readonly stateManager: RuntimeStateManager;
|
|
697
|
-
readonly runtimeAdapter: PDRuntimeAdapter;
|
|
698
|
-
readonly eventEmitter: StoreEventEmitter;
|
|
699
|
-
readonly validator: EvaluatorValidator;
|
|
700
|
-
readonly artifactStore: PIArtifactStore;
|
|
701
|
-
/** Workspace directory containing `.pd/config.yaml` (flag source). */
|
|
702
|
-
readonly workspaceDir: string;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Build the full `EvaluatorRunnerDeps` for CLI production paths, including the
|
|
707
|
-
* PRI-509 repair-loop wiring (EP-02: production path must invoke core logic).
|
|
708
|
-
*
|
|
709
|
-
* - `isRepairLoopEnabled`: reads `.pd/config.yaml` and resolves the
|
|
710
|
-
* `evaluator_artificer_repair_loop` flag. Defaults to false when the config
|
|
711
|
-
* is missing or malformed (rc-9: fail safe — legacy path runs, never throws).
|
|
712
|
-
* - `seedArtificerRepairTask`: creates a new `artificer` task carrying the
|
|
713
|
-
* `repairPayload` in `diagnosticJson` (rc-1, rc-6: serialized via the
|
|
714
|
-
* validated `createPITaskDiagnosticJson`, no `as` bypass). Reuses the
|
|
715
|
-
* artificer runner (PRI-509 D1) rather than introducing a new task kind.
|
|
716
|
-
*
|
|
717
|
-
* Returns the newly created repair task's ID (UUIDv4, rc-7: fresh per call).
|
|
718
|
-
*/
|
|
719
|
-
export function createEvaluatorRunnerDeps(inputs: CreateEvaluatorRunnerDepsInputs): EvaluatorRunnerDeps {
|
|
720
|
-
const { stateManager, runtimeAdapter, eventEmitter, validator, artifactStore, workspaceDir } = inputs;
|
|
721
|
-
return {
|
|
722
|
-
stateManager,
|
|
723
|
-
runtimeAdapter,
|
|
724
|
-
eventEmitter,
|
|
725
|
-
validator,
|
|
726
|
-
artifactStore,
|
|
727
|
-
isRepairLoopEnabled: (): boolean => {
|
|
728
|
-
// rc-9: never throw on malformed config — fail safe to false so the
|
|
729
|
-
// legacy (non-repair) path runs. The malformed config is already
|
|
730
|
-
// surfaced by `pd config doctor` and CLI start-up warnings.
|
|
731
|
-
try {
|
|
732
|
-
const result = loadPdConfig(workspaceDir);
|
|
733
|
-
const effective = result.ok ? result.effective : result.defaults;
|
|
734
|
-
const flags = computeFeatureFlagsFromConfig(effective);
|
|
735
|
-
return isFeatureEnabled(flags, 'evaluator_artificer_repair_loop');
|
|
736
|
-
} catch {
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
},
|
|
740
|
-
seedArtificerRepairTask: async (params: SeedArtificerRepairParams): Promise<string> => {
|
|
741
|
-
// rc-7: each call gets a fresh task ID — never reuse a cached ID.
|
|
742
|
-
// P0-4: deterministic revision identity + reuse on replay.
|
|
743
|
-
// PRI-718: id 约定收敛到 pitask-metadata 的单一 owner。
|
|
744
|
-
const repairTaskId = artificerRepairTaskId(
|
|
745
|
-
params.repairPayload.sourceEvaluatorTaskId,
|
|
746
|
-
params.repairPayload.repairIteration,
|
|
747
|
-
);
|
|
748
|
-
const existing = await stateManager.getTask(repairTaskId);
|
|
749
|
-
if (existing) {
|
|
750
|
-
// PRI-718 (revise ≠ resume): terminal repair rounds are finished
|
|
751
|
-
// corrective work — never vehicles for new corrective work. In-flight
|
|
752
|
-
// rounds are the legitimate replay-reuse population.
|
|
753
|
-
if (existing.status === 'succeeded' || existing.status === 'failed' || existing.status === 'needs_human_review') {
|
|
754
|
-
throw new Error(
|
|
755
|
-
`repair task ${repairTaskId} already reached terminal status ${existing.status}; refusing to reuse it as new corrective work (PRI-718 revise-ne-resume)`,
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
return repairTaskId;
|
|
759
|
-
}
|
|
760
|
-
await stateManager.createTask({
|
|
761
|
-
taskId: repairTaskId,
|
|
762
|
-
// D1 (PRI-509): task kind is 'artificer' — reuses the artificer
|
|
763
|
-
// runner, which detects repairPayload in diagnosticJson and
|
|
764
|
-
// forwards it to the prompt builder as repairFeedback.
|
|
765
|
-
taskKind: 'artificer',
|
|
766
|
-
status: 'pending',
|
|
767
|
-
attemptCount: 0,
|
|
768
|
-
maxAttempts: 3,
|
|
769
|
-
// rc-1, rc-6: serialize via the validated helper — repairPayload
|
|
770
|
-
// is untrusted LLM output but `createPITaskDiagnosticJson` writes
|
|
771
|
-
// it through `serializePITaskMetadata`, which `parsePITaskMetadata`
|
|
772
|
-
// re-validates on read (defense in depth).
|
|
773
|
-
diagnosticJson: createPITaskDiagnosticJson({
|
|
774
|
-
// PITaskMetadata fields are mutable arrays (the metadata envelope
|
|
775
|
-
// serializes them as JSON arrays). The SeedArtificerRepairParams
|
|
776
|
-
// contract carries readonly arrays (rc-1: untrusted input from the
|
|
777
|
-
// evaluator LLM). Spread to a fresh mutable array — no mutation of
|
|
778
|
-
// the caller's data.
|
|
779
|
-
dependencyTaskIds: [...params.inheritedDependencyTaskIds],
|
|
780
|
-
channel: params.inheritedChannel,
|
|
781
|
-
timeoutMs: params.inheritedTimeoutMs,
|
|
782
|
-
inputArtifactRefs: [...params.inheritedInputArtifactRefs],
|
|
783
|
-
outputArtifactRefs: [],
|
|
784
|
-
repairPayload: params.repairPayload,
|
|
785
|
-
}),
|
|
786
|
-
});
|
|
787
|
-
return repairTaskId;
|
|
788
|
-
},
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Run a single pipeline stage with bounded retry for transient `retried` status.
|
|
794
|
-
*
|
|
795
|
-
* Runtime Contract Rule 7: retry/repair loops must distinguish current, next,
|
|
796
|
-
* and recorded state. Each iteration calls `runner.run(taskId)` fresh — the
|
|
797
|
-
* runner reads from the state manager which has the updated task state
|
|
798
|
-
* (including any `retry_wait` → `pending` transition from the previous
|
|
799
|
-
* iteration). No stale state is carried across iterations.
|
|
800
|
-
*
|
|
801
|
-
* `retried` is NOT terminal — it means the runner hit a transient error and
|
|
802
|
-
* marked the task for retry. The pipeline retries up to `maxStageRetries`
|
|
803
|
-
* times (default 2). When exhausted, the stage is marked 'degraded' with a
|
|
804
|
-
* structured reason, and the caller treats it as a non-success outcome.
|
|
805
|
-
*/
|
|
806
|
-
interface RunStageOptions {
|
|
807
|
-
readonly maxStageRetries: number;
|
|
808
|
-
readonly pollIntervalMs: number;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
async function runStage(
|
|
812
|
-
runner: { run(id: string): Promise<PeerRunnerResult<unknown>> },
|
|
813
|
-
taskId: string,
|
|
814
|
-
opts: RunStageOptions,
|
|
815
|
-
): Promise<PeerRunnerResult<unknown>> {
|
|
816
|
-
let attempt = 0;
|
|
817
|
-
// First attempt is not a retry — it's the initial run.
|
|
818
|
-
let result = await runner.run(taskId);
|
|
819
|
-
while (result.status === 'retried' && attempt < opts.maxStageRetries) {
|
|
820
|
-
attempt++;
|
|
821
|
-
// Brief wait before retry to allow transient conditions to clear.
|
|
822
|
-
// The state manager has already marked the task as retry_wait; the runner
|
|
823
|
-
// will re-read it on the next .run() call (fresh state per iteration).
|
|
824
|
-
await new Promise<void>((resolve) => setTimeout(resolve, opts.pollIntervalMs));
|
|
825
|
-
result = await runner.run(taskId);
|
|
826
|
-
}
|
|
827
|
-
return result;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function stageFromResult(
|
|
831
|
-
name: RuleHostPipelineStage['name'],
|
|
832
|
-
taskId: string,
|
|
833
|
-
result: PeerRunnerResult<unknown>,
|
|
834
|
-
): RuleHostPipelineStage {
|
|
835
|
-
// `retried` after exhausting retries is a transient-exhausted state, not a
|
|
836
|
-
// hard failure. Mark as 'degraded' so the caller can distinguish "stage
|
|
837
|
-
// failed permanently" from "stage exhausted transient retries".
|
|
838
|
-
if (result.status === 'succeeded') {
|
|
839
|
-
return { name, taskId, status: 'succeeded' };
|
|
840
|
-
}
|
|
841
|
-
if (result.status === 'retried') {
|
|
842
|
-
return {
|
|
843
|
-
name,
|
|
844
|
-
taskId,
|
|
845
|
-
status: 'degraded',
|
|
846
|
-
reason: `transient_retry_exhausted: ${result.failureReason ?? result.status}`,
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
return {
|
|
850
|
-
name,
|
|
851
|
-
taskId,
|
|
852
|
-
status: 'failed',
|
|
853
|
-
reason: result.failureReason ?? result.status,
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
function rejectedResult(painId: string, stages: RuleHostPipelineStage[], degradationReason: string): RuleHostPipelineResult {
|
|
858
|
-
return {
|
|
859
|
-
decision: 'generation_rejected',
|
|
860
|
-
painId,
|
|
861
|
-
stages,
|
|
862
|
-
scribeTaskId: null,
|
|
863
|
-
ruleArtifactId: null,
|
|
864
|
-
principleArtifactId: null,
|
|
865
|
-
approvalId: null,
|
|
866
|
-
degradationReason,
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* Build a text-principle-only result when the code-rule capability is OFF.
|
|
872
|
-
*
|
|
873
|
-
* The scribe stage already produced a principle artifact (stored via the
|
|
874
|
-
* artifact store). We look it up by the scribe task ID so the caller can
|
|
875
|
-
* reference it for prompt-channel fallback.
|
|
876
|
-
*/
|
|
877
|
-
interface TextPrincipleOnlyParams {
|
|
878
|
-
readonly painId: string;
|
|
879
|
-
readonly stages: RuleHostPipelineStage[];
|
|
880
|
-
readonly scribeTaskId: string;
|
|
881
|
-
readonly disabledReason: string;
|
|
882
|
-
readonly artifactStore: PIArtifactStore;
|
|
883
|
-
/** PRI-804: approval queue used to enqueue the text principle (prompt channel). */
|
|
884
|
-
readonly approvalStore: SqliteApprovalQueueStore;
|
|
885
|
-
readonly now: string;
|
|
886
|
-
/** Phase A: structured identity outcome from the shared post-scribe backfill. */
|
|
887
|
-
readonly identityBinding: ScribeIdentityBinding;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
/**
|
|
891
|
-
* Owner Decision Experience v1 Phase A (SPEC §9.1) — structured ledger
|
|
892
|
-
* identity binding outcome for the internalization chain.
|
|
893
|
-
*
|
|
894
|
-
* `bound`/`already_bound` mean the scribe principle artifact carries a
|
|
895
|
-
* source_principle_id that was READ BACK from the PI store after the write
|
|
896
|
-
* and, when the chain candidate resolved, that it equals the unique existing
|
|
897
|
-
* ledger principle. Every other status is a publication blocker: the reason
|
|
898
|
-
* codes follow the SPEC's suggested vocabulary
|
|
899
|
-
* (candidate_unresolved / ledger_missing / ledger_ambiguous /
|
|
900
|
-
* identity_conflict / binding_write_failed).
|
|
901
|
-
*/
|
|
902
|
-
export type ScribeIdentityBinding =
|
|
903
|
-
| { status: 'bound'; principleId: string }
|
|
904
|
-
| { status: 'already_bound'; principleId: string }
|
|
905
|
-
| { status: 'no_principle_artifact' }
|
|
906
|
-
| { status: 'candidate_unresolved' }
|
|
907
|
-
| { status: 'ledger_missing'; candidateId: string }
|
|
908
|
-
| { status: 'ledger_ambiguous'; candidateId: string; principleIds: string[] }
|
|
909
|
-
| { status: 'identity_conflict'; existingPrincipleId: string; resolvedPrincipleId: string }
|
|
910
|
-
| { status: 'bound_target_missing'; principleId: string }
|
|
911
|
-
| { status: 'binding_write_failed'; errorMessage: string }
|
|
912
|
-
| { status: 'binding_readback_failed'; expectedPrincipleId: string; errorMessage: string };
|
|
913
|
-
|
|
914
|
-
/** Only these two statuses authorize governance publication (approval enqueue). */
|
|
915
|
-
export function isIdentityBindingVerified(
|
|
916
|
-
binding: ScribeIdentityBinding,
|
|
917
|
-
): binding is Extract<ScribeIdentityBinding, { status: 'bound' | 'already_bound' }> {
|
|
918
|
-
return binding.status === 'bound' || binding.status === 'already_bound';
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
/** Stable machine reason code for observability/logs (SPEC §9.1 vocabulary). */
|
|
922
|
-
export function scribeIdentityReasonCode(binding: ScribeIdentityBinding): string {
|
|
923
|
-
switch (binding.status) {
|
|
924
|
-
case 'bound':
|
|
925
|
-
case 'already_bound':
|
|
926
|
-
return 'bound';
|
|
927
|
-
case 'no_principle_artifact':
|
|
928
|
-
return 'no_principle_artifact';
|
|
929
|
-
case 'candidate_unresolved':
|
|
930
|
-
return 'candidate_unresolved';
|
|
931
|
-
case 'ledger_missing':
|
|
932
|
-
return 'ledger_missing';
|
|
933
|
-
case 'ledger_ambiguous':
|
|
934
|
-
return 'ledger_ambiguous';
|
|
935
|
-
case 'identity_conflict':
|
|
936
|
-
return 'identity_conflict';
|
|
937
|
-
case 'bound_target_missing':
|
|
938
|
-
return 'identity_conflict';
|
|
939
|
-
case 'binding_write_failed':
|
|
940
|
-
return 'binding_write_failed';
|
|
941
|
-
case 'binding_readback_failed':
|
|
942
|
-
return 'binding_write_failed';
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
/** Human/telemetry note for the scribe stage; '' on a verified binding. */
|
|
947
|
-
export function scribeIdentityNote(binding: ScribeIdentityBinding): string {
|
|
948
|
-
switch (binding.status) {
|
|
949
|
-
case 'bound':
|
|
950
|
-
case 'already_bound':
|
|
951
|
-
return '';
|
|
952
|
-
case 'no_principle_artifact':
|
|
953
|
-
return '; source_principle_id_backfill_skipped: no principle artifact from scribe';
|
|
954
|
-
case 'candidate_unresolved':
|
|
955
|
-
return '; source_principle_id_backfill_skipped: no candidateId on the dreamer seed (candidate_unresolved)';
|
|
956
|
-
case 'ledger_missing':
|
|
957
|
-
return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches no ledger principle (ledger_missing)`;
|
|
958
|
-
case 'ledger_ambiguous':
|
|
959
|
-
return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches ${binding.principleIds.length} ledger principles (ledger_ambiguous)`;
|
|
960
|
-
case 'identity_conflict':
|
|
961
|
-
return `; source_principle_id_backfill_refused: artifact already bound to ${binding.existingPrincipleId}, chain resolves to ${binding.resolvedPrincipleId} (identity_conflict)`;
|
|
962
|
-
case 'bound_target_missing':
|
|
963
|
-
return `; source_principle_id_backfill_refused: artifact bound to ${binding.principleId} which is absent from the ledger (identity_conflict)`;
|
|
964
|
-
case 'binding_write_failed':
|
|
965
|
-
return `; source_principle_id_backfill_failed: ${binding.errorMessage}`;
|
|
966
|
-
case 'binding_readback_failed':
|
|
967
|
-
return `; source_principle_id_backfill_unverified: read-back mismatch for ${binding.expectedPrincipleId}: ${binding.errorMessage}`;
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* PRI-804(a) + Phase A: resolve the UNIQUE ledger principle for the chain.
|
|
973
|
-
*
|
|
974
|
-
* The ledger principle is the identity the Console groups approvals by and the
|
|
975
|
-
* one `upgradeLedgerPrinciple` activates after approval. The only durable link
|
|
976
|
-
* from the internalization chain to the ledger is the candidateId carried in
|
|
977
|
-
* the dreamer task's diagnosticJson (set by the intake bridge) — the ledger
|
|
978
|
-
* entry stores it in derivedFromPainIds. Phase A hardens the lookup:
|
|
979
|
-
* `listForCandidate` (plural, same adapter the intake bridge uses — one
|
|
980
|
-
* authority, no second truth) rejects zero and multi matches instead of
|
|
981
|
-
* silently taking the first hit.
|
|
982
|
-
*/
|
|
983
|
-
async function resolveLedgerIdentity(
|
|
984
|
-
stateManager: RuntimeStateManager,
|
|
985
|
-
dreamerTaskId: string,
|
|
986
|
-
workspaceDir: string,
|
|
987
|
-
): Promise<ScribeIdentityBinding | { status: 'resolved'; principleId: string }> {
|
|
988
|
-
let candidateId: string | undefined;
|
|
989
|
-
try {
|
|
990
|
-
const dreamerTask = await stateManager.getTask(dreamerTaskId);
|
|
991
|
-
if (typeof dreamerTask?.diagnosticJson === 'string') {
|
|
992
|
-
const parsed: unknown = JSON.parse(dreamerTask.diagnosticJson);
|
|
993
|
-
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
994
|
-
&& Object.hasOwn(parsed, 'candidateId')) {
|
|
995
|
-
const stored = Reflect.get(parsed, 'candidateId');
|
|
996
|
-
if (typeof stored === 'string' && stored.length > 0) candidateId = stored;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
} catch {
|
|
1000
|
-
return { status: 'candidate_unresolved' }; // malformed dreamer diagnosticJson
|
|
1001
|
-
}
|
|
1002
|
-
if (!candidateId) return { status: 'candidate_unresolved' };
|
|
1003
|
-
let matches: { id: string }[];
|
|
1004
|
-
try {
|
|
1005
|
-
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
1006
|
-
matches = ledger.listForCandidate(candidateId);
|
|
1007
|
-
} catch {
|
|
1008
|
-
// Ledger unreadable cannot be distinguished from "no ledger" here — the
|
|
1009
|
-
// write attempt below will surface the concrete error; treat as missing
|
|
1010
|
-
// with the skip note (same observable class as before Phase A).
|
|
1011
|
-
return { status: 'ledger_missing', candidateId };
|
|
1012
|
-
}
|
|
1013
|
-
if (matches.length === 0) return { status: 'ledger_missing', candidateId };
|
|
1014
|
-
if (matches.length > 1) {
|
|
1015
|
-
return { status: 'ledger_ambiguous', candidateId, principleIds: matches.map((m) => m.id) };
|
|
1016
|
-
}
|
|
1017
|
-
const [unique] = matches;
|
|
1018
|
-
if (unique === undefined) return { status: 'ledger_missing', candidateId };
|
|
1019
|
-
return { status: 'resolved', principleId: unique.id };
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
interface BackfillIdentityParams {
|
|
1023
|
-
readonly stateManager: RuntimeStateManager;
|
|
1024
|
-
readonly artifactStore: PIArtifactStore;
|
|
1025
|
-
readonly scribeTaskId: string;
|
|
1026
|
-
readonly dreamerTaskId: string;
|
|
1027
|
-
readonly workspaceDir: string;
|
|
1028
|
-
readonly now: string;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
/**
|
|
1032
|
-
* EP002-R4 follow-up #2 + Phase A: backfill the scribe principle artifact's
|
|
1033
|
-
* source_principle_id with the LEDGER principle UUID (shared by the rule and
|
|
1034
|
-
* text paths — runs right after the scribe stage succeeds). Every downstream
|
|
1035
|
-
* identity resolution reads this column FIRST (extractPrincipleId /
|
|
1036
|
-
* EvaluatorRunner.extractPrincipleIdFromArtifact), so this single write is
|
|
1037
|
-
* what binds approvals, Console grouping, ledger upgrades, and rule
|
|
1038
|
-
* artifacts to the ledger UUID instead of the title namespace.
|
|
1039
|
-
*
|
|
1040
|
-
* Phase A contract changes vs the note-only version:
|
|
1041
|
-
* - the outcome is structured (ScribeIdentityBinding), not a free string;
|
|
1042
|
-
* - an existing binding that DISAGREES with the resolved ledger target is an
|
|
1043
|
-
* identity_conflict — never overwritten;
|
|
1044
|
-
* - a successful write is verified by reading the artifact back from the
|
|
1045
|
-
* PI store (log success alone is not binding evidence — SPEC §9.1);
|
|
1046
|
-
* - a pre-existing binding is only trusted when its target exists in the
|
|
1047
|
-
* ledger read (listForCandidate is not applicable — we check presence by
|
|
1048
|
-
* resolving through the same ledger load).
|
|
1049
|
-
*
|
|
1050
|
-
* Still never throws — identity enrichment must not break the generation
|
|
1051
|
-
* chain; it only gates the later governance publication.
|
|
1052
|
-
*/
|
|
1053
|
-
export async function backfillScribeIdentity(params: BackfillIdentityParams): Promise<ScribeIdentityBinding> {
|
|
1054
|
-
const { stateManager, artifactStore, scribeTaskId, dreamerTaskId, workspaceDir, now } = params;
|
|
1055
|
-
try {
|
|
1056
|
-
const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
|
|
1057
|
-
const principleArt = arts.find((a) => a.artifactKind === 'principle');
|
|
1058
|
-
if (!principleArt) return { status: 'no_principle_artifact' };
|
|
1059
|
-
const resolution = await resolveLedgerIdentity(stateManager, dreamerTaskId, workspaceDir);
|
|
1060
|
-
if (principleArt.sourcePrincipleId) {
|
|
1061
|
-
// Already bound (rerun). Idempotent success only when the binding is
|
|
1062
|
-
// consistent with what the chain resolves to now.
|
|
1063
|
-
if (resolution.status === 'resolved') {
|
|
1064
|
-
if (resolution.principleId === principleArt.sourcePrincipleId) {
|
|
1065
|
-
return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
|
|
1066
|
-
}
|
|
1067
|
-
return {
|
|
1068
|
-
status: 'identity_conflict',
|
|
1069
|
-
existingPrincipleId: principleArt.sourcePrincipleId,
|
|
1070
|
-
resolvedPrincipleId: resolution.principleId,
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
if (resolution.status === 'ledger_ambiguous') return resolution;
|
|
1074
|
-
// Chain candidate unresolved/missing on this replay — the durable
|
|
1075
|
-
// binding still stands only if its target is a real ledger principle.
|
|
1076
|
-
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
1077
|
-
if (ledger.hasPrinciple(principleArt.sourcePrincipleId)) {
|
|
1078
|
-
return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
|
|
1079
|
-
}
|
|
1080
|
-
return { status: 'bound_target_missing', principleId: principleArt.sourcePrincipleId };
|
|
1081
|
-
}
|
|
1082
|
-
if (resolution.status !== 'resolved') return resolution;
|
|
1083
|
-
const ledgerPrincipleId = resolution.principleId;
|
|
1084
|
-
await artifactStore.upsertArtifact({ ...principleArt, sourcePrincipleId: ledgerPrincipleId, updatedAt: now });
|
|
1085
|
-
// Read-back verification: the PI store must return the bound ID.
|
|
1086
|
-
try {
|
|
1087
|
-
const readBack = await artifactStore.getArtifactById(principleArt.artifactId);
|
|
1088
|
-
if (readBack?.sourcePrincipleId !== ledgerPrincipleId) {
|
|
1089
|
-
return {
|
|
1090
|
-
status: 'binding_readback_failed',
|
|
1091
|
-
expectedPrincipleId: ledgerPrincipleId,
|
|
1092
|
-
errorMessage: `read-back sourcePrincipleId=${readBack?.sourcePrincipleId ?? 'null'}`,
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
} catch (readErr: unknown) {
|
|
1096
|
-
const msg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
1097
|
-
return { status: 'binding_readback_failed', expectedPrincipleId: ledgerPrincipleId, errorMessage: msg };
|
|
1098
|
-
}
|
|
1099
|
-
return { status: 'bound', principleId: ledgerPrincipleId };
|
|
1100
|
-
} catch (err: unknown) {
|
|
1101
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1102
|
-
return { status: 'binding_write_failed', errorMessage: msg };
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Phase A (SPEC §9.1.6): before the rule artifact enters the approvable set,
|
|
1108
|
-
* verify the chain's ledger identity is VERIFIED and the rule artifact carries
|
|
1109
|
-
* exactly that binding. A chain without a verified unique ledger target must
|
|
1110
|
-
* not publish, even if the artifact itself carries some id (e.g. a legacy
|
|
1111
|
-
* title-namespace value the evaluator would have echoed). Returns the
|
|
1112
|
-
* verified principle id, or null when publication must be refused.
|
|
1113
|
-
*/
|
|
1114
|
-
async function resolveRuleArtifactIdentity(
|
|
1115
|
-
artifactStore: PIArtifactStore,
|
|
1116
|
-
ruleArtifactId: string,
|
|
1117
|
-
identityBinding: ScribeIdentityBinding,
|
|
1118
|
-
): Promise<string | null> {
|
|
1119
|
-
if (!isIdentityBindingVerified(identityBinding)) return null;
|
|
1120
|
-
let artifactSourcePrincipleId: string | undefined;
|
|
1121
|
-
try {
|
|
1122
|
-
const ruleArt = await artifactStore.getArtifactById(ruleArtifactId);
|
|
1123
|
-
artifactSourcePrincipleId = ruleArt?.sourcePrincipleId;
|
|
1124
|
-
} catch {
|
|
1125
|
-
return null;
|
|
1126
|
-
}
|
|
1127
|
-
if (artifactSourcePrincipleId === undefined || artifactSourcePrincipleId !== identityBinding.principleId) return null;
|
|
1128
|
-
return artifactSourcePrincipleId;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
async function textPrincipleOnlyResult(
|
|
1132
|
-
params: TextPrincipleOnlyParams,
|
|
1133
|
-
): Promise<RuleHostPipelineResult> {
|
|
1134
|
-
const { painId, stages, scribeTaskId, disabledReason, artifactStore, approvalStore, now, identityBinding } = params;
|
|
1135
|
-
// Look up the principle artifact produced by the scribe stage.
|
|
1136
|
-
try {
|
|
1137
|
-
const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
|
|
1138
|
-
const principleArt = arts.find((a) => a.artifactKind === 'principle');
|
|
1139
|
-
// Identity backfill already ran on the shared post-scribe step
|
|
1140
|
-
// (backfillScribeIdentity) for BOTH paths; nothing to do here.
|
|
1141
|
-
// On the text path the evaluator never runs, so nothing else flips the
|
|
1142
|
-
// validation status — mark it validated here (same store API the
|
|
1143
|
-
// evaluator uses; scribe task success already implies its validator
|
|
1144
|
-
// passed — peer-runner contract: output_invalid tasks never succeed).
|
|
1145
|
-
let identityNote = '';
|
|
1146
|
-
if (principleArt && principleArt.validationStatus !== 'validated') {
|
|
1147
|
-
try {
|
|
1148
|
-
await artifactStore.updateValidationStatus(principleArt.artifactId, 'validated');
|
|
1149
|
-
} catch (err: unknown) {
|
|
1150
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1151
|
-
identityNote = `; validation_status_mark_failed: ${msg}`;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
// PRI-804: a text principle is a prompt-channel intervention and follows the
|
|
1155
|
-
// same Owner governance as RuleCode (Owner decision 2026-09-15): enqueue it
|
|
1156
|
-
// into the EXISTING approval queue so the Owner can approve it in Console.
|
|
1157
|
-
// Deterministic approval id (apr_prompt_<artifactId>) + INSERT OR IGNORE make
|
|
1158
|
-
// replays idempotent. Enqueue failure degrades observably (rc-9), never
|
|
1159
|
-
// throwing away the artifact.
|
|
1160
|
-
//
|
|
1161
|
-
// Phase A governance publication boundary: an enqueue is only allowed when
|
|
1162
|
-
// the artifact's ledger identity binding is VERIFIED. Unverified bindings
|
|
1163
|
-
// keep the artifact (and its validation status) but must not create an
|
|
1164
|
-
// Owner approval subject; recovery is a replay after identity repair.
|
|
1165
|
-
let approvalId: string | null = null;
|
|
1166
|
-
let enqueueNote = '';
|
|
1167
|
-
if (!principleArt) {
|
|
1168
|
-
enqueueNote = '; approval_enqueue_skipped: no principle artifact from scribe';
|
|
1169
|
-
} else if (!isIdentityBindingVerified(identityBinding)) {
|
|
1170
|
-
const reasonCode = scribeIdentityReasonCode(identityBinding);
|
|
1171
|
-
enqueueNote = `; approval_enqueue_skipped: identity_binding_unverified (${reasonCode}). Principle artifact preserved; repair the ledger identity and re-run this pipeline to publish.`;
|
|
1172
|
-
} else if (identityBinding.principleId !== principleArt.sourcePrincipleId) {
|
|
1173
|
-
enqueueNote = `; approval_enqueue_skipped: identity_binding_unverified (identity_conflict: chain resolves to ${identityBinding.principleId}, artifact carries ${principleArt.sourcePrincipleId ?? 'no binding'}). Principle artifact preserved; repair the ledger identity and re-run this pipeline to publish.`;
|
|
1174
|
-
} else {
|
|
1175
|
-
try {
|
|
1176
|
-
const record = await approvalStore.enqueue({
|
|
1177
|
-
artifactId: principleArt.artifactId,
|
|
1178
|
-
channel: 'prompt',
|
|
1179
|
-
riskLevel: getChannelRiskLevel('prompt'),
|
|
1180
|
-
summary: `Text principle candidate for pain ${painId}`,
|
|
1181
|
-
triggerReason: `text_principle_only: pain=${painId}, principle=${principleArt.artifactId}; code-rule capability off (${disabledReason})`,
|
|
1182
|
-
}, now);
|
|
1183
|
-
const { approvalId: enqueuedApprovalId } = record;
|
|
1184
|
-
approvalId = enqueuedApprovalId;
|
|
1185
|
-
} catch (err: unknown) {
|
|
1186
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1187
|
-
enqueueNote = `; approval_enqueue_failed: ${msg}. Manual enqueue required: pd activation dispatch --artifact-id ${principleArt.artifactId} --channel prompt`;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
return {
|
|
1191
|
-
decision: 'text_principle_only',
|
|
1192
|
-
painId,
|
|
1193
|
-
stages,
|
|
1194
|
-
scribeTaskId,
|
|
1195
|
-
ruleArtifactId: null,
|
|
1196
|
-
principleArtifactId: principleArt?.artifactId ?? null,
|
|
1197
|
-
approvalId,
|
|
1198
|
-
degradationReason: `code_rule_capability_off: ${disabledReason}${identityNote}${enqueueNote}`,
|
|
1199
|
-
};
|
|
1200
|
-
} catch (error: unknown) {
|
|
1201
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1202
|
-
return {
|
|
1203
|
-
decision: 'text_principle_only',
|
|
1204
|
-
painId,
|
|
1205
|
-
stages,
|
|
1206
|
-
scribeTaskId,
|
|
1207
|
-
ruleArtifactId: null,
|
|
1208
|
-
principleArtifactId: null,
|
|
1209
|
-
approvalId: null,
|
|
1210
|
-
degradationReason: `code_rule_capability_off: ${disabledReason}; principle_artifact_lookup_failed: ${message}`,
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
}
|