@mstar-harness/engine 3.6.3 → 3.7.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.
package/dist/sdd.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type GateResult } from "./core.js";
1
2
  /**
2
3
  * Error carrying the ported script exit code so the CLI can map validation
3
4
  * failures to identical non-zero exits.
@@ -18,17 +19,41 @@ export type SddWorkspaceOptions = {
18
19
  /** Working directory for git probes; default `process.cwd()`. */
19
20
  cwd?: string;
20
21
  };
21
- /** Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path). */
22
+ /**
23
+ * Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path).
24
+ *
25
+ * Bound mode (spec A3): passing
26
+ * `context` makes the destination an artifact gate check (before any
27
+ * mkdir/write), defaults the destination to `{context.sddDir}/task-N-brief.md`
28
+ * and returns/emits an absolute path. Without `context` the legacy helper
29
+ * stays explicitly UNBOUND — a context-less call has no protection claim
30
+ * (A3: "context-less legacy helper calls remain explicitly unbound").
31
+ */
22
32
  export type TaskBriefOptions = {
23
33
  sddDir?: string;
34
+ /** Resolved SDD execution context — binds the artifact write (A3). */
35
+ context?: SddExecutionContext;
36
+ /** Observed invocation cwd for the artifact gate; default `process.cwd()`. */
37
+ cwd?: string;
24
38
  };
25
- /** Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd). */
39
+ /**
40
+ * Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd).
41
+ *
42
+ * Bound mode (spec A3): passing `context` makes the destination an artifact
43
+ * gate check (before any mkdir/write), defaults the git probe cwd to the
44
+ * context's feature worktree ("feature Git cwd, control artifact out") and
45
+ * returns/emits an absolute path. Without `context` the legacy helper stays
46
+ * explicitly UNBOUND — no protection claim.
47
+ */
26
48
  export type ReviewPackageOptions = {
27
49
  sddDir?: string;
50
+ /** Git probe cwd; bound mode defaults to `context.featureCwd`. */
28
51
  cwd?: string;
52
+ /** Resolved SDD execution context — binds the artifact write (A3). */
53
+ context?: SddExecutionContext;
29
54
  };
30
55
  /**
31
- * Git capture ceiling for `gitOut` / `reviewPackage` (qc3 W-2): Node's
56
+ * Git capture ceiling for `gitOut` / `reviewPackage` : Node's
32
57
  * default 1 MiB `maxBuffer` ENOBUFS'd on large review ranges. 64 MiB keeps
33
58
  * realistic iteration-close ranges working while bounding memory; captures
34
59
  * beyond that fail as SddScriptError via the CLI.
@@ -39,13 +64,13 @@ export declare const GIT_CAPTURE_MAX_BYTES: number;
39
64
  * the absolute path). Resolution order:
40
65
  *
41
66
  * 1. fail-closed FIRST: a linked worktree without a control root never
42
- * resolves or creates any SDD tree under the feature checkout (refuses a
43
- * second SDD tree; no override or probe may bypass this guard);
67
+ * resolves or creates any SDD tree under the feature checkout (refuses a
68
+ * second SDD tree; no override or probe may bypass this guard);
44
69
  * 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
45
- * — plan finding 2026-08-08: covers repos the status.json probe misses;
46
- * resolved relative to the established root;
70
+ * — plan finding 2026-08-08: covers repos the status.json probe misses;
71
+ * resolved relative to the established root;
47
72
  * 3. `.mstarc` `[config] harness_dir` at `root` (repo-declared root;
48
- * resolved against the config file's directory);
73
+ * resolved against the config file's directory);
49
74
  * 4. `status.json` probe at root (`.mstar` → `.agents`);
50
75
  * 5. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
51
76
  *
@@ -61,6 +86,14 @@ export declare function sddWorkspace(planId: string, opts?: SddWorkspaceOptions)
61
86
  * EOF for the last task) — a later Task heading resets the section. A
62
87
  * missing task writes an empty file then fails with exit-3
63
88
  * (`SddScriptError.exitCode === 3`).
89
+ *
90
+ * Bound mode (`opts.context`, spec A3): the artifact destination is gated
91
+ * with `checkSddAction` BEFORE any mkdir/write — a refused destination
92
+ * writes nothing — and the returned path is absolute. The INPUT is bound
93
+ * too: the plan file must canonicalize to the context's `planFile`, and a
94
+ * mismatch is refused (exit 1) before any read or write — a foreign plan's
95
+ * content must never land in this plan's SDD dir. Context-less calls
96
+ * keep the legacy unbound behavior (no protection claim).
64
97
  */
65
98
  export declare function taskBrief(planFile: string, taskN: number, outFile?: string, opts?: TaskBriefOptions): string;
