create-principles-disciple 1.144.22 → 1.144.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release/product-identity.json +1 -1
- package/codex-adapter/dist/pd-hook.js +84 -9
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/routes/update-transaction.js +54 -8
- package/console/dist/ui/i18n/en.json +2 -0
- package/console/dist/ui/i18n/zh-CN.json +2 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +9 -5
- package/console/dist/ui/utils/validators.d.ts +16 -0
- package/console/dist/ui/utils/validators.js +14 -1
- package/console/dist/web/assets/app.js +22 -22
- package/core/dist/principle-tree-ledger.d.ts +20 -0
- package/core/dist/principle-tree-ledger.d.ts.map +1 -1
- package/core/dist/principle-tree-ledger.js +13 -9
- package/core/dist/principle-tree-ledger.js.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.js +10 -3
- package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- package/core/dist/runtime-v2/activation/ledger-identity.d.ts +12 -3
- package/core/dist/runtime-v2/activation/ledger-identity.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/ledger-identity.js +18 -3
- package/core/dist/runtime-v2/activation/ledger-identity.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +8 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +11 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +14 -3
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +10 -2
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +4 -0
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js +13 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +3 -2
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/update/release-manager.d.ts +1 -1
- package/dist/update/release-manager.d.ts.map +1 -1
- package/dist/update/release-manager.js +32 -1
- package/dist/update/release-manager.js.map +1 -1
- package/dist/update/transaction-journal.d.ts +12 -1
- package/dist/update/transaction-journal.d.ts.map +1 -1
- package/dist/update/transaction-journal.js +2 -2
- package/dist/update/transaction-journal.js.map +1 -1
- package/host-runtime/dist/internalization-consumer-cycle.js +6 -0
- package/host-runtime/package.json +1 -1
- package/package.json +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +9 -3
- package/pd-cli/dist/commands/pain-retry.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 +3 -0
- package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +6 -0
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +322 -322
- package/release-manager/dist/update/release-manager.d.ts +1 -1
- package/release-manager/dist/update/release-manager.d.ts.map +1 -1
- package/release-manager/dist/update/release-manager.js +32 -1
- package/release-manager/dist/update/release-manager.js.map +1 -1
- package/release-manager/dist/update/transaction-journal.d.ts +12 -1
- package/release-manager/dist/update/transaction-journal.d.ts.map +1 -1
- package/release-manager/dist/update/transaction-journal.js +2 -2
- package/release-manager/dist/update/transaction-journal.js.map +1 -1
- package/release-manager/package.json +1 -1
|
@@ -365,14 +365,75 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
365
365
|
return { stdout: {}, exitCode: 0, stderr: [diagnostic(reason, nextAction)] };
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
+
// PRI-943: process-boundary fail-open belt. Every resolved path sets exitCode 0,
|
|
369
|
+
// so nothing may let an unwritable stream, an unserializable result, or an
|
|
370
|
+
// escaping rejection reach node's default handling, which terminates the hook
|
|
371
|
+
// with exit 1. Three distinct failure modes, each reproduced from a spawned
|
|
372
|
+
// process in tests/pd-hook-entry-belt.test.ts:
|
|
373
|
+
// * a write that THROWS synchronously — caught per write below;
|
|
374
|
+
// * a host closing the pipe, which raises an ASYNC 'error' event on the
|
|
375
|
+
// Socket. try/catch cannot reach that one, so a listener is required
|
|
376
|
+
// (armStreamBelts) or node aborts with "Unhandled 'error' event".
|
|
377
|
+
// * the stdout JSON object failing to SERIALIZE — guarded separately because
|
|
378
|
+
// the object is the contract (serializeStdout).
|
|
379
|
+
// stdout must stay exactly one JSON object, so a belt diagnostic goes to stderr
|
|
380
|
+
// only; when stderr itself is the dead stream nothing is reported rather than
|
|
381
|
+
// written into the machine channel.
|
|
382
|
+
let beltReported = false;
|
|
383
|
+
let stdoutJsonDelivered = false;
|
|
384
|
+
function writeBeltDiagnostic(reason, nextAction) {
|
|
385
|
+
if (beltReported)
|
|
386
|
+
return;
|
|
387
|
+
beltReported = true;
|
|
388
|
+
try {
|
|
389
|
+
process.stderr.write(`${diagnostic(reason, nextAction)}\n`);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Both streams are gone: there is no channel left inside the fail-open
|
|
393
|
+
// contract, and adding I/O here would widen the belt itself.
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function reportStreamFailure(error) {
|
|
397
|
+
writeBeltDiagnostic(`hook_stream_failed:${errorMessage(error)}`, 'The host closed the pipe; retry the tool call — the hook failed open.');
|
|
398
|
+
}
|
|
399
|
+
function writeStream(stream, line) {
|
|
400
|
+
try {
|
|
401
|
+
process[stream].write(`${line}\n`);
|
|
402
|
+
// Tracked here rather than at each call site so no future stdout write can
|
|
403
|
+
// make the entry belt believe nothing was delivered yet.
|
|
404
|
+
if (stream === 'stdout')
|
|
405
|
+
stdoutJsonDelivered = true;
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
reportStreamFailure(error);
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function armStreamBelts() {
|
|
414
|
+
process.stdout.on('error', reportStreamFailure);
|
|
415
|
+
process.stderr.on('error', reportStreamFailure);
|
|
416
|
+
}
|
|
417
|
+
/** rc-8: the stdout JSON object is the contract itself, so serializing it is a
|
|
418
|
+
* failure path of the belt rather than of the encoder — degrade to `{}` and
|
|
419
|
+
* report, never let the throw escape into the entry belt's last-resort branch. */
|
|
420
|
+
function serializeStdout(value) {
|
|
421
|
+
try {
|
|
422
|
+
return JSON.stringify(value) ?? '{}';
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
writeBeltDiagnostic(`hook_stdout_serialize_failed:${errorMessage(error)}`, 'Retry the tool call; the hook failed open with an empty JSON object.');
|
|
426
|
+
return '{}';
|
|
427
|
+
}
|
|
428
|
+
}
|
|
368
429
|
async function main() {
|
|
369
430
|
let raw;
|
|
370
431
|
try {
|
|
371
432
|
raw = readFileSync(0, 'utf8');
|
|
372
433
|
}
|
|
373
434
|
catch (error) {
|
|
374
|
-
|
|
375
|
-
|
|
435
|
+
writeStream('stderr', diagnostic(`stdin_read_failed:${errorMessage(error)}`, 'Run the hook from Codex with JSON stdin.'));
|
|
436
|
+
writeStream('stdout', '{}');
|
|
376
437
|
return;
|
|
377
438
|
}
|
|
378
439
|
let result;
|
|
@@ -383,12 +444,16 @@ async function main() {
|
|
|
383
444
|
// Fail-open belt for an unexpected pre-dispatch throw (e.g. a workspace
|
|
384
445
|
// resolution race): Codex must still receive exactly one JSON object on
|
|
385
446
|
// stdout and a bounded diagnostic on stderr — never a bare crash.
|
|
386
|
-
|
|
447
|
+
writeStream('stderr', diagnostic(`hook_pipeline_unexpected:${errorMessage(error)}`, 'Retry the tool call; if it repeats, inspect PD stderr and the Workspace .pd/config.yaml state.'));
|
|
387
448
|
result = { stdout: {}, exitCode: 0, stderr: [] };
|
|
388
449
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
450
|
+
// Each write is guarded on its own: with stderr dead the stdout JSON object
|
|
451
|
+
// is still what Codex needs to fail open, so one stream must not abort the other.
|
|
452
|
+
for (const line of result.stderr) {
|
|
453
|
+
if (!writeStream('stderr', line))
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
writeStream('stdout', serializeStdout(result.stdout));
|
|
392
457
|
process.exitCode = result.exitCode;
|
|
393
458
|
}
|
|
394
459
|
// PRI-892: Node resolves the main module through symlinks/junctions while
|
|
@@ -414,9 +479,19 @@ function isMainModuleEntry() {
|
|
|
414
479
|
// satisfies it, because its argv[1] is the runner, not pd-hook.
|
|
415
480
|
if (path.basename(entry) !== path.basename(fileURLToPath(import.meta.url)))
|
|
416
481
|
return false;
|
|
417
|
-
|
|
482
|
+
writeStream('stderr', diagnostic(`entry_identity_unverified:${errorMessage(error)}`, 'Reinstall the Codex adapter; if it repeats, report this line together with the command that invoked the hook.'));
|
|
418
483
|
return true;
|
|
419
484
|
}
|
|
420
485
|
}
|
|
421
|
-
if (isMainModuleEntry())
|
|
422
|
-
|
|
486
|
+
if (isMainModuleEntry()) {
|
|
487
|
+
armStreamBelts();
|
|
488
|
+
void main().catch((error) => {
|
|
489
|
+
// Last-resort belt: a rejection escaping main's own guards must still leave
|
|
490
|
+
// Codex with exit 0 and, when nothing reached stdout yet, an empty JSON
|
|
491
|
+
// object instead of a bare crash.
|
|
492
|
+
writeBeltDiagnostic(`hook_fatal:${errorMessage(error)}`, 'Retry the tool call; if it repeats, report this line together with the command that invoked the hook.');
|
|
493
|
+
if (!stdoutJsonDelivered)
|
|
494
|
+
writeStream('stdout', '{}');
|
|
495
|
+
process.exitCode = 0;
|
|
496
|
+
});
|
|
497
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -5,8 +5,8 @@ import { sendSuccess, sendMethodNotAllowed, sendNotFound } from '../utils/respon
|
|
|
5
5
|
/** Transaction ids are installer/console-generated (`<word>-<ts>-<hex>`); this
|
|
6
6
|
* pattern doubles as the path-traversal guard for the journal file name (rc-1). */
|
|
7
7
|
const TRANSACTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
8
|
-
function
|
|
9
|
-
const { transitions
|
|
8
|
+
function factFromTransitions(journal, transactionId, read) {
|
|
9
|
+
const { transitions } = read;
|
|
10
10
|
const last = transitions[transitions.length - 1];
|
|
11
11
|
const [first] = transitions;
|
|
12
12
|
return {
|
|
@@ -14,7 +14,7 @@ function readJournalFact(journal, journalPath, transactionId) {
|
|
|
14
14
|
exists: true,
|
|
15
15
|
lastState: last?.to ?? null,
|
|
16
16
|
terminal: last !== undefined && journal.isTerminalTransactionState(last.to),
|
|
17
|
-
tornTailDetected,
|
|
17
|
+
tornTailDetected: read.tornTailDetected,
|
|
18
18
|
startedAt: first?.at ?? null,
|
|
19
19
|
lastAt: last?.at ?? null,
|
|
20
20
|
productVersion: last?.productVersion ?? first?.productVersion ?? null,
|
|
@@ -26,6 +26,9 @@ function readJournalFact(journal, journalPath, transactionId) {
|
|
|
26
26
|
})),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
function readJournalFact(journal, journalPath, transactionId) {
|
|
30
|
+
return factFromTransitions(journal, transactionId, journal.readTransactionJournalForRecovery(journalPath));
|
|
31
|
+
}
|
|
29
32
|
async function transactionStatus(res, transactionId) {
|
|
30
33
|
if (!TRANSACTION_ID_PATTERN.test(transactionId)) {
|
|
31
34
|
sendSuccess(res, { transactionId, exists: false, reason: 'invalid_transaction_id' });
|
|
@@ -78,22 +81,57 @@ async function recoveryStatus(res) {
|
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
83
|
catch {
|
|
81
|
-
sendSuccess(res, { needsRecovery: false, unfinished: [], broken: [] });
|
|
84
|
+
sendSuccess(res, { needsRecovery: false, unfinished: [], superseded: [], broken: [] });
|
|
82
85
|
return null;
|
|
83
86
|
}
|
|
84
87
|
})();
|
|
85
88
|
if (entries === null)
|
|
86
89
|
return;
|
|
87
90
|
const unfinished = [];
|
|
91
|
+
const superseded = [];
|
|
88
92
|
const broken = [];
|
|
93
|
+
// PRI-896: a non-terminal journal is only an ALARM if the product's own
|
|
94
|
+
// recovery decision cannot explain it away. The live release record is the
|
|
95
|
+
// evidence for that, so a missing/corrupt active record means NO evidence —
|
|
96
|
+
// the alarm then stays, which is the conservative direction.
|
|
97
|
+
//
|
|
98
|
+
// rc-9 (no silent fallback): "file absent" (readActiveRecord returns null) and
|
|
99
|
+
// "file present but unreadable/corrupt" are different facts. The conservative
|
|
100
|
+
// outcome is the same (null → keep the alarm), but collapsing them hides WHY
|
|
101
|
+
// the alarm persists. Track unreadability explicitly so the response can say
|
|
102
|
+
// the alarm is retained for lack of a readable active record.
|
|
103
|
+
let activeRecordUnreadable = false;
|
|
104
|
+
const activeRecord = (() => {
|
|
105
|
+
try {
|
|
106
|
+
return journal.readActiveRecord(paths.activeRecordPath);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
activeRecordUnreadable = true;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
89
113
|
for (const entry of entries) {
|
|
90
114
|
const transactionId = entry.name.slice(0, -'.jsonl'.length);
|
|
91
115
|
if (!TRANSACTION_ID_PATTERN.test(transactionId))
|
|
92
116
|
continue;
|
|
93
117
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
const read = journal.readTransactionJournalForRecovery(path.join(paths.transactionsDir, entry.name));
|
|
119
|
+
const fact = factFromTransitions(journal, transactionId, read);
|
|
120
|
+
if (fact.terminal)
|
|
121
|
+
continue;
|
|
122
|
+
const outcome = journal.recoverUnfinishedTransaction({
|
|
123
|
+
transitions: read.transitions,
|
|
124
|
+
activeRecord,
|
|
125
|
+
transactionId,
|
|
126
|
+
});
|
|
127
|
+
if (activeRecord !== null && outcome.kind === 'old_confirmed') {
|
|
128
|
+
// Benign residue a killed run left behind, already replaced by the
|
|
129
|
+
// live generation. Reported separately (rc-9: never a silent drop),
|
|
130
|
+
// but it must not hold the Owner's attention forever.
|
|
131
|
+
superseded.push({ transactionId, reason: outcome.reason });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
unfinished.push(fact);
|
|
97
135
|
}
|
|
98
136
|
catch (error) {
|
|
99
137
|
broken.push({ transactionId, reason: error instanceof Error ? error.message : String(error) });
|
|
@@ -103,10 +141,18 @@ async function recoveryStatus(res) {
|
|
|
103
141
|
sendSuccess(res, {
|
|
104
142
|
needsRecovery: unfinished.length > 0 || broken.length > 0,
|
|
105
143
|
unfinished,
|
|
144
|
+
superseded,
|
|
106
145
|
broken,
|
|
146
|
+
// rc-9: when the active record could not be read, the recovery decision had
|
|
147
|
+
// no live-release evidence, so every non-terminal journal is conservatively
|
|
148
|
+
// treated as an alarm. Surfacing this explains WHY the alarm persists and
|
|
149
|
+
// points the Owner at repairing active.json rather than the journals.
|
|
150
|
+
activeRecordUnreadable,
|
|
107
151
|
// SPEC §12.1: exact next step, never a vague failure.
|
|
108
152
|
nextAction: unfinished.length > 0 || broken.length > 0
|
|
109
|
-
?
|
|
153
|
+
? activeRecordUnreadable
|
|
154
|
+
? 'The active release record (active.json) could not be read, so no transaction can be proven superseded and every unfinished one is reported. Repair active.json (re-run the official installer recovery), then re-check recovery before starting a new update.'
|
|
155
|
+
: 'An update transaction did not reach a terminal state. Review its phase, then continue, roll back, or repair through the official installer before starting a new update.'
|
|
110
156
|
: undefined,
|
|
111
157
|
});
|
|
112
158
|
}
|
|
@@ -745,6 +745,8 @@
|
|
|
745
745
|
"confirm": "Confirm",
|
|
746
746
|
"cancel": "Cancel",
|
|
747
747
|
"rejectReasonPlaceholder": "Please explain why you reject (required)",
|
|
748
|
+
"approveNoteLabel": "Approval note (optional)",
|
|
749
|
+
"approveNotePlaceholder": "Why you approve — recorded as the decision note (may be left blank)",
|
|
748
750
|
"editReasonLabel": "Edit reason (required)",
|
|
749
751
|
"editReasonPlaceholder": "Explain why the wording or scope needs to change",
|
|
750
752
|
"editNewArtifactPlaceholder": "Enter validated revision artifact ID",
|
|
@@ -745,6 +745,8 @@
|
|
|
745
745
|
"confirm": "确认",
|
|
746
746
|
"cancel": "取消",
|
|
747
747
|
"rejectReasonPlaceholder": "请说明拒绝原因(必填)",
|
|
748
|
+
"approveNoteLabel": "批准备注(可选)",
|
|
749
|
+
"approveNotePlaceholder": "写下批准的理由,将作为裁决记录保存(可留空)",
|
|
748
750
|
"editReasonLabel": "编辑原因(必填)",
|
|
749
751
|
"editReasonPlaceholder": "请说明为什么需要调整措辞或范围",
|
|
750
752
|
"editNewArtifactPlaceholder": "输入新的已验证工件 ID",
|
|
@@ -212,6 +212,7 @@ export function PrincipleDetailPage() {
|
|
|
212
212
|
const [pendingAction, setPendingAction] = useState(null);
|
|
213
213
|
const [rejectReason, setRejectReason] = useState("");
|
|
214
214
|
const [newArtifactId, setNewArtifactId] = useState("");
|
|
215
|
+
const [approveNote, setApproveNote] = useState("");
|
|
215
216
|
const [actionLoading, setActionLoading] = useState(false);
|
|
216
217
|
// Evidence ID copy state (Wave 6: stop showing raw UUIDs as primary content)
|
|
217
218
|
const [evidenceCopied, setEvidenceCopied] = useState(false);
|
|
@@ -345,7 +346,7 @@ export function PrincipleDetailPage() {
|
|
|
345
346
|
// The view's actions are advisory; every submission goes through the real
|
|
346
347
|
// mutation service. On failure: show the service reason, invalidate stale
|
|
347
348
|
// actions, re-GET the view. Never auto-retry a mutation.
|
|
348
|
-
const submitOwnerAction = async (action, reason) => {
|
|
349
|
+
const submitOwnerAction = async (action, reason, note) => {
|
|
349
350
|
if (actionLoading)
|
|
350
351
|
return;
|
|
351
352
|
setActionLoading(true);
|
|
@@ -364,7 +365,7 @@ export function PrincipleDetailPage() {
|
|
|
364
365
|
toast.error(t("principles.detail.ownerDecision.invalidTarget", { defaultValue: "动作目标缺失,无法提交。" }));
|
|
365
366
|
return;
|
|
366
367
|
}
|
|
367
|
-
const approveResult = await approveApproval(approvalId);
|
|
368
|
+
const approveResult = await approveApproval(approvalId, note);
|
|
368
369
|
settle(approveResult);
|
|
369
370
|
// PRI-890: non-fatal server warnings (e.g. prompt injection budget
|
|
370
371
|
// exclusion) must reach the Owner on this path too, not vanish.
|
|
@@ -419,6 +420,7 @@ export function PrincipleDetailPage() {
|
|
|
419
420
|
setPendingAction(null);
|
|
420
421
|
setRejectReason("");
|
|
421
422
|
setNewArtifactId("");
|
|
423
|
+
setApproveNote("");
|
|
422
424
|
// Invalidate the stale action list and re-derive from a fresh GET.
|
|
423
425
|
await loadData();
|
|
424
426
|
}
|
|
@@ -460,13 +462,15 @@ export function PrincipleDetailPage() {
|
|
|
460
462
|
return (_jsxs("li", { children: [t(stateKey, { defaultValue: subject.state }), decidedAt !== undefined ? ` · ${decidedAt.slice(0, 10)}` : '', ' · ', t(`principles.detail.ownerDecision.channel.${subject.channel}`, { defaultValue: subject.channel })] }, `hist-${subject.key}`));
|
|
461
463
|
}) })] })), _jsxs("div", { className: "mt-5 border-t border-line pt-4", "data-testid": "owner-decision-actions", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.decision", { defaultValue: "你的决定" }) }), ownerDecision.availableActions.length === 0 && (_jsx("p", { className: "mt-1 text-ink-3 text-[13px]", children: t("principles.detail.ownerDecision.noActions", { defaultValue: "当前没有可以执行的操作。" }) })), ownerDecision.decisionSubjects.filter((subject) => subject.state === 'pending').length > 0 && (_jsx("div", { className: "mt-3 space-y-3", "data-testid": "owner-decision-subject-materials", children: ownerDecision.decisionSubjects.filter((subject) => subject.state === 'pending').map((subject) => (_jsxs("div", { className: "rounded-[var(--radius-sm)] border border-line p-3", "data-subject-key": subject.key, children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.subjectMaterial", { defaultValue: "这项决定的材料" }) }), subject.decisionMaterial.learnedPrinciple.status === 'known' ? (subject.decisionMaterial.learnedPrinciple.value.map((item, index) => (_jsx("p", { className: "mt-1 text-ink-2 text-[13px] leading-relaxed", children: item.text }, `sm-${index}`)))) : (_jsx("p", { className: "mt-1 text-amber text-[13px]", children: subject.decisionMaterial.learnedPrinciple.reason.ownerText })), subject.decisionMaterial.consequence.status === 'known' && (_jsx("p", { className: "mt-1 text-ink-3 text-[12px] leading-relaxed", children: subject.decisionMaterial.consequence.value.map((item) => item.text).join(' ') }))] }, subject.key))) })), _jsx("div", { className: "mt-2 flex flex-wrap gap-3", children: ownerDecision.availableActions.map((action) => (_jsx(Button, { variant: action.semantic === 'reject' ? 'destructive' : action.semantic === 'disable' ? 'outline' : 'default', size: "sm", disabled: actionLoading, "data-action-semantic": action.semantic, onClick: () => {
|
|
462
464
|
setRejectReason("");
|
|
465
|
+
setApproveNote("");
|
|
463
466
|
setPendingAction(action);
|
|
464
|
-
}, children: localizeActionLabel(action, t) }, action.key))) }), ownerDecision.blockers.length > 0 && (_jsx("ul", { className: "mt-3 space-y-1 text-[12px] text-ink-3", children: ownerDecision.blockers.map((blocker, index) => (_jsx("li", { "data-testid": "owner-decision-blocker", children: localizeAggregatedBlocker(blocker.reason, t) }, `blk-${index}`))) })), pendingAction !== null && (_jsxs("div", { className: "border-t border-line pt-6 mt-6", "data-testid": "owner-decision-confirm", children: [_jsx(SectionTitle, { children: pendingAction.label }), _jsxs("div", { className: "mt-2 p-3 bg-gov/5 border border-gov/20 rounded-[var(--radius-md)]", children: [pendingAction.expectedConsequence.status === 'known' && (_jsx("div", { className: "mb-3", children: pendingAction.expectedConsequence.value.map((item, index) => (_jsx("p", { className: "text-ink-2 text-[13px] leading-relaxed mb-1", children: item.text }, `exp-${index}`))) })), _jsx("p", { className: "text-ink-2 text-[13px] mb-3", children: pendingAction.confirmation.text }), pendingAction.requirements.some((req) => req.name === 'newArtifactId') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.editNewArtifactLabel", { defaultValue: "新的已验证工件 ID" }) }), _jsx("input", { type: "text", value: newArtifactId, onChange: (e) => setNewArtifactId(e.target.value), placeholder: t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] mb-2 font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }) })] })), pendingAction.requirements.some((req) => req.name === 'reason') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.rejectReasonLabel", { defaultValue: "拒绝原因" }) }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), placeholder: t("principles.detail.rejectReasonPlaceholder"), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.rejectReasonPlaceholder") })] })), _jsxs("
|
|
467
|
+
}, children: localizeActionLabel(action, t) }, action.key))) }), ownerDecision.blockers.length > 0 && (_jsx("ul", { className: "mt-3 space-y-1 text-[12px] text-ink-3", children: ownerDecision.blockers.map((blocker, index) => (_jsx("li", { "data-testid": "owner-decision-blocker", children: localizeAggregatedBlocker(blocker.reason, t) }, `blk-${index}`))) })), pendingAction !== null && (_jsxs("div", { className: "border-t border-line pt-6 mt-6", "data-testid": "owner-decision-confirm", children: [_jsx(SectionTitle, { children: pendingAction.label }), _jsxs("div", { className: "mt-2 p-3 bg-gov/5 border border-gov/20 rounded-[var(--radius-md)]", children: [pendingAction.expectedConsequence.status === 'known' && (_jsx("div", { className: "mb-3", children: pendingAction.expectedConsequence.value.map((item, index) => (_jsx("p", { className: "text-ink-2 text-[13px] leading-relaxed mb-1", children: item.text }, `exp-${index}`))) })), _jsx("p", { className: "text-ink-2 text-[13px] mb-3", children: pendingAction.confirmation.text }), pendingAction.requirements.some((req) => req.name === 'newArtifactId') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.editNewArtifactLabel", { defaultValue: "新的已验证工件 ID" }) }), _jsx("input", { type: "text", value: newArtifactId, onChange: (e) => setNewArtifactId(e.target.value), placeholder: t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] mb-2 font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }) })] })), pendingAction.requirements.some((req) => req.name === 'reason') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.rejectReasonLabel", { defaultValue: "拒绝原因" }) }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), placeholder: t("principles.detail.rejectReasonPlaceholder"), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.rejectReasonPlaceholder") })] })), pendingAction.requirements.some((req) => req.name === 'note') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.approveNoteLabel", { defaultValue: "批准备注(可选)" }) }), _jsx("textarea", { "data-testid": "owner-decision-note", value: approveNote, onChange: (e) => setApproveNote(e.target.value), placeholder: pendingAction.requirements.find((req) => req.name === 'note')?.ownerText
|
|
468
|
+
?? t("principles.detail.approveNotePlaceholder", { defaultValue: "写下批准的理由(可留空)" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.approveNoteLabel", { defaultValue: "批准备注(可选)" }) })] })), _jsxs("div", { className: "flex gap-2 mt-3", children: [_jsx(Button, { variant: pendingAction.semantic === 'reject' ? 'destructive' : 'default', size: "sm", disabled: actionLoading
|
|
465
469
|
|| (pendingAction.requirements.some((req) => req.name === 'reason') && !rejectReason.trim())
|
|
466
470
|
|| (pendingAction.requirements.some((req) => req.name === 'newArtifactId') && !newArtifactId.trim()), onClick: () => {
|
|
467
471
|
const action = pendingAction;
|
|
468
|
-
void submitOwnerAction(action, rejectReason.trim() === '' ? undefined : rejectReason.trim());
|
|
469
|
-
}, children: t("principles.detail.confirm", { defaultValue: "确认" }) }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => { setPendingAction(null); setRejectReason(""); setNewArtifactId(""); }, children: t("principles.detail.cancel") })] })] })] }))] })] })] })), ownerDecision === null && ownerDecisionUnavailable !== null && (_jsxs("section", { className: "mb-8", "data-testid": "owner-decision-unavailable", children: [_jsx(SectionTitle, { children: t("principles.detail.ownerDecision.title", { defaultValue: "这条原则的决策视图" }) }), _jsxs("div", { className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("p", { className: "text-ink-2 text-[13px]", children: t("principles.detail.ownerDecision.unavailable", { defaultValue: "决策视图暂时不可用,无法据此做决定。" }) }), _jsx("p", { className: "mt-1 text-ink-4 text-[12px] font-mono", children: ownerDecisionUnavailable.reason })] })] })), (governance !== null || governanceUnavailable !== null) && (_jsxs("section", { className: "mb-8", "aria-labelledby": "governance-summary-title", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.title') }), governance !== null ? (_jsxs("div", { "data-testid": "governance-summary", className: `rounded-[var(--radius-md)] border p-4 ${governance.dataQuality.degraded ? 'border-amber/30 bg-amber/5' : 'border-gov/25 bg-gov/5'}`, children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "governance-summary-title", className: "text-[18px] font-semibold text-ink", children: t(`principles.detail.${governance.summary.headlineCode}`, { defaultValue: governance.summary.headlineCode }) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: t(`principles.detail.${governance.summary.reasonCode}`, { defaultValue: governance.summary.reasonCode }) })] }), _jsx("span", { className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: t(`principles.detail.governance.confidence.${governance.dataQuality.degraded ? 'degraded' : 'strong'}`) })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.principleState') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.state.${governance.principleState.value}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.process') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: governance.process.stage === undefined ? t('principles.detail.governance.none') : t(`principles.detail.governance.stage.${governance.process.stage}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.automation') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.automationState.${governance.automation.state}`) })] })] }), _jsxs("div", { "data-testid": "governance-next-action", className: "mt-4 border-l-2 border-l-gov pl-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.nextAction') }), _jsx("p", { className: "text-[13px] text-ink-2", children: t(`principles.detail.${governance.summary.nextActionCode}`, { defaultValue: governance.summary.nextActionCode }) })] }), governance.attention.items.length > 0 && (_jsxs("div", { className: "mt-4", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.blockers') }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: groupByReasonCode(governance.attention.items).map(group => _jsx("li", { children: formatGroupedReasonLine(t, 'principles.detail.governance.attention', group) }, group.reasonCode)) })] })), governance.dataQuality.degraded && (_jsxs("div", { "data-testid": "governance-data-quality", className: "mt-4 rounded-[var(--radius-sm)] border border-amber/20 p-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-amber", children: t('principles.detail.governance.uncertainty') }), _jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: groupByReasonCode(governance.dataQuality.issues).map(group => _jsx("li", { children: formatGroupedReasonLine(t, 'principles.detail.governance.issue', group) }, group.reasonCode)) })] }))] })) : (_jsxs("div", { "data-testid": "governance-data-quality", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h2", { id: "governance-summary-title", className: "text-[16px] font-semibold text-ink", children: t('principles.detail.governance.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: t('principles.detail.governance.unavailableReason') }), _jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: t('principles.detail.governance.unavailableNextAction') })] }))] })), receipts !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "receipt-history-title", children: [_jsx(SectionTitle, { children: t('principles.detail.receipts.title') }), receipts.status === 'ok' ? (_jsxs("div", { "data-testid": "receipt-history", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("h2", { id: "receipt-history-title", className: "text-[15px] font-semibold text-ink", children: receiptPresentation?.headlineKey === 'principles.detail.receipts.headline'
|
|
472
|
+
void submitOwnerAction(action, rejectReason.trim() === '' ? undefined : rejectReason.trim(), approveNote.trim() === '' ? undefined : approveNote.trim());
|
|
473
|
+
}, children: t("principles.detail.confirm", { defaultValue: "确认" }) }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => { setPendingAction(null); setRejectReason(""); setNewArtifactId(""); setApproveNote(""); }, children: t("principles.detail.cancel") })] })] })] }))] })] })] })), ownerDecision === null && ownerDecisionUnavailable !== null && (_jsxs("section", { className: "mb-8", "data-testid": "owner-decision-unavailable", children: [_jsx(SectionTitle, { children: t("principles.detail.ownerDecision.title", { defaultValue: "这条原则的决策视图" }) }), _jsxs("div", { className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("p", { className: "text-ink-2 text-[13px]", children: t("principles.detail.ownerDecision.unavailable", { defaultValue: "决策视图暂时不可用,无法据此做决定。" }) }), _jsx("p", { className: "mt-1 text-ink-4 text-[12px] font-mono", children: ownerDecisionUnavailable.reason })] })] })), (governance !== null || governanceUnavailable !== null) && (_jsxs("section", { className: "mb-8", "aria-labelledby": "governance-summary-title", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.title') }), governance !== null ? (_jsxs("div", { "data-testid": "governance-summary", className: `rounded-[var(--radius-md)] border p-4 ${governance.dataQuality.degraded ? 'border-amber/30 bg-amber/5' : 'border-gov/25 bg-gov/5'}`, children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "governance-summary-title", className: "text-[18px] font-semibold text-ink", children: t(`principles.detail.${governance.summary.headlineCode}`, { defaultValue: governance.summary.headlineCode }) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: t(`principles.detail.${governance.summary.reasonCode}`, { defaultValue: governance.summary.reasonCode }) })] }), _jsx("span", { className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: t(`principles.detail.governance.confidence.${governance.dataQuality.degraded ? 'degraded' : 'strong'}`) })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.principleState') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.state.${governance.principleState.value}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.process') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: governance.process.stage === undefined ? t('principles.detail.governance.none') : t(`principles.detail.governance.stage.${governance.process.stage}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.automation') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.automationState.${governance.automation.state}`) })] })] }), _jsxs("div", { "data-testid": "governance-next-action", className: "mt-4 border-l-2 border-l-gov pl-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.nextAction') }), _jsx("p", { className: "text-[13px] text-ink-2", children: t(`principles.detail.${governance.summary.nextActionCode}`, { defaultValue: governance.summary.nextActionCode }) })] }), governance.attention.items.length > 0 && (_jsxs("div", { className: "mt-4", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.blockers') }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: groupByReasonCode(governance.attention.items).map(group => _jsx("li", { children: formatGroupedReasonLine(t, 'principles.detail.governance.attention', group) }, group.reasonCode)) })] })), governance.dataQuality.degraded && (_jsxs("div", { "data-testid": "governance-data-quality", className: "mt-4 rounded-[var(--radius-sm)] border border-amber/20 p-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-amber", children: t('principles.detail.governance.uncertainty') }), _jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: groupByReasonCode(governance.dataQuality.issues).map(group => _jsx("li", { children: formatGroupedReasonLine(t, 'principles.detail.governance.issue', group) }, group.reasonCode)) })] }))] })) : (_jsxs("div", { "data-testid": "governance-data-quality", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h2", { id: "governance-summary-title", className: "text-[16px] font-semibold text-ink", children: t('principles.detail.governance.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: t('principles.detail.governance.unavailableReason') }), _jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: t('principles.detail.governance.unavailableNextAction') })] }))] })), receipts !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "receipt-history-title", children: [_jsx(SectionTitle, { children: t('principles.detail.receipts.title') }), receipts.status === 'ok' ? (_jsxs("div", { "data-testid": "receipt-history", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("h2", { id: "receipt-history-title", className: "text-[15px] font-semibold text-ink", children: receiptPresentation?.headlineKey === 'principles.detail.receipts.headline'
|
|
470
474
|
? t(receiptPresentation.headlineKey, {
|
|
471
475
|
defaultValue: '',
|
|
472
476
|
effectCount: receipts.effectCount,
|
|
@@ -874,6 +874,10 @@ export interface UpdateRecoveryItem {
|
|
|
874
874
|
productVersion: string | null;
|
|
875
875
|
tornTailDetected: boolean;
|
|
876
876
|
}
|
|
877
|
+
export interface UpdateRecoverySuperseded {
|
|
878
|
+
transactionId: string;
|
|
879
|
+
reason: string;
|
|
880
|
+
}
|
|
877
881
|
export interface UpdateRecoveryData {
|
|
878
882
|
needsRecovery: boolean;
|
|
879
883
|
unfinished: UpdateRecoveryItem[];
|
|
@@ -881,6 +885,18 @@ export interface UpdateRecoveryData {
|
|
|
881
885
|
transactionId: string;
|
|
882
886
|
reason: string;
|
|
883
887
|
}[];
|
|
888
|
+
/**
|
|
889
|
+
* PRI-896: non-terminal journals the live release has already superseded.
|
|
890
|
+
* Reported separately (rc-9: never a silent drop) but never hold the alarm.
|
|
891
|
+
*/
|
|
892
|
+
superseded: UpdateRecoverySuperseded[];
|
|
893
|
+
/**
|
|
894
|
+
* rc-9 (PRI-896/CR-2): active.json could not be read, so recovery had no
|
|
895
|
+
* live-release evidence and every non-terminal journal is conservatively an
|
|
896
|
+
* alarm. When true the alarm is retained for lack of a readable active
|
|
897
|
+
* record — repair active.json rather than the journals.
|
|
898
|
+
*/
|
|
899
|
+
activeRecordUnreadable?: boolean;
|
|
884
900
|
nextAction?: string;
|
|
885
901
|
}
|
|
886
902
|
export declare function validateUpdateRecovery(v: unknown): UpdateRecoveryData | null;
|
|
@@ -2414,7 +2414,20 @@ export function validateUpdateRecovery(v) {
|
|
|
2414
2414
|
broken.push({ transactionId: item.transactionId, reason: item.reason });
|
|
2415
2415
|
}
|
|
2416
2416
|
}
|
|
2417
|
-
const
|
|
2417
|
+
const superseded = [];
|
|
2418
|
+
if (Object.hasOwn(v, 'superseded') && Array.isArray(v.superseded)) {
|
|
2419
|
+
for (const item of v.superseded) {
|
|
2420
|
+
if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
|
|
2421
|
+
continue;
|
|
2422
|
+
if (!Object.hasOwn(item, 'reason') || !isString(item.reason))
|
|
2423
|
+
continue;
|
|
2424
|
+
superseded.push({ transactionId: item.transactionId, reason: item.reason });
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
const result = { needsRecovery: v.needsRecovery, unfinished, broken, superseded };
|
|
2428
|
+
if (Object.hasOwn(v, 'activeRecordUnreadable') && typeof v.activeRecordUnreadable === 'boolean') {
|
|
2429
|
+
result.activeRecordUnreadable = v.activeRecordUnreadable;
|
|
2430
|
+
}
|
|
2418
2431
|
if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
|
|
2419
2432
|
result.nextAction = v.nextAction;
|
|
2420
2433
|
return result;
|