@lmzhen/dsh-evolution-merge 0.30.24 → 0.30.27

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 (84) hide show
  1. package/lib/index.js +549 -183
  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 +14 -6
  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/host-capabilities.d.ts +22 -0
  23. package/lib/types/host-capabilities.js +24 -0
  24. package/lib/types/index.js +12 -2
  25. package/lib/types/kernel/adapter-v4.d.ts +12 -0
  26. package/lib/types/kernel/adapter-v4.js +54 -0
  27. package/lib/types/kernel/failure.d.ts +8 -0
  28. package/lib/types/kernel/failure.js +33 -0
  29. package/lib/types/kernel/index.d.ts +9 -0
  30. package/lib/types/kernel/index.js +9 -0
  31. package/lib/types/kernel/invariants.d.ts +4 -0
  32. package/lib/types/kernel/invariants.js +47 -0
  33. package/lib/types/kernel/ledger.d.ts +46 -0
  34. package/lib/types/kernel/ledger.js +62 -0
  35. package/lib/types/kernel/machine.d.ts +13 -0
  36. package/lib/types/kernel/machine.js +108 -0
  37. package/lib/types/kernel/model.d.ts +154 -0
  38. package/lib/types/kernel/model.js +5 -0
  39. package/lib/types/kernel/projection.d.ts +28 -0
  40. package/lib/types/kernel/projection.js +44 -0
  41. package/lib/types/kernel/recovery.d.ts +31 -0
  42. package/lib/types/kernel/recovery.js +19 -0
  43. package/lib/types/kernel/unit.d.ts +43 -0
  44. package/lib/types/kernel/unit.js +63 -0
  45. package/lib/types/merge-routes.js +9 -0
  46. package/lib/types/new-child-authoring.d.ts +1 -0
  47. package/lib/types/new-child-authoring.js +15 -7
  48. package/lib/types/pending-actions.d.ts +1 -1
  49. package/lib/types/pending-actions.js +4 -0
  50. package/lib/types/recovery-coordinator.d.ts +2 -0
  51. package/lib/types/recovery-coordinator.js +14 -4
  52. package/lib/types/recovery-projection.d.ts +4 -2
  53. package/lib/types/recovery-projection.js +3 -3
  54. package/lib/types/resume-command.js +2 -1
  55. package/lib/types/round2-outcome.js +26 -9
  56. package/lib/types/run-shell.d.ts +1 -0
  57. package/lib/types/run-shell.js +57 -15
  58. package/lib/types/runtime.d.ts +6 -1
  59. package/lib/types/runtime.js +46 -22
  60. package/lib/types/scan-command.d.ts +1 -0
  61. package/lib/types/scan-command.js +1 -0
  62. package/lib/types/scan-ports.js +3 -0
  63. package/lib/types/scan-run.d.ts +6 -1
  64. package/lib/types/scan-run.js +34 -18
  65. package/lib/types/surface/merge-view.js +12 -4
  66. package/lib/types/undo-operation.d.ts +2 -1
  67. package/lib/types/undo-operation.js +10 -4
  68. package/lib/types/wire-projection.js +7 -2
  69. package/lib/types/wire.d.ts +1 -0
  70. package/package.json +5 -5
  71. package/lib/types/adapters/model.d.ts +0 -31
  72. package/lib/types/adapters/model.js +0 -47
  73. package/lib/types/gate-cache.d.ts +0 -12
  74. package/lib/types/gate-cache.js +0 -36
  75. package/lib/types/gate-filter.d.ts +0 -31
  76. package/lib/types/gate-filter.js +0 -64
  77. package/lib/types/ladder.d.ts +0 -65
  78. package/lib/types/ladder.js +0 -162
  79. package/lib/types/propose.d.ts +0 -26
  80. package/lib/types/propose.js +0 -69
  81. package/lib/types/screening-port.d.ts +0 -47
  82. package/lib/types/screening-port.js +0 -93
  83. package/lib/types/screening.d.ts +0 -30
  84. package/lib/types/screening.js +0 -75
@@ -38,7 +38,7 @@ export async function runApproveCommand(options) {
38
38
  const current = tx.rows().find(item => item.id === row.id);
39
39
  if (current === undefined)
40
40
  return null;
41
- const moved = tx.transition(row.id, { type: 'staged' }, current.version);
41
+ const moved = tx.transition(row.id, { type: 'staged', stagingRef: staged.entry.ref, stagingExpiresAt: staged.entry.expiresAt }, current.version);
42
42
  return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
43
43
  });
44
44
  if (fused === null)
