@mstar-harness/engine 2.4.0 → 3.0.1

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.
@@ -0,0 +1,134 @@
1
+ import { type GateResult, type ValidationResult } from "./core.js";
2
+ import { type ResidualEntry } from "./status.js";
3
+ /** Roadmap file name inside `projects/<id>/` (plan Task 4 — writer contract). */
4
+ export declare const PROJECT_ROADMAP_FILE = "roadmap.md";
5
+ /** Project register file name inside `projects/<id>/` (plan Task 4). */
6
+ export declare const PROJECT_REGISTER_FILE = "residuals.json";
7
+ /** Fallback project id for project-less flows (plan Task 4 — compass ruling 2). */
8
+ export declare const _DEFAULT_PROJECT = "_default";
9
+ /** Roadmap status enum (plan Task 4 — frontmatter schema). */
10
+ export declare const ROADMAP_STATUSES: readonly ["active", "paused", "completed"];
11
+ export type RoadmapStatus = (typeof ROADMAP_STATUSES)[number];
12
+ /**
13
+ * Roadmap frontmatter (plan Task 4): machine-checkable subset. All fields
14
+ * are `unknown` because documents come from YAML at runtime; the validator
15
+ * narrows them. `milestones` / `residuals_ref` are optional; goal-item body
16
+ * conventions are warnings only.
17
+ */
18
+ export type RoadmapFrontmatter = {
19
+ project_id?: unknown;
20
+ title?: unknown;
21
+ status?: unknown;
22
+ created_at?: unknown;
23
+ milestones?: unknown;
24
+ residuals_ref?: unknown;
25
+ [key: string]: unknown;
26
+ };
27
+ /** One register entry: the v1 residual entry verbatim + register provenance. */
28
+ export type ProjectRegisterEntry = ResidualEntry & {
29
+ source_plan: string;
30
+ registered_at: string;
31
+ lifecycle_id?: string;
32
+ };
33
+ /**
34
+ * Register document shape (`projects/<id>/residuals.json`, plan Task 4;
35
+ * QC wave-1 W-E): `entries` keyed by plan id, each value an ARRAY of
36
+ * register entries — v1 `residual_findings[plan-id] = entries[]`
37
+ * multi-finding semantics preserved verbatim (a plan can hold 2+ open
38
+ * residuals). `migration_notes[]` (the old single-entry collapse record)
39
+ * is gone: no entries are ever skipped.
40
+ */
41
+ export type ProjectRegisterDoc = {
42
+ entries?: Record<string, ProjectRegisterEntry[]>;
43
+ [key: string]: unknown;
44
+ };
45
+ /**
46
+ * Roadmap validation result: schema violations decide `ok`; body-convention
47
+ * findings are collected as `warnings` and never flip `ok` (plan Task 4 —
48
+ * goal-item body is not a hard gate).
49
+ */
50
+ export type RoadmapValidation = GateResult & {
51
+ warnings: ValidationResult[];
52
+ };
53
+ /** Findings cleanup policy mirror of Assignment `Findings cleanup`. */
54
+ export type FindingsCleanupMode = "zero-residual" | "allow-residual";
55
+ /** Computed rollup aggregates (jq semantics). */
56
+ export type TechDebtSummary = {
57
+ total_open: number;
58
+ by_severity: Record<string, number>;
59
+ by_target: Record<string, number>;
60
+ by_plan: Record<string, number>;
61
+ };
62
+ export type TechDebtCheck = {
63
+ field: "total_open" | "by_severity" | "by_target" | "by_plan";
64
+ status: "PASS" | "DRIFT";
65
+ };
66
+ /**
67
+ * Result of the project-register rollup. `stored`/`checks`/`overall` are
68
+ * retained for export-surface compatibility (the P2 CLI cutover): the v1
69
+ * stored-summary drift check (`metadata.tech_debt_summary`) is deleted in
70
+ * the v3 cutover — the project register is the source of truth, so `stored`
71
+ * is always null and every check reports DRIFT.
72
+ */
73
+ export type TechDebtRollup = {
74
+ computed: TechDebtSummary;
75
+ stored: Record<string, unknown> | null;
76
+ checks: TechDebtCheck[];
77
+ overall: "PASS" | "DRIFT";
78
+ };
79
+ /**
80
+ * Validate a roadmap.md file (plan Task 4): parse the frontmatter with the
81
+ * shared flat-subset parser and check the schema
82
+ * `{ project_id, title, status: active|paused|completed, created_at,
83
+ * milestones[]?, residuals_ref? }`. A roadmap file whose body follows the
84
+ * documented conventions (a `## Direction` section + goal items as markdown
85
+ * task-list items) is fully green; convention misses are `warnings` only
86
+ * and never flip `ok` (compass Non-Goal / AC-P1).
87
+ */
88
+ export declare function validateRoadmap(filePath: string): RoadmapValidation;
89
+ /**
90
+ * Validate a project register document (`projects/<id>/residuals.json`,
91
+ * plan Task 4; QC wave-1 W-E): `{ entries: { [key]: entry[] } }` keyed by
92
+ * plan id, each value an ARRAY of entries (v1 `residual_findings[plan-id]`
93
+ * multi-finding semantics preserved — a plan may hold 2+ open residuals).
94
+ * Each entry is validated by the v1 `validateResidual` verbatim (severity
95
+ * enum + lifecycle semantics preserved — the register re-hosts, never
96
+ * copies) plus the register provenance fields `source_plan` (must match its
97
+ * entries key) and `registered_at` (YYYY-MM-DD), and the optional
98
+ * `lifecycle_id`.
99
+ */
100
+ export declare function validateProjectRegister(doc: unknown): GateResult;
101
+ /**
102
+ * Findings cleanup gate (status-and-residuals.md § Findings cleanup modes;
103
+ * QC wave-1 W-D relocation — the input is the project register
104
+ * `projects/<id>/residuals.json`, entries keyed by plan id with an ARRAY of
105
+ * residuals per plan, and the plan id links the register entries to the
106
+ * snapshot's plan row). Every OPEN entry of the plan is checked.
107
+ * `zero-residual`: only true blocker-defers (`decision: defer` + non-empty
108
+ * `target`) may stay open — fixable findings, `nit`s, and waived/
109
+ * risk-accepted entries are violations. `allow-residual` (default): open
110
+ * residuals are fine unless an unresolved Critical remains. Mode resolution:
111
+ * explicit `opts.mode` → `allow-residual` (the v1
112
+ * `plans[].metadata.findings_cleanup` mirror is deleted — no dual-track).
113
+ */
114
+ export declare function findingsCleanupGate(register: ProjectRegisterDoc, planId: string, opts?: {
115
+ mode?: FindingsCleanupMode;
116
+ }): GateResult;
117
+ /**
118
+ * Compute the tech-debt rollup over the project registers (QC wave-1 W-D
119
+ * relocation — status-and-residuals.md § `metadata.tech_debt_summary`
120
+ * semantics preserved at the project layer): `total_open` / `by_severity` /
121
+ * `by_target` / `by_plan` over open entries of every
122
+ * `projects/<id>/residuals.json` register under `projectDir` (legacy
123
+ * `"warning"` → `low`, `null`/`""` → `medium`; closed entries skipped;
124
+ * missing `target` groups under `"unspecified"`; `by_plan` keyed by plan id —
125
+ * the snapshot plan linkage; register values are ARRAYS per plan id, so
126
+ * every open entry of a plan counts).
127
+ *
128
+ * The v1 stored-summary drift check (`metadata.tech_debt_summary`) is a v1
129
+ * dead path — the register is the source of truth, so `stored` is always
130
+ * null and the retained `checks`/`overall` fields report DRIFT
131
+ * (export-surface compatibility until the P2 CLI cutover). Does not write
132
+ * anything.
133
+ */
134
+ export declare function techDebtRollup(projectDir: string): TechDebtRollup;
package/dist/sdd.d.ts CHANGED
@@ -35,10 +35,12 @@ export type ReviewPackageOptions = {
35
35
  * resolves or creates any SDD tree under the feature checkout (refuses a
36
36
  * second SDD tree; no override or probe may bypass this guard);
37
37
  * 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
38
- * — plan finding 2026-08-08: covers `.harness`-rooted repos the
39
- * status.json probe misses; resolved relative to the established root;
40
- * 3. `status.json` probe at root (`.mstar` → `.agents`);
41
- * 4. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
38
+ * — plan finding 2026-08-08: covers repos the status.json probe misses;
39
+ * resolved relative to the established root;
40
+ * 3. `.mstarc` `[config] harness_dir` at `root` (repo-declared root;
41
+ * resolved against the config file's directory);
42
+ * 4. `status.json` probe at root (`.mstar` → `.agents`);
43
+ * 5. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
42
44
  *
43
45
  * `controlRoot` (CLI 2nd arg / `MSTAR_CONTROL_ROOT`) pins `root` to the
44
46
  * control worktree instead of the cwd's git top-level.
package/dist/status.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type GateResult } from "./core.js";
2
2
  import { type EnforcementFlag } from "./dispatch.js";
3
+ import { type WorkflowLifecycleType } from "./workflow.js";
3
4
  /**
4
5
  * Loose shape of a parsed status.json document. All fields are `unknown`
5
6
  * because documents come from JSON at runtime; validators narrow them.
@@ -12,6 +13,25 @@ export type StatusDoc = {
12
13
  metadata?: unknown;
13
14
  [key: string]: unknown;
14
15
  };
16
+ /**
17
+ * v2 root status document (`{HARNESS_DIR}/status.json`, plan Task 3 — hard
18
+ * cutover): `version`, `updated_at`, `workflows[]` only. The list holds
19
+ * ACTIVE (non-terminal) lifecycles; terminal writers unregister AFTER the
20
+ * snapshot write (removal-at-terminal).
21
+ */
22
+ export type StatusV2Doc = {
23
+ version: 2;
24
+ updated_at: string;
25
+ workflows: WorkflowEntry[];
26
+ };
27
+ /** One active lifecycle entry of the v2 root `workflows[]` list. */
28
+ export type WorkflowEntry = {
29
+ id: string;
30
+ type: WorkflowLifecycleType;
31
+ started_at: string;
32
+ /** Harness-relative snapshot dir (e.g. `workflows/<id>`), never absolute. */
33
+ dir: string;
34
+ };
15
35
  /** Residual entry as parsed from status.json (loose — validated by `validateResidual`). */
16
36
  export type ResidualEntry = {
17
37
  id?: unknown;
@@ -42,31 +62,6 @@ export type PlanRow = {
42
62
  execution_lease?: unknown;
43
63
  [key: string]: unknown;
44
64
  };
45
- /** Findings cleanup policy mirror of Assignment `Findings cleanup`. */
46
- export type FindingsCleanupMode = "zero-residual" | "allow-residual";
47
- /** Computed rollup aggregates (jq semantics). */
48
- export type TechDebtSummary = {
49
- total_open: number;
50
- by_severity: Record<string, number>;
51
- by_target: Record<string, number>;
52
- by_plan: Record<string, number>;
53
- };
54
- export type TechDebtCheck = {
55
- field: "total_open" | "by_severity" | "by_target" | "by_plan";
56
- status: "PASS" | "DRIFT";
57
- };
58
- /** Result of the rollup + drift check vs stored `metadata.tech_debt_summary`. */
59
- export type TechDebtRollup = {
60
- computed: TechDebtSummary;
61
- stored: Record<string, unknown> | null;
62
- checks: TechDebtCheck[];
63
- overall: "PASS" | "DRIFT";
64
- };
65
- export type ArchiveResult = {
66
- planId: string;
67
- archived: number;
68
- archivePath: string;
69
- };
70
65
  /**
71
66
  * Normalize a residual `severity` value for reading/rolling up
72
67
  * (status-and-residuals.md § severity 5 + rollup `norm_sev`):
@@ -101,44 +96,69 @@ export declare function validatePlanRow(row: unknown): GateResult;
101
96
  */
102
97
  export declare function validateResidual(entry: unknown): GateResult;
103
98
  /**
104
- * Validate a status.json document (schema: status-and-residuals.md
105
- * § Basic structure + § General constraints). Accepts a parsed document or a
106
- * file path (malformed JSON yields a `status.invalid-json` violation, never a
107
- * throw). Required top-level fields: `version`, `updated_at`, `plans[]`,
108
- * root-only `residual_findings`, `metadata`. Root-only canonical: any
109
- * `metadata.residual_findings` key is flagged as dual-write.
99
+ * Validate one v2 root `workflows[]` entry (plan Task 3): required `id`,
100
+ * `type` (plan | iteration), `started_at`, `dir` harness-relative, never
101
+ * absolute and never containing `..`. The removal-at-terminal invariant
102
+ * (snapshot exists and is non-terminal) is checked at document level by
103
+ * `validateStatusV2` when a harness dir is known.
110
104
  */
111
- export declare function validateStatus(docOrPath: StatusDoc | string): GateResult;
105
+ export declare function validateWorkflowEntry(entry: unknown): GateResult;
112
106
  /**
113
- * Archive the open residuals of a plan (status-and-residuals.md
114
- * § Residual findings lifecycle): append every entry of
115
- * `residual_findings[<plan-id>]` to `{HARNESS_DIR}/archived/residuals/
116
- * <plan-id>.json` (stamped `archived_at`), delete the key from the open list,
117
- * and bump root `updated_at`. No-op (archived 0) when the plan has no open
118
- * residuals. `harnessDir` defaults to the resolved `{HARNESS_DIR}` from cwd.
107
+ * Validate a v2 status.json document (plan Task 3 — hard cutover). Accepts a
108
+ * parsed document or a file path (malformed JSON yields a
109
+ * `status.invalid-json` violation, never a throw). v1 or unknown-version
110
+ * inputs including v1-shaped documents carrying a root `plans[]` — fail
111
+ * closed with an explicit `status.migration-required` violation carrying the
112
+ * `mstar migrate` hint; there is no v1 read path anymore (v1 `validateStatus`
113
+ * was deleted in the same task).
119
114
  *
120
- * `planId` is validated as a single safe path component before it is used to
121
- * build the archive path (path traversal guard see
122
- * `assertSafePathComponent`). The status.json read-modify-write runs under
123
- * `withStatusWriteLock` so concurrent coordination writers serialize.
115
+ * Required: `version: 2`, `updated_at` (YYYY-MM-DD, same convention as the
116
+ * v1 root), `workflows[]` of active entries (each validated by
117
+ * `validateWorkflowEntry`, duplicate ids rejected).
124
118
  *
125
- * simplify: archive append + status.json update are two separate writes —
126
- * a crash between them leaves entries both archived and open; re-running is
127
- * safe because appends dedup on `entries[].id` (F-10). Not transactional by
128
- * design (v1); the write lock from F-004 keeps concurrent writers safe.
119
+ * Removal-at-terminal invariant: when a harness dir is known (path input, or
120
+ * `opts.harnessDir` for doc input), every listed entry's snapshot at
121
+ * `{HARNESS_DIR}/<dir>/snapshot.json` must exist and be non-terminal the
122
+ * root holds active lifecycles only, and terminal writers unregister AFTER
123
+ * the snapshot write. The snapshot must also PHYSICALLY live under the
124
+ * harness: a symlinked `workflows/<id>/` (or snapshot file) resolving
125
+ * outside the harness dir is rejected fail-closed (QC wave-1 S-f). Doc
126
+ * input without a harness dir is structure-only.
129
127
  */
130
- export declare function archiveResiduals(planId: string, harnessDir?: string): Promise<ArchiveResult>;
128
+ export declare function validateStatusV2(docOrPath: StatusV2Doc | string, opts?: {
129
+ harnessDir?: string;
130
+ }): GateResult;
131
131
  /**
132
- * Findings cleanup gate (status-and-residuals.md § Findings cleanup modes).
133
- * `zero-residual`: only true blocker-defers (`decision: defer` + non-empty
134
- * `target`) may stay open fixable findings, `nit`s, and waived/
135
- * risk-accepted entries are violations. `allow-residual` (default): open
136
- * residuals are fine unless an unresolved Critical remains. Mode resolution:
137
- * explicit `opts.mode` → `plans[].metadata.findings_cleanup` → `allow-residual`.
132
+ * Relocated v2 root validator (plan Task 3 hard cutover, no dual path):
133
+ * the v1 `validateStatus` implementation was deleted in the same task that
134
+ * introduced the v2 surface; the public export name survives so external
135
+ * consumers (CLI, host hooks cut over in P2) keep compiling and now fail
136
+ * closed on v1 input with the `mstar migrate` hint.
138
137
  */
139
- export declare function findingsCleanupGate(doc: StatusDoc, planId: string, opts?: {
140
- mode?: FindingsCleanupMode;
141
- }): GateResult;
138
+ export declare const validateStatus: typeof validateStatusV2;
139
+ /**
140
+ * Register one active workflow entry in the v2 root file (plan Task 3).
141
+ * Idempotent upsert by entry `id` under the root-file `withStatusWriteLock`,
142
+ * bumping root `updated_at`. A missing/empty root file is initialized from
143
+ * the v2 template (never a v1 tree); a v1 root is refused with the
144
+ * `mstar migrate` hint (no silent mutation of an un-migrated tree).
145
+ *
146
+ * The final document is validated with `validateStatusV2` (including the
147
+ * removal-at-terminal snapshot invariant against `dirname(root)`) before the
148
+ * write — an entry whose snapshot is missing or terminal is refused and
149
+ * nothing is written.
150
+ */
151
+ export declare function registerWorkflow(root: string, entry: WorkflowEntry): Promise<StatusV2Doc>;
152
+ /**
153
+ * Remove one workflow entry from the v2 root file (plan Task 3). Idempotent:
154
+ * removing an absent id is a no-op with no write; a missing/empty root file
155
+ * is a no-op that never creates the file. Runs under the root-file
156
+ * `withStatusWriteLock`, bumping root `updated_at` only when an entry was
157
+ * actually removed. The final document is validated (removal-at-terminal
158
+ * invariant included) before the write — a v1 root is refused with the
159
+ * `mstar migrate` hint.
160
+ */
161
+ export declare function unregisterWorkflow(root: string, id: string): Promise<StatusV2Doc>;
142
162
  /**
143
163
  * Resolve the repo-level hard-enforcement flag from the iteration compass
144
164
  * (roadmap §8.5 C4/D2): `{ITERATION_DIR}/<id>/delivery-compass.md` files are
@@ -156,13 +176,21 @@ export declare function findingsCleanupGate(doc: StatusDoc, planId: string, opts
156
176
  */
157
177
  export declare function resolveCompassEnforcement(harnessDir: string): EnforcementFlag;
158
178
  /**
159
- * Compute the `metadata.tech_debt_summary` rollup (status-and-residuals.md
160
- * § `metadata.tech_debt_summary`): `total_open` / `by_severity` /
161
- * `by_target` / `by_plan` over open entries of root `residual_findings`
162
- * merged with the legacy `metadata.residual_findings` read path (canonical
163
- * keys win; legacy `"warning"` `low`, `null`/`""` `medium`; closed
164
- * entries skipped; missing `target` groups under `"unspecified"`), then
165
- * compare field-by-field against stored `metadata.tech_debt_summary`.
166
- * Accepts a parsed document or a file path. Does not write status.json.
179
+ * Resolve the repo-declared hard-enforcement flag from `.mstarc`
180
+ * `[config] enforcement` (plan-conventions § `.mstarc` 格式): the nearest
181
+ * config at the harness dir or its parent (the repo root) wins —
182
+ * `hard` hard, `soft` soft, absent/invalid value → `none`. Same
183
+ * discovery scope as the sub-directory keys; a config above the repo
184
+ * root is never adopted.
185
+ */
186
+ export declare function resolveMstarcEnforcement(harnessDir: string): EnforcementFlag;
187
+ /**
188
+ * Repo-level hard-enforcement flag: `.mstarc` `[config] enforcement` wins,
189
+ * else the iteration compass frontmatter (`resolveCompassEnforcement`),
190
+ * else warn-only. Hosts compose this BELOW their explicit Config override
191
+ * and the per-dispatch Assignment flag (precedence: Config > Assignment
192
+ * flag > repo `.mstarc` > compass > warn-only) — `.mstarc` `soft` is a
193
+ * local rollback against a hard compass, `.mstarc` `hard` hardens
194
+ * flag-less dispatches and gates.
167
195
  */
168
- export declare function techDebtRollup(docOrPath: StatusDoc | string): TechDebtRollup;
196
+ export declare function resolveRepoEnforcement(harnessDir: string): EnforcementFlag;
@@ -0,0 +1,78 @@
1
+ import type { GateResult } from "./core.js";
2
+ import { type IntegrationMergeLease } from "./lease.js";
3
+ import { type PlanRow } from "./status.js";
4
+ /** Snapshot file name inside `workflows/<id>/` (plan Task 2 — writer contract). */
5
+ export declare const WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
6
+ /** Lifecycle status enum (plan Task 2 — terminal set = completed|failed|stopped). */
7
+ export declare const WORKFLOW_LIFECYCLE_STATUSES: readonly ["running", "paused", "completed", "failed", "stopped"];
8
+ /** Terminal statuses: snapshot must carry `ended_at` and no dangling leases. */
9
+ export declare const WORKFLOW_TERMINAL_STATUSES: readonly ["completed", "failed", "stopped"];
10
+ /** Lifecycle type enum (plan Task 2 — id reuses the orchestration id). */
11
+ export declare const WORKFLOW_LIFECYCLE_TYPES: readonly ["plan", "iteration"];
12
+ export type WorkflowLifecycleStatus = (typeof WORKFLOW_LIFECYCLE_STATUSES)[number];
13
+ export type WorkflowLifecycleType = (typeof WORKFLOW_LIFECYCLE_TYPES)[number];
14
+ /**
15
+ * First-class lifecycle execution policy (plan Task 2 — keys copied from root
16
+ * `metadata` at migrate; values accepted-but-opaque this iteration, no
17
+ * semantic gate).
18
+ */
19
+ export type WorkflowExecutionPolicy = {
20
+ plan_parallelism?: unknown;
21
+ worktree_mode?: unknown;
22
+ push_policy?: unknown;
23
+ };
24
+ /** Iteration branch anchors (plan Task 2 — from root metadata anchors). */
25
+ export type WorkflowBranchAnchors = {
26
+ base?: string;
27
+ integration?: string;
28
+ target?: string;
29
+ };
30
+ /**
31
+ * v3 workflow snapshot (`workflows/<id>/snapshot.json`) — final schema
32
+ * (plan Task 2). `plans[]` rows are the legacy PlanRow shape verbatim;
33
+ * per-row `execution_lease` stays on the row, `integration_merge_lease` is
34
+ * top-level.
35
+ *
36
+ * Notes dual-home SSOT (qc wave-1 S-e): a plan row's `notes` array is the
37
+ * LEGACY VERBATIM copy preserved at migrate time — the RUNTIME ledger is
38
+ * `notes.jsonl` in the workflow dir (`migrate.ts` NOTES_LEDGER_FILE). New
39
+ * notes append to the ledger only; row `notes` is read-only legacy and is
40
+ * never a dual-write target, so the two never diverge by construction.
41
+ */
42
+ export type WorkflowSnapshot = {
43
+ schema_version: 1;
44
+ id: string;
45
+ type: WorkflowLifecycleType;
46
+ status: WorkflowLifecycleStatus;
47
+ started_at: string;
48
+ ended_at?: string;
49
+ updated_at: string;
50
+ phase?: string;
51
+ plans: PlanRow[];
52
+ execution_policy?: WorkflowExecutionPolicy;
53
+ integration_merge_lease?: IntegrationMergeLease;
54
+ branch?: WorkflowBranchAnchors;
55
+ control_worktree_path?: string;
56
+ legacy_metadata?: Record<string, unknown>;
57
+ compass_ref?: string;
58
+ };
59
+ /**
60
+ * Validate a v3 workflow snapshot document (plan Task 2 — final schema):
61
+ * enum/type/id checks, `schema_version: 1`, required timestamps, `plans[]`
62
+ * rows validated by the legacy `validatePlanRow` with row-level
63
+ * `execution_lease` shape delegated to `validateExecutionLease`,
64
+ * `integration_merge_lease` shape delegated to
65
+ * `validateIntegrationMergeLease`, `execution_policy` keys accepted-but-
66
+ * opaque. Terminal invariant: `status` ∈ completed|failed|stopped ⇒
67
+ * `ended_at` present AND no row carries `execution_lease` AND no
68
+ * `integration_merge_lease` (no dangling leases).
69
+ */
70
+ export declare function validateWorkflowSnapshot(doc: unknown): GateResult;
71
+ /**
72
+ * Write a workflow snapshot as a whole-rewrite of `dir/snapshot.json` under
73
+ * `withStatusWriteLock(snapshotPath)` (plan Task 2 — the `.status-write.lockdir`
74
+ * lands inside `workflows/<id>/`, dirname of the snapshot; no harness-root
75
+ * pollution). The snapshot is validated first — an invalid snapshot throws
76
+ * and nothing is written. `dir` is created recursively.
77
+ */
78
+ export declare function writeWorkflowSnapshot(snapshot: WorkflowSnapshot, dir: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "2.4.0",
3
+ "version": "3.0.1",
4
4
  "description": "Morning Star Harness Workflow Engine — deterministic workflow enforcement library (path, status, lease, dispatch, sdd, iteration, lint gates).",
5
5
  "license": "MIT",
6
6
  "repository": {