@lmzhen/dsh-evolution-merge 0.46.0 → 0.48.0

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.
Files changed (81) hide show
  1. package/lib/index.js +248 -1224
  2. package/lib/types/adapters/engine-screen.d.ts +0 -3
  3. package/lib/types/authoring-flow.d.ts +5 -0
  4. package/lib/types/authoring-flow.js +17 -10
  5. package/lib/types/commit-execute.js +1 -22
  6. package/lib/types/contracts.d.ts +0 -1
  7. package/lib/types/contracts.js +0 -1
  8. package/lib/types/doctor.d.ts +0 -1
  9. package/lib/types/doctor.js +2 -31
  10. package/lib/types/domain/consistency.d.ts +0 -1
  11. package/lib/types/domain/consistency.js +2 -11
  12. package/lib/types/domain/merge-limits.d.ts +0 -10
  13. package/lib/types/domain/merge-limits.js +0 -3
  14. package/lib/types/domain/merge-snapshot.js +4 -6
  15. package/lib/types/domain/pair-ledger.d.ts +0 -1
  16. package/lib/types/domain/row-v3.d.ts +0 -45
  17. package/lib/types/domain/row-v3.js +0 -68
  18. package/lib/types/domain/row.d.ts +2 -84
  19. package/lib/types/domain/row.js +1 -110
  20. package/lib/types/domain/run-coordinator.d.ts +0 -24
  21. package/lib/types/domain/run-coordinator.js +0 -1
  22. package/lib/types/domain/stage-aggregate.d.ts +0 -42
  23. package/lib/types/domain/stage-aggregate.js +1 -116
  24. package/lib/types/domain/state.d.ts +1 -53
  25. package/lib/types/domain/state.js +0 -55
  26. package/lib/types/domain/undo-journal.d.ts +4 -4
  27. package/lib/types/domain/undo-journal.js +5 -7
  28. package/lib/types/index.d.ts +0 -1
  29. package/lib/types/index.js +0 -6
  30. package/lib/types/merge-routes.d.ts +5 -21
  31. package/lib/types/merge-routes.js +53 -93
  32. package/lib/types/new-child-authoring.js +4 -10
  33. package/lib/types/prompts.d.ts +0 -1
  34. package/lib/types/prompts.js +0 -1
  35. package/lib/types/read-commands.d.ts +0 -1
  36. package/lib/types/read-commands.js +0 -4
  37. package/lib/types/recovery-projection.js +0 -3
  38. package/lib/types/round2-outcome.js +15 -37
  39. package/lib/types/scan-run.js +0 -3
  40. package/lib/types/skipped.d.ts +1 -2
  41. package/lib/types/skipped.js +0 -8
  42. package/lib/types/staging.d.ts +0 -2
  43. package/lib/types/staging.js +0 -9
  44. package/lib/types/state.d.ts +1 -39
  45. package/lib/types/state.js +2 -78
  46. package/lib/types/surface/merge-view.d.ts +1 -2
  47. package/lib/types/surface/merge-view.js +1 -2
  48. package/lib/types/undo-operation.js +0 -9
  49. package/lib/types/unit-protocol.d.ts +6 -0
  50. package/lib/types/unit-protocol.js +72 -102
  51. package/package.json +4 -4
  52. package/lib/types/commit-recovery.d.ts +0 -14
  53. package/lib/types/commit-recovery.js +0 -96
  54. package/lib/types/coordinator.d.ts +0 -77
  55. package/lib/types/coordinator.js +0 -137
  56. package/lib/types/domain/commit-journal-store.d.ts +0 -6
  57. package/lib/types/domain/commit-journal-store.js +0 -26
  58. package/lib/types/domain/commit-journal.d.ts +0 -16
  59. package/lib/types/domain/commit-journal.js +0 -26
  60. package/lib/types/domain/indexes.d.ts +0 -21
  61. package/lib/types/domain/indexes.js +0 -15
  62. package/lib/types/domain/merge-report.d.ts +0 -41
  63. package/lib/types/domain/merge-report.js +0 -2
  64. package/lib/types/domain/pair-index-store.d.ts +0 -6
  65. package/lib/types/domain/pair-index-store.js +0 -25
  66. package/lib/types/domain/pair-index.d.ts +0 -9
  67. package/lib/types/domain/pair-index.js +0 -28
  68. package/lib/types/domain/resolve-pair.d.ts +0 -4
  69. package/lib/types/domain/resolve-pair.js +0 -4
  70. package/lib/types/domain/row-tx.d.ts +0 -27
  71. package/lib/types/domain/row-tx.js +0 -31
  72. package/lib/types/domain/run-record.d.ts +0 -45
  73. package/lib/types/domain/run-record.js +0 -24
  74. package/lib/types/domain/state-v3.d.ts +0 -4
  75. package/lib/types/domain/state-v3.js +0 -4
  76. package/lib/types/facts.d.ts +0 -26
  77. package/lib/types/facts.js +0 -65
  78. package/lib/types/metrics.d.ts +0 -16
  79. package/lib/types/metrics.js +0 -27
  80. package/lib/types/wire-projection.d.ts +0 -36
  81. package/lib/types/wire-projection.js +0 -77