66
99
  /**
@@ -69,6 +102,14 @@ export declare function taskBrief(planFile: string, taskN: number, outFile?: str
69
102
  * Both refs are validated with `git rev-parse --verify --quiet` (any ref
70
103
  * the original accepted is accepted here; the SHA-only guard is
71
104
  * `assertBaseSha`).
105
+ *
106
+ * Bound mode (`opts.context`, spec A3): the review range is probed in the
107
+ * context's feature worktree (feature Git cwd) while the package lands in
108
+ * the plan's control artifacts — gated with `checkSddAction` BEFORE any
109
+ * mkdir/write (a refused destination writes nothing). The returned path is
110
+ * absolute. Context-less calls keep the legacy unbound behavior (no
111
+ * protection claim); an explicit `opts.cwd` still overrides the git probe
112
+ * cwd in both modes.
72
113
  */
73
114
  export declare function reviewPackage(base: string, head: string, outFile?: string, opts?: ReviewPackageOptions): string;
74
115
  /**
@@ -130,3 +171,152 @@ export type StickyRulesResult = {
130
171
  * resume — that rule lives in the PM flow, not the session ledger.
131
172
  */
132
173
  export declare function implementerSessionStickyRules(input: StickyRulesInput): StickyRulesResult;
174
+ /**
175
+ * Resolved SDD execution context (spec A3): where control artifacts live,
176
+ * where feature source edits happen, and which branch the feature checkout
177
+ * must be on. All paths normalized absolute (canonicalized on resolve);
178
+ * `planFile` / `sddDir` must resolve within the control harness and match
179
+ * `planId`; the feature branch/worktree must match the verified lease when
180
+ * an active workflow supplies one (standalone non-iteration contexts remain
181
+ * possible under the existing branch policy — no new global lease mandate).
182
+ * A declared control root is authoritative: it is never re-inferred from
183
+ * the feature cwd (mstar-branch-worktree «Harness path SSOT»).
184
+ */
185
+ export type SddExecutionContext = {
186
+ planId: string;
187
+ /** Control harness dir (`<control-worktree>/{HARNESS_DIR}`), absolute. */
188
+ controlHarnessRoot: string;
189
+ /** Feature worktree — the required cwd for product/source edits, absolute. */
190
+ featureCwd: string;
191
+ /** Assignment Working branch checked out at `featureCwd`. */
192
+ workingBranch: string;
193
+ /** Control plan file (`{PLAN_DIR}/<plan-id>.md`), absolute. */
194
+ planFile: string;
195
+ /** Control `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/`, absolute. */
196
+ sddDir: string;
197
+ };
198
+ /** One action seam to gate with `checkSddAction` (spec A3). */
199
+ export type SddAction = {
200
+ /**
201
+ * Observed invocation cwd — the real cwd at the seam, never an Assignment
202
+ * echo. Relative `target` values resolve from this cwd.
203
+ */
204
+ cwd: string;
205
+ /** Path the action would touch; optional for source/launch, required for artifact. */
206
+ target?: string;
207
+ kind: SddActionKind;
208
+ };
209
+ /** Action seam kinds (spec A3): feature source write, control artifact write, child-process launch. */
210
+ export type SddActionKind = "source" | "artifact" | "launch";
211
+ /**
212
+ * Resolve and validate a declared SDD execution context (spec A3) into a
213
+ * canonical context. Read-only — resolves/validates, never writes.
214
+ *
215
+ * Validation (reusing the existing machinery — never duplicated here):
216
+ * - shape: all paths absolute, `planId` a single safe path component
217
+ * (`assertSafePathComponent`);
218
+ * - `controlHarnessRoot` exists (declared root is authoritative — never
219
+ * re-inferred from the feature cwd);
220
+ * - `planFile` identity (basename stem = `planId`) + placement via
221
+ * `assertPlanWritingPath` (inside `{PLAN_DIR}` of the control harness,
222
+ * symlink escape checked against the canonical path);
223
+ * - `sddDir` canonicalizes (nearest existing ancestor on BOTH sides) to the
224
+ * same path as `resolveSddDir(controlHarnessRoot, planId)` — the path
225
+ * SSOT composition, `.mstarc` overrides included; equivalent string forms
226
+ * of one physical destination are valid. Composition equality on the
227
+ * canonical pair subsumes the escape case — a declared sddDir cannot
228
+ * equal the canonical composition and escape at the same time — so
229
+ * divergence is classified at one decision point: divergence because the
230
+ * declared path physically canonicalizes OUTSIDE the control harness
231
+ * (symlinked sdd segment routing out) is environmental →
232
+ * `sdd.context.sdd-dir-escape` gate fail (exit 1); any other divergence
233
+ * (wrong declaration) is usage (exit 2). A context matching a
234
+ * `.mstarc`-declared sdd base is honored wherever the repo's own path
235
+ * SSOT composes it — the engine never second-guesses a composition it
236
+ * would itself produce (`resolveSddDir` is authoritative);
237
+ * - `featureCwd` exists and never nests with the control checkout
238
+ * (`featureCwd` inside the control checkout, or the control harness
239
+ * inside the feature checkout, are both refused — L1 hard rules);
240
+ * - branch/lease: when the control harness's workflow snapshots supply a
241
+ * plan row from a REGISTERED ACTIVE workflow (v2 root `status.json`
242
+ * `workflows[]`; a retained terminal snapshot never satisfies lease
243
+ * enforcement, and a plan claimed by multiple active workflows fails
244
+ * closed), its lease is verified (`verifyPlanExecutionLease`) and the
245
+ * L1 checklist runs (`l1PreDispatchCheck` with the control checkout);
246
+ * the context must then match the verified lease exactly. Without an
247
+ * active lease (no row, or a non-InProgress row without lease), the
248
+ * standalone branch policy applies (`assertBranchAlignment`) — an
249
+ * InProgress row without lease is the orphan refusal.
250
+ *
251
+ * Throws `SddScriptError` — exit 2 when the declared context itself is
252
+ * malformed (non-absolute path, identity/composition mismatch, missing plan
253
+ * file), exit 1 when the environment fails the gate (missing dirs, branch
254
+ * mismatch, lease/orphan refusal, symlink escape). A rejected context never
255
+ * reaches an action check.
256
+ */
257
+ export declare function resolveSddExecutionContext(input: SddExecutionContext): SddExecutionContext;
258
+ /**
259
+ * Gate one action seam against a resolved context (spec A3). Read-only —
260
+ * a refused action performs no write and the check itself never writes.
261
+ *
262
+ * - `kind: "source"` — the observed `cwd` must sit inside the feature
263
+ * worktree (nested directories allowed); a relative `target` resolves from
264
+ * that actual cwd. Targets outside the feature — traversal, absolute
265
+ * elsewhere, wrong-cwd, or symlink escape — are refused before mutation.
266
+ * - `kind: "artifact"` — the `target` must stay inside the plan's control
267
+ * `sddDir` or equal the declared `planFile`; legitimate control artifact
268
+ * edits are allowed while arbitrary control source edits are not. A
269
+ * nonexistent leaf canonicalizes through its nearest existing ancestor
270
+ * (`canonicalizeNearestExisting`); symlink escapes are refused.
271
+ * - `kind: "launch"` — verifies the resolved launch destination
272
+ * `featureCwd` (exists + on `workingBranch` via the reused
273
+ * `assertBranchAlignment`); the parent's own cwd is not gated, because a
274
+ * launch may legitimately run from control/main — its purpose is to bind
275
+ * the child's starting cwd to the feature worktree. An optional `target`
276
+ * is checked as a source target relative to `featureCwd` (where the child
277
+ * will start).
278
+ *
279
+ * Violations minted here carry the `sdd.context.*` prefix; violations from
280
+ * the reused branch/lease helpers keep their own codes (`worktree.*`,
281
+ * `lease.*`, `plan-path.*`) — the rules stay single-sourced. A3 limits
282
+ * apply: this is a snapshot check, not a future-write lock, and offers no
283
+ * protection against a concurrent hostile symlink swap.
284
+ *
285
+ * API contract (task-1 review Minor 2): `context` must be a
286
+ * `resolveSddExecutionContext`-produced (or equivalently already-validated)
287
+ * context. This function gates the action seam only and does NOT
288
+ * re-validate the context declaration — identity, branch, lease and
289
+ * control/feature nesting checks run at resolve time — so a hand-assembled
290
+ * structurally-valid context gets no protection claim from this check
291
+ * alone.
292
+ */
293
+ export declare function checkSddAction(context: SddExecutionContext, action: SddAction): GateResult;
294
+ /**
295
+ * Bound argv launcher (spec A3):
296
+ * resolve + gate the context, then spawn a DIRECT executable argv — never
297
+ * a shell (no command-string interpolation; the argv array reaches the
298
+ * child literally, spaces/`$()`/backticks unchanged) — with
299
+ * cwd = the resolved feature worktree, inherited stdio and the inherited
300
+ * process environment unchanged (no HOME/CODEX_HOME edits, no credential
301
+ * copies). The parent's own cwd is deliberately not gated: a launch may
302
+ * run from control/main, and the `launch` seam validates the resolved
303
+ * launch destination (featureCwd + workingBranch) instead.
304
+ *
305
+ * Exit contract (Task 2 CLI mapping):
306
+ * - empty/invalid argv → `SddScriptError` exit 2 (usage);
307
+ * - context/gate failure → `SddScriptError` exit 1 (usage-class declaration
308
+ * errors from resolution keep their own exit 2);
309
+ * - spawn-not-found (ENOENT) → resolves 127;
310
+ * - numeric child exit → resolved unchanged (exit 7 returns 7);
311
+ * - child killed by signal n → resolves 128+n (SIGTERM → 143, SIGINT → 130).
312
+ *
313
+ * SIGINT/SIGTERM are forwarded to the running child; the listeners are
314
+ * removed once the child settles — the launcher leaves no handlers behind.
315
+ *
316
+ * A3 limits apply unchanged: the launcher binds the child's STARTING cwd;
317
+ * it is not a sandbox — a child can later chdir, pass an overriding cwd
318
+ * flag, write absolute paths elsewhere, or use host edit tooling. Other
319
+ * spawn errors (e.g. EACCES) reject; the CLI maps them to exit 1 with the
320
+ * cause in the message.
321
+ */
322
+ export declare function runInSddContext(context: SddExecutionContext, argv: readonly string[]): Promise<number>;
@@ -6,14 +6,14 @@
6
6
  * Source skills (semantic SSOT — this module implements their deterministic
7
7
  * rules, it never redefines them; roadmap §8.5 C2):
