@lmzhen/dsh-evolution-merge 0.30.23 → 0.30.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/lib/index.js +518 -182
  2. package/lib/types/approve-command.js +2 -1
  3. package/lib/types/authoring-flow.d.ts +5 -0
  4. package/lib/types/authoring-flow.js +8 -5
  5. package/lib/types/commands.d.ts +7 -0
  6. package/lib/types/commands.js +8 -1
  7. package/lib/types/commit-execute.d.ts +4 -0
  8. package/lib/types/commit-execute.js +8 -5
  9. package/lib/types/commit-receipt.js +3 -1
  10. package/lib/types/contracts.d.ts +20 -1
  11. package/lib/types/doctor.js +1 -1
  12. package/lib/types/domain/errors.js +5 -1
  13. package/lib/types/domain/merge-snapshot.d.ts +12 -3
  14. package/lib/types/domain/merge-snapshot.js +9 -4
  15. package/lib/types/domain/reconcile.js +1 -1
  16. package/lib/types/domain/row-v4.d.ts +4 -0
  17. package/lib/types/domain/row-v4.js +9 -9
  18. package/lib/types/domain/run-coordinator.d.ts +9 -0
  19. package/lib/types/domain/stage-aggregate.d.ts +6 -1
  20. package/lib/types/domain/stage-aggregate.js +16 -9
  21. package/lib/types/domain/validation-plan.js +5 -1
  22. package/lib/types/index.js +14 -2
  23. package/lib/types/kernel/adapter-v4.d.ts +12 -0
  24. package/lib/types/kernel/adapter-v4.js +54 -0
  25. package/lib/types/kernel/failure.d.ts +8 -0
  26. package/lib/types/kernel/failure.js +33 -0
  27. package/lib/types/kernel/index.d.ts +9 -0
  28. package/lib/types/kernel/index.js +9 -0
  29. package/lib/types/kernel/invariants.d.ts +4 -0
  30. package/lib/types/kernel/invariants.js +47 -0
  31. package/lib/types/kernel/ledger.d.ts +46 -0
  32. package/lib/types/kernel/ledger.js +62 -0
  33. package/lib/types/kernel/machine.d.ts +13 -0
  34. package/lib/types/kernel/machine.js +108 -0
  35. package/lib/types/kernel/model.d.ts +154 -0
  36. package/lib/types/kernel/model.js +5 -0
  37. package/lib/types/kernel/projection.d.ts +28 -0
  38. package/lib/types/kernel/projection.js +44 -0
  39. package/lib/types/kernel/recovery.d.ts +31 -0
  40. package/lib/types/kernel/recovery.js +19 -0
  41. package/lib/types/kernel/unit.d.ts +43 -0
  42. package/lib/types/kernel/unit.js +63 -0
  43. package/lib/types/merge-routes.js +9 -0
  44. package/lib/types/new-child-authoring.d.ts +1 -0
  45. package/lib/types/new-child-authoring.js +15 -7
  46. package/lib/types/pending-actions.d.ts +1 -1
  47. package/lib/types/pending-actions.js +4 -0
  48. package/lib/types/recovery-coordinator.d.ts +2 -0
  49. package/lib/types/recovery-coordinator.js +14 -4
  50. package/lib/types/recovery-projection.d.ts +4 -2
  51. package/lib/types/recovery-projection.js +3 -3
  52. package/lib/types/resume-command.js +2 -1
  53. package/lib/types/round2-outcome.js +26 -9
  54. package/lib/types/run-shell.d.ts +1 -0
  55. package/lib/types/run-shell.js +57 -15
  56. package/lib/types/runtime.d.ts +4 -1
  57. package/lib/types/runtime.js +34 -22
  58. package/lib/types/scan-command.d.ts +1 -0
  59. package/lib/types/scan-command.js +1 -0
  60. package/lib/types/scan-ports.js +3 -0
  61. package/lib/types/scan-run.d.ts +6 -1
  62. package/lib/types/scan-run.js +34 -18
  63. package/lib/types/surface/merge-view.js +12 -4
  64. package/lib/types/undo-operation.d.ts +2 -1
  65. package/lib/types/undo-operation.js +10 -4
  66. package/lib/types/wire-projection.js +7 -2
  67. package/lib/types/wire.d.ts +1 -0
  68. package/package.json +4 -4
  69. package/lib/types/adapters/model.d.ts +0 -31
  70. package/lib/types/adapters/model.js +0 -47
  71. package/lib/types/gate-cache.d.ts +0 -12
  72. package/lib/types/gate-cache.js +0 -36
  73. package/lib/types/gate-filter.d.ts +0 -31
  74. package/lib/types/gate-filter.js +0 -64
  75. package/lib/types/ladder.d.ts +0 -65
  76. package/lib/types/ladder.js +0 -162
  77. package/lib/types/propose.d.ts +0 -26
  78. package/lib/types/propose.js +0 -69
  79. package/lib/types/screening-port.d.ts +0 -47
  80. package/lib/types/screening-port.js +0 -93
  81. package/lib/types/screening.d.ts +0 -30
  82. package/lib/types/screening.js +0 -75
