create-principles-disciple 1.145.25 → 1.145.27
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/console/dist/server/config/pd-config-store.d.ts +1 -0
- package/console/dist/server/config/pd-config-store.js +28 -6
- package/console/dist/server/models/RuleCandidateBindingConsoleModel.d.ts +26 -0
- package/console/dist/server/models/RuleCandidateBindingConsoleModel.js +309 -19
- package/console/dist/server/routes/config.js +7 -3
- package/console/dist/server/routes/rule-candidate-bindings.js +26 -1
- package/console/dist/shared/rule-candidate-binding-contract.d.ts +93 -8
- package/console/dist/shared/rule-candidate-binding-contract.js +4 -0
- package/console/dist/ui/api.d.ts +28 -0
- package/console/dist/ui/api.js +13 -1
- package/console/dist/ui/i18n/en.json +93 -25
- package/console/dist/ui/i18n/zh-CN.json +79 -11
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +32 -67
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +29 -0
- package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +229 -3
- package/console/dist/ui/utils/rule-binding-validators.d.ts +5 -0
- package/console/dist/ui/utils/rule-binding-validators.js +149 -7
- package/console/dist/web/assets/app.css +6 -0
- package/console/dist/web/assets/app.js +32 -32
- package/core/dist/runtime-v2/adapter/index.d.ts +0 -2
- package/core/dist/runtime-v2/adapter/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/index.js +0 -1
- package/core/dist/runtime-v2/adapter/index.js.map +1 -1
- package/core/dist/runtime-v2/adapter/output-schema-registry.d.ts +3 -3
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +1 -2
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
- package/core/dist/runtime-v2/cli/probe.d.ts +4 -15
- package/core/dist/runtime-v2/cli/probe.d.ts.map +1 -1
- package/core/dist/runtime-v2/cli/probe.js +24 -44
- package/core/dist/runtime-v2/cli/probe.js.map +1 -1
- package/core/dist/runtime-v2/config/index.d.ts +1 -1
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/index.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts +10 -17
- package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js +23 -27
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +3 -5
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +17 -20
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js +4 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.js +4 -7
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +5 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js +6 -0
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js +6 -0
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/golden-trace.d.ts.map +1 -1
- package/core/dist/runtime-v2/golden-trace.js +8 -1
- package/core/dist/runtime-v2/golden-trace.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +4 -4
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +2 -3
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-intent-preflight.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js +26 -95
- package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js.map +1 -1
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts +32 -0
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/bash-risk.js +785 -0
- package/core/dist/runtime-v2/internalization/bash-risk.js.map +1 -0
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +25 -2
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +177 -14
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +14 -2
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +47 -0
- package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/internalization-orchestrator.js +164 -3
- package/core/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +9 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
- package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +39 -0
- package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/pitask-metadata.js +59 -0
- package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts +60 -0
- package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/rule-source-correction.js +214 -0
- package/core/dist/runtime-v2/internalization/rule-source-correction.js.map +1 -0
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js +65 -0
- package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts +14 -2
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-output.js +57 -2
- package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +13 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js +15 -3
- package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +34 -0
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js +128 -4
- package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts +72 -0
- package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/textual-basis-anchor.js +100 -0
- package/core/dist/runtime-v2/internalization/textual-basis-anchor.js.map +1 -0
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +9 -7
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +59 -121
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/core/dist/runtime-v2/runtime-protocol.d.ts +2 -3
- package/core/dist/runtime-v2/runtime-protocol.d.ts.map +1 -1
- package/core/dist/runtime-v2/runtime-protocol.js +15 -10
- package/core/dist/runtime-v2/runtime-protocol.js.map +1 -1
- package/core/dist/runtime-v2/store/lifecycle/lease-manager.d.ts.map +1 -1
- package/core/dist/runtime-v2/store/lifecycle/lease-manager.js +11 -2
- package/core/dist/runtime-v2/store/lifecycle/lease-manager.js.map +1 -1
- package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.d.ts.map +1 -1
- package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js +26 -0
- package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js.map +1 -1
- package/core/dist/telemetry-event.d.ts +2 -2
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +5 -0
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/dist/workflow-funnel-loader.d.ts +0 -2
- package/core/dist/workflow-funnel-loader.d.ts.map +1 -1
- package/core/dist/workflow-funnel-loader.js.map +1 -1
- package/core/package.json +5 -1
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +8 -10
- package/dist/mvp-config.js.map +1 -1
- package/host-runtime/dist/internalization-consumer-cycle.js +4 -7
- package/host-runtime/dist/production-rulehost-gate.js +11 -1
- package/host-runtime/package.json +1 -1
- package/package.json +2 -2
- package/pd-cli/dist/commands/diagnose.d.ts +0 -3
- package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
- package/pd-cli/dist/commands/diagnose.js +20 -57
- package/pd-cli/dist/commands/diagnose.js.map +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts +0 -3
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +11 -39
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js +32 -0
- package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-once.js +4 -2
- package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/pd-cli/dist/commands/runtime.d.ts +1 -4
- package/pd-cli/dist/commands/runtime.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime.js +25 -95
- package/pd-cli/dist/commands/runtime.js.map +1 -1
- package/pd-cli/dist/index.js +2 -5
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/services/config-doctor.js +9 -21
- package/pd-cli/dist/services/config-doctor.js.map +1 -1
- package/pd-cli/dist/services/mainline-snapshot-assembler.d.ts.map +1 -1
- package/pd-cli/dist/services/mainline-snapshot-assembler.js +2 -2
- package/pd-cli/dist/services/mainline-snapshot-assembler.js.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts +0 -7
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js +0 -5
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts +1 -8
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.js +13 -39
- package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +468 -458
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/dist/mvp-config.d.ts.map +1 -1
- package/release-manager/dist/mvp-config.js +8 -10
- package/release-manager/dist/mvp-config.js.map +1 -1
- package/release-manager/package.json +2 -2
- package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts +0 -72
- package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts.map +0 -1
- package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js +0 -837
- package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js.map +0 -1
- package/core/dist/runtime-v2/utils/cli-process-runner.d.ts +0 -35
- package/core/dist/runtime-v2/utils/cli-process-runner.d.ts.map +0 -1
- package/core/dist/runtime-v2/utils/cli-process-runner.js +0 -263
- package/core/dist/runtime-v2/utils/cli-process-runner.js.map +0 -1
|
@@ -247,6 +247,7 @@ export declare function createRuntimeProfile(workspaceDir: string, profileId: st
|
|
|
247
247
|
* Update an existing runtime profile in .pd/config.yaml.
|
|
248
248
|
*
|
|
249
249
|
* - Rejects non-existent profile (404 not_found)
|
|
250
|
+
* - Rejects retired legacy profiles (400 retired_profile_type — read-only per ADR-0029)
|
|
250
251
|
* - Rejects type changes (400 bad_request — type change = delete + create)
|
|
251
252
|
* - Merges patch into existing profile, only known fields are persisted
|
|
252
253
|
* - Persists atomically, preserving unknown config sections
|
|
@@ -1083,6 +1083,17 @@ export function createRuntimeProfile(workspaceDir, profileId, payload) {
|
|
|
1083
1083
|
if (!structResult.ok) {
|
|
1084
1084
|
return { ok: false, statusCode: 400, error: structResult.error, message: structResult.message };
|
|
1085
1085
|
}
|
|
1086
|
+
// ADR-0029: the 'openclaw' profile type is read-only legacy. Existing legacy
|
|
1087
|
+
// profiles keep parsing and stay listed, but PD must never write a new one.
|
|
1088
|
+
if (structResult.type === 'openclaw') {
|
|
1089
|
+
return {
|
|
1090
|
+
ok: false,
|
|
1091
|
+
statusCode: 400,
|
|
1092
|
+
error: 'retired_profile_type',
|
|
1093
|
+
message: `Runtime profile type 'openclaw' is retired (ADR-0029) and cannot be created.`,
|
|
1094
|
+
nextAction: 'Create a pi-ai profile instead (provider + model + apiKeyEnv).',
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1086
1097
|
// 3. Load existing config — refuse to write on malformed (rc-9)
|
|
1087
1098
|
const loadResult = loadPdConfig(workspaceDir);
|
|
1088
1099
|
if (!loadResult.ok) {
|
|
@@ -1151,6 +1162,7 @@ export function createRuntimeProfile(workspaceDir, profileId, payload) {
|
|
|
1151
1162
|
* Update an existing runtime profile in .pd/config.yaml.
|
|
1152
1163
|
*
|
|
1153
1164
|
* - Rejects non-existent profile (404 not_found)
|
|
1165
|
+
* - Rejects retired legacy profiles (400 retired_profile_type — read-only per ADR-0029)
|
|
1154
1166
|
* - Rejects type changes (400 bad_request — type change = delete + create)
|
|
1155
1167
|
* - Merges patch into existing profile, only known fields are persisted
|
|
1156
1168
|
* - Persists atomically, preserving unknown config sections
|
|
@@ -1194,7 +1206,17 @@ export function updateRuntimeProfile(workspaceDir, profileId, patch) {
|
|
|
1194
1206
|
message: `Profile '${profileId}' existence check passed but value is undefined.`,
|
|
1195
1207
|
nextAction: 'This is a bug in PD config resolution. Please report it.' };
|
|
1196
1208
|
}
|
|
1197
|
-
// 5. Reject
|
|
1209
|
+
// 5. Reject retired legacy profiles — ADR-0029: accepted on read, never written by PD
|
|
1210
|
+
if (existingProfile.type === 'openclaw') {
|
|
1211
|
+
return {
|
|
1212
|
+
ok: false,
|
|
1213
|
+
statusCode: 400,
|
|
1214
|
+
error: 'retired_profile_type',
|
|
1215
|
+
message: `Runtime profile type 'openclaw' is retired (ADR-0029) and cannot be updated. The profile stays readable for legacy compatibility only.`,
|
|
1216
|
+
nextAction: `DELETE /api/v1/config/profiles/${encodeURIComponent(profileId)} and create a pi-ai profile instead (provider + model + apiKeyEnv)`,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
// 6. Reject type change (type change = delete + create)
|
|
1198
1220
|
if (Object.hasOwn(patch, 'type')) {
|
|
1199
1221
|
const patchType = patch.type;
|
|
1200
1222
|
if (typeof patchType !== 'string' || patchType !== existingProfile.type) {
|
|
@@ -1207,10 +1229,10 @@ export function updateRuntimeProfile(workspaceDir, profileId, patch) {
|
|
|
1207
1229
|
};
|
|
1208
1230
|
}
|
|
1209
1231
|
}
|
|
1210
|
-
//
|
|
1232
|
+
// 7. Merge patch into existing profile, then extract only known fields
|
|
1211
1233
|
const mergedSource = { ...existingProfile, ...patch };
|
|
1212
1234
|
const mergedRecord = extractProfileFields(mergedSource, existingProfile.type);
|
|
1213
|
-
//
|
|
1235
|
+
// 8. Read raw config to preserve unknown sections, update the profile
|
|
1214
1236
|
const configPath = getPdConfigPath(workspaceDir);
|
|
1215
1237
|
const rawConfig = readRawConfig(configPath);
|
|
1216
1238
|
const profilesMap = isRecord(rawConfig.runtimeProfiles)
|
|
@@ -1218,7 +1240,7 @@ export function updateRuntimeProfile(workspaceDir, profileId, patch) {
|
|
|
1218
1240
|
: { ...effective.config.runtimeProfiles };
|
|
1219
1241
|
profilesMap[profileId] = mergedRecord;
|
|
1220
1242
|
rawConfig.runtimeProfiles = profilesMap;
|
|
1221
|
-
//
|
|
1243
|
+
// 9. Validate the full updated config
|
|
1222
1244
|
const validation = validatePdConfig(rawConfig);
|
|
1223
1245
|
if (!validation.ok) {
|
|
1224
1246
|
return {
|
|
@@ -1229,7 +1251,7 @@ export function updateRuntimeProfile(workspaceDir, profileId, patch) {
|
|
|
1229
1251
|
nextAction: 'Fix profile fields and retry',
|
|
1230
1252
|
};
|
|
1231
1253
|
}
|
|
1232
|
-
//
|
|
1254
|
+
// 10. Atomic write
|
|
1233
1255
|
try {
|
|
1234
1256
|
writeConfigAtomic(configPath, rawConfig);
|
|
1235
1257
|
}
|
|
@@ -1237,7 +1259,7 @@ export function updateRuntimeProfile(workspaceDir, profileId, patch) {
|
|
|
1237
1259
|
const message = err instanceof Error ? err.message : String(err);
|
|
1238
1260
|
return { ok: false, statusCode: 500, error: 'write_error', message: `Failed to write config: ${message}`, nextAction: 'Check disk space and file permissions for .pd/config.yaml' };
|
|
1239
1261
|
}
|
|
1240
|
-
//
|
|
1262
|
+
// 11. Return the validated updated profile
|
|
1241
1263
|
// Defensive guard: Object.hasOwn() doesn't narrow TS type.
|
|
1242
1264
|
const updatedProfile = validation.value.runtimeProfiles[profileId];
|
|
1243
1265
|
if (!updatedProfile) {
|
|
@@ -7,6 +7,19 @@ export declare class RuleCandidateBindingConsoleModel {
|
|
|
7
7
|
private readonly workspaceDir;
|
|
8
8
|
constructor(workspaceDir: string);
|
|
9
9
|
private ledger;
|
|
10
|
+
/** One bounded readonly batch read of the pain events behind the queue.
|
|
11
|
+
* `degraded` names an unreadable trajectory store so lookups that found
|
|
12
|
+
* nothing are labeled 不可读 rather than 未记录 (rc-9 — never misattribute). */
|
|
13
|
+
private painSources;
|
|
14
|
+
/** Identity stays with the ledger adapter (the identity authority); the
|
|
15
|
+
* principle entry / decidedAt come from the caller's single file snapshot.
|
|
16
|
+
* bindingRevision rides along so the source-correction preview can anchor
|
|
17
|
+
* its eligibility to the CURRENT binding (PRI-1048 R6 #4). */
|
|
18
|
+
private queueBinding;
|
|
19
|
+
/** PRI-1048 R6 #4: read-only eligibility preview for source-correct-once.
|
|
20
|
+
* Only the bound+blocked shape can offer it; the click-time authority
|
|
21
|
+
* (reviseRuleSourceOnce) re-checks everything server-side. */
|
|
22
|
+
private sourceCorrectionPreview;
|
|
10
23
|
list(): Promise<RuleBindingQueueView>;
|
|
11
24
|
review(candidateId: string, search: string): Promise<{
|
|
12
25
|
ok: true;
|
|
@@ -37,4 +50,17 @@ export declare class RuleCandidateBindingConsoleModel {
|
|
|
37
50
|
scribeTaskId: string;
|
|
38
51
|
taskRevisionEpoch: number;
|
|
39
52
|
}>;
|
|
53
|
+
/**
|
|
54
|
+
* SPEC-PRI-1047 修复 B: 认证入口的一次 Owner source-correction。区别于
|
|
55
|
+
* revise(binding 修订预算):这是「修正上游材料」的独立一次预算,回执
|
|
56
|
+
* 绑定真实 Owner actor/request。调用侧(路由)负责 ownerIdentity 门。
|
|
57
|
+
*/
|
|
58
|
+
correctSource(candidateId: string, input: {
|
|
59
|
+
expectedBindingRevision: number;
|
|
60
|
+
expectedScribeTaskId: string;
|
|
61
|
+
expectedArtificerTaskId: string;
|
|
62
|
+
expectedScribeArtifactDigest: string;
|
|
63
|
+
reason: string;
|
|
64
|
+
requestId: string;
|
|
65
|
+
}, actorId: string): Promise<import("@principles/core/runtime-v2").ReviseRuleSourceOnceResult>;
|
|
40
66
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import { createRuntimeStateHandle, PrincipleTreeLedgerAdapter, reviseRuleBindingOnce, resolveRuleBindingRevisionTarget, reviseRuleSourceOnce, previewRuleSourceCorrection, hydratePITaskRecord, candidateIdFromDreamerSeed, } from '@principles/core/runtime-v2';
|
|
5
|
+
import { readLedgerFileState } from '@principles/core/principle-tree-ledger';
|
|
4
6
|
import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
|
|
5
7
|
function record(value) {
|
|
6
8
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -8,6 +10,108 @@ function record(value) {
|
|
|
8
10
|
function stringField(value, key) {
|
|
9
11
|
return record(value) && typeof value[key] === 'string' ? value[key] : '';
|
|
10
12
|
}
|
|
13
|
+
/** Internalization chain order; the queue projection walks it read-only. */
|
|
14
|
+
const CHAIN_KINDS = ['dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator', 'rollout_reviewer'];
|
|
15
|
+
/** PRI-1048: bounded queue — a governance workspace carries tens of candidates;
|
|
16
|
+
* the cap keeps the derived scan bounded and the response honest via `truncated`. */
|
|
17
|
+
const MAX_QUEUE_ITEMS = 200;
|
|
18
|
+
function buildChainContext(allTasks) {
|
|
19
|
+
const byId = new Map(allTasks.map(task => [task.taskId, task]));
|
|
20
|
+
const dreamersByCandidate = new Map();
|
|
21
|
+
const dependentsByTaskId = new Map();
|
|
22
|
+
for (const task of allTasks) {
|
|
23
|
+
if (task.taskKind === 'dreamer') {
|
|
24
|
+
const seed = candidateIdFromDreamerSeed(task.diagnosticJson ?? null);
|
|
25
|
+
if (seed) {
|
|
26
|
+
const list = dreamersByCandidate.get(seed) ?? [];
|
|
27
|
+
list.push(task);
|
|
28
|
+
dreamersByCandidate.set(seed, list);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const pi = hydratePITaskRecord(task);
|
|
32
|
+
if (!pi)
|
|
33
|
+
continue;
|
|
34
|
+
for (const dependencyId of pi.dependencyTaskIds) {
|
|
35
|
+
const list = dependentsByTaskId.get(dependencyId) ?? [];
|
|
36
|
+
list.push(task);
|
|
37
|
+
dependentsByTaskId.set(dependencyId, list);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { byId, dreamersByCandidate, dependentsByTaskId };
|
|
41
|
+
}
|
|
42
|
+
function walkChain(dreamer, context) {
|
|
43
|
+
let current = dreamer;
|
|
44
|
+
let kind = 'dreamer';
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (!current)
|
|
47
|
+
return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
|
|
48
|
+
if (current.status === 'needs_human_review' || current.status === 'failed') {
|
|
49
|
+
const pi = hydratePITaskRecord(current);
|
|
50
|
+
const review = pi?.humanReviewContext;
|
|
51
|
+
return {
|
|
52
|
+
stage: 'blocked', stageKind: kind,
|
|
53
|
+
reasonCode: review && typeof review.reasonCode === 'string' ? review.reasonCode : null,
|
|
54
|
+
detail: review && typeof review.detail === 'string' ? review.detail : (current.lastError ?? null),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (current.status !== 'succeeded')
|
|
58
|
+
return { stage: 'in_progress', stageKind: kind, reasonCode: null, detail: null };
|
|
59
|
+
const nextIndex = CHAIN_KINDS.indexOf(kind) + 1;
|
|
60
|
+
const nextKind = nextIndex < CHAIN_KINDS.length ? CHAIN_KINDS[nextIndex] : undefined;
|
|
61
|
+
if (!nextKind)
|
|
62
|
+
return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
|
|
63
|
+
const next = (context.dependentsByTaskId.get(current.taskId) ?? []).find(task => task.taskKind === nextKind);
|
|
64
|
+
if (!next)
|
|
65
|
+
return { stage: 'advanced', stageKind: kind, reasonCode: null, detail: null };
|
|
66
|
+
current = next;
|
|
67
|
+
kind = nextKind;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function deriveGeneration(candidateId, context) {
|
|
71
|
+
const dreamers = context.dreamersByCandidate.get(candidateId) ?? [];
|
|
72
|
+
if (dreamers.length === 0)
|
|
73
|
+
return { stage: 'not_built', stageKind: null, reasonCode: null, detail: null };
|
|
74
|
+
const walks = dreamers.map(dreamer => walkChain(dreamer, context));
|
|
75
|
+
const [first] = walks;
|
|
76
|
+
if (!first)
|
|
77
|
+
return { stage: 'ambiguous', stageKind: null, reasonCode: null, detail: null };
|
|
78
|
+
const sameWalk = (a, b) => a.stage === b.stage && a.stageKind === b.stageKind && a.reasonCode === b.reasonCode;
|
|
79
|
+
if (walks.every(walk => sameWalk(walk, first)))
|
|
80
|
+
return first;
|
|
81
|
+
return { stage: 'ambiguous', stageKind: null, reasonCode: null, detail: null };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Real backend eligibility only: the production resolver plus the exact
|
|
85
|
+
* budget rule reviseRuleBindingOnce enforces on the same scribe task. The
|
|
86
|
+
* revise-once route still authoritatively re-checks at click time.
|
|
87
|
+
*/
|
|
88
|
+
async function deriveResume(stateManager, candidateId) {
|
|
89
|
+
const target = await resolveRuleBindingRevisionTarget(stateManager, candidateId);
|
|
90
|
+
if (target.status !== 'ready')
|
|
91
|
+
return { status: 'refused', reason: target.reason, scribeTaskId: null, taskRevisionEpoch: null };
|
|
92
|
+
const scribe = await stateManager.getTask(target.scribeTaskId);
|
|
93
|
+
const pi = scribe ? hydratePITaskRecord(scribe) : null;
|
|
94
|
+
if (!scribe || !pi)
|
|
95
|
+
return { status: 'refused', reason: 'scribe_missing', scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
|
|
96
|
+
if (pi.ruleBindingRevisionCauseId || pi.revisionCauseId?.startsWith('rule-binding:')) {
|
|
97
|
+
return { status: 'refused', reason: 'revision_budget_exhausted', scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
|
|
98
|
+
}
|
|
99
|
+
return { status: 'ready', reason: null, scribeTaskId: target.scribeTaskId, taskRevisionEpoch: target.taskRevisionEpoch };
|
|
100
|
+
}
|
|
101
|
+
/** Uses the caller's ONE ledger snapshot: decidedAt + principle entry, no extra file read. */
|
|
102
|
+
function boundPrincipleView(state, lookup) {
|
|
103
|
+
if (state.availability.status === 'unreadable')
|
|
104
|
+
return null;
|
|
105
|
+
const principle = Object.hasOwn(state.ledger.tree.principles, lookup.principleId) ? state.ledger.tree.principles[lookup.principleId] : undefined;
|
|
106
|
+
if (!principle)
|
|
107
|
+
return null;
|
|
108
|
+
const binding = Object.hasOwn(state.ledger.tree.ruleCandidateBindings ?? {}, lookup.candidateId)
|
|
109
|
+
? state.ledger.tree.ruleCandidateBindings?.[lookup.candidateId]?.revisions.at(-1) : undefined;
|
|
110
|
+
return {
|
|
111
|
+
principleId: lookup.principleId, text: principle.text, status: principle.status,
|
|
112
|
+
version: principle.version, revision: lookup.revision, decidedAt: binding?.decidedAt ?? '',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
11
115
|
/** Derived review only. Binding authority stays inside the locked core Ledger writer. */
|
|
12
116
|
export class RuleCandidateBindingConsoleModel {
|
|
13
117
|
workspaceDir;
|
|
@@ -32,31 +136,172 @@ export class RuleCandidateBindingConsoleModel {
|
|
|
32
136
|
},
|
|
33
137
|
});
|
|
34
138
|
}
|
|
139
|
+
/** One bounded readonly batch read of the pain events behind the queue.
|
|
140
|
+
* `degraded` names an unreadable trajectory store so lookups that found
|
|
141
|
+
* nothing are labeled 不可读 rather than 未记录 (rc-9 — never misattribute). */
|
|
142
|
+
painSources(painIds) {
|
|
143
|
+
const result = new Map();
|
|
144
|
+
const distinct = [...new Set(painIds.filter(id => id !== ''))];
|
|
145
|
+
if (distinct.length === 0)
|
|
146
|
+
return { sources: result, degraded: null };
|
|
147
|
+
const dbPath = path.join(this.workspaceDir, '.state', 'trajectory.db');
|
|
148
|
+
if (!existsSync(dbPath))
|
|
149
|
+
return { sources: result, degraded: null };
|
|
150
|
+
let db = null;
|
|
151
|
+
try {
|
|
152
|
+
db = new Database(dbPath, { readonly: true });
|
|
153
|
+
const placeholders = distinct.map(() => '?').join(',');
|
|
154
|
+
const rows = db.prepare(`SELECT canonical_pain_id AS painId, source, text, created_at AS createdAt
|
|
155
|
+
FROM pain_events WHERE canonical_pain_id IN (${placeholders})`).all(...distinct);
|
|
156
|
+
// Multiple events may share a canonical id; keep the newest (same policy
|
|
157
|
+
// as the pain list ordering — newest first).
|
|
158
|
+
for (const row of rows) {
|
|
159
|
+
if (!record(row))
|
|
160
|
+
continue;
|
|
161
|
+
const painId = typeof row.painId === 'string' ? row.painId : '';
|
|
162
|
+
if (painId === '')
|
|
163
|
+
continue;
|
|
164
|
+
const existing = result.get(painId);
|
|
165
|
+
const createdAt = typeof row.createdAt === 'string' ? row.createdAt : null;
|
|
166
|
+
if (existing && existing.createdAt && createdAt && existing.createdAt >= createdAt)
|
|
167
|
+
continue;
|
|
168
|
+
result.set(painId, {
|
|
169
|
+
painId,
|
|
170
|
+
text: typeof row.text === 'string' && row.text.trim() !== '' ? row.text : null,
|
|
171
|
+
source: typeof row.source === 'string' ? row.source : 'unknown',
|
|
172
|
+
createdAt,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Read failure must not masquerade as never-recorded: the degraded flag
|
|
178
|
+
// turns missing lookups into 记录暂不可读 (rc-9 observable degradation).
|
|
179
|
+
return { sources: new Map(), degraded: 'trajectory_unreadable' };
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
db?.close();
|
|
183
|
+
}
|
|
184
|
+
return { sources: result, degraded: null };
|
|
185
|
+
}
|
|
186
|
+
/** Identity stays with the ledger adapter (the identity authority); the
|
|
187
|
+
* principle entry / decidedAt come from the caller's single file snapshot.
|
|
188
|
+
* bindingRevision rides along so the source-correction preview can anchor
|
|
189
|
+
* its eligibility to the CURRENT binding (PRI-1048 R6 #4). */
|
|
190
|
+
queueBinding(stateManager, ledgerState, candidateId) {
|
|
191
|
+
const identity = this.ledger(stateManager).resolveCandidatePrincipleIdentity(candidateId, 'rule');
|
|
192
|
+
if (identity.status === 'resolved') {
|
|
193
|
+
const view = boundPrincipleView(ledgerState, { candidateId, principleId: identity.principleId, revision: identity.bindingRevision ?? 0 });
|
|
194
|
+
if (view)
|
|
195
|
+
return { view: { status: 'bound', principle: view }, bindingRevision: identity.bindingRevision ?? null };
|
|
196
|
+
// Bound in the ledger but the principle entry itself is unreadable or
|
|
197
|
+
// gone — surface as attention, never silently unlinked.
|
|
198
|
+
return { view: { status: 'attention', reason: 'bound_principle_unreadable' }, bindingRevision: identity.bindingRevision ?? null };
|
|
199
|
+
}
|
|
200
|
+
if (identity.reason === 'missing')
|
|
201
|
+
return { view: { status: 'unlinked' }, bindingRevision: null };
|
|
202
|
+
if (identity.reason === 'binding_revoked')
|
|
203
|
+
return { view: { status: 'binding_revoked' }, bindingRevision: null };
|
|
204
|
+
return { view: { status: 'attention', reason: identity.reason }, bindingRevision: null };
|
|
205
|
+
}
|
|
206
|
+
/** PRI-1048 R6 #4: read-only eligibility preview for source-correct-once.
|
|
207
|
+
* Only the bound+blocked shape can offer it; the click-time authority
|
|
208
|
+
* (reviseRuleSourceOnce) re-checks everything server-side. */
|
|
209
|
+
async sourceCorrectionPreview(stateManager, candidateId, bindingRevision) {
|
|
210
|
+
const ledger = this.ledger(stateManager);
|
|
211
|
+
const preview = await previewRuleSourceCorrection({
|
|
212
|
+
stateManager,
|
|
213
|
+
ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') },
|
|
214
|
+
candidateId,
|
|
215
|
+
expectedBindingRevision: bindingRevision,
|
|
216
|
+
});
|
|
217
|
+
return preview.status === 'ready'
|
|
218
|
+
? {
|
|
219
|
+
status: 'ready', reason: null,
|
|
220
|
+
scribeTaskId: preview.scribeTaskId, artificerTaskId: preview.artificerTaskId,
|
|
221
|
+
scribeArtifactDigest: preview.scribeArtifactDigest, expectedBindingRevision: preview.expectedBindingRevision,
|
|
222
|
+
}
|
|
223
|
+
: { status: 'unavailable', reason: preview.reason, scribeTaskId: null, artificerTaskId: null, scribeArtifactDigest: null, expectedBindingRevision: null };
|
|
224
|
+
}
|
|
35
225
|
async list() {
|
|
36
226
|
// A fresh workspace with no state.db simply has no binding queue yet;
|
|
37
227
|
// any other storage problem still surfaces as an error (503 at the route).
|
|
38
|
-
if (!existsSync(path.join(this.workspaceDir, '.pd', 'state.db')))
|
|
39
|
-
return { items: [] };
|
|
228
|
+
if (!existsSync(path.join(this.workspaceDir, '.pd', 'state.db'))) {
|
|
229
|
+
return { items: [], summary: { painGroups: 0, candidates: 0, bound: 0, unlinked: 0, blocked: 0 }, truncated: false };
|
|
230
|
+
}
|
|
40
231
|
const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
|
|
41
232
|
try {
|
|
42
|
-
const
|
|
233
|
+
const { stateManager } = handle;
|
|
234
|
+
const allTasks = await stateManager.listTasks();
|
|
235
|
+
const taskById = new Map(allTasks.map(task => [task.taskId, task]));
|
|
43
236
|
// Split diagnoses commit their rule candidates on diag_router sub-tasks;
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
for (const candidate of await handle.stateManager.getCandidatesByTaskId(task.taskId)) {
|
|
237
|
+
// getCandidatesByTaskId already spans "own + child" tasks, so traversing
|
|
238
|
+
// BOTH task lists surfaces router-committed candidates twice. The queue
|
|
239
|
+
// aggregation owner (this model) dedupes by canonical candidateId; the
|
|
240
|
+
// shared store query keeps its semantics for every other consumer.
|
|
241
|
+
const diagnosisTasks = allTasks.filter(task => task.taskKind === 'diagnostician' || task.taskKind === 'diag_router');
|
|
242
|
+
const collected = new Map();
|
|
243
|
+
for (const task of diagnosisTasks) {
|
|
244
|
+
for (const candidate of await stateManager.getCandidatesByTaskId(task.taskId)) {
|
|
53
245
|
if (candidate.rawRecommendationKind !== 'rule')
|
|
54
246
|
continue;
|
|
55
|
-
|
|
56
|
-
|
|
247
|
+
if (collected.has(candidate.candidateId))
|
|
248
|
+
continue;
|
|
249
|
+
// Source pain: a router candidate resolves through its parent
|
|
250
|
+
// diagnostician; a direct candidate reads its own inputRef.
|
|
251
|
+
const candidateTask = taskById.get(candidate.taskId) ?? task;
|
|
252
|
+
const parentTask = candidateTask.taskKind === 'diag_router' && candidateTask.inputRef
|
|
253
|
+
? taskById.get(candidateTask.inputRef) : undefined;
|
|
254
|
+
const diagnosisTaskId = parentTask?.taskId ?? candidateTask.taskId;
|
|
255
|
+
const painId = (parentTask?.inputRef ?? candidateTask.inputRef) ?? '';
|
|
256
|
+
collected.set(candidate.candidateId, {
|
|
257
|
+
candidateId: candidate.candidateId,
|
|
258
|
+
text: candidate.description || candidate.title,
|
|
259
|
+
painId, diagnosisTaskId,
|
|
260
|
+
});
|
|
57
261
|
}
|
|
58
262
|
}
|
|
59
|
-
|
|
263
|
+
const truncated = collected.size > MAX_QUEUE_ITEMS;
|
|
264
|
+
const entries = [...collected.values()].slice(0, MAX_QUEUE_ITEMS);
|
|
265
|
+
const painLookup = this.painSources(entries.map(entry => entry.painId));
|
|
266
|
+
const chainContext = buildChainContext(allTasks);
|
|
267
|
+
const ledgerState = readLedgerFileState(path.join(this.workspaceDir, '.state'));
|
|
268
|
+
const items = [];
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
const diagnosisTask = chainContext.byId.get(entry.diagnosisTaskId) ?? null;
|
|
271
|
+
const { view: binding, bindingRevision } = this.queueBinding(stateManager, ledgerState, entry.candidateId);
|
|
272
|
+
const generation = binding.status === 'unlinked' ? null : deriveGeneration(entry.candidateId, chainContext);
|
|
273
|
+
const resume = binding.status === 'bound' ? await deriveResume(stateManager, entry.candidateId) : null;
|
|
274
|
+
// PRI-1048 R6 #4: the source-correction entry applies to the bound+blocked
|
|
275
|
+
// shape only (the binding revision budget is typically already spent there).
|
|
276
|
+
const sourceCorrection = binding.status === 'bound' && generation?.stage === 'blocked' && bindingRevision !== null
|
|
277
|
+
? await this.sourceCorrectionPreview(stateManager, entry.candidateId, bindingRevision)
|
|
278
|
+
: null;
|
|
279
|
+
items.push({
|
|
280
|
+
candidateId: entry.candidateId,
|
|
281
|
+
text: entry.text,
|
|
282
|
+
pain: painLookup.sources.get(entry.painId) ?? {
|
|
283
|
+
painId: entry.painId, text: null,
|
|
284
|
+
source: painLookup.degraded === 'trajectory_unreadable' && entry.painId !== '' ? 'unreadable' : 'unknown',
|
|
285
|
+
createdAt: null,
|
|
286
|
+
},
|
|
287
|
+
diagnosisStatus: diagnosisTask?.status ?? 'unknown',
|
|
288
|
+
binding,
|
|
289
|
+
generation,
|
|
290
|
+
resume,
|
|
291
|
+
sourceCorrection,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
items,
|
|
296
|
+
summary: {
|
|
297
|
+
painGroups: new Set(items.map(item => item.pain.painId === '' ? `candidate:${item.candidateId}` : item.pain.painId)).size,
|
|
298
|
+
candidates: items.length,
|
|
299
|
+
bound: items.filter(item => item.binding.status === 'bound').length,
|
|
300
|
+
unlinked: items.filter(item => item.binding.status === 'unlinked').length,
|
|
301
|
+
blocked: items.filter(item => item.generation?.stage === 'blocked').length,
|
|
302
|
+
},
|
|
303
|
+
truncated,
|
|
304
|
+
};
|
|
60
305
|
}
|
|
61
306
|
finally {
|
|
62
307
|
await handle.close();
|
|
@@ -65,20 +310,44 @@ export class RuleCandidateBindingConsoleModel {
|
|
|
65
310
|
async review(candidateId, search) {
|
|
66
311
|
const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir, readonly: true, bootstrapIfMissing: false });
|
|
67
312
|
try {
|
|
68
|
-
const
|
|
313
|
+
const { stateManager } = handle;
|
|
314
|
+
const result = await this.ledger(stateManager).createRuleCandidateBindingReview(candidateId, { search, limit: 20 });
|
|
69
315
|
if (!result.ok)
|
|
70
316
|
return result;
|
|
71
317
|
const { candidateMaterial: material } = result;
|
|
318
|
+
const parentTaskInputRef = stringField(Reflect.get(material, 'parentTask'), 'inputRef');
|
|
319
|
+
const painId = parentTaskInputRef || stringField(material.task, 'inputRef');
|
|
320
|
+
const painLookup = this.painSources([painId]);
|
|
321
|
+
const pain = painLookup.sources.get(painId) ?? {
|
|
322
|
+
painId, text: null,
|
|
323
|
+
source: painLookup.degraded === 'trajectory_unreadable' && painId !== '' ? 'unreadable' : 'unknown',
|
|
324
|
+
createdAt: null,
|
|
325
|
+
};
|
|
326
|
+
const { currentBinding } = result;
|
|
327
|
+
const isBound = currentBinding?.action === 'bind';
|
|
328
|
+
const boundPrinciple = isBound && currentBinding
|
|
329
|
+
? boundPrincipleView(readLedgerFileState(path.join(this.workspaceDir, '.state')), { candidateId, principleId: currentBinding.principleId, revision: currentBinding.revision })
|
|
330
|
+
: null;
|
|
331
|
+
const chainContext = buildChainContext(await stateManager.listTasks());
|
|
332
|
+
const generation = isBound ? deriveGeneration(candidateId, chainContext) : null;
|
|
333
|
+
const resume = isBound ? await deriveResume(stateManager, candidateId) : null;
|
|
334
|
+
const sourceCorrection = isBound && generation?.stage === 'blocked' && currentBinding
|
|
335
|
+
? await this.sourceCorrectionPreview(stateManager, candidateId, currentBinding.revision)
|
|
336
|
+
: null;
|
|
72
337
|
return { ok: true, review: {
|
|
73
338
|
candidateId, candidateText: stringField(material.candidate, 'description') || stringField(material.candidate, 'title'),
|
|
74
|
-
|
|
339
|
+
sourcePain: pain,
|
|
75
340
|
evidence: stringField(material.artifact, 'contentJson') || stringField(material.candidate, 'sourceRecommendationJson'),
|
|
76
341
|
candidateDigest: result.reviewedCandidateDigest,
|
|
77
342
|
currentRevision: result.currentRevision,
|
|
78
|
-
currentBinding
|
|
343
|
+
currentBinding,
|
|
344
|
+
boundPrinciple,
|
|
79
345
|
eligibleTargets: result.eligibleTargets.map(({ principle, reviewedPrincipleDigest }) => ({
|
|
80
346
|
principleId: principle.id, text: principle.text, status: principle.status, version: principle.version, digest: reviewedPrincipleDigest,
|
|
81
347
|
})),
|
|
348
|
+
generation,
|
|
349
|
+
resume,
|
|
350
|
+
sourceCorrection,
|
|
82
351
|
resumeNotice: '关联后自动启动一次现有配置的正文修订与代码生成、验证;沿用现有调用预算和超时。失败会保留关联与旧工件,不会自动循环,也不会批准或启用规则。',
|
|
83
352
|
} };
|
|
84
353
|
}
|
|
@@ -127,4 +396,25 @@ export class RuleCandidateBindingConsoleModel {
|
|
|
127
396
|
await handle.close();
|
|
128
397
|
}
|
|
129
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* SPEC-PRI-1047 修复 B: 认证入口的一次 Owner source-correction。区别于
|
|
401
|
+
* revise(binding 修订预算):这是「修正上游材料」的独立一次预算,回执
|
|
402
|
+
* 绑定真实 Owner actor/request。调用侧(路由)负责 ownerIdentity 门。
|
|
403
|
+
*/
|
|
404
|
+
async correctSource(candidateId, input, actorId) {
|
|
405
|
+
const handle = await createRuntimeStateHandle({ workspaceDir: this.workspaceDir });
|
|
406
|
+
try {
|
|
407
|
+
const ledger = this.ledger(handle.stateManager);
|
|
408
|
+
return await reviseRuleSourceOnce({
|
|
409
|
+
stateManager: handle.stateManager,
|
|
410
|
+
ledger: { resolveForCandidate: async (id) => ledger.resolveCandidatePrincipleIdentity(id, 'rule') },
|
|
411
|
+
candidateId,
|
|
412
|
+
...input,
|
|
413
|
+
actor: actorId,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
await handle.close();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
130
420
|
}
|
|
@@ -53,6 +53,10 @@ function safeParseBody(text) {
|
|
|
53
53
|
function isObject(value) {
|
|
54
54
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
55
55
|
}
|
|
56
|
+
/** Runtime-profile CRUD errors carry an operator next action — never drop it (rc-9). */
|
|
57
|
+
function sendProfileCrudError(res, result) {
|
|
58
|
+
sendError(res, result.statusCode, result.error, result.message, result.nextAction ? { nextAction: result.nextAction } : undefined);
|
|
59
|
+
}
|
|
56
60
|
// ── Route Handler ────────────────────────────────────────────────────────────
|
|
57
61
|
export async function handleConfigRoute(req, res, ctx) {
|
|
58
62
|
const { workspaceDir, subPath } = ctx;
|
|
@@ -301,7 +305,7 @@ export async function handleConfigRoute(req, res, ctx) {
|
|
|
301
305
|
}
|
|
302
306
|
const result = createRuntimeProfile(workspaceDir, body.id, body.profile);
|
|
303
307
|
if (!result.ok) {
|
|
304
|
-
|
|
308
|
+
sendProfileCrudError(res, result);
|
|
305
309
|
return;
|
|
306
310
|
}
|
|
307
311
|
// 201 Created for new resource
|
|
@@ -335,7 +339,7 @@ export async function handleConfigRoute(req, res, ctx) {
|
|
|
335
339
|
}
|
|
336
340
|
const result = updateRuntimeProfile(workspaceDir, profileId, body);
|
|
337
341
|
if (!result.ok) {
|
|
338
|
-
|
|
342
|
+
sendProfileCrudError(res, result);
|
|
339
343
|
return;
|
|
340
344
|
}
|
|
341
345
|
sendSuccess(res, { profileId: result.profileId, profile: result.profile });
|
|
@@ -344,7 +348,7 @@ export async function handleConfigRoute(req, res, ctx) {
|
|
|
344
348
|
if (method === 'DELETE') {
|
|
345
349
|
const result = deleteRuntimeProfile(workspaceDir, profileId);
|
|
346
350
|
if (!result.ok) {
|
|
347
|
-
|
|
351
|
+
sendProfileCrudError(res, result);
|
|
348
352
|
return;
|
|
349
353
|
}
|
|
350
354
|
sendSuccess(res, { profileId: result.profileId, profile: result.profile });
|
|
@@ -43,7 +43,7 @@ export async function handleRuleCandidateBindingsRoute(req, res, ctx) {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
const model = new RuleCandidateBindingConsoleModel(ctx.workspaceDir);
|
|
46
|
-
const match = /^\/([^/]+)(?:\/(review|revise-once))?$/.exec(ctx.subPath);
|
|
46
|
+
const match = /^\/([^/]+)(?:\/(review|revise-once|source-correct-once))?$/.exec(ctx.subPath);
|
|
47
47
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
48
48
|
try {
|
|
49
49
|
if ((ctx.subPath === '' || ctx.subPath === '/') && req.method === 'GET') {
|
|
@@ -84,6 +84,31 @@ export async function handleRuleCandidateBindingsRoute(req, res, ctx) {
|
|
|
84
84
|
sendBadRequest(res, '请求必须是有效的 JSON。');
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
+
if (match[2] === 'source-correct-once') {
|
|
88
|
+
// SPEC-PRI-1047: one bounded Owner source-correction per candidate. Closed
|
|
89
|
+
// field set: chain identity + digest precondition + reason/requestId —
|
|
90
|
+
// never identity-fabrication or approval fields.
|
|
91
|
+
if (!closed(raw, ['expectedBindingRevision', 'expectedScribeTaskId', 'expectedArtificerTaskId', 'expectedScribeArtifactDigest', 'reason', 'requestId'])
|
|
92
|
+
|| !revision(raw.expectedBindingRevision) || raw.expectedBindingRevision < 1
|
|
93
|
+
|| !text(raw.expectedScribeTaskId, 512) || !text(raw.expectedArtificerTaskId, 512)
|
|
94
|
+
|| !digest(raw.expectedScribeArtifactDigest) || !text(raw.reason, 2000) || !text(raw.requestId, 200)) {
|
|
95
|
+
sendBadRequest(res, '请提交当前关联、原候选链任务与工件摘要;不接受身份或批准字段。');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const result = await model.correctSource(candidateId, {
|
|
99
|
+
expectedBindingRevision: raw.expectedBindingRevision,
|
|
100
|
+
expectedScribeTaskId: raw.expectedScribeTaskId,
|
|
101
|
+
expectedArtificerTaskId: raw.expectedArtificerTaskId,
|
|
102
|
+
expectedScribeArtifactDigest: raw.expectedScribeArtifactDigest,
|
|
103
|
+
reason: raw.reason,
|
|
104
|
+
requestId: raw.requestId,
|
|
105
|
+
}, ctx.ownerIdentity.ownerId);
|
|
106
|
+
if (result.status === 'refused')
|
|
107
|
+
failure(res, result);
|
|
108
|
+
else
|
|
109
|
+
sendSuccess(res, result);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
87
112
|
if (match[2] === 'revise-once') {
|
|
88
113
|
if (!closed(raw, ['expectedBindingRevision', 'expectedScribeTaskId', 'expectedTaskRevisionEpoch', 'requestId'])
|
|
89
114
|
|| !revision(raw.expectedBindingRevision) || raw.expectedBindingRevision < 1
|