@@ -53,6 +53,7 @@ export async function runApproveCommand(options) {
53
53
  command: 'approve',
54
54
  approval: { platformWrite: options.platformWrite, mergeApproval: true },
55
55
  pairKey: row.pairKey,
56
+ ...(row.stagingRef === undefined ? {} : { stagingRef: row.stagingRef }),
56
57
  parentRunId,
57
58
  now: options.now,
58
59
  ...(options.detached === true ? { detached: true } : {}),
@@ -23,6 +23,8 @@ export interface RunAuthoringOptions {
23
23
  readonly stageKey?: string;
24
24
  readonly reuseStaging?: boolean;
25
25
  readonly target: string;
26
+ /** The two participating source skills; conflict detection is limited to these. */
27
+ readonly sources?: readonly string[];
26
28
  readonly mode?: 'merge-into-existing' | 'create-umbrella' | 'keep';
27
29
  readonly cards: readonly SkillCard[];
28
30
  readonly author: (staging: {
@@ -37,6 +39,8 @@ export interface RunAuthoringOptions {
37
39
  readonly limits?: SkillLimits;
38
40
  readonly promptVersion?: string;
39
41
  readonly promptHash?: string;
42
+ /** Propagated to the commit; aborting before the durable point prevents a orphaned commit. */
43
+ readonly signal?: AbortSignal;
40
44
  /** Round-1 decision anchors; when absent the commit keeps the legacy body-only CAS. */
41
45
  readonly revisions?: CommitRevisionRefs;
42
46
  }
@@ -47,6 +51,7 @@ export type AuthoringResult = {
47
51
  readonly commit: Extract<CommitStagedResult, {
48
52
  readonly ok: true;
49
53
  }>;
54
+ readonly lengths: Readonly<Record<string, number>>;
50
55
  } | {
51
56
  readonly ok: false;
52
57
  readonly code?: string;
@@ -10,7 +10,12 @@ export async function runAuthoring(options) {
10
10
  if (options.target.includes('/') || options.target.includes(':'))
11
11
  return { ok: false, code: 'target-invalid', message: 'target must be a bare skill name: ' + options.target };
12
12
  const targetCard = options.cards.find(card => card.name === options.target);
13
- const mode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
13
+ // A decision can name a fresh target yet say merge-into-existing (the model's vocabulary is
14
+ // about intent, not existence). The target card is the authoritative existence fact: when it is
15
+ // absent, the only safe mode is create-umbrella; otherwise commit-assembly correctly refuses to
16
+ // re-create a target that disappeared later.
17
+ const requestedMode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
18
+ const mode = requestedMode === 'merge-into-existing' && targetCard === undefined ? 'create-umbrella' : requestedMode;
14
19
  const createMode = mode === 'create-umbrella';
15
20
  if (!createMode && targetCard !== undefined) {
16
21
  if (targetCard.managed !== true)
@@ -58,25 +63,28 @@ export async function runAuthoring(options) {
58
63
  if (missing !== null)
59
64
  return { ok: false, code: 'merge-manifest-missing', message: 'manifest file missing from staged tree: ' + missing };
60
65
  const limitsSnapshot = limitsSnapshotOf(options.limits ?? DEFAULT_SKILL_LIMITS);
61
- const supportSources = options.cards.flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name })));
66
+ const participants = new Set(options.sources ?? []);
67
+ const supportSources = options.cards.filter(card => participants.has(card.name)).flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name })));
62
68
  const validationPlan = buildValidationPlan({ tree: tree.value, target: options.target, limits: limitsSnapshot, cards: options.cards, supportSources, ...(options.promptVersion === undefined ? {} : { promptVersion: options.promptVersion }), ...(options.promptHash === undefined ? {} : { promptHash: options.promptHash }), supportFiles: tree.value.files.map(file => ({ path: file.path, content: file.content, source: 'staged' })), manifestFiles, existingSupport: (options.cards.find(card => card.name === options.target)?.files ?? []).map(file => file.path) });
63
69
  const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
70
+ const lengths = { bodyBytes: Buffer.byteLength(tree.value.body, 'utf8'), fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0), totalBytes: Buffer.byteLength(tree.value.body, 'utf8') + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) };
64
71
  if (hasBlockingFinding(findings))
65
- return { ok: false, code: findings.some(finding => finding.rule === 'limit') ? 'merge-over-limit' : 'merge-validation', message: 'staged tree failed the rule gate', findings, lengths: { bodyBytes: Buffer.byteLength(tree.value.body, 'utf8'), fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0), totalBytes: Buffer.byteLength(tree.value.body, 'utf8') + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) } };
72
+ return { ok: false, code: findings.some(finding => finding.rule === 'limit') ? 'merge-over-limit' : 'merge-validation', message: 'staged tree failed the rule gate', findings, lengths };
66
73
  const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
67
74
  origin: 'foreground',
68
75
  summary: options.summary,
69
76
  now: options.now,
77
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
70
78
  ...(options.isBlocked === undefined ? {} : { isBlocked: options.isBlocked }),
71
79
  ...(options.unit === undefined ? {} : { unit: options.unit }),
72
- ...(options.limits === undefined ? {} : { limits: options.limits }),
73
- validationPlan: { planVersion: validationPlan.planVersion, stageDigest: validationPlan.stageDigest, revision: validationPlan.revision },
80
+ limits: limitsSnapshot.skillLimits,
81
+ validationPlan: { planVersion: validationPlan.planVersion, stageDigest: validationPlan.stageDigest, revision: validationPlan.revision, manifestFiles: validationPlan.manifestFiles },
74
82
  ...(options.revisions?.targetRevision === undefined ? {} : { targetRevision: options.revisions.targetRevision }),
75
83
  ...(options.revisions?.sourceRevisions === undefined ? {} : { sourceRevisions: options.revisions.sourceRevisions }),
76
84
  ...(options.revisions?.supportDigest === undefined ? {} : { supportDigest: options.revisions.supportDigest }),
77
85
  });
78
86
  if (commit.ok)
79
- return { ok: true, tree: tree.value, validationPlan, commit };
87
+ return { ok: true, tree: tree.value, validationPlan, commit, lengths };
80
88
  return { ok: false, code: commit.code ?? 'commit-failed', message: commit.message };
81
89
  }
82
90
  //# sourceMappingURL=authoring-flow.js.map
@@ -59,5 +59,12 @@ export type ParseMergeCommandResult = {
59
59
  readonly message: string;
60
60
  };
61
61
  export declare function parseMergeCommand(argv: readonly string[]): ParseMergeCommandResult;
62
+ export interface MergeLifecycleVerb {
63
+ readonly verb: string;
64
+ readonly appliesTo: string;
65
+ readonly excludes: readonly string[];
66
+ }
67
+ /** Machine-readable three-verb table: merge undo / skill undo / skill restore. */
68
+ export declare const MERGE_LIFECYCLE_VERBS: readonly MergeLifecycleVerb[];
62
69
  export declare function mergeUsage(): string;
63
70
  //# sourceMappingURL=commands.d.ts.map