@@ -0,0 +1,12 @@
1
+ import type { V4Row } from '../domain/row-v4.ts';
2
+ import type { PairSnapshot } from './model.ts';
3
+ export interface V4AdapterResult {
4
+ readonly snapshot: PairSnapshot;
5
+ readonly warnings: readonly string[];
6
+ }
7
+ export declare function fromV4Row(row: V4Row): V4AdapterResult;
8
+ export declare function fromV4Rows(rows: readonly V4Row[]): {
9
+ readonly snapshots: readonly PairSnapshot[];
10
+ readonly warnings: readonly string[];
11
+ };
12
+ //# sourceMappingURL=adapter-v4.d.ts.map
@@ -0,0 +1,54 @@
1
+ function decisionOf(row) {
2
+ return { target: row.target, sources: row.decision.sources, ...(row.decision.rationale === undefined ? {} : { rationale: row.decision.rationale }) };
3
+ }
4
+ function failureOfRow(failure) {
5
+ const known = new Set(['source-gone', 'source-changed', 'target-taken', 'validation-failed', 'commit-failed', 'schema-invalid', 'timeout', 'capability-missing', 'runtime']);
6
+ const code = known.has(failure.code) ? failure.code : 'runtime';
7
+ return { code, stage: failure.stage === 'screening' ? 'screen' : failure.stage === 'decide' ? 'decide' : failure.stage === 'author' ? 'author' : failure.stage === 'validate' ? 'validate' : 'commit', retryable: failure.retryable, reportOnly: true, userMessageKey: 'failure.' + code, detail: failure.message };
8
+ }
9
+ function closedReasonOf(row) {
10
+ if (row.reason.includes('merge-over-limit') || row.reason.includes('over-limit'))
11
+ return 'over-limit';
12
+ return row.origin === 'human' ? 'reject' : 'keep';
13
+ }
14
+ export function fromV4Row(row) {
15
+ const warnings = [];
16
+ const base = { id: row.id, pairKey: row.pairKey, a: row.a, b: row.b, version: row.version, createdAt: row.createdAt, updatedAt: row.updatedAt, origin: row.origin, attempts: row.attempts, ...(row.runId === undefined ? {} : { runId: row.runId }) };
17
+ if (row.failure !== undefined) {
18
+ warnings.push('row carries failure; kernel state=failed (current row state=' + row.state + ')');
19
+ return { snapshot: { ...base, state: 'failed', failure: failureOfRow(row.failure) }, warnings };
20
+ }
21
+ if (row.state === 'verify') {
22
+ return { snapshot: { ...base, state: row.verifySubState === 'screened' ? 'deciding' : 'candidate' }, warnings };
23
+ }
24
+ if (row.state === 'pending') {
25
+ if (row.expired !== undefined) {
26
+ warnings.push('pending expired without a timestamp; kernel state=expired');
27
+ return { snapshot: { ...base, state: 'expired', target: row.target, decision: decisionOf(row), approval: 'on' }, warnings };
28
+ }
29
+ return { snapshot: { ...base, state: 'pending', target: row.target, decision: decisionOf(row), approval: 'on' }, warnings };
30
+ }
31
+ if (row.state === 'approved')
32
+ return { snapshot: { ...base, state: 'approved', target: row.target, decision: decisionOf(row), approval: 'off' }, warnings };
33
+ if (row.state === 'fuse') {
34
+ warnings.push('current fuse row has no stagingRef; kernel state=authoring');
35
+ return { snapshot: { ...base, state: 'authoring', target: row.target, decision: decisionOf(row), approval: 'off' }, warnings };
36
+ }
37
+ if (row.expiredAt !== undefined) {
38
+ warnings.push('skipped-expired row maps to closed+expiresAt, not a failure');
39
+ return { snapshot: { ...base, state: 'closed', closedReason: closedReasonOf(row), expiresAt: row.expiredAt }, warnings };
40
+ }
41
+ return { snapshot: { ...base, state: 'closed', closedReason: closedReasonOf(row) }, warnings };
42
+ }
43
+ export function fromV4Rows(rows) {
44
+ const snapshots = [];
45
+ const warnings = [];
46
+ for (const row of rows) {
47
+ const result = fromV4Row(row);
48
+ snapshots.push(result.snapshot);
49
+ for (const warning of result.warnings)
50
+ warnings.push(row.id + ': ' + warning);
51
+ }
52
+ return { snapshots, warnings };
53
+ }
54
+ //# sourceMappingURL=adapter-v4.js.map
@@ -0,0 +1,8 @@
1
+ /** Pure failure taxonomy: the ONE place a code becomes a route. */
2
+ import type { ClosedReason, Failure, FailureCode } from './model.ts';
3
+ export declare function failureOf(code: FailureCode, detail?: string): Failure;
4
+ /** Only policy closures are closed; failures never map here. */
5
+ export declare function closedReasonFor(code: string): ClosedReason | undefined;
6
+ export declare function isFailureRoute(reportOnly: boolean): boolean;
7
+ export declare const FAILURE_CODES: readonly FailureCode[];
8
+ //# sourceMappingURL=failure.d.ts.map
@@ -0,0 +1,33 @@
1
+ const TABLE = {
2
+ 'bad-pair': { stage: 'command', retryable: true, reportOnly: false, userMessageKey: 'failure.bad-pair' },
3
+ 'bad-target': { stage: 'command', retryable: true, reportOnly: false, userMessageKey: 'failure.bad-target' },
4
+ 'bad-flag': { stage: 'command', retryable: true, reportOnly: false, userMessageKey: 'failure.bad-flag' },
5
+ 'source-gone': { stage: 'decide', retryable: true, reportOnly: true, userMessageKey: 'failure.source-gone' },
6
+ 'source-changed': { stage: 'commit', retryable: true, reportOnly: true, userMessageKey: 'failure.source-changed' },
7
+ 'target-taken': { stage: 'commit', retryable: false, reportOnly: true, userMessageKey: 'failure.target-taken' },
8
+ 'validation-failed': { stage: 'validate', retryable: true, reportOnly: true, userMessageKey: 'failure.validation' },
9
+ 'commit-failed': { stage: 'commit', retryable: false, reportOnly: true, userMessageKey: 'failure.commit' },
10
+ 'schema-invalid': { stage: 'schema', retryable: false, reportOnly: true, userMessageKey: 'failure.schema' },
11
+ timeout: { stage: 'run', retryable: true, reportOnly: true, userMessageKey: 'failure.timeout' },
12
+ 'capability-missing': { stage: 'surface', retryable: false, reportOnly: false, userMessageKey: 'failure.capability' },
13
+ runtime: { stage: 'run', retryable: true, reportOnly: true, userMessageKey: 'failure.runtime' },
14
+ };
15
+ export function failureOf(code, detail) {
16
+ const spec = TABLE[code];
17
+ return { code, stage: spec.stage, retryable: spec.retryable, reportOnly: spec.reportOnly, userMessageKey: spec.userMessageKey, ...(detail === undefined ? {} : { detail }) };
18
+ }
19
+ /** Only policy closures are closed; failures never map here. */
20
+ export function closedReasonFor(code) {
21
+ if (code === 'merge-over-limit')
22
+ return 'over-limit';
23
+ if (code === 'keep')
24
+ return 'keep';
25
+ if (code === 'reject')
26
+ return 'reject';
27
+ return undefined;
28
+ }
29
+ export function isFailureRoute(reportOnly) {
30
+ return reportOnly;
31
+ }
32
+ export const FAILURE_CODES = Object.keys(TABLE);
33
+ //# sourceMappingURL=failure.js.map
@@ -0,0 +1,9 @@
1
+ export * from './model.ts';
2
+ export * from './failure.ts';
3
+ export * from './machine.ts';
4
+ export * from './unit.ts';
5
+ export * from './ledger.ts';
6
+ export * from './projection.ts';
7
+ export * from './recovery.ts';
8
+ export * from './invariants.ts';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ export * from "./model.js";
2
+ export * from "./failure.js";
3
+ export * from "./machine.js";
4
+ export * from "./unit.js";
5
+ export * from "./ledger.js";
6
+ export * from "./projection.js";
7
+ export * from "./recovery.js";
8
+ export * from "./invariants.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,4 @@
1
+ /** Pure invariant checks over one pair snapshot. */
2
+ import { type PairSnapshot } from './model.ts';
3
+ export declare function checkPair(snapshot: PairSnapshot): string[];
4
+ //# sourceMappingURL=invariants.d.ts.map
@@ -0,0 +1,47 @@
1
+ /** Pure invariant checks over one pair snapshot. */
2
+ import { CLOSED_REASONS } from "./model.js";
3
+ export function checkPair(snapshot) {
4
+ const problems = [];
5
+ if (snapshot.id.length === 0)
6
+ problems.push('id is empty');
7
+ if (snapshot.pairKey.length === 0)
8
+ problems.push('pairKey is empty');
9
+ if (snapshot.a.length === 0 || snapshot.b.length === 0)
10
+ problems.push('a/b is empty');
11
+ if (!Number.isInteger(snapshot.version) || snapshot.version < 0)
12
+ problems.push('version must be a non-negative integer');
13
+ if (snapshot.attempts < 0)
14
+ problems.push('attempts must be non-negative');
15
+ if (snapshot.state === 'closed') {
16
+ if (snapshot.closedReason === undefined || !CLOSED_REASONS.includes(snapshot.closedReason))
17
+ problems.push('closed requires closedReason in ' + CLOSED_REASONS.join('/'));
18
+ if (snapshot.failure !== undefined)
19
+ problems.push('closed must not carry a failure');
20
+ }
21
+ if (snapshot.state === 'failed') {
22
+ if (snapshot.failure === undefined)
23
+ problems.push('failed requires a failure');
24
+ if (snapshot.closedReason !== undefined)
25
+ problems.push('failed must not carry closedReason');
26
+ }
27
+ if (snapshot.state === 'committed') {
28
+ if (snapshot.receipt === undefined)
29
+ problems.push('committed requires a receipt');
30
+ if (snapshot.failure !== undefined)
31
+ problems.push('committed must not carry a failure');
32
+ if (snapshot.unitId === undefined)
33
+ problems.push('committed requires unitId');
34
+ }
35
+ if (snapshot.state === 'committing' && snapshot.unitId === undefined)
36
+ problems.push('committing requires unitId');
37
+ if (snapshot.state === 'staged' && (snapshot.stageRef === undefined || snapshot.stageRef.length === 0))
38
+ problems.push('staged requires stageRef');
39
+ if ((snapshot.state === 'pending' || snapshot.state === 'approved') && snapshot.decision === undefined)
40
+ problems.push(snapshot.state + ' requires a decision');
41
+ if ((snapshot.state === 'pending' || snapshot.state === 'approved') && snapshot.target === undefined)
42
+ problems.push(snapshot.state + ' requires a target');
43
+ if (snapshot.state === 'committed' && snapshot.receipt !== undefined && snapshot.receipt.unitId !== snapshot.unitId)
44
+ problems.push('receipt.unitId must equal unitId');
45
+ return problems;
46
+ }
47
+ //# sourceMappingURL=invariants.js.map
@@ -0,0 +1,46 @@
1
+ /** Pure append-only ledger and deterministic fold. No IO. */
2
+ import type { PairEvent, PairSnapshot } from './model.ts';
3
+ import { type UnitEvent, type UnitSnapshot } from './unit.ts';
4
+ export type LedgerEntry = {
5
+ readonly seq: number;
6
+ readonly at: string;
7
+ readonly type: 'pair-created';
8
+ readonly snapshot: PairSnapshot;
9
+ } | {
10
+ readonly seq: number;
11
+ readonly at: string;
12
+ readonly type: 'pair-transition';
13
+ readonly pairId: string;
14
+ readonly expectedVersion: number;
15
+ readonly event: PairEvent;
16
+ } | {
17
+ readonly seq: number;
18
+ readonly at: string;
19
+ readonly type: 'unit-created';
20
+ readonly snapshot: UnitSnapshot;
21
+ } | {
22
+ readonly seq: number;
23
+ readonly at: string;
24
+ readonly type: 'unit-transition';
25
+ readonly unitId: string;
26
+ readonly expectedVersion: number;
27
+ readonly event: UnitEvent;
28
+ };
29
+ export interface LedgerState {
30
+ readonly seq: number;
31
+ readonly pairs: ReadonlyMap<string, PairSnapshot>;
32
+ readonly units: ReadonlyMap<string, UnitSnapshot>;
33
+ }
34
+ export type FoldResult = {
35
+ readonly ok: true;
36
+ readonly state: LedgerState;
37
+ } | {
38
+ readonly ok: false;
39
+ readonly seq: number;
40
+ readonly message: string;
41
+ };
42
+ export declare function emptyLedger(): LedgerState;
43
+ export declare function fold(entries: readonly LedgerEntry[]): FoldResult;
44
+ /** Deterministic projection bytes for replay-equality tests. */
45
+ export declare function ledgerProjectionJson(state: LedgerState): string;
46
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { transition as transitionPair } from "./machine.js";
2
+ import { checkPair } from "./invariants.js";
3
+ import { checkUnit, transitionUnit } from "./unit.js";
4
+ export function emptyLedger() {
5
+ return { seq: 0, pairs: new Map(), units: new Map() };
6
+ }
7
+ export function fold(entries) {
8
+ let state = emptyLedger();
9
+ for (const entry of entries) {
10
+ if (!Number.isInteger(entry.seq) || entry.seq !== state.seq + 1)
11
+ return { ok: false, seq: entry.seq, message: 'expected seq ' + String(state.seq + 1) + ', got ' + String(entry.seq) };
12
+ if (entry.type === 'pair-created') {
13
+ if (state.pairs.has(entry.snapshot.id))
14
+ return { ok: false, seq: entry.seq, message: 'duplicate pair ' + entry.snapshot.id };
15
+ const problems = checkPair(entry.snapshot);
16
+ if (problems.length > 0)
17
+ return { ok: false, seq: entry.seq, message: 'pair invalid: ' + problems.join('; ') };
18
+ state = { ...state, seq: entry.seq, pairs: new Map(state.pairs).set(entry.snapshot.id, entry.snapshot) };
19
+ continue;
20
+ }
21
+ if (entry.type === 'pair-transition') {
22
+ const current = state.pairs.get(entry.pairId);
23
+ if (current === undefined)
24
+ return { ok: false, seq: entry.seq, message: 'pair not found: ' + entry.pairId };
25
+ const moved = transitionPair(current, entry.event, entry.expectedVersion);
26
+ if (!moved.ok)
27
+ return { ok: false, seq: entry.seq, message: moved.message };
28
+ const problems = checkPair(moved.snapshot);
29
+ if (problems.length > 0)
30
+ return { ok: false, seq: entry.seq, message: 'pair invalid after transition: ' + problems.join('; ') };
31
+ state = { ...state, seq: entry.seq, pairs: new Map(state.pairs).set(entry.pairId, moved.snapshot) };
32
+ continue;
33
+ }
34
+ if (entry.type === 'unit-created') {
35
+ if (state.units.has(entry.snapshot.unitId))
36
+ return { ok: false, seq: entry.seq, message: 'duplicate unit ' + entry.snapshot.unitId };
37
+ const problems = checkUnit(entry.snapshot);
38
+ if (problems.length > 0)
39
+ return { ok: false, seq: entry.seq, message: 'unit invalid: ' + problems.join('; ') };
40
+ state = { ...state, seq: entry.seq, units: new Map(state.units).set(entry.snapshot.unitId, entry.snapshot) };
41
+ continue;
42
+ }
43
+ const current = state.units.get(entry.unitId);
44
+ if (current === undefined)
45
+ return { ok: false, seq: entry.seq, message: 'unit not found: ' + entry.unitId };
46
+ const moved = transitionUnit(current, entry.event, entry.expectedVersion);
47
+ if (!moved.ok)
48
+ return { ok: false, seq: entry.seq, message: moved.message };
49
+ const problems = checkUnit(moved.snapshot);
50
+ if (problems.length > 0)
51
+ return { ok: false, seq: entry.seq, message: 'unit invalid after transition: ' + problems.join('; ') };
52
+ state = { ...state, seq: entry.seq, units: new Map(state.units).set(entry.unitId, moved.snapshot) };
53
+ }
54
+ return { ok: true, state };
55
+ }
56
+ /** Deterministic projection bytes for replay-equality tests. */
57
+ export function ledgerProjectionJson(state) {
58
+ const pairs = [...state.pairs.values()].sort((left, right) => left.id.localeCompare(right.id));
59
+ const units = [...state.units.values()].sort((left, right) => left.unitId.localeCompare(right.unitId));
60
+ return JSON.stringify({ seq: state.seq, pairs, units });
61
+ }
62
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1,13 @@
1
+ /** Pure pair state machine. Every legal transition is explicit; no IO. */
2
+ import type { PairEvent, PairSnapshot } from './model.ts';
3
+ export type TransitionErrorCode = 'illegal-transition' | 'version-conflict' | 'invalid-event';
4
+ export type TransitionResult = {
5
+ readonly ok: true;
6
+ readonly snapshot: PairSnapshot;
7
+ } | {
8
+ readonly ok: false;
9
+ readonly code: TransitionErrorCode;
10
+ readonly message: string;
11
+ };
12
+ export declare function transition(snapshot: PairSnapshot, event: PairEvent, expectedVersion: number): TransitionResult;
13
+ //# sourceMappingURL=machine.d.ts.map
@@ -0,0 +1,108 @@
1
+ function fail(code, message) {
2
+ return { ok: false, code, message };
3
+ }
4
+ function clean(snapshot) {
5
+ const { failure, closedReason, ...rest } = snapshot;
6
+ void failure;
7
+ void closedReason;
8
+ return rest;
9
+ }
10
+ function next(snapshot, state, at, patch = {}) {
11
+ return { ...clean(snapshot), state, version: snapshot.version + 1, updatedAt: at, ...patch };
12
+ }
13
+ function requireText(value, field) {
14
+ return value === undefined || value.length === 0 ? fail('invalid-event', field + ' is required') : null;
15
+ }
16
+ export function transition(snapshot, event, expectedVersion) {
17
+ if (snapshot.version !== expectedVersion)
18
+ return fail('version-conflict', 'expected ' + String(expectedVersion) + ', got ' + String(snapshot.version));
19
+ const at = event.at;
20
+ switch (event.type) {
21
+ case 'screen':
22
+ return snapshot.state === 'candidate' ? { ok: true, snapshot: next(snapshot, 'deciding', at) } : fail('illegal-transition', 'screen from ' + snapshot.state);
23
+ case 'decide': {
24
+ if (snapshot.state !== 'deciding')
25
+ return fail('illegal-transition', 'decide from ' + snapshot.state);
26
+ if (event.decision === 'keep')
27
+ return { ok: true, snapshot: next(snapshot, 'closed', at, { closedReason: 'keep', origin: 'agent' }) };
28
+ const missing = requireText(event.target, 'target');
29
+ if (missing !== null)
30
+ return missing;
31
+ const decision = { target: event.target, sources: [snapshot.a, snapshot.b] };
32
+ const patch = { decision, target: event.target, approval: event.approval, ...(event.runId === undefined ? {} : { runId: event.runId }) };
33
+ return { ok: true, snapshot: next(snapshot, event.approval === 'on' ? 'pending' : 'approved', at, patch) };
34
+ }
35
+ case 'approve':
36
+ return snapshot.state === 'pending' ? { ok: true, snapshot: next(snapshot, 'approved', at) } : fail('illegal-transition', 'approve from ' + snapshot.state);
37
+ case 'reject':
38
+ return snapshot.state === 'pending' ? { ok: true, snapshot: next(snapshot, 'closed', at, { closedReason: 'reject', origin: 'human' }) } : fail('illegal-transition', 'reject from ' + snapshot.state);
39
+ case 'author-start':
40
+ return snapshot.state === 'approved' ? { ok: true, snapshot: next(snapshot, 'authoring', at) } : fail('illegal-transition', 'author-start from ' + snapshot.state);
41
+ case 'staged': {
42
+ if (snapshot.state !== 'authoring')
43
+ return fail('illegal-transition', 'staged from ' + snapshot.state);
44
+ const missing = requireText(event.stageRef, 'stageRef');
45
+ return missing ?? { ok: true, snapshot: next(snapshot, 'staged', at, { stageRef: event.stageRef }) };
46
+ }
47
+ case 'commit-start': {
48
+ if (snapshot.state !== 'staged')
49
+ return fail('illegal-transition', 'commit-start from ' + snapshot.state);
50
+ const missing = requireText(event.unitId, 'unitId');
51
+ return missing ?? { ok: true, snapshot: next(snapshot, 'committing', at, { unitId: event.unitId }) };
52
+ }
53
+ case 'commit-succeeded':
54
+ if (snapshot.state !== 'committing')
55
+ return fail('illegal-transition', 'commit-succeeded from ' + snapshot.state);
56
+ if (event.receipt.unitId.length === 0)
57
+ return fail('invalid-event', 'receipt.unitId is required');
58
+ return { ok: true, snapshot: next(snapshot, 'committed', at, { unitId: event.receipt.unitId, receipt: event.receipt }) };
59
+ case 'commit-failed':
60
+ return snapshot.state === 'committing' ? { ok: true, snapshot: next(snapshot, 'failed', at, { failure: event.failure }) } : fail('illegal-transition', 'commit-failed from ' + snapshot.state);
61
+ case 'commit-unknown':
62
+ return snapshot.state === 'committing' ? { ok: true, snapshot: next(snapshot, 'unknown', at) } : fail('illegal-transition', 'commit-unknown from ' + snapshot.state);
63
+ case 'recover': {
64
+ if (snapshot.state !== 'unknown')
65
+ return fail('illegal-transition', 'recover from ' + snapshot.state);
66
+ if (event.outcome === 'committed') {
67
+ if (event.receipt === undefined || event.receipt.unitId.length === 0)
68
+ return fail('invalid-event', 'receipt is required for recovered commit');
69
+ return { ok: true, snapshot: next(snapshot, 'committed', at, { unitId: event.receipt.unitId, receipt: event.receipt }) };
70
+ }
71
+ if (event.outcome === 'failed') {
72
+ if (event.failure === undefined)
73
+ return fail('invalid-event', 'failure is required for recovered failure');
74
+ return { ok: true, snapshot: next(snapshot, 'failed', at, { failure: event.failure }) };
75
+ }
76
+ return { ok: true, snapshot: next(snapshot, 'cancelled', at) };
77
+ }
78
+ case 'close':
79
+ if (event.reason !== 'over-limit')
80
+ return fail('invalid-event', 'close only accepts over-limit; keep/reject belong to decide/reject');
81
+ return snapshot.state === 'authoring' || snapshot.state === 'staged'
82
+ ? { ok: true, snapshot: next(snapshot, 'closed', at, { closedReason: event.reason }) }
83
+ : fail('illegal-transition', 'close from ' + snapshot.state);
84
+ case 'expire':
85
+ return snapshot.state === 'pending' || snapshot.state === 'staged'
86
+ ? { ok: true, snapshot: next(snapshot, 'expired', at) }
87
+ : fail('illegal-transition', 'expire from ' + snapshot.state);
88
+ case 'cancel':
89
+ return ['candidate', 'deciding', 'pending', 'approved', 'authoring', 'staged', 'unknown'].includes(snapshot.state)
90
+ ? { ok: true, snapshot: next(snapshot, 'cancelled', at) }
91
+ : fail('illegal-transition', 'cancel from ' + snapshot.state);
92
+ case 'fail':
93
+ return ['candidate', 'deciding', 'pending', 'approved', 'authoring', 'staged'].includes(snapshot.state)
94
+ ? { ok: true, snapshot: next(snapshot, 'failed', at, { failure: event.failure }) }
95
+ : fail('illegal-transition', 'fail from ' + snapshot.state);
96
+ case 'retry':
97
+ return snapshot.state === 'failed'
98
+ ? { ok: true, snapshot: next(snapshot, 'candidate', at, { attempts: snapshot.attempts + 1 }) }
99
+ : fail('illegal-transition', 'retry from ' + snapshot.state);
100
+ case 'undo':
101
+ return snapshot.state === 'committed' ? { ok: true, snapshot: next(snapshot, 'undone', at) } : fail('illegal-transition', 'undo from ' + snapshot.state);
102
+ case 'supersede':
103
+ return snapshot.state === 'undone' ? fail('illegal-transition', 'supersede from undone') : { ok: true, snapshot: next(snapshot, 'superseded', at) };
104
+ default:
105
+ return fail('invalid-event', 'unknown event');
106
+ }
107
+ }
108
+ //# sourceMappingURL=machine.js.map
@@ -0,0 +1,154 @@
1
+ /** Pure merge-kernel model. No IO, no host, no clock. */
2
+ export type PairState = 'candidate' | 'deciding' | 'pending' | 'approved' | 'authoring' | 'staged' | 'committing' | 'committed' | 'closed' | 'expired' | 'failed' | 'cancelled' | 'undone' | 'superseded' | 'unknown';
3
+ export type ClosedReason = 'keep' | 'reject' | 'over-limit';
4
+ export type ApprovalState = 'on' | 'off';
5
+ export type FailureStage = 'command' | 'screen' | 'decide' | 'author' | 'validate' | 'commit' | 'run' | 'surface' | 'schema';
6
+ export type FailureCode = 'bad-pair' | 'bad-target' | 'bad-flag' | 'source-gone' | 'source-changed' | 'target-taken' | 'validation-failed' | 'commit-failed' | 'schema-invalid' | 'timeout' | 'capability-missing' | 'runtime';
7
+ export interface Failure {
8
+ readonly code: FailureCode;
9
+ readonly stage: FailureStage;
10
+ readonly retryable: boolean;
11
+ readonly reportOnly: boolean;
12
+ readonly userMessageKey: string;
13
+ readonly detail?: string;
14
+ }
15
+ export interface MergeDecision {
16
+ readonly target: string;
17
+ readonly sources: readonly string[];
18
+ readonly rationale?: string;
19
+ }
20
+ export interface CommitReceipt {
21
+ readonly unitId: string;
22
+ readonly evidenceSha256: string;
23
+ readonly targetRevision?: string;
24
+ readonly sourceRevisions?: Readonly<Record<string, string>>;
25
+ }
26
+ export type RunState = 'queued' | 'screening' | 'deciding' | 'awaiting-approval' | 'authoring' | 'committing' | 'settled' | 'failed' | 'cancelled' | 'recovering';
27
+ export interface SkillSnapshot {
28
+ readonly name: string;
29
+ readonly revision: string;
30
+ readonly protection?: 'managed' | 'bundled' | 'hub' | 'pinned' | 'protected' | 'unknown';
31
+ }
32
+ export interface Candidate {
33
+ readonly pairKey: string;
34
+ readonly a: string;
35
+ readonly b: string;
36
+ readonly evidenceSeq: readonly number[];
37
+ readonly score?: number;
38
+ }
39
+ export interface RunSnapshot {
40
+ readonly id: string;
41
+ readonly state: RunState;
42
+ readonly version: number;
43
+ readonly pairs: readonly string[];
44
+ readonly startedAt: string;
45
+ readonly updatedAt: string;
46
+ }
47
+ export interface UnitEffect {
48
+ readonly kind: 'skill' | 'usage' | 'staging' | 'report';
49
+ readonly ref: string;
50
+ }
51
+ export interface UnitPlan {
52
+ readonly unitId: string;
53
+ readonly pairKey: string;
54
+ readonly target: string;
55
+ readonly sources: readonly string[];
56
+ readonly effects: readonly UnitEffect[];
57
+ }
58
+ export interface PairSnapshot {
59
+ readonly id: string;
60
+ readonly pairKey: string;
61
+ readonly a: string;
62
+ readonly b: string;
63
+ readonly state: PairState;
64
+ readonly version: number;
65
+ readonly createdAt: string;
66
+ readonly updatedAt: string;
67
+ readonly origin?: 'agent' | 'human' | 'system';
68
+ readonly runId?: string;
69
+ readonly target?: string;
70
+ readonly decision?: MergeDecision;
71
+ readonly approval?: ApprovalState;
72
+ readonly failure?: Failure;
73
+ readonly closedReason?: ClosedReason;
74
+ readonly stageRef?: string;
75
+ readonly unitId?: string;
76
+ readonly expiresAt?: number;
77
+ readonly receipt?: CommitReceipt;
78
+ readonly attempts: number;
79
+ }
80
+ export type PairEvent = {
81
+ readonly type: 'screen';
82
+ readonly at: string;
83
+ } | {
84
+ readonly type: 'decide';
85
+ readonly at: string;
86
+ readonly decision: 'merge' | 'keep';
87
+ readonly approval: ApprovalState;
88
+ readonly target?: string;
89
+ readonly runId?: string;
90
+ } | {
91
+ readonly type: 'approve';
92
+ readonly at: string;
93
+ } | {
94
+ readonly type: 'reject';
95
+ readonly at: string;
96
+ } | {
97
+ readonly type: 'author-start';
98
+ readonly at: string;
99
+ } | {
100
+ readonly type: 'staged';
101
+ readonly at: string;
102
+ readonly stageRef: string;
103
+ } | {
104
+ readonly type: 'commit-start';
105
+ readonly at: string;
106
+ readonly unitId: string;
107
+ } | {
108
+ readonly type: 'commit-succeeded';
109
+ readonly at: string;
110
+ readonly receipt: CommitReceipt;
111
+ } | {
112
+ readonly type: 'commit-failed';
113
+ readonly at: string;
114
+ readonly failure: Failure;
115
+ } | {
116
+ readonly type: 'commit-unknown';
117
+ readonly at: string;
118
+ readonly reason: string;
119
+ } | {
120
+ readonly type: 'recover';
121
+ readonly at: string;
122
+ readonly outcome: 'committed' | 'failed' | 'cancelled';
123
+ readonly receipt?: CommitReceipt;
124
+ readonly failure?: Failure;
125
+ } | {
126
+ readonly type: 'close';
127
+ readonly at: string;
128
+ readonly reason: ClosedReason;
129
+ } | {
130
+ readonly type: 'expire';
131
+ readonly at: string;
132
+ readonly reason: 'pending-ttl' | 'staging-ttl';
133
+ } | {
134
+ readonly type: 'cancel';
135
+ readonly at: string;
136
+ } | {
137
+ readonly type: 'fail';
138
+ readonly at: string;
139
+ readonly failure: Failure;
140
+ } | {
141
+ readonly type: 'retry';
142
+ readonly at: string;
143
+ } | {
144
+ readonly type: 'undo';
145
+ readonly at: string;
146
+ } | {
147
+ readonly type: 'supersede';
148
+ readonly at: string;
149
+ };
150
+ export declare const TERMINAL_STATES: readonly PairState[];
151
+ export declare const ACTIVE_STATES: readonly PairState[];
152
+ export declare const ALL_STATES: readonly PairState[];
153
+ export declare const CLOSED_REASONS: readonly ClosedReason[];
154
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1,5 @@
1
+ export const TERMINAL_STATES = ['closed', 'expired', 'cancelled', 'committed', 'undone', 'superseded'];
2
+ export const ACTIVE_STATES = ['candidate', 'deciding', 'pending', 'approved', 'authoring', 'staged', 'committing', 'unknown'];
3
+ export const ALL_STATES = [...ACTIVE_STATES, ...TERMINAL_STATES];
4
+ export const CLOSED_REASONS = ['keep', 'reject', 'over-limit'];
5
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1,28 @@
1
+ /** Pure projection over the ledger. One expired predicate, one queue mapping. */
2
+ import type { PairSnapshot } from './model.ts';
3
+ import type { LedgerState } from './ledger.ts';
4
+ import type { UnitSnapshot } from './unit.ts';
5
+ export type ProjectionQueue = 'verify' | 'pending' | 'fuse' | 'skipped';
6
+ export interface RowProjection {
7
+ readonly id: string;
8
+ readonly pairKey: string;
9
+ readonly state: PairSnapshot['state'];
10
+ readonly queue: ProjectionQueue | null;
11
+ readonly expired: boolean;
12
+ readonly version: number;
13
+ }
14
+ export interface ProjectionCounts {
15
+ readonly active: Readonly<Record<ProjectionQueue, number>>;
16
+ readonly expired: number;
17
+ }
18
+ export interface MergeProjection {
19
+ readonly seq: number;
20
+ readonly rows: readonly RowProjection[];
21
+ readonly units: readonly UnitSnapshot[];
22
+ readonly counts: ProjectionCounts;
23
+ }
24
+ export declare function queueOf(pair: PairSnapshot): ProjectionQueue | null;
25
+ export declare function isExpired(pair: PairSnapshot, now: number): boolean;
26
+ export declare function project(state: LedgerState, now: number): MergeProjection;
27
+ export declare function projectionJson(projection: MergeProjection): string;
28
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1,44 @@
1
+ export function queueOf(pair) {
2
+ switch (pair.state) {
3
+ case 'candidate':
4
+ case 'deciding': return 'verify';
5
+ case 'pending': return 'pending';
6
+ case 'approved':
7
+ case 'authoring':
8
+ case 'staged':
9
+ case 'committing':
10
+ case 'unknown':
11
+ case 'failed': return 'fuse';
12
+ case 'closed': return 'skipped';
13
+ case 'expired': return 'skipped';
14
+ default: return null;
15
+ }
16
+ }
17
+ export function isExpired(pair, now) {
18
+ if (pair.state === 'expired')
19
+ return true;
20
+ if (pair.expiresAt === undefined)
21
+ return false;
22
+ if (pair.state !== 'pending' && pair.state !== 'staged')
23
+ return false;
24
+ return pair.expiresAt <= now;
25
+ }
26
+ export function project(state, now) {
27
+ const counts = { active: { verify: 0, pending: 0, fuse: 0, skipped: 0 }, expired: 0 };
28
+ const rows = [];
29
+ for (const pair of [...state.pairs.values()].sort((left, right) => left.id.localeCompare(right.id))) {
30
+ const expired = isExpired(pair, now);
31
+ const queue = queueOf(pair);
32
+ rows.push({ id: pair.id, pairKey: pair.pairKey, state: pair.state, queue, expired, version: pair.version });
33
+ if (expired)
34
+ counts.expired += 1;
35
+ else if (queue !== null)
36
+ counts.active[queue] += 1;
37
+ }
38
+ const units = [...state.units.values()].sort((left, right) => left.unitId.localeCompare(right.unitId));
39
+ return { seq: state.seq, rows, units, counts };
40
+ }
41
+ export function projectionJson(projection) {
42
+ return JSON.stringify(projection);
43
+ }
44
+ //# sourceMappingURL=projection.js.map