@lmzhen/dsh-evolution-core 0.25.9 → 0.26.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +781 -38
- package/lib/types/curator.d.ts +2 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/package-identity.d.ts +14 -0
- package/lib/types/preset-composition.d.ts +1 -1
- package/lib/types/probe.d.ts +9 -3
- package/lib/types/reachability.d.ts +46 -0
- package/lib/types/skill-store.d.ts +34 -3
- package/lib/types/store-ledger.d.ts +44 -0
- package/lib/types/tree-write.d.ts +53 -0
- package/lib/types/write-authority.d.ts +87 -0
- package/lib/types/write-gates.d.ts +111 -0
- package/package.json +1 -1
package/lib/types/curator.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ export interface CuratorRunReport {
|
|
|
93
93
|
llmReviewEnabled?: boolean;
|
|
94
94
|
/** V6-35 (0.3.36): lenient-parse shape notes from the LLM nomination block. */
|
|
95
95
|
nominationsWarnings?: string[];
|
|
96
|
+
scanWarnings?: string[];
|
|
96
97
|
/** 0.5.0 V1 (design §16.6-④): archived entries past the retention window that
|
|
97
98
|
* the CURRENT policy keeps rather than deletes. Present (even empty) only when
|
|
98
99
|
* the retention policy was `report`, so a report cannot read as "nothing
|
|
@@ -117,6 +118,7 @@ export interface CuratorReportInput {
|
|
|
117
118
|
snapshotPath?: string;
|
|
118
119
|
llmReviewEnabled?: boolean;
|
|
119
120
|
nominationsWarnings?: readonly string[];
|
|
121
|
+
scanWarnings?: readonly string[];
|
|
120
122
|
wouldPrune?: readonly string[] | null;
|
|
121
123
|
}
|
|
122
124
|
export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
* @module @lmzhen/dsh-evolution-core
|
|
33
33
|
*/
|
|
34
34
|
export * from './citations.ts';
|
|
35
|
+
export * from './tree-write.ts';
|
|
36
|
+
export * from './package-identity.ts';
|
|
37
|
+
export * from './write-authority.ts';
|
|
38
|
+
export * from './write-gates.ts';
|
|
35
39
|
export * from './reference-rewrite.ts';
|
|
36
40
|
export * from './cost.ts';
|
|
37
41
|
export * from './skill-lint.ts';
|
|
@@ -74,6 +78,8 @@ export * from './drift-signals.ts';
|
|
|
74
78
|
export * from './skill-history.ts';
|
|
75
79
|
export * from './unit-members.ts';
|
|
76
80
|
export * from './skill-store.ts';
|
|
81
|
+
export * from './store-ledger.ts';
|
|
82
|
+
export * from './reachability.ts';
|
|
77
83
|
export * from './state-store.ts';
|
|
78
84
|
export * from './threats.ts';
|
|
79
85
|
export * from './tool-dispatch.ts';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scope segment of the nearest `package.json` at or above `from`.
|
|
3
|
+
* @param from - the calling module URL (its own manifest is the answer).
|
|
4
|
+
* @returns the scope, e.g. `@deepseek-ai` or `@lmzhen`; an unscoped manifest yields an empty string.
|
|
5
|
+
*/
|
|
6
|
+
export declare function scopeOfNearestManifest(from: string | URL): string;
|
|
7
|
+
/**
|
|
8
|
+
* One family/platform package name under the RUNNING scope.
|
|
9
|
+
* @param bareName - the name without any scope, e.g. `dsh-agent-preset`.
|
|
10
|
+
* @param from - the calling module URL.
|
|
11
|
+
* @returns `scope/bareName`, or `bareName` when the manifest carries no scope.
|
|
12
|
+
*/
|
|
13
|
+
export declare function scopedPackageName(bareName: string, from: string | URL): string;
|
|
14
|
+
//# sourceMappingURL=package-identity.d.ts.map
|
|
@@ -82,7 +82,7 @@ export declare function loadRowOverrides(path?: string): RowOverride[];
|
|
|
82
82
|
*/
|
|
83
83
|
export declare function compositionRowIds(composition: string): Set<string>;
|
|
84
84
|
/** The platform package a DECLARATIVE preset row names (0.2.x replaced preset directories with it). */
|
|
85
|
-
export declare const AGENT_PRESET_PACKAGE
|
|
85
|
+
export declare const AGENT_PRESET_PACKAGE: string;
|
|
86
86
|
/** Identity and display metadata of one generated preset row. */
|
|
87
87
|
export interface PresetRowIdentity {
|
|
88
88
|
/** The composition row id (`- id:`); what {@link mergePresetRow} replaces by. */
|
package/lib/types/probe.d.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* with a value, and only `present` yields a value.
|
|
11
11
|
*/
|
|
12
12
|
import type { EvolutionIoLike } from './io.ts';
|
|
13
|
+
/**
|
|
14
|
+
* The io surface the three-state probes actually use: three READS. Narrowing it here is deliberate -
|
|
15
|
+
* a probe cannot write, and typing these parameters as the full `EvolutionIoLike` made every read-only
|
|
16
|
+
* caller (e.g. reachability.ts) fail to compile for a capability it must not have.
|
|
17
|
+
*/
|
|
18
|
+
export type ProbeIo = Pick<EvolutionIoLike, 'list' | 'readText' | 'mtime'>;
|
|
13
19
|
export type Probe<T> = {
|
|
14
20
|
kind: 'present';
|
|
15
21
|
value: T;
|
|
@@ -51,7 +57,7 @@ export declare function isMissingPath(error: unknown): boolean;
|
|
|
51
57
|
* reads as absent, an EACCES/EIO reads as unknown — where a bare
|
|
52
58
|
* `catch { return [] }` served a broken store as an empty one (the N14 class).
|
|
53
59
|
*/
|
|
54
|
-
export declare function probeList(io:
|
|
60
|
+
export declare function probeList(io: ProbeIo, dir: string): Promise<Probe<string[]>>;
|
|
55
61
|
/**
|
|
56
62
|
* Three-state TEXT read of one file. A missing path (ENOENT/ENOTDIR, or a backend whose read
|
|
57
63
|
* answers null) is `absent`; every other failure is `unknown` with its reason. The distinction is
|
|
@@ -62,13 +68,13 @@ export declare function probeList(io: EvolutionIoLike, dir: string): Promise<Pro
|
|
|
62
68
|
* @param path - the file to read.
|
|
63
69
|
* @returns the probe.
|
|
64
70
|
*/
|
|
65
|
-
export declare function probeText(io:
|
|
71
|
+
export declare function probeText(io: ProbeIo, path: string): Promise<Probe<string>>;
|
|
66
72
|
/**
|
|
67
73
|
* Three-state mtime read. Absent covers both "the path is missing" and "this
|
|
68
74
|
* backend has no mtime probe" — the seam cannot tell those apart, so consumers
|
|
69
75
|
* that must know say so in their own log line. A stat that FAILS is unknown.
|
|
70
76
|
*/
|
|
71
|
-
export declare function probeMtime(io:
|
|
77
|
+
export declare function probeMtime(io: ProbeIo, path: string): Promise<Probe<number>>;
|
|
72
78
|
/** The timestamp a curator run report DECLARES: `startedAt` (the run's own start), then `at` (the
|
|
73
79
|
* stamp the error writer sets on every report — F-327/P2-4).
|
|
74
80
|
* @param parsed - the parsed report, or anything else.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** The io surface the reconciliation needs. */
|
|
2
|
+
export interface ReachabilityIo {
|
|
3
|
+
list(path: string): Promise<string[]>;
|
|
4
|
+
readText(path: string): Promise<string | null>;
|
|
5
|
+
exists(path: string): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
/** One domain's reading. `baseline` is the audit's measured value, quoted for comparison, never a target. */
|
|
8
|
+
export interface DomainReading {
|
|
9
|
+
readonly domain: string;
|
|
10
|
+
readonly unit: 'bytes' | 'files';
|
|
11
|
+
readonly total: number;
|
|
12
|
+
readonly reachable: number;
|
|
13
|
+
readonly unreachable: number;
|
|
14
|
+
/** The audit baseline this line is checked against, verbatim, so a drift is visible without a lookup. */
|
|
15
|
+
readonly baseline: string;
|
|
16
|
+
/** Set when a read FAILURE made this domain unmeasurable: not "nothing unreachable", but "not read". */
|
|
17
|
+
readonly unmeasured?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Every sha256 any skill index references, across the whole history root. */
|
|
20
|
+
export declare function referencedBlobs(io: ReachabilityIo, historyRoot: string): Promise<Set<string> | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* The history domain: blobs on disk minus the blobs the indexes still reference.
|
|
23
|
+
* @param io - the tree reader.
|
|
24
|
+
* @param historyRoot - the history root (`.history`), passed in, never guessed.
|
|
25
|
+
* @returns the reading, with the audit baseline quoted for comparison.
|
|
26
|
+
*/
|
|
27
|
+
export declare function historyReachability(io: ReachabilityIo, historyRoot: string): Promise<DomainReading>;
|
|
28
|
+
/**
|
|
29
|
+
* The reports domain: files no run record points at. A record WITHOUT a `resultRef` still owns the
|
|
30
|
+
* conventional path (`reports/maintain-<id>.json`), so that path counts as reachable - counting only
|
|
31
|
+
* explicit refs would report compliant files as orphans and the number could never match the baseline.
|
|
32
|
+
* @param io - the tree reader.
|
|
33
|
+
* @param reportsDir - the reports directory, passed in.
|
|
34
|
+
* @param runIds - every run id the run records carry.
|
|
35
|
+
* @param resultRefs - every explicit resultRef those records carry.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reportsReachability(io: ReachabilityIo, reportsDir: string, runIds: readonly string[], resultRefs: readonly string[]): Promise<DomainReading>;
|
|
38
|
+
/**
|
|
39
|
+
* The backups domain: snapshot trees and how much each holds. `skillsPerSnapshot` is measured by the
|
|
40
|
+
* caller (it already reads manifests); an empty tree is one whose manifest lists no skill.
|
|
41
|
+
* @param io - the tree reader.
|
|
42
|
+
* @param backupsRoot - the snapshot root, passed in.
|
|
43
|
+
* @param skillsPerSnapshot - the caller's manifest reading, one entry per snapshot directory.
|
|
44
|
+
*/
|
|
45
|
+
export declare function backupsReachability(io: ReachabilityIo, backupsRoot: string, skillsPerSnapshot: readonly number[]): Promise<DomainReading>;
|
|
46
|
+
//# sourceMappingURL=reachability.d.ts.map
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* re-probes after its copies so a mid-copy writer demotes to `skipped`
|
|
33
33
|
* (OPT-07); the movers' probe→rename window is owned by the io.ts protocol.
|
|
34
34
|
*/
|
|
35
|
+
import { type WriteAuthority } from './write-authority.ts';
|
|
35
36
|
import { transactIo, type EvolutionIoLike } from './io.ts';
|
|
36
37
|
import { type Probe } from './probe.ts';
|
|
37
38
|
import type { SkillLimits } from './limits.ts';
|
|
@@ -287,10 +288,13 @@ export declare class SkillLibrary {
|
|
|
287
288
|
/** V10-03 (P2-18): see the constructor's threatExemptLabels. Empty by
|
|
288
289
|
* default — the strict ANY-hit-blocks policy is unchanged. */
|
|
289
290
|
private readonly threatExemptLabels;
|
|
291
|
+
/** G1/1.1: default authority; a call may override (origin is per-invocation). */
|
|
292
|
+
/** G1/1.1/Stage 2: the ONE default every mutator reads. Per-call overrides still win. */
|
|
293
|
+
private readonly authority;
|
|
290
294
|
/** skill-history.ts: the content history is best-effort, so a failure must not be SILENT —
|
|
291
295
|
* but it is a deployment condition, not a per-write event, so it warns once per instance. */
|
|
292
296
|
private historyWarned;
|
|
293
|
-
constructor(root?: string, io?: EvolutionIoLike, limits?: SkillLimits, onMutation?: (event: EvolutionSkillMutatedEvent) => void, transact?: typeof transactIo, threatExemptLabels?: readonly string[]);
|
|
297
|
+
constructor(root?: string, io?: EvolutionIoLike, limits?: SkillLimits, onMutation?: (event: EvolutionSkillMutatedEvent) => void, transact?: typeof transactIo, threatExemptLabels?: readonly string[], authority?: WriteAuthority);
|
|
294
298
|
/**
|
|
295
299
|
* Run one single-file read-modify-write for a mutator. When `transact` was
|
|
296
300
|
* injected the read and the write run inside it (cross-process atomicity);
|
|
@@ -328,6 +332,21 @@ export declare class SkillLibrary {
|
|
|
328
332
|
* a swallowed error would silently downgrade the anchor to
|
|
329
333
|
* last-writer-wins.
|
|
330
334
|
*/
|
|
335
|
+
/**
|
|
336
|
+
* S4-01 (G0/0.7): the ONE three-state read the staging/replay anchors use.
|
|
337
|
+
*
|
|
338
|
+
* The two absorbing reads below absorb ENOENT/EISDIR and rethrow everything else, and the callers then
|
|
339
|
+
* each wrote their own try/catch to turn a rethrow into a decision - three near-copies of one posture,
|
|
340
|
+
* and the newest of them silently dropped the anchor instead of refusing. A read failure is its own
|
|
341
|
+
* state, so it gets its own name here: `unknown` with the reason, next to `present` and `absent`.
|
|
342
|
+
* Failing closed is then the CALLER decision (they refuse on unknown), not something each call site has
|
|
343
|
+
* to re-derive from a thrown error.
|
|
344
|
+
*/
|
|
345
|
+
readSkillProbe(rawName: string): Promise<Probe<string>>;
|
|
346
|
+
/** The support-file face of {@link readSkillProbe}. */
|
|
347
|
+
readSupportFileProbe(rawName: string, filePath: string): Promise<Probe<string>>;
|
|
348
|
+
/** One place that turns the absorbing reads into three states. `null` is absent; a throw is unknown. */
|
|
349
|
+
private probeOf;
|
|
331
350
|
readSupportFile(name: string, filePath: string): Promise<string | null>;
|
|
332
351
|
/**
|
|
333
352
|
* Summarize the skill tree.
|
|
@@ -499,7 +518,7 @@ export declare class SkillLibrary {
|
|
|
499
518
|
* P2-4 (v19): a non-string entry (`skills: [123]`) is refused structurally
|
|
500
519
|
* instead of throwing `name.includes is not a function`. */
|
|
501
520
|
private safeSnapshotEntryName;
|
|
502
|
-
archive(rawName: string, options?: ArchiveOptions): Promise<SkillActionResult>;
|
|
521
|
+
archive(rawName: string, options?: ArchiveOptions, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
503
522
|
/**
|
|
504
523
|
* V2 (design §16.7): what an apply-mode consolidation would do for ONE source,
|
|
505
524
|
* computed BEFORE any side effect. `blocked` means behaviour is exactly the
|
|
@@ -567,7 +586,7 @@ export declare class SkillLibrary {
|
|
|
567
586
|
* recoverability: archival never deletes, and this is the control-plane
|
|
568
587
|
* path back. The `.archive-reason` marker is dropped on restore.
|
|
569
588
|
*/
|
|
570
|
-
restoreFromArchive(rawName: string): Promise<SkillActionResult>;
|
|
589
|
+
restoreFromArchive(rawName: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
571
590
|
writeSupportFile(rawName: string, filePath: string, content: string, origin?: WriteOrigin, anchor?: WriteAnchor): Promise<SkillActionResult>;
|
|
572
591
|
private writeSupportFileCore;
|
|
573
592
|
removeSupportFile(rawName: string, filePath: string, origin?: WriteOrigin, anchor?: WriteAnchor, unitId?: string): Promise<SkillActionResult>;
|
|
@@ -624,6 +643,16 @@ export declare class SkillLibrary {
|
|
|
624
643
|
private sanitizeExtraNames;
|
|
625
644
|
readSnapshotManifest(path: string): Promise<SnapshotManifest | null>;
|
|
626
645
|
/** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
|
|
646
|
+
/**
|
|
647
|
+
* G3/3.4: can a snapshot actually be used right now?
|
|
648
|
+
*
|
|
649
|
+
* The recovery messages used to promise "restore it from a snapshot" without asking whether a usable
|
|
650
|
+
* snapshot exists (audit S1-01: the message promised recoverability from a chain that is five deep and
|
|
651
|
+
* whose newest member held 9 of 35 skills). Three states, like every other reading here: a fragment
|
|
652
|
+
* naming the usable snapshot, a fragment saying there is none, or a fragment saying the directory could
|
|
653
|
+
* not be read - the caller embeds whichever it gets, so the sentence is generated by the capability.
|
|
654
|
+
*/
|
|
655
|
+
private snapshotRecoveryAvailability;
|
|
627
656
|
private retainSnapshots;
|
|
628
657
|
/** Snapshots as a THREE-state read (N14): absent = no .backups directory,
|
|
629
658
|
* unknown = it exists but could not be listed (a caller must not read a
|
|
@@ -680,6 +709,8 @@ export interface NewSkillLibraryOptions {
|
|
|
680
709
|
ctx?: SkillMutationSink | undefined;
|
|
681
710
|
transact?: typeof transactIo | undefined;
|
|
682
711
|
threatExemptLabels?: readonly string[] | undefined;
|
|
712
|
+
/** G1/1.1: WHO writes through this library. Required: no call site can omit it. */
|
|
713
|
+
authority: WriteAuthority;
|
|
683
714
|
}
|
|
684
715
|
export declare function newSkillLibrary(options: NewSkillLibraryOptions): SkillLibrary;
|
|
685
716
|
export { DEFAULT_SKILL_LIMITS, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_SUPPORT_FILE_CHAR_POLICY, POLICY_STAGE_DEFAULTS, policyStageLimits, } from './limits.ts';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G3/3.2 (audit 06-performance.md 接缝 C): ONE declaration for every bounded store.
|
|
3
|
+
*
|
|
4
|
+
* Before this file the family had eight retention rules scattered across four packages and no place
|
|
5
|
+
* that could answer "how long does this record live under the current policy" (S1.md 4.3): six caps
|
|
6
|
+
* were named constants, three lived inline (snapshots 5, reports 20/20/10, archive 365d). Each entry
|
|
7
|
+
* below states its bound AND the code that declares it, so a gate can compare the two sets.
|
|
8
|
+
*
|
|
9
|
+
* `declare` is an ARRAY because one store may have several declaration sites (the report buckets are
|
|
10
|
+
* kept by two packages). The `inline` branch matches a CODE SNIPPET rather than a line number: line
|
|
11
|
+
* numbers rot on every edit above them, a snippet survives (and a snippet that stops matching is the
|
|
12
|
+
* detector the gate wants). No naming convention is assumed anywhere - that is the whole point of a
|
|
13
|
+
* ledger: the list IS the declaration.
|
|
14
|
+
*/
|
|
15
|
+
/** How one bound is declared in code. `named` resolves a constant; `inline` matches a snippet in a file. */
|
|
16
|
+
export type StoreBoundDeclare = {
|
|
17
|
+
readonly kind: 'named';
|
|
18
|
+
readonly module: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly value: number | string;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'inline';
|
|
23
|
+
readonly file: string;
|
|
24
|
+
readonly contains: string;
|
|
25
|
+
readonly value: string;
|
|
26
|
+
};
|
|
27
|
+
/** One bounded store: what it is, who owns it, what bounds it, and where that bound is declared. */
|
|
28
|
+
export interface StoreSpec {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
/** The package that keeps the store (the one a reclamation change belongs to). */
|
|
31
|
+
readonly owner: string;
|
|
32
|
+
readonly unit: 'bytes' | 'files' | 'entries';
|
|
33
|
+
/** The bound itself, as a person reads it (a cap, or a policy phrase when removal is report-only). */
|
|
34
|
+
readonly bound: string;
|
|
35
|
+
/** What makes an entry removable - the cutoff the bound counts over. */
|
|
36
|
+
readonly cutoff: string;
|
|
37
|
+
readonly trigger: 'write' | 'run-end' | 'pass-end' | 'manual';
|
|
38
|
+
/** Where a footprint reading for this store comes from (the field 3.3 reports). */
|
|
39
|
+
readonly artifact: string;
|
|
40
|
+
readonly declare: readonly StoreBoundDeclare[];
|
|
41
|
+
}
|
|
42
|
+
/** The one list. A new bounded store that is not registered here is what the G3 gate refuses. */
|
|
43
|
+
export declare const STORE_LEDGER: readonly StoreSpec[];
|
|
44
|
+
//# sourceMappingURL=store-ledger.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1/1.5 (S7-09): the TYPED critical section around a tree write.
|
|
3
|
+
*
|
|
4
|
+
* The control-plane lock used to wrap a whole pass, LLM call and all: an operator undo/restore waited up to
|
|
5
|
+
* one `curatorReviewTimeoutMs` (~120s) for a review that was merely thinking. The fix is to give the critical
|
|
6
|
+
* section a shape that a review cannot be squeezed into.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS TYPE CAN AND CANNOT DO (the plan wording says "cannot be put inside the lock by TYPE" - that is
|
|
9
|
+
* only half true and the half that is false is the half people rely on): a callback is a CLOSURE, so even
|
|
10
|
+
* with `view` as its only parameter it can still reach `this.io` / `this.ctx` / `this.llm` from the enclosing
|
|
11
|
+
* scope. TypeScript cannot stop that, and a criterion claiming it would never go red.
|
|
12
|
+
*
|
|
13
|
+
* What the type DOES give: new code has no other entry - the callback is handed a `SnapshotView` and nothing
|
|
14
|
+
* else, so a phase that needs `io` or `llm` has to stay OUTSIDE and be passed its result in. What the type
|
|
15
|
+
* CANNOT give is covered by the companion source guard (see the family gates): an `io` / `llm` / `this.`
|
|
16
|
+
* / `ctx.` reference inside a `withTreeWrite` callback is a violation, and that guard is the executable form
|
|
17
|
+
* of "cannot be put inside the lock".
|
|
18
|
+
*
|
|
19
|
+
* @module @lmzhen/dsh-evolution-core/tree-write
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* What a tree write may see: the mutation surface, and nothing that can think or read the medium directly.
|
|
23
|
+
* Deliberately carries NO `io`, NO `llm`, NO context handle - a phase needing those runs outside the section
|
|
24
|
+
* and hands its plan in.
|
|
25
|
+
*/
|
|
26
|
+
export interface SnapshotView {
|
|
27
|
+
/** The skill names the snapshot covers, in list order. */
|
|
28
|
+
readonly names: readonly string[];
|
|
29
|
+
/** One skill body, as read when the section opened. Async because the store read is: the view must not
|
|
30
|
+
* pretend a synchronous read of a medium it does not own. */
|
|
31
|
+
read(name: string): Promise<string | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* The ONLY write a section gets: one skill body, by name. Deliberately not an io handle - the point of
|
|
34
|
+
* the view is that a section CAN mutate (that is its job) without also being able to reach the medium,
|
|
35
|
+
* the model, or the context. The implementation is bound by the caller OUTSIDE the callback, so the
|
|
36
|
+
* callback body itself never names `this` / `io` / `llm` (which is what rule N40 checks).
|
|
37
|
+
*/
|
|
38
|
+
writeSkill(name: string, content: string): Promise<void>;
|
|
39
|
+
/** A record of what this section intends to change, for the audit line. */
|
|
40
|
+
note(text: string): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run one tree-writing section under a caller-owned serial queue, exposing ONLY a {@link SnapshotView}.
|
|
44
|
+
*
|
|
45
|
+
* The queue is passed in rather than owned here: the lock belongs to whoever serializes the control plane
|
|
46
|
+
* (the curator), and a second lock would be a second answer to one question.
|
|
47
|
+
* @param acquire - the caller serial queue (e.g. the curator mutex), returning its release.
|
|
48
|
+
* @param view - what this section sees while it holds the lock.
|
|
49
|
+
* @param fn - the section. It may only use the view.
|
|
50
|
+
* @returns whatever the section returns.
|
|
51
|
+
*/
|
|
52
|
+
export declare function withTreeWrite<T>(acquire: () => Promise<() => void>, view: SnapshotView, fn: (view: SnapshotView) => Promise<T>): Promise<T>;
|
|
53
|
+
//# sourceMappingURL=tree-write.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G1/1.1: the ONE authority behind a write.
|
|
3
|
+
*
|
|
4
|
+
* Before this type existed the inputs of the can-I-write decision came from two places with two
|
|
5
|
+
* lifecycles: `origin` (plus the policy snapshot and the confirm seam) travelled in the tool
|
|
6
|
+
* package gate context, while `threatExemptLabels` rode the SkillLibrary constructor. Callers that
|
|
7
|
+
* reached the bytes through another path built neither, and the mutators defaulted `origin` to
|
|
8
|
+
* foreground - so an omitted authority silently read as the operator acting, and no mechanism said
|
|
9
|
+
* otherwise (measured: two entry points in the same curator file, one passing background_review and
|
|
10
|
+
* one defaulting).
|
|
11
|
+
*
|
|
12
|
+
* The shape below is where those inputs converge. The follow-up batch makes it a REQUIRED argument
|
|
13
|
+
* of every mutator (content and lifecycle alike) and deletes the defaults, so an omission is a
|
|
14
|
+
* compile error instead of a silent claim about who wrote the bytes.
|
|
15
|
+
*/
|
|
16
|
+
import type { WriteOrigin } from './skill-store.ts';
|
|
17
|
+
/**
|
|
18
|
+
* The confirmation contract, HOISTED here from tool-skill-manage (G1/1.1 follow-up): the authority
|
|
19
|
+
* carries the confirm seam, so the seam's vocabulary lives with it. tool-skill-manage re-exports
|
|
20
|
+
* these names from write-gates.ts unchanged, so nothing importing them from there moves.
|
|
21
|
+
*/
|
|
22
|
+
export type WriteConfirmMode = 'auto' | 'ask' | 'timeout';
|
|
23
|
+
/** The mode a deployment that configures nothing gets. */
|
|
24
|
+
export declare const DEFAULT_WRITE_CONFIRM_MODE: WriteConfirmMode;
|
|
25
|
+
/** Seconds a timeout-mode prompt waits before the write is cancelled. */
|
|
26
|
+
export declare const DEFAULT_WRITE_CONFIRM_TIMEOUT_SECONDS = 120;
|
|
27
|
+
/** What the operator is told about the target before answering. */
|
|
28
|
+
export interface ConfirmContext {
|
|
29
|
+
readonly description?: string | undefined;
|
|
30
|
+
readonly versions?: number | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** One confirm question, as the gate writes it and the seam asks it. */
|
|
33
|
+
export interface WriteConfirmRequest {
|
|
34
|
+
readonly action: string;
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly question: {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly header: string;
|
|
39
|
+
readonly question: string;
|
|
40
|
+
readonly options: readonly {
|
|
41
|
+
readonly label: string;
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
readonly confirmLabel: string;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
/** Ask the human to confirm one irreversible skill write. MUST be total: every unavailability
|
|
48
|
+
* resolves to true, so no failure of the question service blocks the operator's own write. */
|
|
49
|
+
export type WriteConfirm = (request: WriteConfirmRequest) => Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* The batch-one migration aid: every call site becomes ONE of these instead of hand-building the
|
|
52
|
+
* object - so the shape cannot drift between callers, and each site still has to say its own
|
|
53
|
+
* origin out loud (origin is required here on purpose: the whole point is that no caller inherits
|
|
54
|
+
* a default identity).
|
|
55
|
+
*/
|
|
56
|
+
export declare function authorityFrom(input: {
|
|
57
|
+
origin: WriteOrigin;
|
|
58
|
+
threatExemptLabels?: readonly string[];
|
|
59
|
+
protectedNames?: readonly string[] | undefined;
|
|
60
|
+
readNames?: ReadonlySet<string> | undefined;
|
|
61
|
+
confirm?: WriteConfirm;
|
|
62
|
+
confirmMode?: WriteConfirmMode;
|
|
63
|
+
confirmTimeoutSeconds?: number;
|
|
64
|
+
/** Optional: a call site that has nothing to report may leave it out, and the gates then stay
|
|
65
|
+
* silent instead of forcing every one of the twelve construction points to invent a logger. */
|
|
66
|
+
warn?: (message: string) => void;
|
|
67
|
+
}): WriteAuthority;
|
|
68
|
+
export interface WriteAuthority {
|
|
69
|
+
/** Who is asking: the operator own session, a background review, the curator, the graph face. */
|
|
70
|
+
readonly origin: WriteOrigin;
|
|
71
|
+
/** Labels the threat scan must not refuse (P2-18). Migrated here from the library constructor:
|
|
72
|
+
* one decision must not read its policy from two objects. */
|
|
73
|
+
readonly threatExemptLabels: readonly string[];
|
|
74
|
+
/** The policy snapshot protected list; undefined when no policy row is mounted. */
|
|
75
|
+
readonly protectedNames: readonly string[] | undefined;
|
|
76
|
+
/** Names this session read successfully; undefined when the session log is not readable. */
|
|
77
|
+
readonly readNames: ReadonlySet<string> | undefined;
|
|
78
|
+
/** The human confirm seam, read only where a question is actually asked. */
|
|
79
|
+
readonly confirm?: WriteConfirm;
|
|
80
|
+
/** skillWriteConfirm: auto (the default) skips the question entirely. */
|
|
81
|
+
readonly confirmMode?: WriteConfirmMode;
|
|
82
|
+
/** skillWriteConfirmTimeoutSeconds, read only in timeout mode. */
|
|
83
|
+
readonly confirmTimeoutSeconds?: number;
|
|
84
|
+
/** Report a degraded gate; must not throw. */
|
|
85
|
+
readonly warn: (message: string) => void;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=write-authority.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The write-admission sequence: ONE ordered table of the reasons a skill write is refused.
|
|
3
|
+
*
|
|
4
|
+
* Two points read this table (design `dsh-evolution-write-gate-design.md` §3). The ADMISSION
|
|
5
|
+
* point (`execute`, BEFORE the approval seam) runs every gate that applies to `'admission'`, so a
|
|
6
|
+
* write that is refused is never staged and never spends the operator's attention twice. The
|
|
7
|
+
* EXECUTION point (the `executeCore` funnel, reached by the direct path and by the approval
|
|
8
|
+
* replay) runs the gates that apply to `'execution'`: the stored plan of a replayed write passed
|
|
9
|
+
* no schema, and the protected list can change while a record sits pending, so those verdicts are
|
|
10
|
+
* re-decided at the bytes.
|
|
11
|
+
*
|
|
12
|
+
* Order is part of the contract: a gate that can only refuse comes before one that needs a round
|
|
13
|
+
* trip with the operator, so a write that is refused anyway never asks anything.
|
|
14
|
+
*
|
|
15
|
+
* A gate decides whether a write may be ATTEMPTED. The library decides whether it can be
|
|
16
|
+
* PERFORMED — its own structured refusals (a restructure element that is not an object, a patch
|
|
17
|
+
* anchor that no longer matches, an `absorbed_into` that does not exist) stay where the bytes are
|
|
18
|
+
* read.
|
|
19
|
+
*
|
|
20
|
+
* Rule bodies live in `evolution-core` (the read-before-write rule in `skill-reads`, the
|
|
21
|
+
* required-argument table in `SKILL_ACTION_REQUIRED_FIELDS`); this module owns the ORDER and the
|
|
22
|
+
* applicability of each gate, which is an execution-point fact of this tool.
|
|
23
|
+
* @module @lmzhen/dsh-evolution-core/write-gates
|
|
24
|
+
*/
|
|
25
|
+
import { type WriteConfirm, type WriteConfirmMode } from './write-authority.ts';
|
|
26
|
+
import type { ConfirmContext } from './write-authority.ts';
|
|
27
|
+
import { type WriteOrigin } from './index.ts';
|
|
28
|
+
/** The point whose verdict is being taken. */
|
|
29
|
+
export type WriteGatePoint = 'admission' | 'execution';
|
|
30
|
+
/** What every gate reads. */
|
|
31
|
+
/**
|
|
32
|
+
* G1/1.2: the policy snapshot's protected list, read through an `unknown` boundary.
|
|
33
|
+
*
|
|
34
|
+
* This used to be package-private to tool-skill-manage, which is why the graph commands could not ask
|
|
35
|
+
* the same question and ended up enforcing nothing. It lives here so every write entry point reads the
|
|
36
|
+
* SAME list from the SAME place; the `unknown` boundary stays because the Context augmentation types the
|
|
37
|
+
* getter non-optionally while at runtime the policy row may be absent.
|
|
38
|
+
* @param policy - `ctx.get('evolutionPolicy')`, or anything else when the row is not mounted.
|
|
39
|
+
* @returns the protected names, or undefined when no policy snapshot is readable.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* G1/1.2: what a write entry point must supply for the shared admission sequence.
|
|
43
|
+
*
|
|
44
|
+
* The gates need policy, confirm, the session's read set and a warn sink; every caller can obtain those
|
|
45
|
+
* from its own `ctx`, but assembling them into a valid `WriteGateContext` is the seam's business, not each
|
|
46
|
+
* caller's. Without this, "all five entry points call the same entry" means five hand-built contexts and
|
|
47
|
+
* five chances to leave `protectedNames` out - which is exactly how the graph commands ended up enforcing
|
|
48
|
+
* nothing.
|
|
49
|
+
*/
|
|
50
|
+
export interface SkillWriteGateServices {
|
|
51
|
+
/** `ctx.get('evolutionPolicy')`, or anything else when the row is not mounted. */
|
|
52
|
+
readonly policy: unknown;
|
|
53
|
+
/** The human confirm seam; `undefined` refuses to ask (the gates then refuse the write). */
|
|
54
|
+
readonly confirm: WriteConfirm | undefined;
|
|
55
|
+
/** Names this session read successfully; `undefined` when the session log is not readable. */
|
|
56
|
+
readonly readNames: ReadonlySet<string> | undefined;
|
|
57
|
+
/** Report a degraded gate; must not throw. */
|
|
58
|
+
readonly warn: (message: string) => void;
|
|
59
|
+
/** The registry's `skillWriteConfirm`; `auto` (the default) skips the question. */
|
|
60
|
+
readonly confirmMode?: WriteConfirmMode;
|
|
61
|
+
/** The registry's `skillWriteConfirmTimeoutSeconds`, read only in `timeout` mode. */
|
|
62
|
+
readonly confirmTimeoutSeconds?: number;
|
|
63
|
+
/** Lazily read the target's own words for the confirmation card. */
|
|
64
|
+
readonly describeTarget?: (() => Promise<ConfirmContext | undefined>) | undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Assemble a gate context from a caller's services. The protected list is read HERE, so no caller can
|
|
68
|
+
* forget it or read it from a second place.
|
|
69
|
+
* @param services - the four required services plus the optional registry knobs.
|
|
70
|
+
* @param request - the point asking, the arguments under test, and the write origin.
|
|
71
|
+
* @returns a context the shared sequence accepts.
|
|
72
|
+
*/
|
|
73
|
+
export declare function skillWriteGateContext(services: SkillWriteGateServices, request: {
|
|
74
|
+
readonly point: WriteGatePoint;
|
|
75
|
+
readonly args: unknown;
|
|
76
|
+
readonly origin: WriteOrigin;
|
|
77
|
+
}): WriteGateContext;
|
|
78
|
+
export declare function protectedSkillNamesOf(policy: unknown): readonly string[] | undefined;
|
|
79
|
+
export interface WriteGateContext {
|
|
80
|
+
/** The point asking: the tool's admission of a call, or its execution at the bytes. */
|
|
81
|
+
readonly point: WriteGatePoint;
|
|
82
|
+
/** The parsed (admission) or stored (replay) arguments — unvalidated, hence the shape gate. */
|
|
83
|
+
readonly args: unknown;
|
|
84
|
+
/** The library write origin: `foreground` is the operator's own session. */
|
|
85
|
+
readonly origin: WriteOrigin;
|
|
86
|
+
/** The policy snapshot's protected list; `undefined` when no policy row is mounted. */
|
|
87
|
+
readonly protectedNames: readonly string[] | undefined;
|
|
88
|
+
/** Names this session read successfully; `undefined` when the session log is not readable. */
|
|
89
|
+
readonly readNames: ReadonlySet<string> | undefined;
|
|
90
|
+
/** The human confirm seam — read only by the gates that apply to `'admission'`. */
|
|
91
|
+
readonly confirm: WriteConfirm | undefined;
|
|
92
|
+
/** The registry's `skillWriteConfirm`: `auto` (the default) skips the question entirely. */
|
|
93
|
+
readonly confirmMode?: WriteConfirmMode;
|
|
94
|
+
/** The registry's `skillWriteConfirmTimeoutSeconds`, read only in `timeout` mode. */
|
|
95
|
+
readonly confirmTimeoutSeconds?: number;
|
|
96
|
+
/** Read the target's own words for the confirmation card. LAZY on purpose: a write that is not
|
|
97
|
+
* asked about must not pay a read for a question nobody sees. Absent means "say nothing extra". */
|
|
98
|
+
readonly describeTarget?: (() => Promise<ConfirmContext | undefined>) | undefined;
|
|
99
|
+
/** Report a degraded gate; must not throw. The implementation decides how often it speaks — the
|
|
100
|
+
* shipped seam latches once per PROCESS, because the conditions it reports (no question service,
|
|
101
|
+
* an unreadable session log) belong to the deployment, not to one write. */
|
|
102
|
+
readonly warn: (message: string) => void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run the sequence for one write attempt.
|
|
106
|
+
* @param context - the asking point, the write, and the seams the human-facing gate needs.
|
|
107
|
+
* @returns the FIRST refusal, or null when every applicable gate passed. Both points return the
|
|
108
|
+
* message as the tool result unchanged, so the two entries refuse with identical wording.
|
|
109
|
+
*/
|
|
110
|
+
export declare function runWriteGates(context: WriteGateContext): Promise<string | null>;
|
|
111
|
+
//# sourceMappingURL=write-gates.d.ts.map
|
package/package.json
CHANGED