@@ -37,9 +37,6 @@ export interface CandidateSelection {
37
37
  readonly reportOnly: readonly unknown[];
38
38
  readonly candidatePairs?: number;
39
39
  }
40
- export type ScreeningCandidates = readonly MergeCandidate[] & {
41
- readonly candidatePairs?: number;
42
- };
43
40
  export interface EngineScreeningDeps {
44
41
  readonly io: EvolutionIoLike;
45
42
  readonly home: string;
@@ -60,5 +60,10 @@ export type AuthoringResult = {
60
60
  readonly findings?: readonly RuleFinding[];
61
61
  readonly lengths?: Readonly<Record<string, number>>;
62
62
  };
63
+ /** The target facts both authoring entries may refuse on before writing anything. */
64
+ export declare function targetAdmission(target: string, targetCard: Pick<SkillCard, 'managed' | 'protectionUnknown' | 'protectedBy'> | undefined, createMode: boolean, isBlocked?: (name: string) => boolean | Promise<boolean>): Promise<{
65
+ readonly code: string;
66
+ readonly message: string;
67
+ } | null>;
63
68
  export declare function runAuthoring(options: RunAuthoringOptions): Promise<AuthoringResult>;
64
69
  //# sourceMappingURL=authoring-flow.d.ts.map
@@ -6,9 +6,21 @@ import { DEFAULT_SKILL_LIMITS } from '@lmzhen/dsh-evolution-core';
6
6
  import { limitsSnapshotOf } from "./domain/merge-limits.js";
7
7
  import { buildValidationPlan } from "./domain/validation-plan.js";
8
8
  import { manifestFilesOf, missingManifestFile, parseAuthoringManifest } from "./domain/authoring-manifest.js";
9
+ /** The target facts both authoring entries may refuse on before writing anything. */
10
+ export async function targetAdmission(target, targetCard, createMode, isBlocked) {
11
+ if (target.includes('/') || target.includes(':'))
12
+ return { code: 'target-invalid', message: 'target must be a bare skill name: ' + target };
13
+ if (!createMode && targetCard !== undefined) {
14
+ if (targetCard.managed !== true)
15
+ return { code: 'target-not-managed', message: 'target is not family-managed: ' + target };
16
+ if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
17
+ return { code: 'target-protected', message: 'target is protected (' + (targetCard.protectedBy ?? 'unknown') + '): ' + target };
18
+ }
19
+ if (!createMode && await isBlocked?.(target) === true)
20
+ return { code: 'target-blocked', message: 'target is blocked by curator: ' + target };
21
+ return null;
22
+ }
9
23
  export async function runAuthoring(options) {
10
- if (options.target.includes('/') || options.target.includes(':'))
11
- return { ok: false, code: 'target-invalid', message: 'target must be a bare skill name: ' + options.target };
12
24
  const targetCard = options.cards.find(card => card.name === options.target);
13
25
  // A decision can name a fresh target yet say merge-into-existing (the model's vocabulary is
14
26
  // about intent, not existence). The target card is the authoritative existence fact: when it is
@@ -17,14 +29,9 @@ export async function runAuthoring(options) {
17
29
  const requestedMode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
18
30
  const mode = requestedMode === 'merge-into-existing' && targetCard === undefined ? 'create-umbrella' : requestedMode;
19
31
  const createMode = mode === 'create-umbrella';
20
- if (!createMode && targetCard !== undefined) {
21
- if (targetCard.managed !== true)
22
- return { ok: false, code: 'target-not-managed', message: 'target is not family-managed: ' + options.target };
23
- if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
24
- return { ok: false, code: 'target-protected', message: 'target is protected (' + (targetCard.protectedBy ?? 'unknown') + '): ' + options.target };
25
- }
26
- if (!createMode && await options.isBlocked?.(options.target) === true)
27
- return { ok: false, code: 'target-blocked', message: 'target is blocked by curator: ' + options.target };
32
+ const targetRefusal = await targetAdmission(options.target, targetCard, createMode, options.isBlocked);
33
+ if (targetRefusal !== null)
34
+ return { ok: false, code: targetRefusal.code, message: targetRefusal.message };
28
35
  const stageKey = options.stageKey ?? options.runId;
29
36
  if (options.reuseStaging === true) {
30
37
  if (!await setStagingTarget(options.io, options.home, stageKey, options.target, mode))
@@ -1,7 +1,6 @@
1
1
  import { assembleStagedCommit } from "./commit-assembly.js";
2
2
  import { writeCommitReceipt } from "./commit-receipt.js";
3
3
  import { pairKey } from "./contracts.js";
4
- import { writeJournal } from "./domain/commit-journal-store.js";
5
4
  import { evidenceSha256 as digestEvidence } from "./domain/evidence-hash.js";
6
5
  export async function commitStagedRun(io, home, runId, port, options) {
7
6
  const assembled = await assembleStagedCommit(io, home, runId, port, {
@@ -17,7 +16,6 @@ export async function commitStagedRun(io, home, runId, port, options) {
17
16
  if (await options.isBlocked?.(assembled.plan.target) === true) {
18
17
  return { ok: false, message: 'target is blocked by curator: ' + assembled.plan.target, plan: assembled.plan };
19
18
  }
20
- const journal = { runId, target: assembled.plan.target, create: !assembled.plan.targetExists, sources: assembled.plan.sources, phase: 'prepared', updatedAt: new Date(options.now()).toISOString() };
21
19
  let result;
22
20
  let evidenceSha256;
23
21
  let warning;
@@ -29,22 +27,12 @@ export async function commitStagedRun(io, home, runId, port, options) {
29
27
  // P1-6: the unit's run identity is the canonical parent run, not the stage key.
30
28
  const unitRunId = options.parentRunId ?? runId;
31
29
  const unitPlan = { runId: unitRunId, pairKey: pairKey(assembled.plan.target, first), target: assembled.plan.target, targetExists: assembled.plan.targetExists, sources: assembled.plan.sources, body: assembled.plan.body, supportFiles: assembled.plan.supportFiles, expectedTargetBody: assembled.plan.expectedTargetBody, expectedSourceBodies: assembled.plan.expectedSourceBodies, origin: options.origin, summary: options.summary, ...(options.limits === undefined ? {} : { skillLimits: options.limits }), ...(options.validationPlan === undefined ? {} : { validationPlan: options.validationPlan }), ...(options.targetRevision === undefined ? {} : { targetRevision: options.targetRevision }), ...(options.sourceRevisions === undefined ? {} : { sourceRevisions: options.sourceRevisions }), ...(options.supportDigest === undefined ? {} : { supportDigest: options.supportDigest }) };
32
- try {
33
- await writeJournal(io, home, journal);
34
- }
35
- catch { /* compat mirror; unit journal is authority */ }
36
30
  const prepared = await transaction.prepare(unitPlan);
37
31
  if (!prepared.ok)
38
32
  return { ok: false, message: prepared.message, plan: assembled.plan };
39
33
  const committed = await transaction.commit(prepared.prepared, options.signal === undefined ? undefined : { signal: options.signal });
40
- if (!committed.ok) {
41
- try {
42
- await writeJournal(io, home, { ...journal, phase: 'failed', updatedAt: new Date(options.now()).toISOString() });
43
- }
44
- catch { }
45
- ;
34
+ if (!committed.ok)
46
35
  return { ok: false, message: committed.message, plan: assembled.plan, ...(committed.code === 'target-changed' || committed.code === 'source-changed' || committed.code === 'support-changed' ? { code: committed.code } : {}) };
47
- }
48
36
  const unitId = committed.evidence.unitId;
49
37
  if (committed.evidence.unitId !== prepared.prepared.unitId && !prepared.prepared.unitId.startsWith('legacy:'))
50
38
  return { ok: false, message: 'unit evidence does not match prepared unitId', plan: assembled.plan };
@@ -60,17 +48,8 @@ export async function commitStagedRun(io, home, runId, port, options) {
60
48
  result = { ok: true, message: 'merged', unitId, archived: committed.evidence.sources.filter(source => source.archived).map(source => source.name), mutations: committed.evidence.mutations };
61
49
  }
62
50
  if (!result.ok) {
63
- try {
64
- await writeJournal(io, home, { ...journal, phase: 'failed', updatedAt: new Date(options.now()).toISOString() });
65
- }
66
- catch { }
67
51
  return { ok: false, message: result.message, plan: assembled.plan };
68
52
  }
69
- const committed = { ...journal, phase: 'committed', updatedAt: new Date(options.now()).toISOString(), ...(result.unitId === undefined ? {} : { unitId: result.unitId }) };
70
- try {
71
- await writeJournal(io, home, committed);
72
- }
73
- catch { }
74
53
  if (result.unitId === undefined)
75
54
  return { ok: true, plan: assembled.plan, result, warning: 'commit landed without a unitId; receipt not written' };
76
55
  const receipt = {
@@ -10,7 +10,6 @@ export type MergeQueue = 'verify' | 'pending' | 'fuse' | 'skipped';
10
10
  export type MergeStage = 'verify' | 'decide' | 'author' | 'commit';
11
11
  export type MergeCommand = 'scan' | 'pair' | 'approve' | 'resume';
12
12
  export declare const MERGE_PAIR_STATES: readonly MergePairState[];
13
- export declare const MERGE_QUEUES: readonly MergeQueue[];
14
13
  /** User-facing queue of one data state. verify/fuse share retry, not two tabs. */
15
14
  export declare function queueOf(state: MergePairState): MergeQueue;
16
15
  /** Coarse stage chip; skipped has no active stage. */
@@ -6,7 +6,6 @@
6
6
  */
7
7
  export const MERGE_STATE_VERSION = 3;
8
8
  export const MERGE_PAIR_STATES = ['verify', 'pending', 'approved', 'fuse', 'skipped'];
9
- export const MERGE_QUEUES = ['verify', 'pending', 'fuse', 'skipped'];
10
9
  /** User-facing queue of one data state. verify/fuse share retry, not two tabs. */
11
10
  export function queueOf(state) {
12
11
  switch (state) {
@@ -1,7 +1,6 @@
1
1
  import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
2
  import { type ReconcileReport } from './domain/reconcile.ts';
3
3
  import type { DoctorView } from './wire.ts';
4
- export declare const MERGE_DOCTOR_SCHEMA_VERSION = 3;
5
4
  /** The global doctor's merge line; built from the DoctorView envelope, never from raw files. */
6
5
  export interface MergeDoctorFact {
7
6
  readonly status: 'ok' | 'warn' | 'fail' | 'unknown';
@@ -12,11 +12,9 @@ import { readUnitJournal } from '@lmzhen/dsh-evolution-core/write';
12
12
  import { migrationJournalPath, migrationReportPath, v2BackupPath } from "./domain/state-bootstrap.js";
13
13
  import { readMergeSnapshot } from "./domain/merge-snapshot.js";
14
14
  import { validateArtifacts } from "./domain/consistency.js";
15
- import { readJournal } from "./domain/commit-journal-store.js";
16
15
  import { readCommitReceipt } from "./commit-receipt.js";
17
16
  import { stagingRoot } from "./staging.js";
18
17
  import { readReconcileReport } from "./domain/reconcile.js";
19
- export const MERGE_DOCTOR_SCHEMA_VERSION = 3;
20
18
  /** Compose the one-line global-doctor fact from buildDoctorView + the last reconcile report. */
21
19
  export function mergeDoctorFact(view, report) {
22
20
  const counts = view.counts;
@@ -65,15 +63,6 @@ export async function mergeDoctor(io, home, configuredRoot) {
65
63
  status: snapshot.blocked === undefined || !hasState ? 'ok' : 'warn',
66
64
  detail: snapshot.blocked === undefined ? 'pairs ' + String(snapshot.rows.length) + ';verify ' + String(counts.verify) + ' / pending ' + String(counts.pending) + ' / fuse ' + String(counts.fuse) + ' / skipped ' + String(counts.skipped) + ' / expired ' + String(snapshot.doctor.counts.expired) : snapshot.blocked.code + ': ' + snapshot.blocked.message,
67
65
  });
68
- const recoveryRuns = new Set();
69
- try {
70
- const raw = await io.readText(join(home, 'merge', 'recovery.json'));
71
- if (raw !== null)
72
- for (const report of JSON.parse(raw))
73
- if (report.ok === true && report.runId !== undefined)
74
- recoveryRuns.add(report.runId);
75
- }
76
- catch { }
77
66
  const invalid = [];
78
67
  if (snapshot.blocked !== undefined)
79
68
  invalid.push({ id: 'state', reason: snapshot.blocked.code });
@@ -84,10 +73,10 @@ export async function mergeDoctor(io, home, configuredRoot) {
84
73
  const run = runId === undefined ? 'none' : row.state === 'pending' ? 'awaiting-approval' : 'running';
85
74
  const staging = item.stage === null ? (row.state === 'fuse' ? 'missing' : 'none') : item.stage.state === 'present' || item.stage.state === 'reserved' ? 'kept' : row.state === 'fuse' ? 'missing' : 'none';
86
75
  const receipt = runId === undefined ? null : await readCommitReceipt(io, home, runId);
87
- const phase = receipt !== null ? await readUnitJournal(io, home, receipt.unitId) : runId === undefined ? null : await readJournal(io, home, runId);
76
+ const phase = receipt === null ? null : await readUnitJournal(io, home, receipt.unitId);
88
77
  const journal = phase === null ? 'none' : phase.phase === 'prepared' || phase.phase === 'target-pending' ? 'prepared' : phase.phase === 'target-written' || phase.phase === 'sources-archived' || phase.phase === 'support-pending' || phase.phase === 'source-archiving' ? 'committing' : phase.phase === 'committed' ? 'committed' : phase.phase === 'rolled-back' ? 'rolled-back' : 'failed';
89
78
  const evidence = receipt === null ? 'none' : 'receipt';
90
- const result = validateArtifacts({ row: artifactRow, run, staging, journal, evidence, activeRuns: run === 'running' || run === 'awaiting-approval' ? 1 : 0, ...(runId !== undefined && recoveryRuns.has(runId) ? { recovery: true } : {}) });
79
+ const result = validateArtifacts({ row: artifactRow, run, staging, journal, evidence, activeRuns: run === 'running' || run === 'awaiting-approval' ? 1 : 0 });
91
80
  if (!result.ok)
92
81
  invalid.push({ id: row.id, reason: result.reason ?? 'not-allowed' });
93
82
  }
@@ -179,24 +168,6 @@ export async function mergeDoctor(io, home, configuredRoot) {
179
168
  status: 'ok',
180
169
  detail: stagingRoot(home, '<runId>'),
181
170
  });
182
- try {
183
- const raw = await io.readText(join(home, 'merge', 'recovery.json'));
184
- if (raw !== null) {
185
- const reports = JSON.parse(raw);
186
- const failed = reports.filter(report => report.ok !== true);
187
- rows.push({
188
- key: 'recovery',
189
- label: '提交恢复',
190
- status: failed.length === 0 ? 'ok' : 'warn',
191
- detail: failed.length === 0
192
- ? String(reports.length) + ' 条恢复报告,无待处理'
193
- : String(failed.length) + ' 条待处理:' + failed.map(report => (report.runId ?? '?') + '=' + (report.action ?? '?')).join(', '),
194
- });
195
- }
196
- }
197
- catch {
198
- rows.push({ key: 'recovery', label: '提交恢复', status: 'unknown', detail: '无法读取 merge/recovery.json' });
199
- }
200
171
  return rows;
201
172
  }
202
173
  //# sourceMappingURL=doctor.js.map
@@ -12,7 +12,6 @@ export interface ArtifactState {
12
12
  readonly evidence: EvidenceState;
13
13
  readonly activeRuns: number;
14
14
  readonly hasOwner?: boolean;
15
- readonly recovery?: boolean;
16
15
  }
17
16
  export interface ConsistencyResult {
18
17
  readonly ok: boolean;
@@ -10,14 +10,6 @@ const ALLOWED = [
10
10
  { row: 'fuse', run: 'running', staging: 'kept', journal: 'none', evidence: 'none', activeRuns: 1 },
11
11
  { row: 'fuse', run: 'failed', staging: 'kept', journal: 'none', evidence: 'none', activeRuns: 0 },
12
12
  { row: 'fuse', run: 'orphan', staging: 'kept', journal: 'none', evidence: 'none', activeRuns: 0 },
13
- { row: 'fuse', run: 'none', staging: 'none', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
14
- { row: 'fuse', run: 'failed', staging: 'none', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
15
- { row: 'fuse', run: 'orphan', staging: 'none', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
16
- { row: 'fuse', run: 'running', staging: 'none', journal: 'none', evidence: 'none', activeRuns: 1, recovery: true },
17
- { row: 'fuse', run: 'none', staging: 'missing', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
18
- { row: 'fuse', run: 'failed', staging: 'missing', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
19
- { row: 'fuse', run: 'orphan', staging: 'missing', journal: 'none', evidence: 'none', activeRuns: 0, recovery: true },
20
- { row: 'fuse', run: 'running', staging: 'missing', journal: 'none', evidence: 'none', activeRuns: 1, recovery: true },
21
13
  { row: 'fuse', run: 'running', staging: 'kept', journal: 'committing', evidence: 'none', activeRuns: 1 },
22
14
  { row: 'skipped', run: 'none', staging: 'none', journal: 'none', evidence: 'none', activeRuns: 0 },
23
15
  { row: 'merged', run: 'succeeded', staging: 'cleaned', journal: 'committed', evidence: 'receipt', activeRuns: 0 },
@@ -25,8 +17,7 @@ const ALLOWED = [
25
17
  ];
26
18
  function same(a, b) {
27
19
  return a.row === b.row && a.run === b.run && a.staging === b.staging && a.journal === b.journal
28
- && a.evidence === b.evidence && a.activeRuns === b.activeRuns
29
- && (b.recovery ?? false) === (a.recovery ?? false);
20
+ && a.evidence === b.evidence && a.activeRuns === b.activeRuns;
30
21
  }
31
22
  export function validateArtifacts(state) {
32
23
  if (state.activeRuns > 1)
@@ -39,7 +30,7 @@ export function validateArtifacts(state) {
39
30
  return { ok: false, reason: 'pending-staging' };
40
31
  if (state.row === 'skipped' && state.run === 'running')
41
32
  return { ok: false, reason: 'skipped-running' };
42
- if (state.row === 'fuse' && state.staging === 'missing' && state.recovery !== true)
33
+ if (state.row === 'fuse' && state.staging === 'missing')
43
34
  return { ok: false, reason: 'fuse-lost-staging' };
44
35
  if (state.journal === 'committed' && state.evidence !== 'receipt')
45
36
  return { ok: false, reason: 'committed-no-evidence' };
@@ -5,14 +5,4 @@ export interface MergeLimitsSnapshot {
5
5
  }
6
6
  export declare function resolveLimitsSnapshot(policy?: Parameters<typeof policyStageLimits>[0]): MergeLimitsSnapshot;
7
7
  export declare function limitsSnapshotOf(skillLimits: SkillLimits): MergeLimitsSnapshot;
8
- export declare function mergeLimitFinding(path: string, limit: string, observed: number, limitValue: number): {
9
- readonly rule: string;
10
- readonly path: string;
11
- readonly severity: 'block';
12
- readonly code: 'limit';
13
- readonly limit: string;
14
- readonly observed: number;
15
- readonly limitValue: number;
16
- readonly message: string;
17
- };
18
8
  //# sourceMappingURL=merge-limits.d.ts.map
@@ -16,7 +16,4 @@ export function resolveLimitsSnapshot(policy) {
16
16
  export function limitsSnapshotOf(skillLimits) {
17
17
  return { skillLimits, limitsDigest: createHash('sha256').update(canonical(skillLimits)).digest('hex') };
18
18
  }
19
- export function mergeLimitFinding(path, limit, observed, limitValue) {
20
- return { rule: 'limit', path, severity: 'block', code: 'limit', limit, observed, limitValue, message: path + ' exceeds ' + limit + ' (' + String(observed) + ' > ' + String(limitValue) + ')' };
21
- }
22
19
  //# sourceMappingURL=merge-limits.js.map
@@ -4,7 +4,6 @@ import { readV4Readiness } from "./schema4.js";
4
4
  import { readPairLedgerV4 } from "./pair-ledger.js";
5
5
  import { readAllStages } from "./stage-aggregate.js";
6
6
  import { readRunMeta } from "./run-coordinator.js";
7
- import { loadPairIndex } from "./pair-index-store.js";
8
7
  import { mergeReportPath, readMergeReport } from "../report.js";
9
8
  import { readCommitReceipts } from "../commit-receipt.js";
10
9
  import { pendingExpiry, persistedExpiry } from "./live-facts.js";
@@ -41,14 +40,13 @@ export async function readMergeSnapshot(options) {
41
40
  rows.push({ row, stage, bodies, queue, expired: expiredKind !== null, ...(expiredKind === null ? {} : { expiredKind }) });
42
41
  bodyRefs.push(...bodies);
43
42
  }
44
- const pairIndex = await loadPairIndex(options.io, options.home);
45
43
  const runs = [];
46
44
  for (const record of options.runs) {
47
45
  const meta = await readRunMeta(options.io, options.home, record.id);
48
- const pairs = [...pairIndex.values()].filter(item => item.runId === record.id).map(item => ({ pairKey: item.pairKey, runId: item.runId, result: item.result, ...(item.target === undefined ? {} : { target: item.target }) }));
49
46
  const reportPath = mergeReportPath(options.home, record.id);
50
47
  const probe = await readMergeReport(options.io, options.home, record.id);
51
48
  const report = probe.kind === 'present' ? probe.value : null;
49
+ const pairs = (report?.pairs ?? []).map(pair => ({ pairKey: pair.pairKey, runId: record.id, result: pair.outcome, ...(pair.target === undefined ? {} : { target: pair.target.requested }) }));
52
50
  runs.push({ record, meta, pairs, report, reportRef: report === null ? null : reportPath });
53
51
  }
54
52
  const reports = runs.filter(run => run.report !== null).map(run => ({ runId: run.record.id, path: run.reportRef, report: run.report }));
@@ -70,10 +68,10 @@ export async function readMergeSnapshot(options) {
70
68
  const notes = [...(options.notes ?? [])];
71
69
  if (historyDegraded)
72
70
  notes.push('history archive port unavailable; receipts only');
73
- const ledgerConflicts = [...pairIndex.values()].filter(entry => (entry.result === 'merged' || entry.result === 'undone')
74
- && rows.some(item => item.row.pairKey === entry.pairKey && (item.row.state === 'verify' || item.row.state === 'pending' || item.row.state === 'approved' || item.row.state === 'fuse')));
71
+ const activePairKeys = new Set(rows.filter(item => item.row.state === 'verify' || item.row.state === 'pending' || item.row.state === 'approved' || item.row.state === 'fuse').map(item => item.row.pairKey));
72
+ const ledgerConflicts = runs.flatMap(run => run.pairs).filter(entry => (entry.result === 'merged' || entry.result === 'undone') && activePairKeys.has(entry.pairKey));
75
73
  if (ledgerConflicts.length > 0)
76
- notes.push('ledger-conflict: pair-index says completed but the ledger still has it active (' + String(ledgerConflicts.length) + ')');
74
+ notes.push('ledger-conflict: a run says completed but the ledger still has it active (' + String(ledgerConflicts.length) + ')');
77
75
  return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision, consistency: 'eventual', ...(hostOnly === undefined ? {} : { hostOnly }), rows, stages, runs, reports, report: reports.at(-1)?.report ?? null, history, bodyRefs, doctor, approval: { mergeApproval: options.approval, ...(options.platformWrite === undefined ? {} : { platformWrite: options.platformWrite }) }, notes, degraded: notes.length > 0 };
78
76
  }
79
77
  async function readHistory(io, home) {
@@ -2,7 +2,6 @@ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
2
  import type { V4Row } from './row-v4.ts';
3
3
  import { type V3Row } from './row-v3.ts';
4
4
  import { type V4RowTx } from './schema4.ts';
5
- export type PairRow = V3Row;
6
5
  export interface PairLedgerRead {
7
6
  readonly schemaVersion: 3;
8
7
  readonly ledgerVersion?: number;
@@ -63,51 +63,6 @@ export interface SkippedRowV3 extends RowBaseV3 {
63
63
  readonly attempts: number;
64
64
  }
65
65
  export type V3Row = VerifyRowV3 | PendingRowV3 | ApprovedRowV3 | FuseRowV3 | SkippedRowV3;
66
- export type V3Event = {
67
- readonly type: 'verify-passed';
68
- readonly decision: MergeDecision;
69
- readonly target: string;
70
- readonly sourceRevision?: string;
71
- } | {
72
- readonly type: 'approved';
73
- readonly runId: string;
74
- } | {
75
- readonly type: 'auto-approved';
76
- readonly decision: MergeDecision;
77
- readonly target: string;
78
- readonly runId?: string;
79
- } | {
80
- readonly type: 'staged';
81
- readonly ref?: string;
82
- readonly expiresAt?: number;
83
- } | {
84
- readonly type: 'committed';
85
- readonly unitId: string;
86
- } | {
87
- readonly type: 'skipped';
88
- readonly reason: string;
89
- readonly origin?: 'agent' | 'human' | 'system';
90
- } | {
91
- readonly type: 'expired';
92
- readonly kind: 'target-taken' | 'source-changed';
93
- } | {
94
- readonly type: 'failed';
95
- readonly failure: MergeFailure;
96
- } | {
97
- readonly type: 'restored';
98
- } | {
99
- readonly type: 'superseded';
100
- };
101
- export type V3Transition = {
102
- readonly ok: true;
103
- readonly row: V3Row | null;
104
- readonly version: number;
105
- } | {
106
- readonly ok: false;
107
- readonly code: 'version-conflict' | 'illegal-transition';
108
- readonly message: string;
109
- };
110
- export declare function transitionV3(row: V3Row, event: V3Event, expectedVersion: number, at?: string): V3Transition;
111
66
  /** Runtime validation for a persisted v3 row. */
112
67
  export declare function isV3Row(value: unknown): value is V3Row;
113
68
  //# sourceMappingURL=row-v3.d.ts.map
@@ -1,71 +1,3 @@
1
- function bad(code, message) { return { ok: false, code, message }; }
2
- function advance(row, at, patch) { return { ...row, ...patch, version: row.version + 1, updatedAt: at }; }
3
- export function transitionV3(row, event, expectedVersion, at = new Date().toISOString()) {
4
- if (row.version !== expectedVersion)
5
- return bad('version-conflict', 'expected ' + String(expectedVersion) + ', got ' + String(row.version));
6
- switch (row.state) {
7
- case 'verify':
8
- if (event.type === 'verify-passed')
9
- return { ok: true, row: advance(row, at, { state: 'pending', target: event.target, decision: event.decision, sourceRevision: event.sourceRevision, attempts: 0 }), version: expectedVersion + 1 };
10
- if (event.type === 'auto-approved')
11
- return { ok: true, row: advance(row, at, { state: 'approved', target: event.target, decision: event.decision, attempts: 0, ...(event.runId === undefined ? {} : { runId: event.runId }) }), version: expectedVersion + 1 };
12
- if (event.type === 'failed')
13
- return { ok: true, row: advance(row, at, { failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
14
- if (event.type === 'skipped')
15
- return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason, attempts: row.attempts }), version: expectedVersion + 1 };
16
- if (event.type === 'superseded')
17
- return { ok: true, row: null, version: expectedVersion + 1 };
18
- return bad('illegal-transition', 'verify/' + event.type);
19
- case 'pending':
20
- if (event.type === 'approved')
21
- return { ok: true, row: advance(row, at, { state: 'approved', runId: event.runId }), version: expectedVersion + 1 };
22
- if (event.type === 'auto-approved')
23
- return { ok: true, row: advance(row, at, { state: 'approved', ...(event.runId === undefined ? {} : { runId: event.runId }) }), version: expectedVersion + 1 };
24
- if (event.type === 'staged')
25
- return { ok: true, row: advance(row, at, { state: 'fuse', staging: event.ref === undefined ? { kind: 'absent' } : { kind: 'present', ref: event.ref, expiresAt: event.expiresAt ?? Date.now() + 7 * 86_400_000 } }), version: expectedVersion + 1 };
26
- if (event.type === 'skipped')
27
- return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason, ...(event.origin === undefined ? {} : { origin: event.origin }) }), version: expectedVersion + 1 };
28
- if (event.type === 'expired')
29
- return { ok: true, row: advance(row, at, { expired: event.kind }), version: expectedVersion + 1 };
30
- if (event.type === 'failed')
31
- return { ok: true, row: advance(row, at, { failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
32
- if (event.type === 'superseded')
33
- return { ok: true, row: null, version: expectedVersion + 1 };
34
- return bad('illegal-transition', 'pending/' + event.type);
35
- case 'approved':
36
- if (event.type === 'staged')
37
- return { ok: true, row: advance(row, at, { state: 'fuse', staging: event.ref === undefined ? { kind: 'absent' } : { kind: 'present', ref: event.ref, expiresAt: event.expiresAt ?? Date.now() + 7 * 86_400_000 } }), version: expectedVersion + 1 };
38
- if (event.type === 'committed')
39
- return { ok: true, row: null, version: expectedVersion + 1 };
40
- if (event.type === 'skipped')
41
- return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason }), version: expectedVersion + 1 };
42
- if (event.type === 'failed')
43
- return event.failure.retryable
44
- ? { ok: true, row: advance(row, at, { state: 'pending', failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 }
45
- : { ok: true, row: advance(row, at, { state: 'skipped', reason: event.failure.code, failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
46
- if (event.type === 'superseded')
47
- return { ok: true, row: null, version: expectedVersion + 1 };
48
- return bad('illegal-transition', 'approved/' + event.type);
49
- case 'fuse':
50
- if (event.type === 'staged')
51
- return { ok: true, row: advance(row, at, { staging: event.ref === undefined ? { kind: 'absent' } : { kind: 'present', ref: event.ref, expiresAt: event.expiresAt ?? Date.now() + 7 * 86_400_000 } }), version: expectedVersion + 1 };
52
- if (event.type === 'committed')
53
- return { ok: true, row: null, version: expectedVersion + 1 };
54
- if (event.type === 'skipped')
55
- return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason }), version: expectedVersion + 1 };
56
- if (event.type === 'failed')
57
- return event.failure.retryable
58
- ? { ok: true, row: advance(row, at, { state: 'pending', failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 }
59
- : { ok: true, row: advance(row, at, { state: 'skipped', reason: event.failure.code, failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
60
- if (event.type === 'superseded')
61
- return { ok: true, row: null, version: expectedVersion + 1 };
62
- return bad('illegal-transition', 'fuse/' + event.type);
63
- case 'skipped':
64
- if (event.type === 'restored' || event.type === 'superseded')
65
- return { ok: true, row: null, version: expectedVersion + 1 };
66
- return bad('illegal-transition', 'skipped is terminal');
67
- }
68
- }
69
1
  function object(value) { return value !== null && typeof value === 'object'; }
70
2
  function string(value) { return typeof value === 'string' && value.length > 0; }
71
3
  function number(value) { return typeof value === 'number' && Number.isFinite(value); }
@@ -1,4 +1,5 @@
1
- import { type MergeState } from './state.ts';
1
+ /** The v2 row shape still read by the ledger/migration bridge. No IO. */
2
+ import type { MergeState } from './state.ts';
2
3
  import type { MergeDecision, MergeFailure } from '../contracts.ts';
3
4
  export interface MergeRowV2 {
4
5
  readonly id: string;
@@ -28,87 +29,4 @@ export interface MergeRowV2 {
28
29
  readonly createdAt: string;
29
30
  readonly updatedAt: string;
30
31
  }
31
- export type MergeRowEvent = {
32
- readonly type: 'candidate';
33
- readonly a: string;
34
- readonly b: string;
35
- readonly at: string;
36
- } | {
37
- readonly type: 'round1-start';
38
- readonly at: string;
39
- } | {
40
- readonly type: 'round1-keep';
41
- readonly at: string;
42
- } | {
43
- readonly type: 'round1-merge';
44
- readonly approval: 'on' | 'off';
45
- readonly decision?: MergeDecision;
46
- readonly stagingRef?: string;
47
- readonly stagingExpiresAt?: number;
48
- readonly at: string;
49
- } | {
50
- readonly type: 'round1-fail';
51
- readonly failure: MergeFailure;
52
- readonly at: string;
53
- } | {
54
- readonly type: 'approve';
55
- readonly runId?: string;
56
- readonly at: string;
57
- } | {
58
- readonly type: 'reject';
59
- readonly at: string;
60
- } | {
61
- readonly type: 'round2-start';
62
- readonly at: string;
63
- } | {
64
- readonly type: 'round2-fail';
65
- readonly failure: MergeFailure;
66
- readonly at: string;
67
- } | {
68
- readonly type: 'round2-success';
69
- readonly unitId?: string;
70
- readonly at: string;
71
- } | {
72
- readonly type: 'restore';
73
- readonly at: string;
74
- } | {
75
- readonly type: 'discard';
76
- readonly at: string;
77
- } | {
78
- readonly type: 'source-gone';
79
- readonly at: string;
80
- } | {
81
- readonly type: 'target-taken';
82
- readonly at: string;
83
- } | {
84
- readonly type: 'source-changed';
85
- readonly at: string;
86
- } | {
87
- readonly type: 'staging-expired';
88
- readonly at: string;
89
- } | {
90
- readonly type: 'round2-cancel';
91
- readonly at: string;
92
- } | {
93
- readonly type: 'timeout';
94
- readonly at: string;
95
- } | {
96
- readonly type: 'cancel';
97
- readonly at: string;
98
- } | {
99
- readonly type: 'gate-keep';
100
- readonly a: string;
101
- readonly b: string;
102
- readonly reason?: string;
103
- readonly at: string;
104
- };
105
- export type RowTransitionResult = {
106
- readonly ok: true;
107
- readonly row: MergeRowV2 | null;
108
- } | {
109
- readonly ok: false;
110
- readonly code: 'invalid-transition';
111
- readonly message: string;
112
- };
113
- export declare function transitionRow(current: MergeRowV2 | undefined, event: MergeRowEvent): RowTransitionResult;
114
32
  //# sourceMappingURL=row.d.ts.map