8
8
  * - `mstar-skill-authoring` SKILL.md § Frontmatter Contract — `name` stable
9
- * lowercase-hyphen; `description` is the trigger contract (not a workflow
10
- * summary), third person.
9
+ * lowercase-hyphen; `description` is the trigger contract (not a workflow
10
+ * summary), third person.
11
11
  * - `mstar-skill-authoring` SKILL.md § Body 必须回答的 5 问 + § 默认 Body
12
- * 结构 — a SKILL.md body answers five questions via key sections (Load
13
- * Order, Workflow, Decision Rules, Evidence, References).
12
+ * 结构 — a SKILL.md body answers five questions via key sections (Load
13
+ * Order, Workflow, Decision Rules, Evidence, References).
14
14
  * - `mstar-skill-authoring` SKILL.md § Skill-relative script and asset
15
- * paths ("skill `my-skill` → scripts/do-thing") + `mstar-host` SKILL.md
16
- * § Resolve loaded skill root (per-host resolution).
15
+ * paths ("skill `my-skill` → scripts/do-thing") + `mstar-host` SKILL.md
16
+ * § Resolve loaded skill root (per-host resolution).
17
17
  *
18
18
  * The SkillsBench six principles and trigger-contract reasoning stay prompt.
19
19
  */
@@ -46,10 +46,37 @@ export declare function stripFrontmatter(text: string): string;
46
46
  * or `runtime` (canonical plus the locked `RUNTIME_HEADING_ALIASES` table
47
47
  * — shipped `mstar-*` topic skills). */