@@ -34,7 +34,14 @@ export function parseMergeCommand(argv) {
34
34
  default: return { ok: false, code: 'usage', message: 'unknown merge command: ' + parsed.id };
35
35
  }
36
36
  }
37
+ /** Machine-readable three-verb table: merge undo / skill undo / skill restore. */
38
+ export const MERGE_LIFECYCLE_VERBS = [
39
+ { verb: 'merge undo', appliesTo: '一次 merge run(按 run 反序撤销 unit)', excludes: ['skill undo', 'skill restore'] },
40
+ { verb: 'skill undo', appliesTo: '单个技能的一次 patch', excludes: ['merge undo'] },
41
+ { verb: 'skill restore', appliesTo: '从 .archive 恢复整棵技能树', excludes: ['merge undo'] },
42
+ ];
37
43
  export function mergeUsage() {
38
- return 'usage: ' + MERGE_COMMAND_DEFS.filter(def => def.id !== 'merge.doctor' && def.usage !== false).map(def => def.fallback).join(' | ') + '\ndefaults: mergeApproval=false, mergeSkippedTtlDays=0, mergeRunTimeoutMinutes=0, mergePairTimeoutMinutes=15, mergeModelsRoot=""';
44
+ return 'usage: ' + MERGE_COMMAND_DEFS.filter(def => def.id !== 'merge.doctor' && def.usage !== false).map(def => def.fallback).join(' | ') + '\ndefaults: mergeApproval=false, mergeSkippedTtlDays=0, mergeRunTimeoutMinutes=0, mergePairTimeoutMinutes=15, mergeModelsRoot=""'
45
+ + '\nlifecycle: ' + MERGE_LIFECYCLE_VERBS.map(item => item.verb + '=' + item.appliesTo + ';互斥 ' + item.excludes.join('/')).join(' | ');
39
46
  }
40
47
  //# sourceMappingURL=commands.js.map
@@ -26,6 +26,10 @@ export interface CommitStagedOptions {
26
26
  readonly targetRevision?: import('./contracts.ts').RevisionRef;
27
27
  readonly sourceRevisions?: Readonly<Record<string, import('./contracts.ts').RevisionRef>>;
28
28
  readonly supportDigest?: string;
29
+ /** The run that started this child; absent means the receipt itself is the root run. */
30
+ readonly parentRunId?: string;
31
+ /** Aborts a commit before its durable point; a landed commit still gets a receipt. */
32
+ readonly signal?: AbortSignal;
29
33
  /** G2 transitional: when present, commit through core UnitTransaction. */
30
34
  readonly unit?: UnitTransaction;
31
35
  }
