@lmzhen/dsh-evolution-merge 0.30.16 → 0.30.18
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 +4 -3
- package/lib/index.js +2325 -855
- package/lib/types/adapters/library.js +4 -1
- package/lib/types/adapters/model.d.ts +31 -0
- package/lib/types/adapters/model.js +46 -0
- package/lib/types/approve-command.d.ts +2 -0
- package/lib/types/approve-command.js +41 -46
- package/lib/types/authoring-flow.d.ts +3 -1
- package/lib/types/authoring-flow.js +15 -5
- package/lib/types/command-surface.d.ts +5 -0
- package/lib/types/command-surface.js +44 -0
- package/lib/types/commit-assembly.d.ts +2 -0
- package/lib/types/commit-assembly.js +7 -2
- package/lib/types/commit-execute.d.ts +7 -1
- package/lib/types/commit-execute.js +40 -10
- package/lib/types/commit-recovery.d.ts +7 -2
- package/lib/types/commit-recovery.js +73 -96
- package/lib/types/contracts.d.ts +1 -1
- package/lib/types/contracts.js +1 -1
- package/lib/types/coordinator.d.ts +3 -2
- package/lib/types/coordinator.js +4 -3
- package/lib/types/decision.js +1 -1
- package/lib/types/doctor.d.ts +7 -0
- package/lib/types/doctor.js +104 -4
- package/lib/types/domain/claim.d.ts +4 -1
- package/lib/types/domain/claim.js +2 -2
- package/lib/types/domain/commit-journal-store.js +5 -1
- package/lib/types/domain/commit-journal.js +1 -1
- package/lib/types/domain/consistency.d.ts +22 -0
- package/lib/types/domain/consistency.js +50 -0
- package/lib/types/domain/ledger.js +10 -1
- package/lib/types/domain/migrate-v3.d.ts +20 -0
- package/lib/types/domain/migrate-v3.js +68 -0
- package/lib/types/domain/row-tx.d.ts +27 -0
- package/lib/types/domain/row-tx.js +31 -0
- package/lib/types/domain/row-v3-bridge.d.ts +6 -0
- package/lib/types/domain/row-v3-bridge.js +47 -0
- package/lib/types/domain/row-v3.d.ts +113 -0
- package/lib/types/domain/row-v3.js +107 -0
- package/lib/types/domain/row.d.ts +1 -0
- package/lib/types/domain/staging-index.d.ts +14 -0
- package/lib/types/domain/staging-index.js +41 -0
- package/lib/types/domain/state-v3.d.ts +15 -0
- package/lib/types/domain/state-v3.js +80 -0
- package/lib/types/facts.d.ts +24 -0
- package/lib/types/facts.js +68 -0
- package/lib/types/gate-filter.d.ts +31 -0
- package/lib/types/gate-filter.js +64 -0
- package/lib/types/index.d.ts +5 -2
- package/lib/types/index.js +16 -26
- package/lib/types/ladder.js +1 -1
- package/lib/types/merge-routes.d.ts +2 -2
- package/lib/types/merge-routes.js +21 -22
- package/lib/types/metrics.d.ts +16 -0
- package/lib/types/metrics.js +27 -0
- package/lib/types/new-child-authoring.d.ts +3 -1
- package/lib/types/new-child-authoring.js +14 -3
- package/lib/types/pending-actions.d.ts +2 -2
- package/lib/types/pending-actions.js +17 -17
- package/lib/types/propose.d.ts +26 -0
- package/lib/types/propose.js +69 -0
- package/lib/types/read-commands.d.ts +2 -2
- package/lib/types/read-commands.js +5 -3
- package/lib/types/resume-command.d.ts +2 -0
- package/lib/types/resume-command.js +4 -1
- package/lib/types/round2-outcome.js +17 -9
- package/lib/types/runtime.d.ts +4 -1
- package/lib/types/runtime.js +99 -29
- package/lib/types/scan-ports.d.ts +3 -1
- package/lib/types/scan-ports.js +2 -0
- package/lib/types/scan-run.js +116 -51
- package/lib/types/screening-port.d.ts +47 -0
- package/lib/types/screening-port.js +93 -0
- package/lib/types/screening.d.ts +30 -0
- package/lib/types/screening.js +75 -0
- package/lib/types/state.d.ts +14 -0
- package/lib/types/state.js +72 -1
- package/lib/types/status.d.ts +3 -0
- package/lib/types/status.js +17 -0
- package/lib/types/target-claim.d.ts +1 -0
- package/lib/types/target-claim.js +63 -18
- package/lib/types/unit-claim.d.ts +5 -0
- package/lib/types/unit-claim.js +15 -0
- package/lib/types/wire-projection.d.ts +36 -0
- package/lib/types/wire-projection.js +69 -0
- package/lib/types/wire.d.ts +254 -0
- package/lib/types/wire.js +3 -0
- package/package.json +8 -4
- package/lib/types/routes.d.ts +0 -70
- package/lib/types/routes.js +0 -78
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { loadMergeState } from "./state.js";
|
|
2
|
+
import { MERGE_COMMAND_SURFACE } from "./command-surface.js";
|
|
3
|
+
const queueOf = (row) => row.state === 'approved' ? 'fuse' : row.queue;
|
|
4
|
+
const isExpired = (row) => row.expired !== undefined;
|
|
5
|
+
export function countsOf(rows) {
|
|
6
|
+
const active = { verify: 0, pending: 0, fuse: 0, skipped: 0 };
|
|
7
|
+
let expired = 0;
|
|
8
|
+
for (const row of rows) {
|
|
9
|
+
if (isExpired(row)) {
|
|
10
|
+
expired += 1;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
active[queueOf(row)] += 1;
|
|
14
|
+
}
|
|
15
|
+
return { active, expired };
|
|
16
|
+
}
|
|
17
|
+
export function toRunView(record, readAt) {
|
|
18
|
+
const state = (record.mergeStatus ?? record.state);
|
|
19
|
+
return {
|
|
20
|
+
wireVersion: 1,
|
|
21
|
+
readAt,
|
|
22
|
+
id: record.id,
|
|
23
|
+
state,
|
|
24
|
+
command: record.command ?? 'scan',
|
|
25
|
+
...(record.stage === undefined ? {} : { stage: record.stage }),
|
|
26
|
+
progress: record.progress === undefined ? null : { done: record.progress.done, total: record.progress.total },
|
|
27
|
+
startedAt: record.startedAt,
|
|
28
|
+
...(record.endedAt === undefined ? {} : { endedAt: record.endedAt }),
|
|
29
|
+
...(record.failure === undefined ? {} : { failure: { stage: 'settle', code: 'run-failed', retryable: false, retryFrom: 'none', detail: record.failure } }),
|
|
30
|
+
...(record.parentRunId === undefined ? {} : { parentRunId: record.parentRunId }),
|
|
31
|
+
...(record.pairKey === undefined ? {} : { currentPairKey: record.pairKey }),
|
|
32
|
+
actions: [
|
|
33
|
+
{ id: 'status', command: '/evolution merge status ' + record.id, confirm: false, enabled: true },
|
|
34
|
+
{ id: 'report', command: '/evolution merge report ' + record.id, confirm: false, enabled: record.resultRef !== undefined },
|
|
35
|
+
{ id: 'cancel', command: '/evolution merge cancel ' + record.id, confirm: false, enabled: state === 'running' },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** GET projection: reads state + runs; never writes. */
|
|
40
|
+
export async function readFacts(options) {
|
|
41
|
+
const loaded = await loadMergeState(options.io, options.home);
|
|
42
|
+
return buildFacts({
|
|
43
|
+
rows: loaded.state.rows,
|
|
44
|
+
runs: options.runs,
|
|
45
|
+
approval: options.approval,
|
|
46
|
+
readAt: options.readAt,
|
|
47
|
+
notes: loaded.ok ? [] : [loaded.note ?? 'state-unreadable'],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function buildFacts(input) {
|
|
51
|
+
const ordered = [...input.runs].sort((a, b) => b.startedAt - a.startedAt);
|
|
52
|
+
const active = ordered.find(run => (run.mergeStatus ?? run.state) === 'running');
|
|
53
|
+
const last = ordered.find(run => (run.mergeStatus ?? run.state) !== 'running');
|
|
54
|
+
const notes = input.notes ?? [];
|
|
55
|
+
return {
|
|
56
|
+
wireVersion: 1,
|
|
57
|
+
readAt: input.readAt,
|
|
58
|
+
approval: { mergeApproval: input.approval },
|
|
59
|
+
counts: countsOf(input.rows),
|
|
60
|
+
active: active === undefined ? null : toRunView(active, input.readAt),
|
|
61
|
+
last: last === undefined ? null : toRunView(last, input.readAt),
|
|
62
|
+
startCommands: { scan: '/evolution merge scan', pair: '/evolution merge pair <a> <b>' },
|
|
63
|
+
fallback: MERGE_COMMAND_SURFACE.fallback,
|
|
64
|
+
notes,
|
|
65
|
+
degraded: notes.length > 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=facts.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate filter: turn lexical/proposed candidates into the candidates scan may process.
|
|
3
|
+
* L0 keeps are dropped unless the deterministic 10% audit re-runs L2 and overrides.
|
|
4
|
+
*/
|
|
5
|
+
import type { MergeCandidate } from './contracts.ts';
|
|
6
|
+
import { type GateCache, type GateFacts, type GateModelPort, type GateVerdict } from './ladder.ts';
|
|
7
|
+
export interface GateFilterOptions {
|
|
8
|
+
readonly facts: readonly GateFacts[];
|
|
9
|
+
readonly model: GateModelPort;
|
|
10
|
+
readonly modelName: string;
|
|
11
|
+
readonly signal: AbortSignal;
|
|
12
|
+
readonly runSeed: string;
|
|
13
|
+
readonly cache?: GateCache;
|
|
14
|
+
readonly auditRate?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface GateDecision {
|
|
17
|
+
readonly candidate: MergeCandidate;
|
|
18
|
+
readonly verdicts: readonly GateVerdict[];
|
|
19
|
+
readonly auditOverride: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface GateFilterResult {
|
|
22
|
+
readonly kept: readonly GateDecision[];
|
|
23
|
+
readonly dropped: readonly GateDecision[];
|
|
24
|
+
readonly errors: readonly {
|
|
25
|
+
readonly candidate: MergeCandidate;
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}[];
|
|
28
|
+
readonly calls: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function filterCandidates(candidates: readonly MergeCandidate[], options: GateFilterOptions): Promise<GateFilterResult>;
|
|
31
|
+
//# sourceMappingURL=gate-filter.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { pairKey } from "./contracts.js";
|
|
2
|
+
import { runLadder, selectedForAudit } from "./ladder.js";
|
|
3
|
+
function withGate(candidate, verdicts, auditOverride) {
|
|
4
|
+
const last = verdicts[verdicts.length - 1];
|
|
5
|
+
if (last === undefined)
|
|
6
|
+
return candidate;
|
|
7
|
+
return {
|
|
8
|
+
...candidate,
|
|
9
|
+
gate: {
|
|
10
|
+
level: last.level,
|
|
11
|
+
verdict: last.verdict,
|
|
12
|
+
reason: last.reason,
|
|
13
|
+
...(last.cached ? { cached: true } : {}),
|
|
14
|
+
...(auditOverride ? { auditOverride: true } : {}),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export async function filterCandidates(candidates, options) {
|
|
19
|
+
const kept = [];
|
|
20
|
+
const dropped = [];
|
|
21
|
+
const errors = [];
|
|
22
|
+
let calls = 0;
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const first = await runLadder({
|
|
25
|
+
level: 'L0', candidate, facts: options.facts, model: options.model,
|
|
26
|
+
modelName: options.modelName, signal: options.signal,
|
|
27
|
+
...(options.cache === undefined ? {} : { cache: options.cache }),
|
|
28
|
+
});
|
|
29
|
+
calls += first.calls;
|
|
30
|
+
if (first.route === 'error') {
|
|
31
|
+
errors.push({ candidate, message: first.error ?? 'gate error' });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (first.route !== 'keep' || first.droppedAt !== 'L0') {
|
|
35
|
+
if (first.route === 'merge' || first.route === 'undecided')
|
|
36
|
+
kept.push({ candidate: withGate(candidate, first.verdicts, false), verdicts: first.verdicts, auditOverride: false });
|
|
37
|
+
else
|
|
38
|
+
dropped.push({ candidate, verdicts: first.verdicts, auditOverride: false });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!selectedForAudit(pairKey(candidate.a, candidate.b), options.runSeed, options.auditRate ?? 0.1)) {
|
|
42
|
+
dropped.push({ candidate, verdicts: first.verdicts, auditOverride: false });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const audit = await runLadder({
|
|
46
|
+
level: 'L0', startLevel: 'L2', candidate, facts: options.facts, model: options.model,
|
|
47
|
+
modelName: options.modelName, signal: options.signal,
|
|
48
|
+
...(options.cache === undefined ? {} : { cache: options.cache }),
|
|
49
|
+
});
|
|
50
|
+
calls += audit.calls;
|
|
51
|
+
if (audit.route === 'merge' || audit.route === 'undecided') {
|
|
52
|
+
const all = [...first.verdicts, ...audit.verdicts];
|
|
53
|
+
kept.push({ candidate: withGate(candidate, all, true), verdicts: all, auditOverride: true });
|
|
54
|
+
}
|
|
55
|
+
else if (audit.route === 'error') {
|
|
56
|
+
errors.push({ candidate, message: audit.error ?? 'audit gate error' });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
dropped.push({ candidate, verdicts: [...first.verdicts, ...audit.verdicts], auditOverride: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { kept, dropped, errors, calls };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=gate-filter.js.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -22,7 +22,11 @@ export interface MergePresentationScope {
|
|
|
22
22
|
}, owner?: unknown) => unknown;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
export
|
|
25
|
+
export interface EngineModelConfig {
|
|
26
|
+
readonly embedder_model: string;
|
|
27
|
+
readonly embedder_model_last: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function engineConfigFromRoot(configuredRoot?: string): EngineModelConfig | undefined;
|
|
26
30
|
export interface MergePresentationContext {
|
|
27
31
|
inject(names: string[], run: (scope: MergePresentationScope) => void): void;
|
|
28
32
|
readonly fiber?: unknown;
|
|
@@ -69,7 +73,6 @@ export * from './new-child-authoring.ts';
|
|
|
69
73
|
export * from './status.ts';
|
|
70
74
|
export * from './skipped.ts';
|
|
71
75
|
export * from './read-commands.ts';
|
|
72
|
-
export * from './routes.ts';
|
|
73
76
|
export * from './merge-routes.ts';
|
|
74
77
|
export * from './merge-command.ts';
|
|
75
78
|
export * from './params.ts';
|
package/lib/types/index.js
CHANGED
|
@@ -4,13 +4,13 @@ import { screen as engineScreen } from '@lmzhen/dsh-evolution-merge-engine';
|
|
|
4
4
|
import { selectWithErrors } from "./adapters/select.js";
|
|
5
5
|
import { createEngineScreening } from "./adapters/engine-screen.js";
|
|
6
6
|
import { createEngineRunner } from "./adapters/model-runner.js";
|
|
7
|
-
import { authorityFrom, createSubagentChannel, DEFAULT_SKILL_LIMITS, evolutionHome, evolutionIoAdapter, newRunRegistry, newSkillLibrary, policyStageLimits
|
|
7
|
+
import { authorityFrom, createSubagentChannel, DEFAULT_SKILL_LIMITS, evolutionHome, evolutionIoAdapter, newRunRegistry, newSkillLibrary, policyStageLimits } from '@lmzhen/dsh-evolution-core';
|
|
8
8
|
import { registerHostRoutes } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
9
|
-
import { loadMergeState,
|
|
9
|
+
import { loadMergeState, transactRows } from "./state.js";
|
|
10
10
|
import { cleanupExpiredStaging } from "./staging.js";
|
|
11
11
|
import { recoverCommitJournals } from "./commit-recovery.js";
|
|
12
12
|
import { readCommitReceipts } from "./commit-receipt.js";
|
|
13
|
-
import {
|
|
13
|
+
import { pairKey, rowIdOf } from "./contracts.js";
|
|
14
14
|
import { makeMergeRoutes } from "./merge-routes.js";
|
|
15
15
|
import { createMergeCommand, runMergeCommand } from "./merge-command.js";
|
|
16
16
|
import { createMergeRuntime } from "./runtime.js";
|
|
@@ -27,15 +27,11 @@ export const Config = z.object({
|
|
|
27
27
|
mergePairTimeoutMinutes: z.number().min(1).default(15),
|
|
28
28
|
mergeModelsRoot: z.string().default(''),
|
|
29
29
|
});
|
|
30
|
-
export function
|
|
31
|
-
const root = configuredRoot
|
|
32
|
-
if (root ===
|
|
30
|
+
export function engineConfigFromRoot(configuredRoot) {
|
|
31
|
+
const root = configuredRoot?.trim() ?? '';
|
|
32
|
+
if (root === '')
|
|
33
33
|
return undefined;
|
|
34
34
|
return {
|
|
35
|
-
models_dir: '',
|
|
36
|
-
offline: true,
|
|
37
|
-
auto_provision: false,
|
|
38
|
-
embed_backend: 'local',
|
|
39
35
|
embedder_model: join(root, 'potion-multilingual-128M-int8'),
|
|
40
36
|
embedder_model_last: join(root, 'potion-multilingual-128M-pca64-int8'),
|
|
41
37
|
};
|
|
@@ -91,7 +87,6 @@ export * from "./new-child-authoring.js";
|
|
|
91
87
|
export * from "./status.js";
|
|
92
88
|
export * from "./skipped.js";
|
|
93
89
|
export * from "./read-commands.js";
|
|
94
|
-
export * from "./routes.js";
|
|
95
90
|
export * from "./merge-routes.js";
|
|
96
91
|
export * from "./merge-command.js";
|
|
97
92
|
export * from "./params.js";
|
|
@@ -123,7 +118,6 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
123
118
|
const home = evolutionHome();
|
|
124
119
|
void loadMergeState(io, home).then(loaded => cleanupExpiredStaging(io, home, Date.now(), new Set(loaded.state.rows.map(row => row.runId).filter((id) => id !== undefined)))).catch((error) => { ctx.logger.warn('merge staging cleanup failed: ' + (error instanceof Error ? error.message : String(error))); });
|
|
125
120
|
const runs = newRunRegistry({ io, home, warn: (message) => { ctx.logger.warn(message); } });
|
|
126
|
-
void runs.load();
|
|
127
121
|
ctx.effect(() => {
|
|
128
122
|
const host = ctx;
|
|
129
123
|
if (typeof host.on !== 'function')
|
|
@@ -138,18 +132,14 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
138
132
|
io, ctx, authority: authorityFrom({ origin: 'subagent' }),
|
|
139
133
|
limits: { ...DEFAULT_SKILL_LIMITS, ...policyStageLimits(policySnapshot?.get?.()) },
|
|
140
134
|
});
|
|
141
|
-
|
|
142
|
-
void recoverCommitJournals(io, home, Date.now(), mergeSkillsRoot, async (name) => {
|
|
143
|
-
const result = await skills.restoreFromArchive(name);
|
|
144
|
-
return result.ok;
|
|
145
|
-
}, (runId) => {
|
|
135
|
+
void runs.load().then(() => recoverCommitJournals(io, home, Date.now(), (runId) => {
|
|
146
136
|
const run = runs.find(runId);
|
|
147
137
|
return run !== undefined && run.state === 'running';
|
|
148
|
-
}).then((reports) => { if (reports.length > 0)
|
|
138
|
+
})).then((reports) => { if (reports.length > 0)
|
|
149
139
|
ctx.logger.info('merge commit recovery: ' + reports.map(report => report.runId + '=' + report.action).join(', ')); }).catch((error) => { ctx.logger.warn('merge commit recovery failed: ' + (error instanceof Error ? error.message : String(error))); });
|
|
150
140
|
const channel = subagentChannelAdapter(createSubagentChannel(ctx));
|
|
151
141
|
const stage = new StageToolRegistry();
|
|
152
|
-
const engineConfig =
|
|
142
|
+
const engineConfig = engineConfigFromRoot(params.mergeModelsRoot);
|
|
153
143
|
const llm = ctx.get('llm');
|
|
154
144
|
const defaultModel = ctx.get('agentDefaultModel');
|
|
155
145
|
const runner = llm === undefined || defaultModel === undefined
|
|
@@ -160,7 +150,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
160
150
|
home,
|
|
161
151
|
engine: { screen: engineScreen },
|
|
162
152
|
select: selectWithErrors,
|
|
163
|
-
cfg: { ...
|
|
153
|
+
cfg: { ...engineConfig, judge: 'deepseek' },
|
|
164
154
|
...(runner === undefined ? {} : { runner }),
|
|
165
155
|
async onSelection(selection) {
|
|
166
156
|
const loaded = await loadMergeState(io, home);
|
|
@@ -168,12 +158,12 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
168
158
|
for (const skipped of selection.skipped) {
|
|
169
159
|
if (loaded.state.rows.some(row => row.pairKey === skipped.pairKey))
|
|
170
160
|
continue;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
const key = pairKey(skipped.a, skipped.b);
|
|
162
|
+
await transactRows(io, home, tx => tx.createSkipped({
|
|
163
|
+
id: rowIdOf(key), pairKey: key, a: skipped.a, b: skipped.b, version: 0,
|
|
164
|
+
createdAt: at, updatedAt: at, origin: 'agent',
|
|
165
|
+
state: 'skipped', reason: skipped.reason ?? 'gate-keep', attempts: 0,
|
|
166
|
+
}).ok);
|
|
177
167
|
}
|
|
178
168
|
},
|
|
179
169
|
});
|
package/lib/types/ladder.js
CHANGED
|
@@ -57,7 +57,7 @@ function payloadFor(level, candidate, facts) {
|
|
|
57
57
|
}
|
|
58
58
|
const SYSTEM = 'Judge whether two skill documents describe the same reusable capability. Shared vocabulary or a shared preamble is not duplication. Return only strict JSON: {"verdict":1|0|-1,"reason":"..."}';
|
|
59
59
|
function extractJson(text) {
|
|
60
|
-
const fenced =
|
|
60
|
+
const fenced = /`\``(?:json)?\s*([\s\S]*?)`\``/i.exec(text);
|
|
61
61
|
const body = fenced?.[1] ?? text;
|
|
62
62
|
const start = body.indexOf('{');
|
|
63
63
|
if (start < 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Four read-only merge routes: view / rows /
|
|
3
|
-
*
|
|
2
|
+
* Four read-only merge routes: view / rows / body / history.
|
|
3
|
+
* The host loads rows and runs; these handlers only project wire envelopes.
|
|
4
4
|
*/
|
|
5
5
|
import type { RunRecord } from '@lmzhen/dsh-evolution-core';
|
|
6
6
|
import { type ConnectionFetchRouteLike, type DecisionAudit } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HOST_ROUTES, json, methodNotAllowed } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { buildFacts } from "./facts.js";
|
|
3
|
+
import { toBodyView, toHistoryView, toQueuePage } from "./wire-projection.js";
|
|
4
4
|
const MERGE_PATHS = HOST_ROUTES.merge;
|
|
5
5
|
const MERGE_BASE = MERGE_PATHS.view.slice(0, -'view'.length);
|
|
6
6
|
export const MERGE_ROUTES = {
|
|
@@ -10,7 +10,8 @@ export const MERGE_ROUTES = {
|
|
|
10
10
|
history: (MERGE_PATHS.history ?? MERGE_BASE + 'history'),
|
|
11
11
|
};
|
|
12
12
|
export function makeMergeRoutes(deps, _audit) {
|
|
13
|
-
const absent = () => json(
|
|
13
|
+
const absent = () => json(404, { wireVersion: 1, code: 'E-325', message: 'merge service is not mounted' });
|
|
14
|
+
const bad = (message) => json(400, { wireVersion: 1, code: 'merge-bad-query', message });
|
|
14
15
|
const get = (request) => (request.method === 'GET' ? null : methodNotAllowed(['GET']));
|
|
15
16
|
const url = (request) => new URL(request.url, 'http://local');
|
|
16
17
|
return [
|
|
@@ -25,12 +26,10 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
25
26
|
const source = deps.getSource();
|
|
26
27
|
if (source === undefined)
|
|
27
28
|
return absent();
|
|
29
|
+
const readAt = Date.now();
|
|
30
|
+
const rows = await source.rows();
|
|
28
31
|
const runs = await source.runs(undefined, 20);
|
|
29
|
-
|
|
30
|
-
return json(200, {
|
|
31
|
-
ok: true,
|
|
32
|
-
data: buildMergeView(await source.rows(), source.commands(), latestMerge === undefined ? undefined : toRunFact(latestMerge), source.approval?.()),
|
|
33
|
-
});
|
|
32
|
+
return json(200, buildFacts({ rows, runs, approval: source.approval?.() === true, readAt }));
|
|
34
33
|
},
|
|
35
34
|
},
|
|
36
35
|
{
|
|
@@ -46,15 +45,16 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
46
45
|
return absent();
|
|
47
46
|
const query = url(request).searchParams;
|
|
48
47
|
const queue = query.get('queue') ?? 'verify';
|
|
49
|
-
if (queue !== 'verify' && queue !== 'pending' && queue !== 'fuse' && queue !== 'skipped')
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
if (queue !== 'verify' && queue !== 'pending' && queue !== 'fuse' && queue !== 'skipped')
|
|
49
|
+
return bad('queue must be verify/pending/fuse/skipped');
|
|
50
|
+
const expiredOnly = query.get('expired') === '1';
|
|
51
|
+
if (expiredOnly && queue !== 'pending')
|
|
52
|
+
return bad('expired is only allowed for pending');
|
|
53
53
|
const limit = Number.parseInt(query.get('limit') ?? '50', 10);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
54
|
+
const offset = Number.parseInt(query.get('cursor') ?? '0', 10);
|
|
55
|
+
const rows = await source.rows();
|
|
56
|
+
const page = toQueuePage(rows, queue, expiredOnly, Date.now(), Number.isFinite(limit) ? limit : 50);
|
|
57
|
+
return json(200, Number.isFinite(offset) && offset > 0 ? { ...page, rows: page.rows.slice(offset) } : page);
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
{
|
|
@@ -71,13 +71,12 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
71
71
|
const query = url(request).searchParams;
|
|
72
72
|
const id = query.get('id');
|
|
73
73
|
const side = query.get('side');
|
|
74
|
-
if (id === null || id === '' || (side !== 'a' && side !== 'b'))
|
|
75
|
-
return
|
|
76
|
-
}
|
|
74
|
+
if (id === null || id === '' || (side !== 'a' && side !== 'b'))
|
|
75
|
+
return bad('id and side=a|b are required');
|
|
77
76
|
const content = await source.body(id, side);
|
|
78
77
|
return content === null
|
|
79
|
-
? json(
|
|
80
|
-
: json(200,
|
|
78
|
+
? json(200, { wireVersion: 1, code: 'merge-body-missing', message: 'merge body not found: ' + id })
|
|
79
|
+
: json(200, toBodyView(id, side === 'a' ? 'live' : 'archive', content, Date.now()));
|
|
81
80
|
},
|
|
82
81
|
},
|
|
83
82
|
{
|
|
@@ -91,7 +90,7 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
91
90
|
const source = deps.getSource();
|
|
92
91
|
if (source === undefined)
|
|
93
92
|
return absent();
|
|
94
|
-
return json(200,
|
|
93
|
+
return json(200, toHistoryView(await source.history(), Date.now()));
|
|
95
94
|
},
|
|
96
95
|
},
|
|
97
96
|
];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** RG6 merge metrics projection: stable names/labels from state + runs. */
|
|
2
|
+
import type { RunRecord } from '@lmzhen/dsh-evolution-core';
|
|
3
|
+
import type { MergeRow } from './contracts.ts';
|
|
4
|
+
export interface MetricSample {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
7
|
+
readonly value: number;
|
|
8
|
+
}
|
|
9
|
+
export interface MergeMetricCounters {
|
|
10
|
+
readonly lockWaitMs?: number;
|
|
11
|
+
readonly claimConflict?: number;
|
|
12
|
+
readonly recoveryFail?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const MERGE_METRIC_NAMES: readonly ["merge_queue_depth", "merge_run_total", "merge_run_duration_ms", "merge_lock_wait_ms", "merge_claim_conflict_total", "merge_recovery_fail_total"];
|
|
15
|
+
export declare function buildMergeMetrics(rows: readonly MergeRow[], runs: readonly RunRecord[], counters?: MergeMetricCounters): readonly MetricSample[];
|
|
16
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { countsOf } from "./facts.js";
|
|
2
|
+
export const MERGE_METRIC_NAMES = [
|
|
3
|
+
'merge_queue_depth',
|
|
4
|
+
'merge_run_total',
|
|
5
|
+
'merge_run_duration_ms',
|
|
6
|
+
'merge_lock_wait_ms',
|
|
7
|
+
'merge_claim_conflict_total',
|
|
8
|
+
'merge_recovery_fail_total',
|
|
9
|
+
];
|
|
10
|
+
export function buildMergeMetrics(rows, runs, counters = {}) {
|
|
11
|
+
const counts = countsOf(rows);
|
|
12
|
+
const samples = [];
|
|
13
|
+
for (const queue of ['verify', 'pending', 'fuse', 'skipped'])
|
|
14
|
+
samples.push({ name: 'merge_queue_depth', labels: { queue, kind: 'active' }, value: counts.active[queue] });
|
|
15
|
+
samples.push({ name: 'merge_queue_depth', labels: { queue: 'pending', kind: 'expired' }, value: counts.expired });
|
|
16
|
+
for (const run of runs) {
|
|
17
|
+
const state = run.mergeStatus ?? run.state;
|
|
18
|
+
samples.push({ name: 'merge_run_total', labels: { state }, value: 1 });
|
|
19
|
+
if (run.endedAt !== undefined)
|
|
20
|
+
samples.push({ name: 'merge_run_duration_ms', labels: { state }, value: Math.max(0, run.endedAt - run.startedAt) });
|
|
21
|
+
}
|
|
22
|
+
samples.push({ name: 'merge_lock_wait_ms', labels: {}, value: counters.lockWaitMs ?? 0 });
|
|
23
|
+
samples.push({ name: 'merge_claim_conflict_total', labels: {}, value: counters.claimConflict ?? 0 });
|
|
24
|
+
samples.push({ name: 'merge_recovery_fail_total', labels: {}, value: counters.recoveryFail ?? 0 });
|
|
25
|
+
return samples;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Round2 with a NEW child (approval after the pending window). The child gets the round2
|
|
3
3
|
* prompt and writes the staging tree; the plugin then runs the same gate/commit path.
|
|
4
4
|
*/
|
|
5
|
-
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
5
|
+
import type { EvolutionIoLike, UnitTransaction } from '@lmzhen/dsh-evolution-core';
|
|
6
6
|
import type { MergeDecision, MergeRow } from './contracts.ts';
|
|
7
7
|
import type { SkillCard } from './skill-card.ts';
|
|
8
8
|
import type { ConversationChannelLike } from './channel-port.ts';
|
|
@@ -21,8 +21,10 @@ export interface NewChildAuthoringOptions {
|
|
|
21
21
|
readonly timeoutMs: number;
|
|
22
22
|
readonly commitPort: CommitPort;
|
|
23
23
|
readonly now: () => number;
|
|
24
|
+
readonly isBlocked?: (name: string) => boolean | Promise<boolean>;
|
|
24
25
|
readonly onChild?: (context: StageToolContext) => void;
|
|
25
26
|
readonly onChildClosed?: (childId: string) => void;
|
|
27
|
+
readonly unit?: UnitTransaction | undefined;
|
|
26
28
|
}
|
|
27
29
|
export declare function runNewChildAuthoring(options: NewChildAuthoringOptions, row: MergeRow, decision: MergeDecision, signal: AbortSignal): Promise<ScanRound2Outcome>;
|
|
28
30
|
export declare function createNewChildRound2(options: Omit<NewChildAuthoringOptions, 'runId'>): (row: MergeRow, decision: MergeDecision, signal: AbortSignal, runId: string) => Promise<ScanRound2Outcome>;
|
|
@@ -6,8 +6,15 @@ import { validateStagedTree, hasBlockingFinding } from "./rules.js";
|
|
|
6
6
|
import { commitStagedRun } from "./commit-execute.js";
|
|
7
7
|
export async function runNewChildAuthoring(options, row, decision, signal) {
|
|
8
8
|
const targetCard = options.cards.find(card => card.name === decision.target);
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const createMode = decision.mode === 'create-umbrella';
|
|
10
|
+
if (!createMode && targetCard !== undefined) {
|
|
11
|
+
if (targetCard.managed !== true)
|
|
12
|
+
return { kind: 'failed', failure: { stage: 'author', code: 'target-not-managed', message: 'target is not family-managed: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
13
|
+
if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
|
|
14
|
+
return { kind: 'failed', failure: { stage: 'author', code: 'target-protected', message: 'target is protected: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
15
|
+
}
|
|
16
|
+
if (!createMode && await options.isBlocked?.(decision.target) === true)
|
|
17
|
+
return { kind: 'failed', failure: { stage: 'author', code: 'target-blocked', message: 'target is blocked by curator: ' + decision.target, retryable: false, retryFrom: 'none' } };
|
|
11
18
|
const stageKey = row.stagingRef ?? stageKeyOf(options.runId, pairKey(row.a, row.b));
|
|
12
19
|
const existing = await readStaging(options.io, options.home, stageKey);
|
|
13
20
|
if (existing.kind === 'present') {
|
|
@@ -58,7 +65,11 @@ export async function runNewChildAuthoring(options, row, decision, signal) {
|
|
|
58
65
|
const findings = validateStagedTree(tree.value);
|
|
59
66
|
if (hasBlockingFinding(findings))
|
|
60
67
|
return { kind: 'failed', failure: { stage: 'validate', code: 'rule-gate', message: 'staged tree failed the rule gate', retryable: true, retryFrom: 'agent2' } };
|
|
61
|
-
const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
|
|
68
|
+
const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
|
|
69
|
+
origin: 'foreground', summary: 'merge ' + row.a + ' + ' + row.b, now: options.now,
|
|
70
|
+
...(options.isBlocked === undefined ? {} : { isBlocked: options.isBlocked }),
|
|
71
|
+
...(options.unit === undefined ? {} : { unit: options.unit }),
|
|
72
|
+
});
|
|
62
73
|
if (!commit.ok)
|
|
63
74
|
return { kind: 'failed', failure: { stage: 'commit', code: 'commit-failed', message: commit.message, retryable: true, retryFrom: 'agent2' } };
|
|
64
75
|
const receipt = commit.receipt;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pending approval actions. The row lookup and
|
|
3
|
-
*
|
|
2
|
+
* Pending approval actions. The row lookup and transition happen in one v3
|
|
3
|
+
* transactRows transaction, so two clients approving the same row cannot both win.
|
|
4
4
|
*/
|
|
5
5
|
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
6
6
|
import type { MergeRow } from './contracts.ts';
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { v3ToLegacy } from "./domain/row-v3-bridge.js";
|
|
2
|
+
import { transactRows } from "./state.js";
|
|
3
3
|
async function act(io, home, now, id, type) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
return {
|
|
4
|
+
void now;
|
|
5
|
+
return await transactRows(io, home, tx => {
|
|
6
|
+
const row = tx.rows().find(item => item.id === id);
|
|
7
|
+
if (row === undefined)
|
|
8
|
+
return { ok: false, reason: 'not-found', message: 'pending row not found: ' + id };
|
|
9
|
+
if (row.state !== 'pending')
|
|
10
|
+
return { ok: false, reason: 'not-pending', message: 'row is not pending: ' + row.state };
|
|
11
11
|
const event = type === 'approve'
|
|
12
|
-
? { type: '
|
|
13
|
-
: { type: '
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
16
|
-
return {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return {
|
|
12
|
+
? { type: 'approved', runId: row.runId ?? 'approve:' + id }
|
|
13
|
+
: { type: 'skipped', reason: 'rejected', origin: 'human' };
|
|
14
|
+
const moved = tx.transition(id, event, row.version);
|
|
15
|
+
if (!moved.ok)
|
|
16
|
+
return { ok: false, reason: 'invalid', message: moved.message };
|
|
17
|
+
if (moved.row === null)
|
|
18
|
+
return { ok: false, reason: 'invalid', message: 'transition produced no row' };
|
|
19
|
+
return { ok: true, row: v3ToLegacy(moved.row) };
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
export async function approvePending(io, home, now, id) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C3 semantic proposer: one library-level model call (sharded when the library is large)
|
|
3
|
+
* that proposes candidate pairs. It never judges merge quality; L0–L2 does that.
|
|
4
|
+
*/
|
|
5
|
+
import type { MergeCandidate } from './contracts.ts';
|
|
6
|
+
import type { SkillCard } from './screening.ts';
|
|
7
|
+
export interface ModelPort {
|
|
8
|
+
callJson(request: {
|
|
9
|
+
readonly system: string;
|
|
10
|
+
readonly user: string;
|
|
11
|
+
readonly maxTokens: number;
|
|
12
|
+
readonly signal: AbortSignal;
|
|
13
|
+
}): Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface ProposeOptions {
|
|
16
|
+
readonly shardSize?: number;
|
|
17
|
+
readonly maxPairs?: number;
|
|
18
|
+
readonly signal: AbortSignal;
|
|
19
|
+
}
|
|
20
|
+
export interface ProposeResult {
|
|
21
|
+
readonly candidates: readonly MergeCandidate[];
|
|
22
|
+
readonly shards: number;
|
|
23
|
+
readonly degraded: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function proposePairs(cards: readonly SkillCard[], port: ModelPort, options: ProposeOptions): Promise<ProposeResult>;
|
|
26
|
+
//# sourceMappingURL=propose.d.ts.map
|