48
48
  export type FiveQuestionMode = "authoring" | "runtime";
49
+ /** Skill-lint profile kind (spec A4): `core` = the `mstar-harness-core` hub
50
+ * (five-question exempt), `runtime` = shipped `mstar-*` topic skills, and
51
+ * `authoring` = the standard plus every non-`mstar-*`/unknown input. */
52
+ export type SkillLintKind = "core" | "runtime" | "authoring";
53
+ /** Classification result: the profile kind plus the five-question mode to
54
+ * apply — `null` mode means the five-question check is skipped entirely
55
+ * (core exemption only; every other check stays active). */
56
+ export type SkillLintProfile = {
57
+ kind: SkillLintKind;
58
+ mode: FiveQuestionMode | null;
59
+ };
60
+ /**
61
+ * One lint-classification policy (spec A4
62
+ * the single profile SSOT the CLI, dsh and drift Guard 5 consume.
63
+ * Semantics preserve the CLI `skill lint` selection verbatim:
64
+ * - exact `mstar-harness-core` → `core` / `null` (five-question exempt by
65
+ * design — hub headings; frontmatter + ephemeral checks still run);
66
+ * - exact `mstar-skill-authoring` → `authoring` (the standard's own
67
+ * definition stays strict despite the `mstar-` prefix);
68
+ * - any other `mstar-*` → `runtime` (locked alias table applies);
69
+ * - unknown / non-`mstar-*` / missing identity → `authoring` (strict
70
+ * default — greenfield inputs are never loosened).
71
+ * The `skillId` is the resolved target directory basename at the CLI and
72
+ * dsh configured skill-root boundaries — never an arbitrary YAML `name`
73
+ * alone (a misleading frontmatter name must not select a looser profile).
74
+ * These are lint profiles only: not authorization, not trusted origin.
75
+ */
76
+ export declare function classifySkillLint(skillId: string | undefined): SkillLintProfile;
49
77
  /**
50
- * Locked runtime alias table (plan 20260816-audit-001-five-question-runtime-
51
- * alignment, Step 2): heading synonyms that answer the same question for
52
- * shipped topic skills, verified against the corpus at `81480e7`. Tokens are
78
+ * Locked runtime alias table: heading synonyms that answer the same question
79
+ * for shipped topic skills, verified against the shipped-skill corpus. Tokens are
53
80
  * case-insensitive heading substrings (any heading level); the `decision-rules`
54
81
  * breadth is bounded by the corpus regression test pinning current state.
55
82
  * `load-order` has no aliases — the canonical label covers 15/16 skills and
package/dist/status.d.ts CHANGED
@@ -14,7 +14,7 @@ export type StatusDoc = {
14
14
  [key: string]: unknown;
15
15
  };
16
16
  /**
17
- * v2 root status document (`{HARNESS_DIR}/status.json`, plan Task 3 — hard
17
+ * v2 root status document (`{HARNESS_DIR}/status.json`( — hard
18
18
  * cutover): `version`, `updated_at`, `workflows[]` only. The list holds
19
19
  * ACTIVE (non-terminal) lifecycles; terminal writers unregister AFTER the
20
20
  * snapshot write (removal-at-terminal).
@@ -96,7 +96,7 @@ export declare function validatePlanRow(row: unknown): GateResult;
96
96
  */
