@lmzhen/dsh-evolution-core 0.30.18 → 0.30.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -84,6 +84,7 @@ export * from './drift-signals.ts';
84
84
  export * from './skill-history.ts';
85
85
  export * from './unit-members.ts';
86
86
  export * from './skill-store.ts';
87
+ export * from './skill-revision.ts';
87
88
  export * from './store-ledger.ts';
88
89
  export * from './reachability.ts';
89
90
  export * from './state-store.ts';
@@ -85,6 +85,7 @@ export * from "./drift-signals.js";
85
85
  export * from "./skill-history.js";
86
86
  export * from "./unit-members.js";
87
87
  export * from "./skill-store.js";
88
+ export * from "./skill-revision.js";
88
89
  export * from "./store-ledger.js";
89
90
  export * from "./reachability.js";
90
91
  export * from "./state-store.js";
@@ -186,7 +186,7 @@ export const PARAM_EXPOSURE = Object.freeze([
186
186
  { id: 'threatExemptLabels', group: 'deployment', tier: 'E1', authority: 'cordis', owner: 'evolution-threat', applies: 'none', docAnchor: 'PARAMETERS.md#deployment', summary: 'Threat labels the deployment declares benign (safety surface, never user-writable).', why: 'security' },
187
187
  { id: 'evolution-threat.enabled', group: 'deployment', tier: 'E2', authority: 'cordis', owner: 'evolution-threat', applies: 'none', docAnchor: 'PARAMETERS.md#deployment', summary: 'Mount the write-time threat guard at all.', why: 'security' },
188
188
  { id: 'evolution-threat.maxScanChars', group: 'deployment', tier: 'E2', authority: 'cordis', owner: 'evolution-threat', applies: 'none', docAnchor: 'PARAMETERS.md#deployment', summary: 'Scan window size of the threat guard.', why: 'security' },
189
- { id: 'evolution-approval.enabled', group: 'deployment', tier: 'E2', authority: 'cordis', owner: 'evolution-approval', applies: 'none', docAnchor: 'PARAMETERS.md#deployment', summary: 'Require approval before a staged write executes.', why: 'security' },
189
+ { id: 'evolution-approval.enabled', group: 'deployment', tier: 'E3', authority: 'cordis', owner: 'evolution-approval', applies: 'live', docAnchor: 'PARAMETERS.md#deployment', summary: 'Master approval switch: run the approval pipeline for foreground writes.', label: '启用批准流水线', hint: '关闭后前台写入不再经批准。', control: 'switch', unit: '', values: '', card: 'evolution-approval' },
190
190
  { id: 'evolution-approval.stageForeground', group: 'deployment', tier: 'E2', authority: 'cordis', owner: 'evolution-approval', applies: 'none', docAnchor: 'PARAMETERS.md#deployment', summary: 'Stage foreground agent writes for approval too.', why: 'security' },
191
191
  { id: 'evolution-approval.pendingTtlMs', group: 'deployment', tier: 'E3', authority: 'cordis', owner: 'evolution-approval', applies: 'live', docAnchor: 'PARAMETERS.md#deployment', summary: 'How long a staged write may wait before it expires: expired writes are closed as rejected and are NEVER executed; 0 (the default) means the window never expires.', label: '待批过期时间', hint: '一条待批写入最多等多久:超时就按「拒绝」关掉(不会执行,写效果不会落地);0 = 永不过期(默认)。', control: 'number', unit: '毫秒', values: '', card: 'evolution-approval' },
192
192
  { id: 'approvalConfirm', group: 'write-caps', tier: 'E3', authority: 'cordis', owner: 'evolution-approval', applies: 'live', docAnchor: 'PARAMETERS.md#write-caps', summary: 'What the confirmation before an APPROVE does: write straight through, ask first, or treat an unanswered prompt as a cancel. The same three words skillWriteConfirm uses for a skill write.', label: '批准前先确认', hint: '点「批准」时要不要再确认一次:直接执行 / 先确认 / 等待超时即取消。默认直接执行(不打断批量批准)。', control: 'select', unit: '', values: 'auto|ask|timeout', valueLabels: '直接执行|先确认|等待超时即取消', card: 'evolution-approval' },
@@ -20,7 +20,7 @@ export interface PlatformServiceProbe {
20
20
  /** Where the probe runs. */
21
21
  readonly side: 'host' | 'client';
22
22
  }
23
- /** The 25 probes, in service-name order (the guard checks this list against the tree). */
23
+ /** The 26 probes, in service-name order (the guard checks this list against the tree). */
24
24
  export declare const PLATFORM_SERVICE_PROBES: readonly PlatformServiceProbe[];
25
25
  /**
26
26
  * The HOST-side probes that do not resolve in this runtime — what `/evolution doctor` prints as
@@ -11,7 +11,7 @@
11
11
  * `client` probes run in the browser half, where the host has no view of the seats.
12
12
  * @module @lmzhen/dsh-evolution-core
13
13
  */
14
- /** The 25 probes, in service-name order (the guard checks this list against the tree). */
14
+ /** The 26 probes, in service-name order (the guard checks this list against the tree). */
15
15
  export const PLATFORM_SERVICE_PROBES = Object.freeze([
16
16
  { service: 'agents', feature: 'review and curator subagent wake-ups', side: 'host' },
17
17
  { service: 'approval', feature: 'the platform approval policy the approval row shadows', side: 'host' },
@@ -20,7 +20,7 @@ export const PLATFORM_SERVICE_PROBES = Object.freeze([
20
20
  { service: 'evolutionApproval', feature: 'pending writes: list, approve and reject from the command surface', side: 'host' },
21
21
  { service: 'evolutionCurator', feature: '/evolution curator subcommands, skill undo and history', side: 'host' },
22
22
  { service: 'evolutionIo', feature: 'every durable read and write (the IO seam)', side: 'host' },
23
- { service: 'evolutionMerge', feature: 'merge rows and run state', side: 'host' },
23
+ { service: 'evolutionMerge', feature: 'merge command delegation and the merge doctor fact', side: 'host' },
24
24
  { service: 'hmr', feature: 'the preset install read-merge-write commit (the platform HMR lock serializes it)', side: 'host' },
25
25
  { service: 'evolutionMemoryBudget', feature: 'the memory-budget divergence section of this report', side: 'host' },
26
26
  { service: 'evolutionPolicy', feature: 'the policy snapshot that shadows row knobs', side: 'host' },
@@ -75,10 +75,13 @@ export interface RunRecord {
75
75
  supersededAt?: number | undefined;
76
76
  /** The run this run was spawned from (approve/resume after an earlier command). */
77
77
  parentRunId?: string | undefined;
78
- /** Merge-side projection; core lifecycle stays four-state. */
78
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
79
79
  mergeStatus?: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'awaiting-approval' | 'undone' | 'superseded' | 'interrupted' | 'orphan' | undefined;
80
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
80
81
  stagingRef?: string | undefined;
82
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
81
83
  pairKey?: string | undefined;
84
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
82
85
  pairs?: readonly unknown[] | undefined;
83
86
  }
84
87
  /** What a started run gets: its id and the signal its work must honour. */
@@ -90,10 +93,13 @@ export interface RunHandle {
90
93
  readonly startedAt: number;
91
94
  readonly command?: RunCommand | undefined;
92
95
  readonly parentRunId?: string | undefined;
93
- /** Merge-side projection; core lifecycle stays four-state. */
96
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
94
97
  readonly mergeStatus?: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'awaiting-approval' | 'undone' | 'superseded' | 'interrupted' | 'orphan' | undefined;
98
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
95
99
  readonly stagingRef?: string | undefined;
100
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
96
101
  readonly pairKey?: string | undefined;
102
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
97
103
  readonly pairs?: readonly unknown[] | undefined;
98
104
  }
99
105
  /** How a run ended, as reported by its owner. */
@@ -101,6 +107,7 @@ export interface RunOutcome {
101
107
  state: 'succeeded' | 'failed' | 'cancelled';
102
108
  resultRef?: string | undefined;
103
109
  failure?: string | undefined;
110
+ /** @deprecated Legacy merge projection; read-only compatibility, never written. */
104
111
  mergeStatus?: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'awaiting-approval' | 'undone' | 'superseded' | 'interrupted' | 'orphan' | undefined;
105
112
  }
106
113
  /** The registry surface the commands row drives. */
@@ -131,13 +138,19 @@ export interface RunRegistry {
131
138
  command?: RunCommand;
132
139
  total?: number;
133
140
  parentRunId?: string;
134
- mergeStatus?: 'running' | 'succeeded' | 'failed' | 'cancelled' | 'awaiting-approval' | 'undone' | 'superseded' | 'interrupted' | 'orphan';
135
- stagingRef?: string;
136
- pairKey?: string;
137
- pairs?: readonly unknown[];
138
141
  }): Promise<RunHandle>;
139
142
  /** Record a run's terminal state (first writer wins). */
140
143
  settle(id: string, outcome: RunOutcome): Promise<void>;
144
+ /**
145
+ * Settle a run this process does not own: the controlled orphan upgrade.
146
+ *
147
+ * Startup recovery runs AFTER `load()` (which materializes the orphan verdict for a
148
+ * dead owner). A committed unit then proves the run really succeeded, so the verdict
149
+ * must be upgradeable — but ONLY from an explicit orphan stamp (or a still-running
150
+ * row whose owner is gone). A run that is genuinely in flight elsewhere is refused.
151
+ * @returns whether the index now carries the requested terminal state.
152
+ */
153
+ settleRecovered(id: string, outcome: RunOutcome): Promise<boolean>;
141
154
  /** Record the current stage/progress of a running run. */
142
155
  stage(id: string, stage: RunStage, progress?: RunProgress): Promise<void>;
143
156
  /** Mark runs as superseded by a tree restore; idempotent. */
@@ -110,10 +110,6 @@ function parseIndex(raw) {
110
110
  ...(typeof record.failure === 'string' ? { failure: record.failure } : {}),
111
111
  ...(typeof record.supersededAt === 'number' ? { supersededAt: record.supersededAt } : {}),
112
112
  ...(typeof record.parentRunId === 'string' ? { parentRunId: record.parentRunId } : {}),
113
- ...(record.mergeStatus === 'running' || record.mergeStatus === 'succeeded' || record.mergeStatus === 'failed' || record.mergeStatus === 'cancelled' || record.mergeStatus === 'awaiting-approval' || record.mergeStatus === 'undone' || record.mergeStatus === 'superseded' || record.mergeStatus === 'interrupted' || record.mergeStatus === 'orphan' ? { mergeStatus: record.mergeStatus } : {}),
114
- ...(typeof record.stagingRef === 'string' ? { stagingRef: record.stagingRef } : {}),
115
- ...(typeof record.pairKey === 'string' ? { pairKey: record.pairKey } : {}),
116
- ...(Array.isArray(record.pairs) ? { pairs: record.pairs } : {}),
117
113
  });
118
114
  }
119
115
  return { ok: true, records, foreign, ...(notes.length > 0 ? { note: notes.join('; ') } : {}) };
@@ -139,7 +135,7 @@ export function newRunRegistry(options) {
139
135
  /** A row whose owner cannot be reached: `pid` absent (an older build) or not alive. */
140
136
  const ownerIsGone = (row) => row.pid === undefined || !isProcessAlive(row.pid);
141
137
  /** The verdict is materialized ONCE, so its `endedAt` (and the duration a reader sees) is stable. */
142
- const orphanOf = (row) => ({ ...row, state: 'failed', mergeStatus: 'orphan', endedAt: now(), failure: ORPHAN_FAILURE });
138
+ const orphanOf = (row) => ({ ...row, state: 'failed', endedAt: now(), failure: ORPHAN_FAILURE });
143
139
  /** Every row this process knows, newest first. ALL reads go through here. */
144
140
  const view = () => [...owned.values(), ...foreign.values()].sort((a, b) => b.startedAt - a.startedAt);
145
141
  /** Retained history: every running row plus the newest terminal rows per kind. */
@@ -308,7 +304,7 @@ export function newRunRegistry(options) {
308
304
  const run = this.find(id);
309
305
  if (run?.state !== 'running')
310
306
  return false;
311
- await this.settle(id, { state: 'failed', failure: 'orphan', mergeStatus: 'orphan' });
307
+ await this.settle(id, { state: 'failed', failure: 'orphan' });
312
308
  return true;
313
309
  },
314
310
  async begin(kind, seed) {
@@ -325,10 +321,6 @@ export function newRunRegistry(options) {
325
321
  pid: process.pid,
326
322
  ...(seed?.command !== undefined ? { command: seed.command } : {}),
327
323
  ...(seed?.parentRunId !== undefined ? { parentRunId: seed.parentRunId } : {}),
328
- ...(seed?.mergeStatus !== undefined ? { mergeStatus: seed.mergeStatus } : {}),
329
- ...(seed?.stagingRef !== undefined ? { stagingRef: seed.stagingRef } : {}),
330
- ...(seed?.pairKey !== undefined ? { pairKey: seed.pairKey } : {}),
331
- ...(seed?.pairs !== undefined ? { pairs: seed.pairs } : {}),
332
324
  ...(seed?.total !== undefined ? { progress: { done: 0, total: seed.total } } : {}),
333
325
  });
334
326
  // Write-through (0.19.1, review P2): answering "started" promises a run another
@@ -360,8 +352,6 @@ export function newRunRegistry(options) {
360
352
  if (row === undefined || row.supersededAt !== undefined)
361
353
  continue;
362
354
  row.supersededAt = now();
363
- if (row.kind === 'merge')
364
- row.mergeStatus = 'superseded';
365
355
  changed = true;
366
356
  }
367
357
  if (changed)
@@ -380,8 +370,6 @@ export function newRunRegistry(options) {
380
370
  if (row.kind !== kind || row.supersededAt !== undefined)
381
371
  continue;
382
372
  row.supersededAt = now();
383
- if (row.kind === 'merge')
384
- row.mergeStatus = 'superseded';
385
373
  marked += 1;
386
374
  }
387
375
  const kept = capped([...merged.values()]);
@@ -403,11 +391,46 @@ export function newRunRegistry(options) {
403
391
  row.resultRef = outcome.resultRef;
404
392
  if (outcome.failure !== undefined)
405
393
  row.failure = outcome.failure;
406
- if (outcome.mergeStatus !== undefined)
407
- row.mergeStatus = outcome.mergeStatus;
408
394
  controllers.delete(id);
409
395
  await persist();
410
396
  },
397
+ async settleRecovered(id, outcome) {
398
+ const local = owned.get(id);
399
+ if (local !== undefined) {
400
+ await this.settle(id, outcome);
401
+ return owned.get(id)?.state === outcome.state;
402
+ }
403
+ let changed = false;
404
+ await transactIo(options.io, path, (current) => {
405
+ const onDisk = parseIndex(current);
406
+ const row = onDisk.records.find(item => item.id === id);
407
+ if (row === undefined)
408
+ return current;
409
+ // The two admissible sources: an unresolved running row with a dead owner, or
410
+ // the orphan verdict load() already materialized. Anything else is a real
411
+ // terminal answer (someone's cancel/success) and stays put.
412
+ const recoverable = (row.state === 'running' && ownerIsGone(row))
413
+ || (row.state === 'failed' && row.failure === ORPHAN_FAILURE);
414
+ if (!recoverable)
415
+ return current;
416
+ changed = true;
417
+ const next = {
418
+ ...row,
419
+ state: outcome.state,
420
+ endedAt: now(),
421
+ ...(outcome.resultRef === undefined ? {} : { resultRef: outcome.resultRef }),
422
+ ...(outcome.failure === undefined ? {} : { failure: outcome.failure }),
423
+ };
424
+ const kept = capped(onDisk.records.map(item => item.id === id ? next : item));
425
+ const live = kept.filter(item => item.state === 'running');
426
+ const done = kept.filter(item => TERMINAL.has(item.state));
427
+ return JSON.stringify({ schemaVersion: RUNS_SCHEMA_VERSION, runs: [...live, ...done, ...onDisk.foreign] }, null, 2);
428
+ });
429
+ // Rebuild the foreign view so this process answers the upgraded verdict at once.
430
+ if (changed)
431
+ await this.load();
432
+ return changed;
433
+ },
411
434
  inFlight(kind) {
412
435
  return view().find(row => row.state === 'running' && (kind === undefined || row.kind === kind));
413
436
  },
@@ -36,6 +36,10 @@ export declare const HISTORY_INDEX_VERSION = 1;
36
36
  export declare const BASELINE_ACTION = "baseline";
37
37
  /** G3b: one unit's head. Exactly one of the two forms is present (they never coexist). */
38
38
  export interface UnitHead {
39
+ /** The merge run this unit belongs to, when the writer recorded one. */
40
+ runId?: string;
41
+ /** This unit starts a fresh target baseline; older versions remain listable but outside it. */
42
+ baseline?: true;
39
43
  /** Representative shard: the unit's WHOLE manifest of moves (one entry per written path). */
40
44
  moves?: readonly Readonly<Record<string, unknown>>[];
41
45
  /** Representative shard: the skills this unit wrote, so members resolve without a directory scan. */
@@ -0,0 +1,37 @@
1
+ /** One support file as read through the store (path is normalized `references/x.md`). */
2
+ export interface RevisionFile {
3
+ readonly path: string;
4
+ readonly content: string;
5
+ }
6
+ /** The two-state content anchor: absent (no live skill) or a content hash. */
7
+ export type RevisionAnchor = {
8
+ readonly kind: 'absent';
9
+ } | {
10
+ readonly kind: 'present';
11
+ readonly sha256: string;
12
+ };
13
+ /**
14
+ * Canonical fingerprint of one skill's content.
15
+ *
16
+ * Field order and separators are a wire format: decisions persist anchors derived from
17
+ * this string, and a later commit compares against it. Do not reorder.
18
+ * @param name - the (trimmed) skill name.
19
+ * @param body - the SKILL.md text.
20
+ * @param files - every support file under SUPPORT_DIRS.
21
+ * @returns the fingerprint string to hash.
22
+ */
23
+ export declare function revisionFingerprint(name: string, body: string, files: readonly RevisionFile[]): string;
24
+ /** Hash one fingerprint into its anchor (CRLF-normalized by contentHash). */
25
+ export declare function revisionAnchor(name: string, body: string, files: readonly RevisionFile[]): RevisionAnchor;
26
+ /** Anchor for a skill whose body may be absent. */
27
+ export declare function revisionAnchorOf(name: string, body: string | null, files: readonly RevisionFile[]): RevisionAnchor;
28
+ /**
29
+ * Support digest over several skills' fingerprints: the union of their full content.
30
+ *
31
+ * Sort is by fingerprint (name-prefixed), so name order and fingerprint order agree and
32
+ * a decision-time digest can be recomputed by a committer that only has hashes.
33
+ * @param fingerprints - one fingerprint per involved skill (missing skills simply absent).
34
+ * @returns the digest recorded on a decision and re-checked at commit.
35
+ */
36
+ export declare function revisionSupportDigest(fingerprints: readonly string[]): string;
37
+ //# sourceMappingURL=skill-revision.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Content-revision identity shared by every planner that records a decision and every
3
+ * committer that re-checks it.
4
+ *
5
+ * A skill's revision covers the trimmed NAME, the SKILL.md body and every support file
6
+ * (`references/…`, `templates/…`, `scripts/…`, `assets/…`), each as read through the
7
+ * store. The merge domain records a decision-time anchor and the store compares the live
8
+ * anchor before writing; both sides must hash the same bytes or the compare is meaningless,
9
+ * so the hash lives HERE and nowhere else.
10
+ */
11
+ import { contentHash } from "./mutations.js";
12
+ /**
13
+ * Canonical fingerprint of one skill's content.
14
+ *
15
+ * Field order and separators are a wire format: decisions persist anchors derived from
16
+ * this string, and a later commit compares against it. Do not reorder.
17
+ * @param name - the (trimmed) skill name.
18
+ * @param body - the SKILL.md text.
19
+ * @param files - every support file under SUPPORT_DIRS.
20
+ * @returns the fingerprint string to hash.
21
+ */
22
+ export function revisionFingerprint(name, body, files) {
23
+ const ordered = [...files].sort((left, right) => left.path.localeCompare(right.path));
24
+ return name + '\u0000' + body + '\u0000' + ordered.map(file => file.path + '\u0000' + file.content).join('\u0000');
25
+ }
26
+ /** Hash one fingerprint into its anchor (CRLF-normalized by contentHash). */
27
+ export function revisionAnchor(name, body, files) {
28
+ return { kind: 'present', sha256: contentHash(revisionFingerprint(name, body, files)) };
29
+ }
30
+ /** Anchor for a skill whose body may be absent. */
31
+ export function revisionAnchorOf(name, body, files) {
32
+ return body === null ? { kind: 'absent' } : revisionAnchor(name, body, files);
33
+ }
34
+ /**
35
+ * Support digest over several skills' fingerprints: the union of their full content.
36
+ *
37
+ * Sort is by fingerprint (name-prefixed), so name order and fingerprint order agree and
38
+ * a decision-time digest can be recomputed by a committer that only has hashes.
39
+ * @param fingerprints - one fingerprint per involved skill (missing skills simply absent).
40
+ * @returns the digest recorded on a decision and re-checked at commit.
41
+ */
42
+ export function revisionSupportDigest(fingerprints) {
43
+ return contentHash([...fingerprints].sort().join('\u0000'));
44
+ }
45
+ //# sourceMappingURL=skill-revision.js.map
@@ -37,6 +37,7 @@ import { transactIo, type EvolutionIoLike } from './io.ts';
37
37
  import { type Probe } from './probe.ts';
38
38
  import type { SkillLimits } from './limits.ts';
39
39
  import { type MutationRecord } from './mutations.ts';
40
+ import { type RevisionAnchor, type RevisionFile } from './skill-revision.ts';
40
41
  import { type UnitHead, type SkillVersion } from './skill-history.ts';
41
42
  import { type SkillHealthAssessment, type SkillHealthThresholds } from './skill-health.ts';
42
43
  import type { EvolutionSkillMutatedEvent } from './events.ts';
@@ -181,6 +182,9 @@ export interface ArchiveOptions {
181
182
  }
182
183
  /** One multi-skill merge commit: target body + support files, with the source names it absorbs. */
183
184
  export interface CommitMergePlan {
185
+ readonly runId?: string;
186
+ /** The unit transaction id; history must record this exact id for undo. */
187
+ readonly unitId?: string;
184
188
  readonly target: string;
185
189
  /** Whether the target already exists. The caller planned this; commit re-checks it. */
186
190
  readonly targetExists: boolean;
@@ -196,6 +200,11 @@ export interface CommitMergePlan {
196
200
  readonly name: string;
197
201
  readonly body: string;
198
202
  }[];
203
+ /** Stage limits snapshot; absent uses the deployed store limits. */
204
+ readonly limits?: import('./limits.ts').SkillLimits;
205
+ readonly targetRevision?: RevisionAnchor;
206
+ readonly sourceRevisions?: Readonly<Record<string, RevisionAnchor>>;
207
+ readonly supportDigest?: string;
199
208
  readonly origin: WriteOrigin;
200
209
  readonly summary: string;
201
210
  /** Real write-phase callback used by UnitTransaction; not persisted. */
@@ -210,6 +219,8 @@ export interface CommitMergePlan {
210
219
  export interface CommitMergeResult {
211
220
  readonly ok: boolean;
212
221
  readonly message: string;
222
+ /** Typed refusal kind for the revision CAS (target/source/support changed after the plan). */
223
+ readonly code?: 'target-changed' | 'source-changed' | 'support-changed';
213
224
  readonly unitId?: string;
214
225
  readonly targetPath?: string;
215
226
  readonly archived?: readonly string[];
@@ -422,7 +433,20 @@ export declare class SkillLibrary {
422
433
  }): Promise<SkillSummary[]>;
423
434
  read(rawName: string): Promise<string | null>;
424
435
  /**
425
-
436
+ * Content anchor of one live skill: name + body + every support file under SUPPORT_DIRS.
437
+ *
438
+ * This is the commit-side counterpart of the decision-time anchor recorded by the merge
439
+ * domain (see skill-revision.ts): both sides call the same fingerprint, so a changed
440
+ * source, target or support file cannot slip past the commit CAS.
441
+ * @param rawName - caller-supplied skill name (trimmed here).
442
+ * @returns present anchor, or absent when the skill has no SKILL.md.
443
+ */
444
+ revisionFingerprint(rawName: string): Promise<string | null>;
445
+ /** Content anchor of one live skill; the hash a decision records and a commit re-checks. */
446
+ revisionAnchor(rawName: string): Promise<RevisionAnchor>;
447
+ /** Every support file of one live skill, in listing order; unreadable/missing files fail loud. */
448
+ liveSupportFiles(rawName: string): Promise<readonly RevisionFile[]>;
449
+ /**
426
450
  * Single path-building choke point (rc.42 audit P2-5): every directory path
427
451
 
428
452
  * is built from the TRIMMED name, so a name that passes `badName` (which
@@ -527,6 +551,8 @@ export declare class SkillLibrary {
527
551
  * @returns the versions, oldest first.
528
552
  */
529
553
  listVersions(rawName: string): Promise<SkillVersion[]>;
554
+ /** Versions from the latest merge baseline onward; old versions stay listable via listVersions. */
555
+ listBaselineVersions(rawName: string): Promise<SkillVersion[]>;
530
556
  /**
531
557
  * One version's content, read back from the content-addressed blob store.
532
558
  * @param rawName - the skill's name.
@@ -535,6 +561,10 @@ export declare class SkillLibrary {
535
561
  * blob cannot be read — never an empty string.
536
562
  */
537
563
  readVersion(rawName: string, v: number): Promise<string | null>;
564
+ /** Read an archived skill's human-readable reason, exact or stamped. */
565
+ readArchiveReason(rawName: string): Promise<string | null>;
566
+ /** Read an archived skill body (SKILL.md) by its original name, exact or stamped. */
567
+ readArchived(rawName: string): Promise<string | null>;
538
568
  /**
539
569
  * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
540
570
  * deletion, from background-review writes, and from the lifecycle — a
@@ -577,6 +607,8 @@ export declare class SkillLibrary {
577
607
  * P2-4 (v19): a non-string entry (`skills: [123]`) is refused structurally
578
608
  * instead of throwing `name.includes is not a function`. */
579
609
  private safeSnapshotEntryName;
610
+ /** Mark merge-absorbed sources archived in the usage sidecar (FR-DATA-07). */
611
+ private foldArchivedUsage;
580
612
  archive(rawName: string, options?: ArchiveOptions, origin?: WriteOrigin): Promise<SkillActionResult>;
581
613
  /**
582
614
  * Commit one merge plan as a single multi-skill transaction.