@lmzhen/dsh-evolution-merge 0.30.18 → 0.30.19
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/README.md +21 -4
- package/data/minhash_params.json +1063 -0
- package/lib/index.js +7313 -3272
- package/lib/types/adapters/channel.d.ts +1 -0
- package/lib/types/adapters/channel.js +15 -1
- package/lib/types/adapters/engine-screen.d.ts +1 -1
- package/lib/types/adapters/engine-screen.js +5 -2
- package/lib/types/adapters/model-runner.d.ts +0 -5
- package/lib/types/adapters/model-runner.js +8 -2
- package/lib/types/adapters/model.js +2 -1
- package/lib/types/adapters/select.js +12 -8
- package/lib/types/approve-command.d.ts +12 -1
- package/lib/types/approve-command.js +39 -6
- package/lib/types/authoring-flow.d.ts +16 -0
- package/lib/types/authoring-flow.js +37 -13
- package/lib/types/channel-port.d.ts +3 -1
- package/lib/types/channel-port.js +8 -3
- package/lib/types/command-defs.d.ts +5 -0
- package/lib/types/command-defs.js +33 -0
- package/lib/types/command-surface.d.ts +1 -1
- package/lib/types/command-surface.js +10 -43
- package/lib/types/commands.d.ts +3 -4
- package/lib/types/commands.js +33 -72
- package/lib/types/commit-assembly.d.ts +14 -1
- package/lib/types/commit-assembly.js +65 -9
- package/lib/types/commit-execute.d.ts +7 -1
- package/lib/types/commit-execute.js +54 -22
- package/lib/types/commit-plan.d.ts +7 -1
- package/lib/types/commit-plan.js +4 -0
- package/lib/types/commit-receipt.d.ts +3 -0
- package/lib/types/contracts.d.ts +43 -0
- package/lib/types/doctor.d.ts +11 -1
- package/lib/types/doctor.js +81 -27
- package/lib/types/domain/authoring-manifest.d.ts +26 -0
- package/lib/types/domain/authoring-manifest.js +43 -0
- package/lib/types/domain/engine-criteria.d.ts +10 -0
- package/lib/types/domain/engine-criteria.js +11 -0
- package/lib/types/domain/engine-input.d.ts +10 -0
- package/lib/types/domain/engine-input.js +26 -0
- package/lib/types/domain/errors.d.ts +15 -1
- package/lib/types/domain/errors.js +23 -4
- package/lib/types/domain/evidence-hash.d.ts +3 -0
- package/lib/types/domain/evidence-hash.js +14 -0
- package/lib/types/domain/failure-route.d.ts +14 -0
- package/lib/types/domain/failure-route.js +12 -0
- package/lib/types/domain/live-facts.d.ts +31 -0
- package/lib/types/domain/live-facts.js +54 -0
- package/lib/types/domain/merge-limits.d.ts +18 -0
- package/lib/types/domain/merge-limits.js +22 -0
- package/lib/types/domain/merge-report.d.ts +41 -0
- package/lib/types/domain/merge-report.js +2 -0
- package/lib/types/domain/merge-snapshot.d.ts +99 -0
- package/lib/types/domain/merge-snapshot.js +82 -0
- package/lib/types/domain/pair-ledger.d.ts +34 -0
- package/lib/types/domain/pair-ledger.js +96 -0
- package/lib/types/domain/pre-1b-backup.d.ts +43 -0
- package/lib/types/domain/pre-1b-backup.js +140 -0
- package/lib/types/domain/provider-key.d.ts +2 -0
- package/lib/types/domain/provider-key.js +11 -0
- package/lib/types/domain/read-probe.d.ts +5 -0
- package/lib/types/domain/read-probe.js +11 -0
- package/lib/types/domain/reconcile.d.ts +47 -0
- package/lib/types/domain/reconcile.js +178 -0
- package/lib/types/domain/resolve-pair.d.ts +2 -9
- package/lib/types/domain/resolve-pair.js +2 -15
- package/lib/types/domain/row-v4.d.ts +148 -0
- package/lib/types/domain/row-v4.js +159 -0
- package/lib/types/domain/run-coordinator.d.ts +51 -0
- package/lib/types/domain/run-coordinator.js +45 -0
- package/lib/types/domain/schema4.d.ts +53 -0
- package/lib/types/domain/schema4.js +129 -0
- package/lib/types/domain/selection-policy.d.ts +14 -0
- package/lib/types/domain/selection-policy.js +6 -0
- package/lib/types/domain/skipped-expiry.d.ts +5 -0
- package/lib/types/domain/skipped-expiry.js +16 -0
- package/lib/types/domain/stage-aggregate.d.ts +96 -0
- package/lib/types/domain/stage-aggregate.js +293 -0
- package/lib/types/domain/staging-index.d.ts +1 -0
- package/lib/types/domain/state-bootstrap.d.ts +43 -0
- package/lib/types/domain/state-bootstrap.js +267 -0
- package/lib/types/domain/state-v3.d.ts +3 -14
- package/lib/types/domain/state-v3.js +3 -79
- package/lib/types/domain/undo-journal.d.ts +19 -0
- package/lib/types/domain/undo-journal.js +20 -0
- package/lib/types/domain/undo-receipt.d.ts +18 -0
- package/lib/types/domain/undo-receipt.js +22 -0
- package/lib/types/domain/validation-plan.d.ts +65 -0
- package/lib/types/domain/validation-plan.js +49 -0
- package/lib/types/facts.d.ts +3 -1
- package/lib/types/facts.js +13 -16
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +181 -68
- package/lib/types/merge-command.d.ts +3 -1
- package/lib/types/merge-command.js +10 -6
- package/lib/types/merge-routes.d.ts +10 -5
- package/lib/types/merge-routes.js +72 -7
- package/lib/types/merge-tool.d.ts +1 -1
- package/lib/types/merge-tool.js +2 -2
- package/lib/types/metrics.js +1 -1
- package/lib/types/new-child-authoring.d.ts +2 -0
- package/lib/types/new-child-authoring.js +57 -10
- package/lib/types/params.d.ts +2 -4
- package/lib/types/params.js +13 -0
- package/lib/types/pending-actions.d.ts +2 -2
- package/lib/types/pending-actions.js +4 -4
- package/lib/types/prompts.d.ts +2 -4
- package/lib/types/prompts.js +21 -8
- package/lib/types/read-commands.d.ts +3 -3
- package/lib/types/read-commands.js +19 -28
- package/lib/types/recovery-coordinator.d.ts +34 -0
- package/lib/types/recovery-coordinator.js +84 -0
- package/lib/types/recovery-projection.d.ts +31 -0
- package/lib/types/recovery-projection.js +96 -0
- package/lib/types/resume-command.d.ts +5 -1
- package/lib/types/resume-command.js +46 -13
- package/lib/types/revision.d.ts +17 -0
- package/lib/types/revision.js +39 -3
- package/lib/types/round2-outcome.d.ts +2 -1
- package/lib/types/round2-outcome.js +59 -11
- package/lib/types/run-shell.d.ts +16 -2
- package/lib/types/run-shell.js +49 -5
- package/lib/types/runtime.d.ts +0 -5
- package/lib/types/runtime.js +274 -89
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +2 -0
- package/lib/types/scan-ports.d.ts +18 -0
- package/lib/types/scan-ports.js +49 -23
- package/lib/types/scan-run.d.ts +17 -0
- package/lib/types/scan-run.js +143 -48
- package/lib/types/stage-tool-registry.d.ts +7 -0
- package/lib/types/stage-tool-registry.js +12 -1
- package/lib/types/state.d.ts +5 -5
- package/lib/types/state.js +22 -69
- package/lib/types/status.d.ts +6 -1
- package/lib/types/status.js +47 -0
- package/lib/types/surface/body.d.ts +4 -0
- package/lib/types/surface/body.js +16 -0
- package/lib/types/surface/merge-view.d.ts +14 -0
- package/lib/types/surface/merge-view.js +126 -0
- package/lib/types/undo-operation.d.ts +24 -0
- package/lib/types/undo-operation.js +135 -0
- package/lib/types/wire.d.ts +22 -3
- package/package.json +6 -4
|
@@ -19,6 +19,7 @@ export interface RunScanCommandOptions {
|
|
|
19
19
|
readonly detached?: boolean;
|
|
20
20
|
readonly notes?: readonly string[];
|
|
21
21
|
readonly isOwnerActive?: (runId: string) => boolean | Promise<boolean>;
|
|
22
|
+
readonly cards?: readonly import('./skill-card.ts').SkillCard[];
|
|
22
23
|
}
|
|
23
24
|
export declare function runScanCommand(options: RunScanCommandOptions): Promise<WithMergeRunResult>;
|
|
24
25
|
//# sourceMappingURL=scan-command.d.ts.map
|
|
@@ -9,6 +9,7 @@ export async function runScanCommand(options) {
|
|
|
9
9
|
approval: { platformWrite: options.platformWrite, mergeApproval: options.approval },
|
|
10
10
|
now: options.now,
|
|
11
11
|
...(options.detached === undefined ? {} : { detached: options.detached }),
|
|
12
|
+
...(options.timeoutMinutes === undefined ? {} : { timeoutMinutes: options.timeoutMinutes }),
|
|
12
13
|
async body(ctx) {
|
|
13
14
|
const ports = options.makePorts === undefined ? options.ports : options.makePorts(ctx.runId);
|
|
14
15
|
if (ports === undefined)
|
|
@@ -26,6 +27,7 @@ export async function runScanCommand(options) {
|
|
|
26
27
|
...(options.timeoutMinutes === undefined ? {} : { timeoutMinutes: options.timeoutMinutes }),
|
|
27
28
|
...(options.notes === undefined ? {} : { notes: options.notes }),
|
|
28
29
|
...(options.isOwnerActive === undefined ? {} : { isOwnerActive: options.isOwnerActive }),
|
|
30
|
+
...(options.cards === undefined ? {} : { cards: options.cards }),
|
|
29
31
|
});
|
|
30
32
|
},
|
|
31
33
|
});
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* approve run must open a new child (not handled by this factory).
|
|
7
7
|
*/
|
|
8
8
|
import type { EvolutionIoLike, UnitTransaction } from '@lmzhen/dsh-evolution-core';
|
|
9
|
+
import type { MergeDecision, MergeRow } from './contracts.ts';
|
|
9
10
|
import type { SkillCard } from './skill-card.ts';
|
|
10
11
|
import { type ConversationChannelLike } from './channel-port.ts';
|
|
11
12
|
import type { CommitPort } from './commit-execute.ts';
|
|
12
13
|
import type { StageToolContext } from './stage-tool.ts';
|
|
14
|
+
import { type StageEntry } from './domain/stage-aggregate.ts';
|
|
13
15
|
import type { ScanPorts } from './scan-run.ts';
|
|
14
16
|
export interface CreateScanPortsOptions {
|
|
15
17
|
readonly io: EvolutionIoLike;
|
|
@@ -17,6 +19,7 @@ export interface CreateScanPortsOptions {
|
|
|
17
19
|
readonly runId: string;
|
|
18
20
|
readonly cards: readonly SkillCard[];
|
|
19
21
|
readonly screening: ScanPorts['screening'];
|
|
22
|
+
readonly resolve?: ScanPorts['resolve'];
|
|
20
23
|
readonly channel: ConversationChannelLike;
|
|
21
24
|
readonly parent: unknown;
|
|
22
25
|
readonly allow: readonly string[];
|
|
@@ -25,10 +28,25 @@ export interface CreateScanPortsOptions {
|
|
|
25
28
|
readonly approval: boolean;
|
|
26
29
|
readonly commitPort: CommitPort;
|
|
27
30
|
readonly unit?: UnitTransaction | undefined;
|
|
31
|
+
readonly limits?: import('@lmzhen/dsh-evolution-core').SkillLimits | undefined;
|
|
28
32
|
readonly now: () => number;
|
|
29
33
|
readonly isBlocked?: (name: string) => boolean | Promise<boolean>;
|
|
30
34
|
readonly onChild?: (context: StageToolContext) => void;
|
|
31
35
|
readonly onChildClosed?: (childId: string) => void;
|
|
32
36
|
}
|
|
37
|
+
export interface StageForCardsOptions {
|
|
38
|
+
readonly io: EvolutionIoLike;
|
|
39
|
+
readonly home: string;
|
|
40
|
+
readonly runId: string;
|
|
41
|
+
readonly cards: readonly SkillCard[];
|
|
42
|
+
readonly now: () => number;
|
|
43
|
+
}
|
|
44
|
+
export declare function createStageForCards(options: StageForCardsOptions): (row: MergeRow, decision: MergeDecision) => Promise<{
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
readonly entry: StageEntry;
|
|
47
|
+
} | {
|
|
48
|
+
readonly ok: false;
|
|
49
|
+
readonly message: string;
|
|
50
|
+
}>;
|
|
33
51
|
export declare function createChannelScanPorts(options: CreateScanPortsOptions): ScanPorts;
|
|
34
52
|
//# sourceMappingURL=scan-ports.d.ts.map
|
package/lib/types/scan-ports.js
CHANGED
|
@@ -1,39 +1,49 @@
|
|
|
1
1
|
import { pairKey } from "./contracts.js";
|
|
2
|
+
function classifyCommitFailure(message) {
|
|
3
|
+
if (message.includes('目标技能') && (message.includes('被改过') || message.includes('不存在')))
|
|
4
|
+
return message.includes('被改过') ? 'target-changed' : 'target-taken';
|
|
5
|
+
if (message.includes('来源技能') && message.includes('被改过'))
|
|
6
|
+
return 'source-changed';
|
|
7
|
+
if (message.includes('来源技能') && message.includes('不存在'))
|
|
8
|
+
return 'source-gone';
|
|
9
|
+
return 'authoring-failed';
|
|
10
|
+
}
|
|
2
11
|
import { runRound1OverChannel } from "./channel-port.js";
|
|
3
12
|
import { runAuthoring } from "./authoring-flow.js";
|
|
4
|
-
import {
|
|
13
|
+
import { buildRound2Prompt, MERGE_PROMPT_VERSION, promptHashOf } from "./prompts.js";
|
|
14
|
+
import { stageKeyOf } from "./staging.js";
|
|
15
|
+
import { createStageTree, reserveStage } from "./domain/stage-aggregate.js";
|
|
5
16
|
import { sourceRevisionOf, targetRevisionOf } from "./revision.js";
|
|
17
|
+
export function createStageForCards(options) {
|
|
18
|
+
return async (row, decision) => {
|
|
19
|
+
const stageKey = stageKeyOf(options.runId, pairKey(row.a, row.b));
|
|
20
|
+
const entry = { version: 1, rowId: row.id, pairKey: row.pairKey, runId: options.runId, ref: stageKey, state: 'reserved', createdAt: options.now(), expiresAt: options.now() + 7 * 86_400_000, updatedAt: options.now() };
|
|
21
|
+
await reserveStage(options.io, options.home, entry);
|
|
22
|
+
const staged = await createStageTree(options.io, options.home, {
|
|
23
|
+
runId: stageKey, target: decision.target,
|
|
24
|
+
sources: options.cards.filter(card => card.name === row.a || card.name === row.b).map(card => ({ name: card.name, body: card.body, files: card.files ?? [] })),
|
|
25
|
+
promptVersion: 'merge-b/v1', ttlDays: 7, now: options.now,
|
|
26
|
+
});
|
|
27
|
+
return staged.ok ? { ok: true, entry } : { ok: false, message: staged.message };
|
|
28
|
+
};
|
|
29
|
+
}
|
|
6
30
|
export function createChannelScanPorts(options) {
|
|
7
31
|
const sessions = new Map();
|
|
8
32
|
return {
|
|
9
33
|
screening: options.screening,
|
|
34
|
+
...(options.resolve === undefined ? {} : { resolve: options.resolve }),
|
|
10
35
|
async round1(candidate, signal) {
|
|
11
36
|
const stageKey = stageKeyOf(options.runId, pairKey(candidate.a, candidate.b));
|
|
12
|
-
const staged = await ensureStaging(options.io, options.home, {
|
|
13
|
-
runId: stageKey,
|
|
14
|
-
target: candidate.a + '-' + candidate.b,
|
|
15
|
-
sources: options.cards
|
|
16
|
-
.filter(card => card.name === candidate.a || card.name === candidate.b)
|
|
17
|
-
.map(card => ({ name: card.name, body: card.body, files: card.files ?? [] })),
|
|
18
|
-
promptVersion: 'merge-b/v1',
|
|
19
|
-
ttlDays: 7,
|
|
20
|
-
now: options.now,
|
|
21
|
-
});
|
|
22
|
-
if (!staged.ok)
|
|
23
|
-
return { kind: 'failed', failure: { stage: 'decide', code: 'staging-create', message: staged.message, retryable: true, retryFrom: 'agent1' } };
|
|
24
37
|
const result = await runRound1OverChannel({
|
|
25
38
|
channel: options.channel,
|
|
26
39
|
parent: options.parent,
|
|
27
40
|
cards: options.cards,
|
|
28
41
|
allow: options.allow,
|
|
29
|
-
cwd:
|
|
42
|
+
cwd: options.cwd ?? options.home,
|
|
30
43
|
timeoutMs: options.timeoutMs,
|
|
31
44
|
onChildOpened: childId => options.onChild?.({ runId: stageKey, childId, expectedChildId: childId, phase: 'decide' }),
|
|
32
45
|
}, candidate, signal);
|
|
33
|
-
if (result.outcome.kind
|
|
34
|
-
await dropStaging(options.io, options.home, stageKey);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
46
|
+
if (result.outcome.kind === 'merge') {
|
|
37
47
|
if (options.approval)
|
|
38
48
|
await result.dispose();
|
|
39
49
|
else {
|
|
@@ -43,6 +53,7 @@ export function createChannelScanPorts(options) {
|
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
}
|
|
56
|
+
const round1Usage = result.usage?.();
|
|
46
57
|
const sourceRevision = result.outcome.kind === 'merge' ? sourceRevisionOf(options.cards, candidate.a, candidate.b) : undefined;
|
|
47
58
|
const target = result.outcome.kind === 'merge' ? result.outcome.decision?.target : undefined;
|
|
48
59
|
const targetRevision = target === undefined ? undefined : targetRevisionOf(options.cards, target);
|
|
@@ -51,9 +62,13 @@ export function createChannelScanPorts(options) {
|
|
|
51
62
|
...result.outcome, stagingRef: stageKey, stagingExpiresAt: options.now() + 7 * 86_400_000,
|
|
52
63
|
...(sourceRevision === undefined ? {} : { sourceRevision }),
|
|
53
64
|
...(targetRevision === undefined ? {} : { targetRevision }),
|
|
65
|
+
...(round1Usage === undefined ? {} : { usage: round1Usage }),
|
|
54
66
|
}
|
|
55
|
-
: result.outcome
|
|
67
|
+
: result.outcome.kind === 'failed'
|
|
68
|
+
? { ...result.outcome, degraded: ['round1 child failed: ' + (result.outcome.failure?.code ?? 'unknown')], ...(round1Usage === undefined ? {} : { usage: round1Usage }) }
|
|
69
|
+
: { ...result.outcome, ...(round1Usage === undefined ? {} : { usage: round1Usage }) };
|
|
56
70
|
},
|
|
71
|
+
stage: createStageForCards({ io: options.io, home: options.home, runId: options.runId, cards: options.cards, now: options.now }),
|
|
57
72
|
async round2(row, decision, signal) {
|
|
58
73
|
const key = pairKey(row.a, row.b);
|
|
59
74
|
const entry = sessions.get(key);
|
|
@@ -76,18 +91,29 @@ export function createChannelScanPorts(options) {
|
|
|
76
91
|
now: options.now,
|
|
77
92
|
...(options.isBlocked === undefined ? {} : { isBlocked: options.isBlocked }),
|
|
78
93
|
summary: 'merge ' + row.a + ' + ' + row.b,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
...(options.limits === undefined ? {} : { limits: options.limits }),
|
|
95
|
+
promptVersion: MERGE_PROMPT_VERSION,
|
|
96
|
+
promptHash: promptHashOf(buildRound2Prompt(row, decision)),
|
|
97
|
+
revisions: {
|
|
98
|
+
...(decision.targetRevisionRef === undefined ? {} : { targetRevision: decision.targetRevisionRef }),
|
|
99
|
+
...(decision.sourceRevisions === undefined ? {} : { sourceRevisions: decision.sourceRevisions }),
|
|
100
|
+
...(decision.supportDigest === undefined ? {} : { supportDigest: decision.supportDigest }),
|
|
82
101
|
},
|
|
102
|
+
author: async () => (await entry.result.author?.(row, decision, signal)) ?? 'authored',
|
|
83
103
|
});
|
|
104
|
+
const usage = entry.result.usage?.();
|
|
84
105
|
if (!authored.ok) {
|
|
85
|
-
|
|
106
|
+
const limit = authored.findings?.some(finding => finding.rule === 'limit') === true;
|
|
107
|
+
const code = authored.code ?? (limit ? 'merge-over-limit' : authored.findings === undefined ? classifyCommitFailure(authored.message) : 'merge-validation');
|
|
108
|
+
return { kind: 'failed', failure: { stage: 'author', code, message: authored.message, retryable: code !== 'target-changed' && code !== 'source-changed' && code !== 'support-changed', retryFrom: code === 'target-changed' || code === 'source-changed' || code === 'support-changed' ? 'none' : 'agent2', ...(authored.lengths === undefined ? {} : { lengths: authored.lengths }), ...(usage === undefined ? {} : { usage }) }, };
|
|
86
109
|
}
|
|
87
110
|
const receipt = authored.commit.receipt;
|
|
88
111
|
return {
|
|
89
112
|
kind: 'merged',
|
|
90
113
|
...(receipt?.unitId === undefined ? {} : { unitId: receipt.unitId }),
|
|
114
|
+
...(receipt?.evidenceSha256 === undefined ? {} : { evidenceSha256: receipt.evidenceSha256 }),
|
|
115
|
+
...(receipt?.validationPlan === undefined ? {} : { validationPlan: receipt.validationPlan }),
|
|
116
|
+
...(usage === undefined ? {} : { usage }),
|
|
91
117
|
archived: receipt?.archived ?? [],
|
|
92
118
|
target: { requested: decision.target, actual: authored.commit.plan.target, created: receipt?.create ?? false },
|
|
93
119
|
};
|
package/lib/types/scan-run.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* candidate → round1 → pending/fuse → commit transitions and returns the report draft.
|
|
4
4
|
*/
|
|
5
5
|
import { type MergeCandidate, type MergeDecision, type MergeFailure, type MergeRow } from './contracts.ts';
|
|
6
|
+
import { type StageEntry } from './domain/stage-aggregate.ts';
|
|
6
7
|
import type { EvolutionIoLike, RunStage } from '@lmzhen/dsh-evolution-core';
|
|
8
|
+
import type { SkillCard } from './skill-card.ts';
|
|
7
9
|
import type { MergeRunReportDraft } from './run-shell.ts';
|
|
8
10
|
export interface ScanRound1Outcome {
|
|
9
11
|
readonly kind: 'keep' | 'merge' | 'failed';
|
|
@@ -13,10 +15,14 @@ export interface ScanRound1Outcome {
|
|
|
13
15
|
readonly stagingExpiresAt?: number;
|
|
14
16
|
readonly sourceRevision?: string;
|
|
15
17
|
readonly targetRevision?: string;
|
|
18
|
+
readonly degraded?: readonly string[];
|
|
19
|
+
readonly usage?: import('./contracts.ts').MergeUsage;
|
|
16
20
|
}
|
|
17
21
|
export interface ScanRound2Outcome {
|
|
18
22
|
readonly kind: 'merged' | 'failed';
|
|
19
23
|
readonly unitId?: string;
|
|
24
|
+
readonly evidenceSha256?: string;
|
|
25
|
+
readonly validationPlan?: import('./contracts.ts').MergeValidationPlanRef;
|
|
20
26
|
readonly archived?: readonly string[];
|
|
21
27
|
readonly target?: {
|
|
22
28
|
readonly requested: string;
|
|
@@ -24,10 +30,20 @@ export interface ScanRound2Outcome {
|
|
|
24
30
|
readonly created: boolean;
|
|
25
31
|
};
|
|
26
32
|
readonly failure?: MergeFailure;
|
|
33
|
+
readonly usage?: import('./contracts.ts').MergeUsage;
|
|
34
|
+
readonly lengths?: Readonly<Record<string, number>>;
|
|
27
35
|
}
|
|
28
36
|
export interface ScanPorts {
|
|
29
37
|
screening(signal: AbortSignal): Promise<readonly MergeCandidate[]>;
|
|
38
|
+
resolve?(candidate: MergeCandidate): Promise<boolean>;
|
|
30
39
|
round1(candidate: MergeCandidate, signal: AbortSignal): Promise<ScanRound1Outcome>;
|
|
40
|
+
stage?(row: MergeRow, decision: MergeDecision): Promise<{
|
|
41
|
+
readonly ok: true;
|
|
42
|
+
readonly entry: StageEntry;
|
|
43
|
+
} | {
|
|
44
|
+
readonly ok: false;
|
|
45
|
+
readonly message: string;
|
|
46
|
+
}>;
|
|
31
47
|
round2(row: MergeRow, decision: MergeDecision, signal: AbortSignal): Promise<ScanRound2Outcome>;
|
|
32
48
|
}
|
|
33
49
|
export interface RunScanOptions {
|
|
@@ -46,6 +62,7 @@ export interface RunScanOptions {
|
|
|
46
62
|
readonly notes?: readonly string[];
|
|
47
63
|
readonly runId?: string;
|
|
48
64
|
readonly isOwnerActive?: (runId: string) => boolean | Promise<boolean>;
|
|
65
|
+
readonly cards?: readonly SkillCard[];
|
|
49
66
|
}
|
|
50
67
|
export declare function runScan(options: RunScanOptions): Promise<MergeRunReportDraft>;
|
|
51
68
|
//# sourceMappingURL=scan-run.d.ts.map
|
package/lib/types/scan-run.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* candidate → round1 → pending/fuse → commit transitions and returns the report draft.
|
|
4
4
|
*/
|
|
5
5
|
import { pairKey, rowIdOf, } from "./contracts.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { loadMergeState } from "./state.js";
|
|
7
|
+
import { pairLedger } from "./domain/pair-ledger.js";
|
|
8
|
+
import { presentStage, dropStage } from "./domain/stage-aggregate.js";
|
|
9
|
+
import { recordPair } from "./domain/run-coordinator.js";
|
|
10
|
+
import { v4ToLegacy } from "./domain/row-v4.js";
|
|
11
|
+
import { criteriaFrom } from "./domain/engine-criteria.js";
|
|
12
|
+
import { withDecisionRevisions } from "./revision.js";
|
|
10
13
|
const isAborted = (signal) => signal.aborted;
|
|
11
14
|
function at(now) {
|
|
12
15
|
return new Date(now()).toISOString();
|
|
@@ -46,8 +49,10 @@ export async function runScan(options) {
|
|
|
46
49
|
const startedAt = options.startedAt ?? options.now();
|
|
47
50
|
const timeoutMs = (options.timeoutMinutes ?? 0) > 0 ? options.timeoutMinutes * 60_000 : 0;
|
|
48
51
|
const pairs = [];
|
|
52
|
+
const degradedNotes = [];
|
|
49
53
|
const totals = { merged: 0, kept: 0, pending: 0, failed: 0 };
|
|
50
54
|
const remaining = [];
|
|
55
|
+
const finalizeRows = [];
|
|
51
56
|
let stopReason = 'completed';
|
|
52
57
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
53
58
|
if (isAborted(options.signal)) {
|
|
@@ -72,7 +77,7 @@ export async function runScan(options) {
|
|
|
72
77
|
&& (await options.isOwnerActive?.(before.runId)) === true) {
|
|
73
78
|
continue;
|
|
74
79
|
}
|
|
75
|
-
const status = await
|
|
80
|
+
const status = await pairLedger(options.io, options.home).transact(tx => {
|
|
76
81
|
const current = tx.rows().find(item => item.pairKey === row.pairKey);
|
|
77
82
|
if (current !== undefined && current.state !== 'verify' && current.state !== 'skipped')
|
|
78
83
|
return 'conflict:' + current.state;
|
|
@@ -104,6 +109,15 @@ export async function runScan(options) {
|
|
|
104
109
|
});
|
|
105
110
|
continue;
|
|
106
111
|
}
|
|
112
|
+
if (options.ports.resolve !== undefined && !(await options.ports.resolve(candidate))) {
|
|
113
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
114
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
115
|
+
return current === undefined ? false : tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
116
|
+
});
|
|
117
|
+
pairs.push(pairOf(row, candidate, 'failed', { failure: { stage: 'decide', code: 'source-gone', message: 'source skill disappeared before round1', retryable: false, retryFrom: 'none' } }));
|
|
118
|
+
totals.failed += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
107
121
|
let round1;
|
|
108
122
|
try {
|
|
109
123
|
round1 = await options.ports.round1(candidate, options.signal);
|
|
@@ -117,13 +131,15 @@ export async function runScan(options) {
|
|
|
117
131
|
break;
|
|
118
132
|
}
|
|
119
133
|
if (round1.kind === 'failed') {
|
|
134
|
+
if (round1.degraded !== undefined)
|
|
135
|
+
degradedNotes.push(...round1.degraded);
|
|
120
136
|
const failure = round1.failure ?? failureOf('decide', 'round1 failed');
|
|
121
|
-
const projected = await
|
|
137
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
122
138
|
const current = tx.rows().find(item => item.id === row.id);
|
|
123
139
|
if (current === undefined)
|
|
124
140
|
return null;
|
|
125
141
|
const moved = tx.transition(row.id, { type: 'failed', failure }, current.version);
|
|
126
|
-
return moved.ok && moved.row !== null ?
|
|
142
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
127
143
|
});
|
|
128
144
|
if (projected === null) {
|
|
129
145
|
pairs.push(pairOf(row, candidate, 'failed', { failure: failureOf('decide', 'row changed before failure record') }));
|
|
@@ -136,12 +152,13 @@ export async function runScan(options) {
|
|
|
136
152
|
continue;
|
|
137
153
|
}
|
|
138
154
|
if (round1.kind === 'keep') {
|
|
139
|
-
const projected = await
|
|
155
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
140
156
|
const current = tx.rows().find(item => item.id === row.id);
|
|
141
157
|
if (current === undefined)
|
|
142
158
|
return null;
|
|
143
|
-
const
|
|
144
|
-
|
|
159
|
+
const keepReason = round1.decision?.rationale?.trim();
|
|
160
|
+
const moved = tx.transition(row.id, { type: 'skipped', ...(keepReason === undefined || keepReason === '' ? { reason: 'kept' } : { reason: keepReason }), origin: 'agent' }, current.version);
|
|
161
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
145
162
|
});
|
|
146
163
|
if (projected === null) {
|
|
147
164
|
pairs.push(pairOf(row, candidate, 'failed', { failure: failureOf('decide', 'row changed before keep record') }));
|
|
@@ -150,7 +167,7 @@ export async function runScan(options) {
|
|
|
150
167
|
}
|
|
151
168
|
row = projected;
|
|
152
169
|
await recordPair(options.io, options.home, { pairKey: row.pairKey, runId: row.runId ?? 'merge', result: 'kept' });
|
|
153
|
-
pairs.push(pairOf(row, candidate, 'kept', round1.decision === undefined ? {} : { decision: round1.decision }));
|
|
170
|
+
pairs.push(pairOf(row, candidate, 'kept', { ...(round1.decision === undefined ? {} : { decision: round1.decision }), ...(round1.usage === undefined ? {} : { usage: round1.usage }) }));
|
|
154
171
|
totals.kept += 1;
|
|
155
172
|
continue;
|
|
156
173
|
}
|
|
@@ -159,52 +176,71 @@ export async function runScan(options) {
|
|
|
159
176
|
totals.failed += 1;
|
|
160
177
|
continue;
|
|
161
178
|
}
|
|
179
|
+
const mergeDecision = options.cards !== undefined ? withDecisionRevisions(round1.decision, options.cards, row.a, row.b) : round1.decision;
|
|
162
180
|
if (options.approval) {
|
|
163
|
-
const event = { type: 'verify-passed', decision:
|
|
164
|
-
const projected = await
|
|
181
|
+
const event = { type: 'verify-passed', decision: mergeDecision, target: mergeDecision.target, ...(round1.sourceRevision === undefined ? {} : { sourceRevision: round1.sourceRevision }), ...(mergeDecision.promptVersion === undefined ? {} : { promptVersion: mergeDecision.promptVersion }), ...(mergeDecision.promptHash === undefined ? {} : { promptHash: mergeDecision.promptHash }) };
|
|
182
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
165
183
|
const current = tx.rows().find(item => item.id === row.id);
|
|
166
184
|
if (current === undefined)
|
|
167
185
|
return null;
|
|
168
186
|
const moved = tx.transition(row.id, event, current.version);
|
|
169
|
-
return moved.ok && moved.row !== null ?
|
|
187
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
170
188
|
});
|
|
171
189
|
if (projected === null) {
|
|
172
|
-
pairs.push(pairOf(row, candidate, 'failed', { decision:
|
|
190
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('decide', 'row changed before pending record') }));
|
|
173
191
|
totals.failed += 1;
|
|
174
192
|
continue;
|
|
175
193
|
}
|
|
176
194
|
row = projected;
|
|
177
|
-
pairs.push(pairOf(row, candidate, 'pending', { decision:
|
|
195
|
+
pairs.push(pairOf(row, candidate, 'pending', { decision: mergeDecision }));
|
|
178
196
|
totals.pending += 1;
|
|
179
197
|
continue;
|
|
180
198
|
}
|
|
181
|
-
const
|
|
199
|
+
const approved = await pairLedger(options.io, options.home).transact(tx => {
|
|
182
200
|
const current = tx.rows().find(item => item.id === row.id);
|
|
183
201
|
if (current === undefined)
|
|
184
202
|
return null;
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
return null;
|
|
188
|
-
const moved = tx.transition(row.id, { type: 'staged', ...(round1.stagingRef === undefined ? {} : { ref: round1.stagingRef }), ...(round1.stagingExpiresAt === undefined ? {} : { expiresAt: round1.stagingExpiresAt }) }, approved.row.version);
|
|
189
|
-
return moved.ok && moved.row !== null ? v3ToLegacy(moved.row) : null;
|
|
203
|
+
const moved = tx.transition(row.id, { type: 'auto-approved', decision: mergeDecision, target: mergeDecision.target, ...(row.runId === undefined ? {} : { runId: row.runId }) }, current.version);
|
|
204
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
190
205
|
});
|
|
191
|
-
if (
|
|
192
|
-
pairs.push(pairOf(row, candidate, 'failed', { decision:
|
|
206
|
+
if (approved === null) {
|
|
207
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before approve record') }));
|
|
193
208
|
totals.failed += 1;
|
|
194
209
|
continue;
|
|
195
210
|
}
|
|
196
|
-
row =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
row = approved;
|
|
212
|
+
let stageEntry;
|
|
213
|
+
if (options.ports.stage !== undefined) {
|
|
214
|
+
const staged = await options.ports.stage(row, mergeDecision);
|
|
215
|
+
if (!staged.ok) {
|
|
216
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', staged.message) }));
|
|
217
|
+
totals.failed += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
stageEntry = staged.entry;
|
|
221
|
+
}
|
|
222
|
+
const fused = await pairLedger(options.io, options.home).transact(tx => {
|
|
223
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
224
|
+
if (current === undefined)
|
|
225
|
+
return null;
|
|
226
|
+
const moved = tx.transition(row.id, { type: 'staged' }, current.version);
|
|
227
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
228
|
+
});
|
|
229
|
+
if (fused === null) {
|
|
230
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before staging record') }));
|
|
231
|
+
totals.failed += 1;
|
|
232
|
+
continue;
|
|
200
233
|
}
|
|
234
|
+
row = fused;
|
|
235
|
+
if (stageEntry !== undefined)
|
|
236
|
+
await presentStage(options.io, options.home, stageEntry);
|
|
201
237
|
if (isAborted(options.signal)) {
|
|
202
|
-
const projected = await
|
|
238
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
203
239
|
const current = tx.rows().find(item => item.id === row.id);
|
|
204
240
|
if (current === undefined)
|
|
205
241
|
return null;
|
|
206
242
|
const moved = tx.transition(row.id, { type: 'skipped', reason: 'cancelled' }, current.version);
|
|
207
|
-
return moved.ok && moved.row !== null ?
|
|
243
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
208
244
|
});
|
|
209
245
|
if (projected !== null)
|
|
210
246
|
row = projected;
|
|
@@ -215,44 +251,85 @@ export async function runScan(options) {
|
|
|
215
251
|
await stage('author', { done: index, total: candidates.length });
|
|
216
252
|
let round2;
|
|
217
253
|
try {
|
|
218
|
-
round2 = await options.ports.round2(row,
|
|
254
|
+
round2 = await options.ports.round2(row, mergeDecision, options.signal);
|
|
219
255
|
}
|
|
220
256
|
catch (error) {
|
|
221
257
|
round2 = { kind: 'failed', failure: failureOf('author', error) };
|
|
222
258
|
}
|
|
223
259
|
if (round2.kind === 'merged') {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
...(round2.
|
|
233
|
-
...(round2.target === undefined ? {} : { target: round2.target }),
|
|
234
|
-
archived: round2.archived ?? [],
|
|
235
|
-
}));
|
|
260
|
+
finalizeRows.push({ id: row.id, unitId: round2.unitId ?? 'unknown', evidenceSha256: round2.evidenceSha256 ?? 'unknown' });
|
|
261
|
+
pairs.push({ ...pairOf(row, candidate, 'merged', {
|
|
262
|
+
decision: mergeDecision,
|
|
263
|
+
...(round2.unitId === undefined ? {} : { unitId: round2.unitId }),
|
|
264
|
+
...(round2.target === undefined ? {} : { target: round2.target }),
|
|
265
|
+
...(round2.usage === undefined ? {} : { usage: round2.usage }),
|
|
266
|
+
...(round2.lengths === undefined ? {} : { lengths: round2.lengths }),
|
|
267
|
+
archived: round2.archived ?? [],
|
|
268
|
+
}), ...(round2.validationPlan === undefined ? {} : { validationPlan: round2.validationPlan }) });
|
|
236
269
|
totals.merged += 1;
|
|
237
270
|
continue;
|
|
238
271
|
}
|
|
239
272
|
const failed = round2.failure ?? failureOf('author', 'round2 failed');
|
|
240
|
-
|
|
273
|
+
if (failed.code === 'merge-over-limit' || failed.code === 'limit' || failed.code === 'merge-validation') {
|
|
274
|
+
const reason = failed.code === 'merge-validation' ? 'validation-failed' : 'merge-over-limit';
|
|
275
|
+
const skipped = await pairLedger(options.io, options.home).transact(tx => {
|
|
276
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
277
|
+
return current === undefined ? false : tx.transition(row.id, { type: 'skipped', reason, origin: 'system', skipSource: 'system' }, current.version).ok;
|
|
278
|
+
});
|
|
279
|
+
if (skipped)
|
|
280
|
+
await dropStage(options.io, options.home, row.id);
|
|
281
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
282
|
+
totals.failed += 1;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (failed.code === 'target-changed' || failed.code === 'target-taken' || failed.code === 'source-changed' || failed.code === 'support-changed' || failed.code === 'source-gone') {
|
|
286
|
+
const code = failed.code === 'target-changed' ? 'target-taken' : failed.code === 'support-changed' ? 'source-changed' : failed.code;
|
|
287
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
288
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
289
|
+
if (current === undefined)
|
|
290
|
+
return false;
|
|
291
|
+
if (code === 'source-gone')
|
|
292
|
+
return tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
293
|
+
return tx.transition(row.id, { type: 'skipped', reason: code, origin: 'system', skipSource: 'system' }, current.version).ok;
|
|
294
|
+
});
|
|
295
|
+
await dropStage(options.io, options.home, row.id);
|
|
296
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: { ...failed, code, retryable: false, retryFrom: 'none' } }));
|
|
297
|
+
totals.failed += 1;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const terminal = failed.code === 'commit-failed' || failed.code === 'validation-failed' || failed.code === 'merge-validation';
|
|
301
|
+
const projected = terminal ? null : await pairLedger(options.io, options.home).transact(tx => {
|
|
241
302
|
const current = tx.rows().find(item => item.id === row.id);
|
|
242
303
|
if (current === undefined)
|
|
243
304
|
return null;
|
|
244
305
|
const moved = tx.transition(row.id, { type: 'failed', failure: failed }, current.version);
|
|
245
|
-
return moved.ok && moved.row !== null ?
|
|
306
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
246
307
|
});
|
|
308
|
+
if (projected === null && terminal) {
|
|
309
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
310
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
311
|
+
return current === undefined ? false : tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
312
|
+
});
|
|
313
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
314
|
+
totals.failed += 1;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
247
317
|
if (projected === null) {
|
|
248
|
-
pairs.push(pairOf(row, candidate, 'failed', { decision:
|
|
318
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before failure record') }));
|
|
249
319
|
totals.failed += 1;
|
|
250
320
|
continue;
|
|
251
321
|
}
|
|
252
322
|
row = projected;
|
|
253
|
-
pairs.push(pairOf(row, candidate, 'failed', { decision:
|
|
323
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
254
324
|
totals.failed += 1;
|
|
255
325
|
}
|
|
326
|
+
const selectionReportOnly = candidates.reportOnly ?? [];
|
|
327
|
+
const selectionReportOnlyPairs = selectionReportOnly.flatMap(raw => {
|
|
328
|
+
const value = raw;
|
|
329
|
+
if (typeof value.a !== 'string' || typeof value.b !== 'string')
|
|
330
|
+
return [];
|
|
331
|
+
return [{ id: '', pairKey: typeof value.pairKey === 'string' ? value.pairKey : pairKey(value.a, value.b), a: value.a, b: value.b, queue: 'verify', candidate: { a: value.a, b: value.b }, outcome: 'kept', archived: [], ...(value.gate === undefined ? {} : { gate: value.gate }) }];
|
|
332
|
+
});
|
|
256
333
|
return {
|
|
257
334
|
stopReason,
|
|
258
335
|
scope: {
|
|
@@ -261,8 +338,26 @@ export async function runScan(options) {
|
|
|
261
338
|
},
|
|
262
339
|
pairs,
|
|
263
340
|
totals,
|
|
264
|
-
notes: [...(options.notes ?? [])],
|
|
341
|
+
notes: [...(options.notes ?? []), ...degradedNotes],
|
|
342
|
+
reportOnly: [...pairs.filter(pair => pair.outcome === 'kept' || pair.gate?.level === 'L0' || pair.gate?.level === 'L1'), ...selectionReportOnlyPairs],
|
|
343
|
+
validationFailures: pairs.filter(pair => pair.failure !== undefined && (pair.failure.stage === 'validate' || pair.failure.code === 'merge-validation' || pair.failure.code === 'merge-over-limit')),
|
|
344
|
+
archived: [...new Set(pairs.flatMap(pair => pair.archived))],
|
|
345
|
+
...(pairs.find(pair => pair.outcome === 'merged' && pair.unitId !== undefined) === undefined ? {} : { commit: (() => { const pair = pairs.find(item => item.outcome === 'merged' && item.unitId !== undefined); return { ...(pair.unitId === undefined ? {} : { unitId: pair.unitId }), ...(pair.target === undefined ? {} : { target: pair.target.actual ?? pair.target.requested }), ...(pair.target === undefined ? {} : { created: pair.target.created }) }; })() }),
|
|
346
|
+
...(pairs.every(pair => pair.usage === undefined) ? {} : { usage: { inputTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.inputTokens ?? 0), 0), outputTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.outputTokens ?? 0), 0), totalTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.totalTokens ?? 0), 0), costCny: pairs.reduce((sum, pair) => sum + (pair.usage?.costCny ?? 0), 0) } }),
|
|
347
|
+
timing: { screeningMs: 0, gateMs: 0, agentMs: Math.max(0, options.now() - startedAt), commitMs: 0 },
|
|
348
|
+
criteria: pairs.map(pair => pair.criteria ?? criteriaFrom(pair.gate?.level ?? 'L2', pair.gate?.verdict ?? 0, pair.decision?.shouldMerge === true ? 'merge' : 'keep')),
|
|
265
349
|
...(remaining.length === 0 ? {} : { remaining }),
|
|
350
|
+
...(finalizeRows.length === 0 ? {} : { finalize: async () => {
|
|
351
|
+
for (const item of finalizeRows) {
|
|
352
|
+
const removed = await pairLedger(options.io, options.home).transact(tx => {
|
|
353
|
+
const current = tx.rows().find(entry => entry.id === item.id);
|
|
354
|
+
return current === undefined ? true : tx.remove(item.id, 'committed', { unitId: item.unitId, evidenceSha256: item.evidenceSha256 }, current.version).ok;
|
|
355
|
+
});
|
|
356
|
+
if (!removed)
|
|
357
|
+
throw new Error('row changed before commit record: ' + item.id);
|
|
358
|
+
await dropStage(options.io, options.home, item.id);
|
|
359
|
+
}
|
|
360
|
+
} })
|
|
266
361
|
};
|
|
267
362
|
}
|
|
268
363
|
//# sourceMappingURL=scan-run.js.map
|
|
@@ -9,6 +9,13 @@ export declare class StageToolRegistry {
|
|
|
9
9
|
bind(childId: string, context: StageToolContext): void;
|
|
10
10
|
unbind(childId: string): void;
|
|
11
11
|
clear(): void;
|
|
12
|
+
/** Diagnostic view for a refused tool call; names only, no content. */
|
|
13
|
+
snapshot(exec: unknown): {
|
|
14
|
+
readonly childId?: string;
|
|
15
|
+
readonly bound: number;
|
|
16
|
+
readonly authors: number;
|
|
17
|
+
readonly keys: readonly string[];
|
|
18
|
+
};
|
|
12
19
|
resolve(exec: unknown): StageToolContext | undefined;
|
|
13
20
|
}
|
|
14
21
|
//# sourceMappingURL=stage-tool-registry.d.ts.map
|
|
@@ -9,9 +9,20 @@ export class StageToolRegistry {
|
|
|
9
9
|
clear() {
|
|
10
10
|
this.bindings.clear();
|
|
11
11
|
}
|
|
12
|
+
/** Diagnostic view for a refused tool call; names only, no content. */
|
|
13
|
+
snapshot(exec) {
|
|
14
|
+
const childId = childIdOf(exec);
|
|
15
|
+
return { ...(childId === undefined ? {} : { childId }), bound: this.bindings.size, authors: [...this.bindings.values()].filter(context => context.phase === 'author').length, keys: [...this.bindings.keys()] };
|
|
16
|
+
}
|
|
12
17
|
resolve(exec) {
|
|
13
18
|
const childId = childIdOf(exec);
|
|
14
|
-
|
|
19
|
+
const direct = childId === undefined ? undefined : this.bindings.get(childId);
|
|
20
|
+
if (direct !== undefined)
|
|
21
|
+
return direct;
|
|
22
|
+
// New-child sessions may expose a session id that differs from the channel's childId.
|
|
23
|
+
// Only a single author-phase binding is unambiguous; never guess with concurrent bindings.
|
|
24
|
+
const authors = [...this.bindings.values()].filter(context => context.phase === 'author');
|
|
25
|
+
return authors.length === 1 ? authors[0] : undefined;
|
|
15
26
|
}
|
|
16
27
|
}
|
|
17
28
|
function childIdOf(exec) {
|