97
97
  export declare function validateResidual(entry: unknown): GateResult;
98
98
  /**
99
- * Validate one v2 root `workflows[]` entry (plan Task 3): required `id`,
99
+ * Validate one v2 root `workflows[]` entry (): required `id`,
100
100
  * `type` (plan | iteration), `started_at`, `dir` — harness-relative, never
101
101
  * absolute and never containing `..`. The removal-at-terminal invariant
102
102
  * (snapshot exists and is non-terminal) is checked at document level by
@@ -104,7 +104,7 @@ export declare function validateResidual(entry: unknown): GateResult;
104
104
  */
105
105
  export declare function validateWorkflowEntry(entry: unknown): GateResult;
106
106
  /**
107
- * Validate a v2 status.json document (plan Task 3 — hard cutover). Accepts a
107
+ * Validate a v2 status.json document ( — hard cutover). Accepts a
108
108
  * parsed document or a file path (malformed JSON yields a
109
109
  * `status.invalid-json` violation, never a throw). v1 or unknown-version
110
110
  * inputs — including v1-shaped documents carrying a root `plans[]` — fail
@@ -122,14 +122,14 @@ export declare function validateWorkflowEntry(entry: unknown): GateResult;
122
122
  * root holds active lifecycles only, and terminal writers unregister AFTER
123
123
  * the snapshot write. The snapshot must also PHYSICALLY live under the
124
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
125
+ * outside the harness dir is rejected fail-closed . Doc
126
126
  * input without a harness dir is structure-only.
127
127
  */
128
128
  export declare function validateStatusV2(docOrPath: StatusV2Doc | string, opts?: {
129
129
  harnessDir?: string;
130
130
  }): GateResult;
131
131
  /**
132
- * Relocated v2 root validator (plan Task 3 — hard cutover, no dual path):
132
+ * Relocated v2 root validator ( — hard cutover, no dual path):
133
133
  * the v1 `validateStatus` implementation was deleted in the same task that
134
134
  * introduced the v2 surface; the public export name survives so external
135
135
  * consumers (CLI, host hooks — cut over in P2) keep compiling and now fail
@@ -158,14 +158,14 @@ export declare const validateStatus: typeof validateStatusV2;
158
158
  * Async-only (architect-locked 2026-08-27): the durable write goes through
159
159
  * `getArtifactStore().put({ kind: "status", key: "root", ... })` inside the
160
160
  * caller's lock — the store is the persist backend, never a second lock.
161
- * Fails loud (qc3 F-201) when the active FsStore would resolve its
161
+ * Fails loud when the active FsStore would resolve its
162
162
  * `status.json` to a path other than `statusPath` — callers whose root
163
163
  * differs from the active store's root MUST
164
164
  * `setArtifactStore(createFsStore(root))` first.
165
165
  */
166
166
  export declare function registerWorkflowEntryLocked(statusPath: string, entry: WorkflowEntry): Promise<StatusV2Doc>;
167
167
  /**
168
- * Register one active workflow entry in the v2 root file (plan Task 3).
168
+ * Register one active workflow entry in the v2 root file ().
169
169
  * Idempotent upsert by entry `id` under the root-file `withStatusWriteLock`,
170
170
  * bumping root `updated_at`. A missing/empty root file is initialized from
171
171
  * the v2 template (never a v1 tree); a v1 root is refused with the
@@ -178,7 +178,7 @@ export declare function registerWorkflowEntryLocked(statusPath: string, entry: W
178
178
  */
179
179
  export declare function registerWorkflow(root: string, entry: WorkflowEntry): Promise<StatusV2Doc>;
180
180
  /**
181
- * Remove one workflow entry from the v2 root file (plan Task 3). Idempotent:
181
+ * Remove one workflow entry from the v2 root file (). Idempotent:
182
182
  * removing an absent id is a no-op with no write; a missing/empty root file
183
183
  * is a no-op that never creates the file. Runs under the root-file
184
184
  * `withStatusWriteLock`, bumping root `updated_at` only when an entry was
@@ -186,7 +186,7 @@ export declare function registerWorkflow(root: string, entry: WorkflowEntry): Pr
186
186
  * invariant included) before the write — a v1 root is refused with the
187
187
  * `mstar migrate` hint.
188
188
  *
189
- * Fails loud (qc3 F-201) when the active FsStore would resolve its
189
+ * Fails loud when the active FsStore would resolve its
190
190
  * `status.json` to a path other than the caller's root — the no-op branches
191
191
  * below never mask a store/path mismatch.
192
192
  */