@@ -21,6 +21,7 @@ export async function commitStagedRun(io, home, runId, port, options) {
21
21
  const journal = { runId, target: assembled.plan.target, create: !assembled.plan.targetExists, sources: assembled.plan.sources, phase: 'prepared', updatedAt: new Date(options.now()).toISOString() };
22
22
  let result;
23
23
  let evidenceSha256;
24
+ let warning;
24
25
  const transaction = options.unit ?? port.unit ?? legacyUnitAdapter(port, assembled.plan, options, runId);
25
26
  if (transaction === undefined)
26
27
  return { ok: false, message: 'commit port missing: UnitTransaction or commitMerge required', plan: assembled.plan };
@@ -34,7 +35,7 @@ export async function commitStagedRun(io, home, runId, port, options) {
34
35
  const prepared = await transaction.prepare(unitPlan);
35
36
  if (!prepared.ok)
36
37
  return { ok: false, message: prepared.message, plan: assembled.plan };
37
- const committed = await transaction.commit(prepared.prepared);
38
+ const committed = await transaction.commit(prepared.prepared, options.signal === undefined ? undefined : { signal: options.signal });
38
39
  if (!committed.ok) {
39
40
  try {
40
41
  await writeJournal(io, home, { ...journal, phase: 'failed', updatedAt: new Date(options.now()).toISOString() });
@@ -49,8 +50,10 @@ export async function commitStagedRun(io, home, runId, port, options) {
49
50
  const finalize = transaction.finalize;
50
51
  if (finalize !== undefined) {
51
52
  const finalized = await finalize.call(transaction, unitId);
53
+ // The tree is already committed. Finalize is post-commit bookkeeping; failing it must
54
+ // not turn a landed merge into a failed run. Keep the receipt path alive and warn.
52
55
  if (!finalized.ok)
53
- return { ok: false, message: 'unit committed but finalize failed: ' + finalized.message, plan: assembled.plan };
56
+ warning = 'commit landed but finalize failed: ' + finalized.message;
54
57
  }
55
58
  evidenceSha256 = digestEvidence(committed.evidence);
56
59
  result = { ok: true, message: 'merged', unitId, archived: committed.evidence.sources.filter(source => source.archived).map(source => source.name), mutations: committed.evidence.mutations };
@@ -73,7 +76,7 @@ export async function commitStagedRun(io, home, runId, port, options) {
73
76
  runId,
74
77
  ...(evidenceSha256 === undefined ? {} : { evidenceSha256 }),
75
78
  ...(options.validationPlan === undefined ? {} : { validationPlan: options.validationPlan }),
76
- parentRunId: runId.split('/pairs/')[0] ?? runId,
79
+ ...(options.parentRunId === undefined ? {} : { parentRunId: options.parentRunId }),
77
80
  unitId: result.unitId,
78
81
  target: assembled.plan.target,
79
82
  create: !assembled.plan.targetExists,
@@ -88,14 +91,14 @@ export async function commitStagedRun(io, home, runId, port, options) {
88
91
  await io.remove(snapshotPath(home, runId));
89
92
  }
90
93
  catch { /* committed journal is the record */ }
91
- return { ok: true, plan: assembled.plan, result, receipt };
94
+ return { ok: true, plan: assembled.plan, result, receipt, ...(warning === undefined ? {} : { warning }) };
92
95
  }
93
96
  catch (error) {
94
97
  return {
95
98
  ok: true,
96
99
  plan: assembled.plan,
97
100
  result,
98
- warning: 'commit landed but receipt write failed: ' + (error instanceof Error ? error.message : String(error)),
101
+ warning: (warning === undefined ? '' : warning + '; ') + 'commit landed but receipt write failed: ' + (error instanceof Error ? error.message : String(error)),
99
102
  };
100
103
  }
101
104
  }
@@ -35,7 +35,9 @@ export async function writeCommitReceipt(io, home, receipt) {
35
35
  const path = commitReceiptPath(home, receipt.runId);
36
36
  await io.writeText(path, JSON.stringify(receipt, null, 2) + '\n');
37
37
  const parent = receipt.parentRunId ?? receipt.runId.split('/pairs/')[0] ?? receipt.runId;
38
- await io.writeText(durableReceiptPath(home, parent, receipt.runId), JSON.stringify({ ...receipt, parentRunId: parent }, null, 2) + '\n');
38
+ // One durable file per unit: two pairs of the same run/target must not overwrite each other.
39
+ const leaf = (receipt.runId + '-' + receipt.unitId).replace(/[^A-Za-z0-9._-]+/g, '-');
40
+ await io.writeText(durableReceiptPath(home, parent, leaf), JSON.stringify({ ...receipt, parentRunId: parent }, null, 2) + '\n');
39
41
  return path;
40
42
  }
41
43
  export async function readCommitReceipts(io, home) {
@@ -57,6 +57,7 @@ export interface MergeDecision {
57
57
  readonly sources: readonly string[];
58
58
  readonly rationale: string;
59
59
  readonly confidence?: number;
60
+ readonly model?: string;
60
61
  readonly promptVersion?: string;
61
62
  readonly promptHash?: string;
62
63
  readonly aSummary?: string;
@@ -72,6 +73,7 @@ export interface MergeValidationPlanRef {
72
73
  readonly planVersion: 1;
73
74
  readonly stageDigest: string;
74
75
  readonly revision: string;
76
+ readonly manifestFiles?: readonly string[];
75
77
  }
76
78
  export interface MergeUsage {
77
79
  readonly inputTokens?: number;
@@ -108,7 +110,11 @@ export interface MergeReportPair {
108
110
  readonly lengths?: Readonly<Record<string, number>>;
109
111
  readonly timing?: MergeTiming;
110
112
  readonly validationPlan?: MergeValidationPlanRef;
113
+ readonly manifestFiles?: readonly string[];
111
114
  readonly criteria?: MergeCriteria;
115
+ readonly validation?: {
116
+ readonly issues: readonly MergeValidationIssue[];
117
+ };
112
118
  readonly reportOnly?: boolean;
113
119
  }
114
120
  export interface MergeCriteria {
@@ -118,6 +124,12 @@ export interface MergeCriteria {
118
124
  readonly archive: 'none' | 'sources';
119
125
  readonly evidence: 'none' | 'receipt';
120
126
  }
127
+ export interface MergeValidationIssue {
128
+ readonly rule: string;
129
+ readonly path: string;
130
+ readonly severity: 'block' | 'warn';
131
+ readonly message: string;
132
+ }
121
133
  export interface MergeReport {
122
134
  readonly schemaVersion: 2;
123
135
  readonly runId: string;
@@ -125,7 +137,7 @@ export interface MergeReport {
125
137
  readonly command: MergeRunCommand;
126
138
  readonly startedAt: number;
127
139
  readonly endedAt: number;
128
- readonly stopReason: 'completed' | 'failed' | 'cancelled' | 'timeout';
140
+ readonly stopReason: 'completed' | 'failed' | 'cancelled' | 'timeout' | 'interrupted';
129
141
  readonly approval: {
130
142
  readonly platformWrite: boolean;
131
143
  readonly mergeApproval: boolean;
@@ -135,12 +147,19 @@ export interface MergeReport {
135
147
  readonly queueBefore: Record<MergeQueue, number>;
136
148
  };
137
149
  readonly pairs: readonly MergeReportPair[];
150
+ /** Backward-compatible alias; same objects as pairs[]. */
151
+ readonly processedPairs?: readonly MergeReportPair[];
138
152
  readonly totals: Record<'merged' | 'kept' | 'pending' | 'failed', number>;
139
153
  readonly notes: readonly string[];
140
154
  readonly remaining?: readonly {
141
155
  readonly pairKey: string;
142
156
  readonly queue: MergeQueue;
143
157
  }[];
158
+ /** Backward-compatible alias; same objects as remaining[]. */
159
+ readonly remainingPairs?: readonly {
160
+ readonly pairKey: string;
161
+ readonly queue: MergeQueue;
162
+ }[];
144
163
  readonly reportOnly?: readonly MergeReportPair[];
145
164
  readonly validationFailures?: readonly MergeReportPair[];
146
165
  readonly archived?: readonly string[];
@@ -61,7 +61,7 @@ export async function mergeDoctor(io, home, configuredRoot) {
61
61
  key: 'state',
62
62
  label: '合并状态',
63
63
  status: snapshot.blocked === undefined || !hasState ? 'ok' : 'warn',
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) : snapshot.blocked.code + ': ' + snapshot.blocked.message,
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,
65
65
  });
66
66
  const recoveryRuns = new Set();
67
67
  try {
@@ -1,8 +1,12 @@
1
1
  function redact(text) {
2
2
  return text
3
3
  .replace(/sk-[A-Za-z0-9_-]{16,}/g, '[redacted]')
4
+ .replace(/ghp_[A-Za-z0-9]{20,}/g, '[redacted]')
5
+ .replace(/github_pat_[A-Za-z0-9_]{20,}/g, '[redacted]')
6
+ .replace(/xox[baprs]-[A-Za-z0-9-]{10,}/g, '[redacted]')
7
+ .replace(/AKIA[0-9A-Z]{16}/g, '[redacted]')
4
8
  .replace(/Bearer\s+\S+/gi, 'Bearer [redacted]')
5
- .replace(/(api[_-]?key\s*[:=]\s*)\S+/gi, '$1[redacted]');
9
+ .replace(/((?:api[_-]?key|token|password|secret)\s*[:=]\s*)\S+/gi, '$1[redacted]');
6
10
  }
7
11
  /** Build the typed failure every command/CLI/runtime branch returns. */
8
12
  export function commandError(stage, code, hint, next, runId) {
@@ -4,15 +4,15 @@ import { type StageEntry } from './stage-aggregate.ts';
4
4
  import { type MergeRunMeta } from './run-coordinator.ts';
5
5
  import type { MergeReport } from '../contracts.ts';
6
6
  import type { V4Row } from './row-v4.ts';
7
- import { type LiveRowFacts, type PendingExpiryKind } from './live-facts.ts';
8
- import type { QueueId, MergeCounts, DoctorFact } from '../wire.ts';
7
+ import { type LiveRowFacts } from './live-facts.ts';
8
+ import type { QueueId, MergeCounts, DoctorFact, ExpiredKind } from '../wire.ts';
9
9
  export interface SnapshotRow {
10
10
  readonly row: V4Row;
11
11
  readonly stage: StageEntry | null;
12
12
  readonly bodies: readonly BodyRef[];
13
13
  readonly queue: QueueId;
14
14
  readonly expired: boolean;
15
- readonly expiredKind?: PendingExpiryKind;
15
+ readonly expiredKind?: ExpiredKind;
16
16
  }
17
17
  export interface BodyRef {
18
18
  readonly rowId: string;
@@ -76,6 +76,10 @@ export interface MergeSnapshot {
76
76
  readonly history: readonly HistoryItem[];
77
77
  readonly bodyRefs: readonly BodyRef[];
78
78
  readonly doctor: SnapshotDoctor;
79
+ readonly hostOnly?: {
80
+ readonly code: 'E-323';
81
+ readonly detail?: string;
82
+ };
79
83
  readonly approval: {
80
84
  readonly mergeApproval: boolean;
81
85
  readonly platformWrite?: boolean;
@@ -89,6 +93,11 @@ export interface ReadSnapshotOptions {
89
93
  readonly runs: readonly RunRecord[];
90
94
  readonly approval: boolean;
91
95
  readonly platformWrite?: boolean;
96
+ /** Host data-plane capability probe; absent means available/unknown. */
97
+ readonly hostOnly?: () => Promise<{
98
+ readonly code: 'E-323';
99
+ readonly detail?: string;
100
+ } | undefined>;
92
101
  readonly now?: () => number;
93
102
  readonly history?: () => Promise<readonly HistoryItem[]>;
94
103
  readonly notes?: readonly string[];
@@ -13,9 +13,10 @@ const queueOf = (state) => state === 'approved' || state === 'fuse' ? 'fuse' : s
13
13
  export async function readMergeSnapshot(options) {
14
14
  const now = options.now ?? (() => Date.now());
15
15
  const readAt = now();
16
+ const hostOnly = options.hostOnly === undefined ? undefined : await options.hostOnly().catch((error) => ({ code: 'E-323', detail: error instanceof Error ? error.message : String(error) }));
16
17
  const readiness = await readV4Readiness(options.io, options.home);
17
18
  if (!readiness.ok) {
18
- return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision: 'blocked', consistency: 'eventual', blocked: { code: readiness.code, message: readiness.message, readiness }, rows: [], stages: [], runs: [], reports: [], report: null, history: [], bodyRefs: [], doctor: { counts: emptyCounts, staging: { state: 'absent', dirs: 0 }, rows: [] }, approval: { mergeApproval: options.approval, ...(options.platformWrite === undefined ? {} : { platformWrite: options.platformWrite }) }, notes: options.notes ?? [], degraded: true };
19
+ return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision: 'blocked', consistency: 'eventual', ...(hostOnly === undefined ? {} : { hostOnly }), blocked: { code: readiness.code, message: readiness.message, readiness }, rows: [], stages: [], runs: [], reports: [], report: null, history: [], bodyRefs: [], doctor: { counts: emptyCounts, staging: { state: 'absent', dirs: 0 }, rows: [] }, approval: { mergeApproval: options.approval, ...(options.platformWrite === undefined ? {} : { platformWrite: options.platformWrite }) }, notes: options.notes ?? [], degraded: true };
19
20
  }
20
21
  const ledger = await readPairLedgerV4(options.io, options.home);
21
22
  // Read-time derivation only: a stage past its TTL reads as expired even when the
@@ -31,8 +32,12 @@ export async function readMergeSnapshot(options) {
31
32
  for (const row of ledger?.rows ?? []) {
32
33
  const stage = stages.find(item => item.rowId === row.id) ?? null;
33
34
  const queue = queueOf(row.state);
34
- const bodies = [{ rowId: row.id, name: row.a, side: 'live' }, { rowId: row.id, name: row.b, side: 'archive' }];
35
- const expiredKind = persistedExpiry(row) ?? pendingExpiry(row, live);
35
+ // A row's b is a live source until it is actually archived; only then is the archive
36
+ // body the addressable one. Pending/fuse/verify rows therefore expose two live bodies.
37
+ const bSide = live !== undefined && live.managed(row.b) ? 'live' : 'archive';
38
+ const bodies = [{ rowId: row.id, name: row.a, side: 'live' }, { rowId: row.id, name: row.b, side: bSide }];
39
+ const skippedExpired = row.state === 'skipped' && row.expiredAt !== undefined;
40
+ const expiredKind = skippedExpired ? 'skipped-ttl' : persistedExpiry(row) ?? pendingExpiry(row, live);
36
41
  rows.push({ row, stage, bodies, queue, expired: expiredKind !== null, ...(expiredKind === null ? {} : { expiredKind }) });
37
42
  bodyRefs.push(...bodies);
38
43
  }
@@ -69,7 +74,7 @@ export async function readMergeSnapshot(options) {
69
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')));
70
75
  if (ledgerConflicts.length > 0)
71
76
  notes.push('ledger-conflict: pair-index says completed but the ledger still has it active (' + String(ledgerConflicts.length) + ')');
72
- return { wireVersion: 1, schemaVersion: 4, readAt, queueRevision, consistency: 'eventual', 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 };
77
+ 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 };
73
78
  }
74
79
  async function readHistory(io, home) {
75
80
  const receipts = await readCommitReceipts(io, home);
@@ -106,7 +106,7 @@ export async function reconcileMergeState(options) {
106
106
  continue;
107
107
  const entry = byRow.get(row.id);
108
108
  if (usable.get(row.id) === true) {
109
- const moved = tx.transition(row.id, { type: 'staged' }, current.version);
109
+ const moved = tx.transition(row.id, { type: 'staged', ...(entry === undefined ? {} : { stagingRef: entry.ref, stagingExpiresAt: entry.expiresAt }) }, current.version);
110
110
  if (moved.ok) {
111
111
  actions.push({ kind: 'approved-fused', rowId: row.id, from: 'approved', to: 'fuse', code: 'stage-present' });
112
112
  if (entry !== undefined && entry.state === 'reserved')
@@ -49,6 +49,8 @@ export interface FuseRowV4 extends RowBaseV4 {
49
49
  readonly sourceRevision?: string;
50
50
  readonly targetRevision?: string;
51
51
  readonly runId?: string;
52
+ readonly stagingRef?: string;
53
+ readonly stagingExpiresAt?: number;
52
54
  readonly attempts: number;
53
55
  }
54
56
  export interface SkippedRowV4 extends RowBaseV4 {
@@ -76,6 +78,8 @@ export type V4Event = {
76
78
  readonly runId?: string;
77
79
  } | {
78
80
  readonly type: 'staged';
81
+ readonly stagingRef?: string;
82
+ readonly stagingExpiresAt?: number;
79
83
  } | {
80
84
  readonly type: 'committed';
81
85
  readonly unitId: CommitUnitRef;
@@ -24,7 +24,7 @@ export function transitionV4(row, event, expectedVersion, at = new Date().toISOS
24
24
  if (event.type === 'auto-approved')
25
25
  return { ok: true, row: advance(row, at, { state: 'approved', ...(event.runId === undefined ? {} : { runId: event.runId }) }), version: expectedVersion + 1 };
26
26
  if (event.type === 'staged')
27
- return { ok: true, row: advance(row, at, { state: 'fuse' }), version: expectedVersion + 1 };
27
+ return { ok: true, row: advance(row, at, { state: 'fuse', ...(event.stagingRef === undefined ? {} : { stagingRef: event.stagingRef }), ...(event.stagingExpiresAt === undefined ? {} : { stagingExpiresAt: event.stagingExpiresAt }) }), version: expectedVersion + 1 };
28
28
  if (event.type === 'skipped')
29
29
  return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason, ...(event.origin === undefined ? {} : { origin: event.origin }), ...(event.skipSource === undefined ? {} : { skipSource: event.skipSource }) }), version: expectedVersion + 1 };
30
30
  if (event.type === 'expired')
@@ -36,15 +36,15 @@ export function transitionV4(row, event, expectedVersion, at = new Date().toISOS
36
36
  return illegal('pending/' + event.type);
37
37
  case 'approved':
38
38
  if (event.type === 'staged')
39
- return { ok: true, row: advance(row, at, { state: 'fuse' }), version: expectedVersion + 1 };
39
+ return { ok: true, row: advance(row, at, { state: 'fuse', ...(event.stagingRef === undefined ? {} : { stagingRef: event.stagingRef }), ...(event.stagingExpiresAt === undefined ? {} : { stagingExpiresAt: event.stagingExpiresAt }) }), version: expectedVersion + 1 };
40
40
  if (event.type === 'committed')
41
41
  return { ok: true, row: null, version: expectedVersion + 1 };
42
42
  if (event.type === 'skipped')
43
43
  return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason, ...(event.skipSource === undefined ? {} : { skipSource: event.skipSource }) }), version: expectedVersion + 1 };
44
+ // A failure is never a "no merge" decision. Keep the row in fuse with the failure
45
+ // recorded; retryable is a property of the failure, not a different queue.
44
46
  if (event.type === 'failed')
45
- return event.failure.retryable
46
- ? { ok: true, row: advance(row, at, { state: 'fuse', failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 }
47
- : { ok: true, row: advance(row, at, { state: 'skipped', reason: event.failure.code, failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
47
+ return { ok: true, row: advance(row, at, { state: 'fuse', failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
48
48
  if (event.type === 'superseded')
49
49
  return { ok: true, row: null, version: expectedVersion + 1 };
50
50
  return illegal('approved/' + event.type);
@@ -54,9 +54,7 @@ export function transitionV4(row, event, expectedVersion, at = new Date().toISOS
54
54
  if (event.type === 'skipped')
55
55
  return { ok: true, row: advance(row, at, { state: 'skipped', reason: event.reason, ...(event.skipSource === undefined ? {} : { skipSource: event.skipSource }) }), version: expectedVersion + 1 };
56
56
  if (event.type === 'failed')
57
- return event.failure.retryable
58
- ? { ok: true, row: advance(row, at, { state: 'fuse', 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 };
57
+ return { ok: true, row: advance(row, at, { state: 'fuse', failure: event.failure, attempts: row.attempts + 1 }), version: expectedVersion + 1 };
60
58
  if (event.type === 'superseded')
61
59
  return { ok: true, row: null, version: expectedVersion + 1 };
62
60
  return illegal('fuse/' + event.type);
@@ -97,7 +95,7 @@ export function v3ToV4(row) {
97
95
  if (row.state === 'approved')
98
96
  return { ...base, state: 'approved', target: row.target, decision: row.decision, ...(row.sourceRevision === undefined ? {} : { sourceRevision: row.sourceRevision }), ...(row.runId === undefined ? {} : { runId: row.runId }) };
99
97
  if (row.state === 'fuse')
100
- return { ...base, state: 'fuse', target: row.target, decision: row.decision, ...(row.sourceRevision === undefined ? {} : { sourceRevision: row.sourceRevision }), ...(row.runId === undefined ? {} : { runId: row.runId }) };
98
+ return { ...base, state: 'fuse', target: row.target, decision: row.decision, ...(row.sourceRevision === undefined ? {} : { sourceRevision: row.sourceRevision }), ...(row.runId === undefined ? {} : { runId: row.runId }), ...(row.staging?.kind === 'present' ? { stagingRef: row.staging.ref, stagingExpiresAt: row.staging.expiresAt } : row.staging?.kind === 'expired' && row.staging.ref !== undefined ? { stagingRef: row.staging.ref, stagingExpiresAt: row.staging.expiresAt } : {}) };
101
99
  return { ...base, state: 'skipped', reason: row.reason, ...(row.expiredAt === undefined ? {} : { expiredAt: row.expiredAt }) };
102
100
  }
103
101
  export function applyV4Write(rows, write, at) {
@@ -151,6 +149,8 @@ export function v4ToLegacy(row) {
151
149
  ...((row.state === 'pending' || row.state === 'approved' || row.state === 'fuse') && row.sourceRevision !== undefined ? { sourceRevision: row.sourceRevision } : {}),
152
150
  ...((row.state === 'pending' || row.state === 'approved' || row.state === 'fuse') && row.targetRevision !== undefined ? { targetRevision: row.targetRevision } : {}),
153
151
  ...(row.runId === undefined ? {} : { runId: row.runId }),
152
+ ...(row.state === 'fuse' && row.stagingRef !== undefined ? { stagingRef: row.stagingRef } : {}),
153
+ ...(row.state === 'fuse' && row.stagingExpiresAt !== undefined ? { stagingExpiresAt: row.stagingExpiresAt } : {}),
154
154
  ...(row.origin === undefined ? {} : { origin: row.origin }),
155
155
  ...(row.attempts === undefined ? {} : { attempts: row.attempts }),
156
156
  createdAt: row.createdAt, updatedAt: row.updatedAt,
@@ -6,7 +6,16 @@ export interface MergeRunMeta {
6
6
  readonly pairCount?: number;
7
7
  /** The pair a single-pair command (pair/approve/resume) is working on; absent for scan. */
8
8
  readonly pairKey?: string;
9
+ /** Staging tree this run authors/commits; absent for scan before staging. */
10
+ readonly stagingRef?: string;
11
+ /** Pair rows this run claims, when it is a single-pair command. */
12
+ readonly pairs?: readonly {
13
+ readonly pairKey: string;
14
+ readonly state: 'pending' | 'approved' | 'fused' | 'skipped';
15
+ }[];
9
16
  readonly parentRunId?: string;
17
+ /** Owner process; lets a shared run-meta distinguish a live foreign plane from a dead writer. */
18
+ readonly pid?: number;
10
19
  readonly phase: MergeRunPhase;
11
20
  readonly stopReason?: string;
12
21
  readonly approval?: 'pending' | 'approved' | 'rejected';
@@ -80,12 +80,17 @@ export declare function readStageIndex(io: EvolutionIoLike, home: string, rowId:
80
80
  export declare function readStageEntry(io: EvolutionIoLike, home: string, rowId: string): Promise<StageEntry | null>;
81
81
  export declare function reserveStage(io: EvolutionIoLike, home: string, entry: StageEntry): Promise<void>;
82
82
  export declare function presentStage(io: EvolutionIoLike, home: string, entry: StageEntry): Promise<void>;
83
- export declare function dropStage(io: EvolutionIoLike, home: string, rowId: string, ref?: string): Promise<void>;
83
+ export declare function dropStage(io: EvolutionIoLike, home: string, rowId: string, ref?: string, onError?: (path: string, error: unknown) => void): Promise<void>;
84
+ export interface StageCleanupFailure {
85
+ readonly path: string;
86
+ readonly message: string;
87
+ }
84
88
  export interface StageCleanupResult {
85
89
  readonly removed: readonly string[];
86
90
  readonly expired: readonly string[];
87
91
  readonly kept: readonly string[];
88
92
  readonly orphans: readonly string[];
93
+ readonly failures: readonly StageCleanupFailure[];
89
94
  }
90
95
  /** Bounded cleanup: reserved/present TTL, pending/verify stale index removal, terminal/absent rows, orphan trees. */
91
96
  export declare function cleanupStage(io: EvolutionIoLike, home: string, now: number, rows: ReadonlyMap<string, {
@@ -205,10 +205,13 @@ export async function reserveStage(io, home, entry) {
205
205
  export async function presentStage(io, home, entry) {
206
206
  await writeStageIndex(io, home, { rowId: entry.rowId, pairKey: entry.pairKey, ref: entry.ref, expiresAt: entry.expiresAt, ...(entry.runId === undefined ? {} : { runId: entry.runId }), createdAt: entry.createdAt, state: 'present' });
207
207
  }
208
- export async function dropStage(io, home, rowId, ref) {
209
- await io.remove(join(home, 'merge', 'staging-index', encodeURIComponent(rowId) + '.json')).catch(() => { });
210
- if (ref !== undefined && ref !== '')
211
- await io.remove(join(home, 'merge', 'runs', ref)).catch(() => { });
208
+ export async function dropStage(io, home, rowId, ref, onError) {
209
+ const index = join(home, 'merge', 'staging-index', encodeURIComponent(rowId) + '.json');
210
+ await io.remove(index).catch(error => onError?.(index, error));
211
+ if (ref !== undefined && ref !== '') {
212
+ const tree = join(home, 'merge', 'runs', ref);
213
+ await io.remove(tree).catch(error => onError?.(tree, error));
214
+ }
212
215
  }
213
216
  /** Bounded cleanup: reserved/present TTL, pending/verify stale index removal, terminal/absent rows, orphan trees. */
214
217
  export async function cleanupStage(io, home, now, rows, limit = 100) {
@@ -216,6 +219,8 @@ export async function cleanupStage(io, home, now, rows, limit = 100) {
216
219
  const expired = [];
217
220
  const kept = [];
218
221
  const orphans = [];
222
+ const failures = [];
223
+ const fail = (path, error) => { failures.push({ path, message: error instanceof Error ? error.message : String(error) }); };
219
224
  const refs = new Set();
220
225
  const indexRoot = join(home, 'merge', 'staging-index');
221
226
  const names = await io.exists(indexRoot) ? await listOrEmpty(io, indexRoot) : [];
@@ -239,7 +244,7 @@ export async function cleanupStage(io, home, now, rows, limit = 100) {
239
244
  const row = rows.get(entry.rowId);
240
245
  const terminal = row === undefined || row.state === 'skipped';
241
246
  if (terminal || row?.state === 'pending' || row?.state === 'verify') {
242
- await dropStage(io, home, entry.rowId, entry.ref);
247
+ await dropStage(io, home, entry.rowId, entry.ref, fail);
243
248
  removed.push(entry.rowId);
244
249
  continue;
245
250
  }
@@ -250,11 +255,13 @@ export async function cleanupStage(io, home, now, rows, limit = 100) {
250
255
  try {
251
256
  await writeStagingIndex(io, home, { rowId: entry.rowId, pairKey: typeof entry.pairKey === 'string' ? entry.pairKey : '', ref: entry.ref, expiresAt: entry.expiresAt, createdAt: typeof entry.createdAt === 'number' ? entry.createdAt : now, state: 'expired' });
252
257
  }
253
- catch { /* the read face still derives expired */ }
258
+ catch (error) { /* the read face still derives expired; visibility is still required */
259
+ fail(join(indexRoot, name), error);
260
+ }
254
261
  expired.push(entry.rowId);
255
262
  continue;
256
263
  }
257
- await dropStage(io, home, entry.rowId, entry.ref);
264
+ await dropStage(io, home, entry.rowId, entry.ref, fail);
258
265
  removed.push(entry.rowId);
259
266
  continue;
260
267
  }
@@ -266,10 +273,10 @@ export async function cleanupStage(io, home, now, rows, limit = 100) {
266
273
  const prefix = run + '/';
267
274
  if ([...refs].some(ref => ref === run || ref.startsWith(prefix)))
268
275
  continue;
269
- await io.remove(join(runRoot, run)).catch(() => { });
276
+ await io.remove(join(runRoot, run)).catch(error => fail(join(runRoot, run), error));
270
277
  orphans.push(run);
271
278
  }
272
- return { removed, expired, kept, orphans };
279
+ return { removed, expired, kept, orphans, failures };
273
280
  }
274
281
  /** Read all production v2 stage entries. */
275
282
  export async function readAllStages(io, home) {
@@ -14,7 +14,11 @@ export function buildValidationPlan(input) {
14
14
  const existing = new Set(input.existingSupport ?? []);
15
15
  const orphanFiles = [...existing].filter(path => !staged.has(path)).sort();
16
16
  const byPath = new Map();
17
- for (const file of input.supportSources ?? []) {
17
+ // Conflict means TWO SKILLS contributing different bytes at one path. Staged edits and
18
+ // the target's previous file are not conflicts: the target file is a CAS baseline that
19
+ // round 2 is expected to update, so including either here would block every support edit.
20
+ const supportSources = input.supportSources ?? [];
21
+ for (const file of supportSources) {
18
22
  const entry = byPath.get(file.path) ?? { contents: new Set(), sources: [] };
19
23
  entry.contents.add(file.content);
20
24
  entry.sources.push(file.source);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The host's data-plane capability as ONE value shared by snapshot, doctor, HTTP and run preflight.
3
+ * The probe is injected so the Cordis wiring stays in index.ts; a rejected probe is the same E-323
4
+ * answer as an unavailable one, never an unhandled promise.
5
+ * @module @lmzhen/dsh-evolution-merge
6
+ */
7
+ /** The one unavailable answer the merge surface understands. */
8
+ export interface HostOnlyUnavailable {
9
+ readonly code: 'E-323';
10
+ readonly detail?: string;
11
+ }
12
+ /** A host capability probe; undefined means the data plane is usable. */
13
+ export interface HostCapabilities {
14
+ readonly hostOnly: () => Promise<HostOnlyUnavailable | undefined>;
15
+ }
16
+ /**
17
+ * Wrap one probe with the shared error normalization.
18
+ * @param probe - the platform-specific availability check.
19
+ * @returns the capability value consumed by snapshot and runtime.
20
+ */
21
+ export declare function createHostCapabilities(probe: () => Promise<HostOnlyUnavailable | undefined>): HostCapabilities;
22
+ //# sourceMappingURL=host-capabilities.d.ts.map