@@ -199,8 +199,7 @@ export declare function unregisterWorkflow(root: string, id: string): Promise<St
199
199
  * frontmatter declares `enforcement: hard` hardens the gate in this repo.
200
200
  * A COMPLETED (or status-less/archived) iteration's compass NEVER hardens:
201
201
  * D2 rollback = unset the flag in the ACTIVE compass, and that must work
202
- * while older completed compasses still declare hard (qc1 F-001 / qc2 F-002).
203
- * A counting compass declaring a non-hard value, or no compass at all,
202
+ * while older completed compasses still declare hard. * A counting compass declaring a non-hard value, or no compass at all,
204
203
  * leaves the flag unset (`source: none`) — hard gates are never the default
205
204
  * and the flag is inert when the engine is absent. Frontmatter is
206
205
  * `---`-fenced; hard declarations in the compass BODY do not count (the
package/dist/store.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- /** JSON coordination-doc kinds the store persists (spec SP2). */
2
- export type ArtifactKind = "status" | "snapshot" | "residuals" | "review" | "json";
1
+ /** JSON coordination-doc kinds the store persists */ export type ArtifactKind = "status" | "snapshot" | "residuals" | "review" | "json";
3
2
  /** Stable key inside the kind. Workflow id, project id, or review id;
4
3
  * `kind: "status"` always uses key `"root"`. */
5
4
  export type ArtifactRef = {
@@ -23,14 +22,13 @@ export interface ArtifactStore {
23
22
  get<T = unknown>(ref: ArtifactRef): Promise<T | undefined>;
24
23
  delete?(ref: ArtifactRef): Promise<void>;
25
24
  /** Enumerate refs of `kind`, sorted by key ascending (spec D4). Uniform
26
- * rule: report what exists — missing backing dir/file → `[]`; every
27
- * listed key round-trips through `get`. `json` is not enumerable. */
25
+ * rule: report what exists — missing backing dir/file → `[]`; every
26
+ * listed key round-trips through `get`. `json` is not enumerable. */
28
27
  list?(kind: ArtifactKind): Promise<ArtifactRef[]>;
29
28
  }
30
- /** Map an artifact ref to its file path under `harnessRoot` (spec SP2
31
- * FsStore path table the single kind→path mapping, shared with SP3;
32
- * SP3 never re-implements or extends it). Exported (qc1 S-002) so SP3
33
- * imports the contract instead of re-deriving it textually. */
29
+ /** Map an artifact ref to its file path under `harnessRoot` (the store contract — the single kind→path
30
+ * mapping). Exported so host adapters import the contract instead of
31
+ * re-deriving it textually. */
34
32
  export declare function resolveArtifactPath(harnessRoot: string, ref: ArtifactRef): string;
35
33
  /** Default local adapter: maps kinds to the existing `.mstar/` paths.
36
34
  * `put` uses the sync `writeJson` (atomic temp+rename unchanged) and
@@ -39,7 +37,7 @@ export declare function resolveArtifactPath(harnessRoot: string, ref: ArtifactRe
39
37
  * malformed JSON → throw with the path in the message. The returned store
40
38
  * also exposes its resolved `root` so the routed writers can fail loud
41
39
  * when a caller's explicit target path diverges from the store-resolved
42
- * path (qc3 F-201); `root` is not part of the `ArtifactStore` contract.
40
+ * path ; `root` is not part of the `ArtifactStore` contract.
43
41
  * `list` enumerates per the D4 table: report what exists — missing
44
42
  * backing dir/file → `[]`, `json` throws, keys sorted ascending. */
45
43
  export declare function createFsStore(harnessRoot: string): ArtifactStore & {
@@ -52,7 +50,7 @@ export declare function setArtifactStore(store: ArtifactStore | undefined): void
52
50
  * `resolveHarnessSubdir` — never a silent cwd fallback. */
53
51
  export declare function getArtifactStore(): ArtifactStore;
54
52
  /**
55
- * Fail-loud path-agreement guard for the routed writers (qc3 F-201): when
53
+ * Fail-loud path-agreement guard for the routed writers : when
56
54
  * `store` is an FsStore, resolve the path the store would compute for
57
55
  * `ref` and require it to equal `expectedPath` (the caller's explicit
58
56
  * target). A divergence means the caller's path lives outside the active
@@ -62,8 +60,7 @@ export declare function getArtifactStore(): ArtifactStore;
62
60
  * Cheap: pure path resolution, no I/O.
63
61
  */
64
62
  export declare function assertFsStorePath(store: ArtifactStore, ref: ArtifactRef, expectedPath: string): void;
65
- /** Load a store module from a filesystem path (spec SP2 § Injection 2–3,
66
- * SP2-AC6 / SP2-AC7). Resolves against cwd; rejects empty values and any
63
+ /** Load a store module from a filesystem path (the store module-loading trust boundary). Resolves against cwd; rejects empty values and any
67
64
  * URI scheme before `import()`; throws when the file is missing. Accepts a
68
65
  * `createArtifactStore` named export, a default-exported factory, or a
69
66
  * default-exported object; the result is structurally verified (`put` +
@@ -1,18 +1,18 @@
1
1
  import type { GateResult } from "./core.js";
2
2
  import { type IntegrationMergeLease } from "./lease.js";
3
3
  import { type PlanRow } from "./status.js";
4
- /** Snapshot file name inside `workflows/<id>/` (plan Task 2 — writer contract). */
4
+ /** Snapshot file name inside `workflows/<id>/` ( — writer contract). */
5
5
  export declare const WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
6
- /** Lifecycle status enum (plan Task 2 — terminal set = completed|failed|stopped). */
6
+ /** Lifecycle status enum ( — terminal set = completed|failed|stopped). */
7
7
  export declare const WORKFLOW_LIFECYCLE_STATUSES: readonly ["running", "paused", "completed", "failed", "stopped"];
8
8
  /** Terminal statuses: snapshot must carry `ended_at` and no dangling leases. */
9
9
  export declare const WORKFLOW_TERMINAL_STATUSES: readonly ["completed", "failed", "stopped"];
10
- /** Lifecycle type enum (plan Task 2 — id reuses the orchestration id). */
10
+ /** Lifecycle type enum ( — id reuses the orchestration id). */
11
11
  export declare const WORKFLOW_LIFECYCLE_TYPES: readonly ["plan", "iteration"];
12
12
  export type WorkflowLifecycleStatus = (typeof WORKFLOW_LIFECYCLE_STATUSES)[number];
13
13
  export type WorkflowLifecycleType = (typeof WORKFLOW_LIFECYCLE_TYPES)[number];
14
14
  /**
15
- * First-class lifecycle execution policy (plan Task 2 — keys copied from root
15
+ * First-class lifecycle execution policy ( — keys copied from root
16
16
  * `metadata` at migrate; values accepted-but-opaque this iteration, no
17
17
  * semantic gate).
18
18
  */
@@ -21,7 +21,7 @@ export type WorkflowExecutionPolicy = {
21
21
  worktree_mode?: unknown;
22
22
  push_policy?: unknown;
23
23
  };
24
- /** Iteration branch anchors (plan Task 2 — from root metadata anchors). */
24
+ /** Iteration branch anchors ( — from root metadata anchors). */
25
25
  export type WorkflowBranchAnchors = {
26
26
  base?: string;
27
27
  integration?: string;
@@ -29,11 +29,11 @@ export type WorkflowBranchAnchors = {
29
29
  };
30
30
  /**
31
31
  * v3 workflow snapshot (`workflows/<id>/snapshot.json`) — final schema
32
- * (plan Task 2). `plans[]` rows are the legacy PlanRow shape verbatim;
32
+ * (). `plans[]` rows are the legacy PlanRow shape verbatim;
33
33
  * per-row `execution_lease` stays on the row, `integration_merge_lease` is
34
34
  * top-level.
35
35
  *
36
- * Notes dual-home SSOT (qc wave-1 S-e): a plan row's `notes` array is the
36
+ * Notes dual-home SSOT: a plan row's `notes` array is the
37
37
  * LEGACY VERBATIM copy preserved at migrate time — the RUNTIME ledger is
38
38
  * `notes.jsonl` in the workflow dir (`migrate.ts` NOTES_LEDGER_FILE). New
39
39
  * notes append to the ledger only; row `notes` is read-only legacy and is
@@ -57,7 +57,7 @@ export type WorkflowSnapshot = {
57
57
  compass_ref?: string;
58
58
  };
59
59
  /**
60
- * Validate a v3 workflow snapshot document (plan Task 2 — final schema):
60
+ * Validate a v3 workflow snapshot document ( — final schema):
61
61
  * enum/type/id checks, `schema_version: 1`, required timestamps, `plans[]`
62
62
  * rows validated by the legacy `validatePlanRow` with row-level
63
63
  * `execution_lease` shape delegated to `validateExecutionLease`,
@@ -70,16 +70,16 @@ export type WorkflowSnapshot = {
70
70
  export declare function validateWorkflowSnapshot(doc: unknown): GateResult;
71
71
  /**
72
72
  * Write a workflow snapshot as a whole-rewrite of `dir/snapshot.json` under
73
- * `withStatusWriteLock(snapshotPath)` (plan Task 2 — the `.status-write.lockdir`
73
+ * `withStatusWriteLock(snapshotPath)` ( — the `.status-write.lockdir`
74
74
  * lands inside `workflows/<id>/`, dirname of the snapshot; no harness-root
75
75
  * pollution). The snapshot is validated first — an invalid snapshot throws
76
76
  * and nothing is written. `dir` is created recursively. The durable write
77
- * routes through the active `ArtifactStore` (spec SP2: snapshot →
77
+ * routes through the active `ArtifactStore` (the store contract: snapshot →
78
78
  * `{ kind: "snapshot", key: <workflow id> }`) inside the existing lock — the
79
79
  * default FsStore resolves `{WORKFLOW_DIR}/<key>/snapshot.json`, identical
80
80
  * to `join(dir, WORKFLOW_SNAPSHOT_FILE)` for canonical callers. The write
81
81
  * fails loud when the active FsStore would resolve a different path than
82
- * the caller's `join(dir, WORKFLOW_SNAPSHOT_FILE)` (qc3 F-201) — callers
82
+ * the caller's `join(dir, WORKFLOW_SNAPSHOT_FILE)` — callers
83
83
  * whose target root differs from the active store's root MUST
84
84
  * `setArtifactStore(createFsStore(root))` first.
85
85
  */
@@ -38,8 +38,7 @@ export type BranchProbeOptions = {
38
38
  /**
39
39
  * Git probe timeout in ms (default 10s; `MSTAR_GIT_PROBE_TIMEOUT_MS` env
40
40
  * overrides; per-call value wins). On timeout the probe fails closed into
41
- * `branch-probe-failed` — never hangs, never guesses a branch (qc3 F-4).
42
- */
41
+ * `branch-probe-failed` — never hangs, never guesses a branch. */
43
42
  timeoutMs?: number;
44
43
  };
45
44
  /**
@@ -56,6 +55,27 @@ export type QcSnapshotAssignment = QcAlignmentAssignment & {
56
55
  /** Precomputed review HEAD (full SHA preferred) for that assignment. */
57
56
  head?: string;
58
57
  };
58
+ /**
59
+ * True when `candidatePath` is a Git checkout DISTINCT from `controlPath` —
60
+ * the canonical per-worktree git dirs differ. A linked worktree from
61
+ * `git worktree add` (nested inside the control checkout or a sibling) has
62
+ * its own git dir and is distinct; the same checkout, a plain subdirectory
63
+ * of it, or a symlink alias of it resolves to the same git dir and is NOT
64
+ * distinct. Everything unprovable — a non-repo path or a probe failure —
65
+ * is NOT distinct: fail closed, never guess an identity.
66
+ */
67
+ export declare function isDistinctCheckout(controlPath: string, candidatePath: string, opts?: BranchProbeOptions): boolean;
68
+ /**
69
+ * Probe the repository top-level (worktree root) of a checkout via
70
+ * `git -C <path> rev-parse --show-toplevel` — bounded exactly like
71
+ * `probeBranch` / `probeCheckout`, fail-closed (null on any failure). The
72
+ * canonical top-level is the checkout root regardless of where inside it
73
+ * the probed path sits — a `.mstarc`-declared nested harness dir like
74
+ * `<control>/state/.mstar` included — so callers never infer the checkout
75
+ * root from `dirname(harness)` (a layout assumption that only holds when
76
+ * the harness sits directly under the checkout).
77
+ */
78
+ export declare function probeCheckoutRoot(path: string, opts?: BranchProbeOptions): string | null;
59
79
  /**
60
80
  * L1 cross-plan pre-dispatch checklist (mstar-branch-worktree L1 table +
61
81
  * Harness path SSOT hard rules): control path recorded, feature worktree
@@ -75,11 +95,15 @@ export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: Bra
75
95
  export declare function l2PreDispatchCheck(input: L2PreDispatchInput, opts?: BranchProbeOptions): GateResult;
76
96
  /**
77
97
  * L1 hard rule (Harness path SSOT): `execution_lease.worktree_path` MUST
78
- * differ from `metadata.control_worktree_path`. String equality on the two
79
- * paths canonical absolute paths are the caller's contract (the lease
80
- * validator already requires `worktree_path` to be absolute).
98
+ * be a Git checkout DISTINCT from `metadata.control_worktree_path` the
99
+ * same checkout, a plain subdirectory, or a symlink alias of the control
100
+ * checkout is refused (checkout identity via the canonical per-worktree
101
+ * git dir; probe failure fails closed). Both-empty stays a match (nothing
102
+ * recorded, per the lease validator contract); one empty has nothing to
103
+ * compare and passes (the lease validator's absolute-path requirement owns
104
+ * empty lease paths).
81
105
  */
82
- export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string): GateResult;
106
+ export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string, opts?: BranchProbeOptions): GateResult;
83
107
  /**
84
108
  * Assert the branch checked out at `worktreePath` matches `expectedBranch`
85
109
  * (the Assignment Working branch). Probe = `git -C <path> branch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "3.6.3",
3
+ "version": "3.7.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": {