@mstar-harness/engine 3.8.3 → 3.9.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,174 @@
1
+ import type { GateResult } from "./core.js";
2
+ import type { SddExecutionContext } from "./sdd.js";
3
+ export type EvidenceEnvironmentKey = "CI" | "NODE_ENV" | "TZ" | "LANG";
4
+ export type EvidenceInputSpec = {
5
+ path: string;
6
+ kind: "file" | "directory";
7
+ purpose: "source" | "test" | "fixture" | "config" | "dependency";
8
+ };
9
+ export type EvidenceCoverage = {
10
+ acIds: string[];
11
+ behavior: string;
12
+ declaration: "reviewed" | "unknown";
13
+ sourceRationale: string;
14
+ dependencyRationale: string;
15
+ runtimeRationale: string;
16
+ environmentRationale: string;
17
+ };
18
+ export type EvidenceLimits = {
19
+ timeoutMs: number;
20
+ maxLogBytesPerStream: number;
21
+ maxInputBytes: number;
22
+ maxInputEntries: number;
23
+ maxInputMs: number;
24
+ maxSnapshotBytes: number;
25
+ };
26
+ export type EvidenceCaptureRequest = {
27
+ context: SddExecutionContext;
28
+ taskId: string;
29
+ coverage: EvidenceCoverage;
30
+ inputs: EvidenceInputSpec[];
31
+ environmentKeys: EvidenceEnvironmentKey[];
32
+ timeoutMs?: number;
33
+ };
34
+ export type EvidenceInputEntry = {
35
+ path: string;
36
+ kind: "file" | "directory" | "symlink" | "missing" | "unknown";
37
+ sha256: string | null;
38
+ bytes: number | null;
39
+ executable: boolean | null;
40
+ linkText: string | null;
41
+ resolvedRelativePath: string | null;
42
+ error: string | null;
43
+ };
44
+ export type EvidenceToolFingerprint = {
45
+ requested: string;
46
+ resolvedPath: string | null;
47
+ sha256: string | null;
48
+ bytes: number | null;
49
+ platform: string;
50
+ arch: string;
51
+ runnerRuntimeVersion: string;
52
+ error: string | null;
53
+ };
54
+ export type EvidenceInputSnapshot = {
55
+ repoCommonDir: string | null;
56
+ head: string | null;
57
+ branch: string | null;
58
+ dirty: boolean | null;
59
+ dirtyStatusSha256: string | null;
60
+ entries: EvidenceInputEntry[];
61
+ tool: EvidenceToolFingerprint;
62
+ environment: Partial<Record<EvidenceEnvironmentKey, string | null>>;
63
+ unknowns: string[];
64
+ stable: boolean;
65
+ digest: string;
66
+ };
67
+ export type EvidenceOutcome = {
68
+ kind: "running";
69
+ } | {
70
+ kind: "exit";
71
+ code: number;
72
+ } | {
73
+ kind: "signal";
74
+ signal: string;
75
+ } | {
76
+ kind: "timeout";
77
+ } | {
78
+ kind: "interrupted";
79
+ signal: "SIGINT" | "SIGTERM";
80
+ } | {
81
+ kind: "spawn-error";
82
+ code: string;
83
+ };
84
+ export type EvidenceLog = {
85
+ path: "stdout.log" | "stderr.log";
86
+ bytes: number;
87
+ sha256: string | null;
88
+ truncated: boolean;
89
+ };
90
+ export type SddEvidenceRecord = {
91
+ schema: "mstar.sdd-evidence/v1";
92
+ producer: {
93
+ name: "mstar-harness";
94
+ version: string;
95
+ };
96
+ runId: string;
97
+ request: EvidenceCaptureRequest;
98
+ command: {
99
+ argv: string[];
100
+ cwd: string;
101
+ };
102
+ startedAt: string;
103
+ endedAt: string | null;
104
+ state: "running" | "finished";
105
+ outcome: EvidenceOutcome;
106
+ before: EvidenceInputSnapshot;
107
+ after: EvidenceInputSnapshot | null;
108
+ logs: {
109
+ stdout: EvidenceLog;
110
+ stderr: EvidenceLog;
111
+ };
112
+ limits: EvidenceLimits;
113
+ captureErrors: string[];
114
+ counts: null;
115
+ };
116
+ export type EvidenceArtifactFact = {
117
+ path: "stdout.log" | "stderr.log";
118
+ state: "regular" | "missing" | "symlink" | "other" | "unreadable";
119
+ bytes: number | null;
120
+ sha256: string | null;
121
+ };
122
+ export type EvidenceExpectation = {
123
+ planId: string;
124
+ taskId: string;
125
+ runId: string;
126
+ };
127
+ export type EvidenceAssessment = {
128
+ integrity: GateResult;
129
+ outcome: "passed" | "failed" | "incomplete" | "unknown";
130
+ applicability: "not-assessed" | "candidate" | "changed" | "uncertain";
131
+ coverage: "review-required";
132
+ changedInputs: string[];
133
+ reasons: string[];
134
+ };
135
+ /**
136
+ * SHA-256 over the canonical serialization of the snapshot projection.
137
+ * Expects the typed, shape-bounded snapshot; out-of-contract direct input
138
+ * (missing fields, cycles, non-JSON values) throws `TypeError` rather than
139
+ * fabricating a digest. Public unknown-record validators catch that as an
140
+ * `evidence.schema` violation. Never call full record validation from here.
141
+ */
142
+ export declare function evidenceInputDigest(snapshot: EvidenceInputSnapshot): string;
143
+ /**
144
+ * Validate one retained evidence record. Malformed unknown input yields
145
+ * `GateResult` violations (all severity high) and never throws. Checks the
146
+ * exact property sets at every object level, the fixed v1 collection
147
+ * limits, state/outcome/endedAt consistency, log slot literals and caps,
148
+ * entry/tool/environment fact rules, command cwd agreement with the
149
+ * recorded context, and recomputation of every snapshot digest.
150
+ */
151
+ export declare function validateSddEvidenceRecord(record: unknown): GateResult;
152
+ /**
153
+ * Verify that the retained artifacts match the recorded facts and that the
154
+ * record is complete: exactly one regular-file fact per fixed log slot,
155
+ * exact bytes and hash, finished state, non-truncated logs and no capture
156
+ * errors. A completed nonzero exit with complete artifacts verifies as
157
+ * valid failure evidence — integrity is independent of the recorded
158
+ * outcome. Gate codes are fixed; no hard-blocked override exists.
159
+ */
160
+ export declare function verifySddEvidence(record: unknown, artifacts: readonly EvidenceArtifactFact[], expected: EvidenceExpectation): GateResult;
161
+ /**
162
+ * Assess whether one retained evidence bundle still applies to the current
163
+ * declared inputs. Read-only: no child execution, discovery, version probe
164
+ * or write. The four outputs stay separate — a damaged log or an identity
165
+ * mismatch never rewrites the recorded outcome, and a failed or incomplete
166
+ * run can never become a reuse candidate. `changedInputs` discloses sorted
167
+ * known differing paths (or `$tool`/`$environment`/`$repository` labels)
168
+ * even when an earlier rule already forces uncertainty; no-target mode
169
+ * returns an empty list. Digest equality is consulted only after the
170
+ * preceding first-match checks, so an unknown marker never becomes a false
171
+ * changed/candidate result. A malformed target snapshot is downgraded to
172
+ * the unknown lane instead of throwing.
173
+ */
174
+ export declare function assessSddEvidenceReuse(record: unknown, artifacts: readonly EvidenceArtifactFact[], expected: EvidenceExpectation, target?: EvidenceInputSnapshot): EvidenceAssessment;
package/dist/index.d.ts CHANGED
@@ -24,7 +24,12 @@
24
24
  * gate core (target classification + content/edit validation + reason
25
25
  * formatting, shared by the omp and ZCode host gates), and `skill-authoring`
26
26
  * lints frontmatter +
27
- * 5-question bodies and resolves skill-relative asset paths.
27
+ * 5-question bodies and resolves skill-relative asset paths, and
28
+ * `cleanup` is the pure worktree/branch cleanup planner (immutable facts
29
+ * in, stable `cleanup.*` remove/keep/refuse decisions out), and `evidence`
30
+ * is the pure SDD test-evidence contract (record schema validation,
31
+ * artifact verification, input fingerprinting and reuse assessment —
32
+ * values in, decisions out).
28
33
  */
29
34
  export type { GateResult, Severity, ValidationResult } from "./core.js";
30
35
  export { DSH_LLM_FALLBACKS_VERSION, SEVERITY_ORDER, applyEnforcement, readHarnessVersion, readJson, resolveProjectRoot, writeJson } from "./core.js";
@@ -36,16 +41,20 @@ export type { PlanRow, ResidualEntry, StatusDoc, StatusV2Doc, WorkflowEntry, } f
36
41
  export { normalizeSeverity, registerWorkflow, resolveCompassEnforcement, resolveMstarcEnforcement, resolveRepoEnforcement, unregisterWorkflow, validatePlanRow, validateResidual, validateStatus, validateStatusV2, validateWorkflowEntry, } from "./status.js";
37
42
  export type { ClaimLeaseFields, ExecutionLease, ExecutionLeaseLocations, IntegrationMergeLease, LeaseTransition, LeaseVerifyResult, } from "./lease.js";
38
43
  export { canSteal, claimLease, planExecutionLeaseLocations, releaseLease, sameHolderResume, validateExecutionLease, validateIntegrationMergeLease, verifyPlanExecutionLease, withStatusWriteLock, } from "./lease.js";
39
- export type { WorkflowBranchAnchors, WorkflowExecutionPolicy, WorkflowLifecycleStatus, WorkflowLifecycleType, WorkflowSnapshot, } from "./workflow.js";
40
- export { WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_LIFECYCLE_TYPES, WORKFLOW_SNAPSHOT_FILE, WORKFLOW_TERMINAL_STATUSES, validateWorkflowSnapshot, writeWorkflowSnapshot, } from "./workflow.js";
44
+ export type { CloseWorkflowOptions, WorkflowBranchAnchors, WorkflowExecutionPolicy, WorkflowLifecycleStatus, WorkflowLifecycleType, WorkflowSnapshot, WorkflowSnapshotRead, } from "./workflow.js";
45
+ export { closeWorkflow, isTerminalSnapshot, LEGACY_WORKTREE_PATH_CODE, WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_LIFECYCLE_TYPES, WORKFLOW_SNAPSHOT_FILE, WORKFLOW_TERMINAL_STATUSES, readWorkflowSnapshot, validateWorkflowSnapshot, writeWorkflowSnapshot, } from "./workflow.js";
46
+ export type { CleanupDecision, CleanupFacts, CleanupTarget, CleanupTargetKind, } from "./cleanup.js";
47
+ export { planWorktreeCleanup } from "./cleanup.js";
48
+ export type { EvidenceArtifactFact, EvidenceAssessment, EvidenceCaptureRequest, EvidenceCoverage, EvidenceEnvironmentKey, EvidenceExpectation, EvidenceInputEntry, EvidenceInputSnapshot, EvidenceInputSpec, EvidenceLimits, EvidenceLog, EvidenceOutcome, EvidenceToolFingerprint, SddEvidenceRecord, } from "./evidence.js";
49
+ export { assessSddEvidenceReuse, evidenceInputDigest, validateSddEvidenceRecord, verifySddEvidence, } from "./evidence.js";
41
50
  export type { AssignmentBranchForms, AssignmentFields, ComposeDispatchGateOptions, ComposeDispatchGateResult, DefaultBranchOptions, EnforcementFlag, EnforcementSource, ExecutionModeToNOptions, ExecutionModeToNResult, ValidateAssignmentFieldsOptions, } from "./dispatch.js";
42
51
  export { antiRecursionPrecheck, assertDefaultBranchProtected, assertTriIdentity, assignmentHeaderRegion, composeDispatchGate, executionModeToN, isReadOnlyAssignmentRole, parseAssignmentBranchForms, parseAssignmentFields, parseBranchPolicyDirectOnBranch, parseEnforcementFlag, validateAssignmentFields, } from "./dispatch.js";
43
- export type { BranchProbeOptions, L1PreDispatchInput, L2PreDispatchInput, QcAlignmentAssignment, QcSnapshotAssignment, WorktreeTrack, } from "./worktree.js";
44
- export { assertBranchAlignment, assertControlVsFeaturePath, assertQcAlignment, isDistinctCheckout, l1PreDispatchCheck, l2PreDispatchCheck, probeCheckoutRoot, singleReviewSnapshot, } from "./worktree.js";
52
+ export type { BranchProbeOptions, L1PreDispatchInput, L2PreDispatchInput, MainWorktreeInfo, QcAlignmentAssignment, QcSnapshotAssignment, WorktreeTrack, } from "./worktree.js";
53
+ export { assertBranchAlignment, assertControlVsFeaturePath, assertMainWorktreeResidency, assertQcAlignment, isDistinctCheckout, l1PreDispatchCheck, l2PreDispatchCheck, probeCheckoutRoot, readMainWorktree, singleReviewSnapshot, } from "./worktree.js";
45
54
  export type { ImplementerSessionLedger, ReviewPackageOptions, SddAction, SddActionKind, SddExecutionContext, SddWorkspaceOptions, StickyRulesInput, StickyRulesResult, TaskBriefOptions, } from "./sdd.js";
46
55
  export { GIT_CAPTURE_MAX_BYTES, SddScriptError, assertBaseSha, checkSddAction, implementerSessionStickyRules, readProgressLedger, resolveSddExecutionContext, reviewPackage, runInSddContext, sddWorkspace, taskBrief, taskReportExists, } from "./sdd.js";
47
- export type { CompassDoc, PhaseGateOptions, PhaseGateResult, PhaseTransition, } from "./iteration.js";
48
- export { assertIndexRowObligations, evaluatePhaseGate, parseCompassFrontmatter, parseCompassFrontmatterText, pushCadenceProbe, validateCompassFrontmatter, } from "./iteration.js";
56
+ export type { CompassDoc, PhaseGateOptions, PhaseGateResult, PhaseTransition, SnapshotDoc, } from "./iteration.js";
57
+ export { assertIndexRowObligations, evaluatePhaseGate, evaluatePostMergeClose, parseCompassFrontmatter, parseCompassFrontmatterText, pushCadenceProbe, validateCompassFrontmatter, } from "./iteration.js";
49
58
  export type { AppendProjectRegisterEntriesOpts, CloseProjectRegisterEntryOpts, FindingsCleanupMode, ProjectRegisterDoc, ProjectRegisterEntry, RoadmapFrontmatter, RoadmapStatus, RoadmapValidation, TechDebtCheck, TechDebtRollup, TechDebtSummary, } from "./project.js";
50
59
  export { PROJECT_REFERENCES_DIR, PROJECT_REGISTER_FILE, PROJECT_ROADMAP_FILE, ROADMAP_STATUSES, _DEFAULT_PROJECT, appendProjectRegisterEntries, closeProjectRegisterEntry, findingsCleanupGate, listProjectReferenceFiles, techDebtRollup, validateProjectRegister, validateRoadmap, } from "./project.js";
51
60
  export type { HarnessDocKind, ValidateStatusWriteDocOptions } from "./gates.js";
@@ -70,3 +79,5 @@ export type { MergeClass, MstarReviewFinding, MstarReviewV1, PrReportTarget, PrR
70
79
  export { MERGE_CLASSES, PR_REVIEW_TIER_BUDGETS, PR_VERDICTS, REVIEW_EMOJI, computePrTally, pickReviewBranchName, planReviewPost, preflightChangeset, prReviewReportPath, prReviewSeatPrompt, prReviewSizing, resolvePrReviewTier, synthesizeReview, validateFindingDoc, validateMstarReviewV1, validatePrReviewReport, } from "./prreview.js";
71
80
  export type { ArtifactDoc, ArtifactKind, ArtifactRef, ArtifactStore } from "./store.js";
72
81
  export { assertFsStorePath, createFsStore, getArtifactStore, loadStoreModule, resolveArtifactPath, setArtifactStore } from "./store.js";
82
+ export { collectActiveLifecycleBranches, scanActiveLifecycleBranches, type ActiveLifecycleScan } from "./lifecycle-branches.js";
83
+ export { WorkflowSnapshotValidationError } from "./workflow.js";
@@ -6,11 +6,13 @@ import type { GateResult, ValidationResult } from "./core.js";
6
6
  * verbatim () — `findPlanRow` accepts `id` or `plan_id`.
7
7
  *
8
8
  * Deliberate decoupling: this is a loose LOCAL re-declaration,
9
- * NOT an import of `WorkflowSnapshot` from workflow.ts. This module only
10
- * reads `plans[].status`; importing the full schema would add a module edge
11
- * to workflow.ts (which imports status.ts, which workflow.ts cycles back
12
- * through the call-time-safe loop family stays static-edge-free this
13
- * way). Keep this shape in sync manually when the snapshot schema changes.
9
+ * NOT an import of `WorkflowSnapshot` from workflow.ts. The loose phase-gate
10
+ * reads (`evaluatePhaseGate`) only touch `plans[].status`, so the full
11
+ * schema stays out of those call paths. The Phase-6 gate
12
+ * (`evaluatePostMergeClose`) DOES consume the strict T1 validator directly
13
+ * (static edge iteration workflow; workflow's closure never imports this
14
+ * module, so no cycle is created). Keep this shape in sync manually when
15
+ * the snapshot schema changes.
14
16
  */
15
17
  export type SnapshotDoc = {
16
18
  plans?: unknown;
@@ -93,6 +95,34 @@ export declare function validateCompassFrontmatter(doc: unknown): GateResult;
93
95
  * `prBaseBranch`) come from the caller via `opts`.
94
96
  */
95
97
  export declare function evaluatePhaseGate(snapshotDoc: SnapshotDoc, compassDoc: CompassDoc, opts?: PhaseGateOptions): PhaseGateResult;
98
+ /**
99
+ * Phase 6 post-merge close local-state gate (phase-6-post-merge-close.md
100
+ * §6.4 + Evidence): verifies the checkable post-close state — the workflow
101
+ * snapshot is a valid v3 document (T1 `validateWorkflowSnapshot`, with the
102
+ * single legacy `control_worktree_path` alias non-blocking exactly as the
103
+ * canonical reader accepts and migrates it) in a
104
+ * terminal status (completed | failed | stopped, T1 `isTerminalSnapshot`),
105
+ * no `plans[].execution_lease` or top-level `integration_merge_lease`
106
+ * survived the close, and the root `status.json` validates as a v2 registry
107
+ * (`validateStatusV2`, structure-only) and no longer registers the
108
+ * workflow (removal-at-terminal).
109
+ *
110
+ * Deliberately does NOT verify remote merge evidence or physical cleanup —
111
+ * the gate only reads local state. An invalid/unreadable ROOT — any
112
+ * `validateStatusV2` failure (non-v2 version, missing `updated_at`,
113
+ * malformed `workflows[]` or entries) — is a
114
+ * violation (`PHASE6_INVALID_ROOT`): it is not proof of the entry's
115
+ * absence. The lease probe runs on terminal documents only — mid-flight
116
+ * leases on a running lifecycle are legitimate and the actionable code is
117
+ * `PHASE6_NOT_TERMINAL`.
118
+ *
119
+ * Pure and additive: consumes T1's validators unchanged and does not touch
120
+ * `evaluatePhaseGate` / `PhaseGateResult` (Phase 2–5 exit codes stay
121
+ * intact). Stable machine codes: `PHASE6_NOT_TERMINAL`,
122
+ * `PHASE6_ROOT_ENTRY_PRESENT`, `PHASE6_DANGLING_LEASE`,
123
+ * `PHASE6_INVALID_SNAPSHOT`, `PHASE6_INVALID_ROOT`.
124
+ */
125
+ export declare function evaluatePostMergeClose(snapshotDoc: SnapshotDoc, rootDoc: unknown): GateResult;
96
126
  /**
97
127
  * §5.1a push-cadence probe (HARD): never push the PR head while required CI
98
128
  * is still queued/in_progress or an AI/bot review wave is running. Pure
@@ -0,0 +1,17 @@
1
+ import { type ValidationResult } from "./core.js";
2
+ /** Collect ownership, including retained L2 tracks; base/target are not ownership. */
3
+ export declare function collectActiveLifecycleBranches(snapshots: readonly Record<string, unknown>[]): string[];
4
+ export type ActiveLifecycleScan = {
5
+ kind: "ok";
6
+ branches: string[];
7
+ notes: ValidationResult[];
8
+ } | {
9
+ kind: "refusal";
10
+ code: string;
11
+ detail: string;
12
+ };
13
+ /** Read all other registered active snapshots through the canonical reader.
14
+ * Missing register means no siblings; malformed register or unreadable sibling
15
+ * refuses. Governing snapshot is supplied by the caller, never read twice.
16
+ */
17
+ export declare function scanActiveLifecycleBranches(harnessDir: string, governingWorkflowId: string | null): ActiveLifecycleScan;
package/dist/sdd.d.ts CHANGED
@@ -12,7 +12,7 @@ export declare class SddScriptError extends Error {
12
12
  * usage plus the harness-root override (plan finding 2026-08-08).
13
13
  */
14
14
  export type SddWorkspaceOptions = {
15
- /** Control worktree repo root — CLI 2nd arg / `MSTAR_CONTROL_ROOT`. */
15
+ /** Main worktree repo root — CLI 2nd arg / `MSTAR_CONTROL_ROOT`. */
16
16
  controlRoot?: string;
17
17
  /** Explicit harness root — `MSTAR_HARNESS_DIR` / `--harness-dir`. */
18
18
  harnessDir?: string;
@@ -63,19 +63,24 @@ export declare const GIT_CAPTURE_MAX_BYTES: number;
63
63
  * Resolve and ensure `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/` (prints
64
64
  * the absolute path). Resolution order:
65
65
  *
66
- * 1. fail-closed FIRST: a linked worktree without a control root never
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);
69
- * 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
70
- * plan finding 2026-08-08: covers repos the status.json probe misses;
71
- * resolved relative to the established root;
72
- * 3. `.mstarc` `[config] harness_dir` at `root` (repo-declared root;
73
- * resolved against the config file's directory);
74
- * 4. `status.json` probe at root (`.mstar` → `.agents`);
75
- * 5. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
76
- *
77
- * `controlRoot` (CLI 2nd arg / `MSTAR_CONTROL_ROOT`) pins `root` to the
78
- * control worktree instead of the cwd's git top-level.
66
+ * 1. Git-derived MAIN discovery FIRST (fail-closed): the process-SSOT
67
+ * control root is the MAIN worktree (`readMainWorktree` the first
68
+ * `git worktree list --porcelain -z` record). From a linked checkout the
69
+ * first record still reaches main; a failed/unavailable probe (null)
70
+ * refuses BEFORE any harness resolution or mkdir nothing is written.
71
+ * 2. An explicit `controlRoot` (CLI 2nd arg / `MSTAR_CONTROL_ROOT`) must BE
72
+ * the main worktree when Git is available an integration/foreign
73
+ * linked checkout is refused, never silently redirected; explicit
74
+ * non-Git standalone roots are preserved;
75
+ * 3. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
76
+ * — resolved relative to the established main root;
77
+ * 4. `.mstarc` `[config] harness_dir` at the main root (repo-declared root;
78
+ * resolved against the config file's directory);
79
+ * 5. `status.json` probe at the main root (`.mstar` → `.agents`);
80
+ * 6. fallback: existing `.mstar`/`.agents` dir under the main root, else
81
+ * `.mstar`;
82
+ * 7. the resolved harness dir must not redirect the process SSOT into a
83
+ * linked/foreign Git checkout — refused before any mkdir/write.
79
84
  */
80
85
  export declare function sddWorkspace(planId: string, opts?: SddWorkspaceOptions): string;
81
86
  /**
@@ -242,7 +247,9 @@ export type SddActionKind = "source" | "artifact" | "launch";
242
247
  * `workflows[]`; a retained terminal snapshot never satisfies lease
243
248
  * enforcement, and a plan claimed by multiple active workflows fails
244
249
  * closed), its lease is verified (`verifyPlanExecutionLease`) and the
245
- * L1 checklist runs (`l1PreDispatchCheck` with the control checkout);
250
+ * L1 checklist runs (`l1PreDispatchCheck` with the Git-derived MAIN
251
+ * worktree, the governing snapshot's integration topology, the recorded
252
+ * residency expectation and the active lifecycle-branch ownership set);
246
253
  * the context must then match the verified lease exactly. Without an
247
254
  * active lease (no row, or a non-InProgress row without lease), the
248
255
  * standalone branch policy applies (`assertBranchAlignment`) — an
package/dist/status.d.ts CHANGED
@@ -58,6 +58,7 @@ export type PlanRow = {
58
58
  title?: unknown;
59
59
  file?: unknown;
60
60
  status?: unknown;
61
+ /** Opaque plan metadata; metadata.track_branches retains active L2 Assignment working branches. */
61
62
  metadata?: unknown;
62
63
  execution_lease?: unknown;
63
64
  [key: string]: unknown;
@@ -1,4 +1,4 @@
1
- import type { GateResult } from "./core.js";
1
+ import type { GateResult, ValidationResult } from "./core.js";
2
2
  import { type IntegrationMergeLease } from "./lease.js";
3
3
  import { type PlanRow } from "./status.js";
4
4
  /** Snapshot file name inside `workflows/<id>/` ( — writer contract). */
@@ -33,6 +33,14 @@ export type WorkflowBranchAnchors = {
33
33
  * per-row `execution_lease` stays on the row, `integration_merge_lease` is
34
34
  * top-level.
35
35
  *
36
+ * Integration worktree path: the canonical member is
37
+ * `integration_worktree_path` — the dedicated integration checkout, on
38
+ * `branch.integration`, distinct from the main worktree. The v1
39
+ * `control_worktree_path` key has NO canonical member: legacy-only
40
+ * documents stay readable through `readWorkflowSnapshot` (in-memory
41
+ * normalization + medium migration diagnostic); writers emit only the
42
+ * canonical shape — no dual writer.
43
+ *
36
44
  * Notes dual-home SSOT: a plan row's `notes` array is the
37
45
  * LEGACY VERBATIM copy preserved at migrate time — the RUNTIME ledger is
38
46
  * `notes.jsonl` in the workflow dir (`migrate.ts` NOTES_LEDGER_FILE). New
@@ -52,7 +60,7 @@ export type WorkflowSnapshot = {
52
60
  execution_policy?: WorkflowExecutionPolicy;
53
61
  integration_merge_lease?: IntegrationMergeLease;
54
62
  branch?: WorkflowBranchAnchors;
55
- control_worktree_path?: string;
63
+ integration_worktree_path?: string;
56
64
  legacy_metadata?: Record<string, unknown>;
57
65
  compass_ref?: string;
58
66
  };
@@ -63,11 +71,48 @@ export type WorkflowSnapshot = {
63
71
  * `execution_lease` shape delegated to `validateExecutionLease`,
64
72
  * `integration_merge_lease` shape delegated to
65
73
  * `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).
74
+ * opaque. Integration worktree path: the canonical member is
75
+ * `integration_worktree_path`; the v1 `control_worktree_path` key is a
76
+ * read-only alias whose presence keeps this STRICT validation a failing
77
+ * gate carrying the medium `workflow.snapshot.legacy-control-worktree-path`
78
+ * migration diagnostic (read acceptance is not write permission — the
79
+ * canonical reader is the only consumer that normalizes it, in memory);
80
+ * both keys present is `workflow.snapshot.conflicting-worktree-paths`
81
+ * (high), even when the values are equal. Terminal invariant: `status` ∈
82
+ * completed|failed|stopped ⇒ `ended_at` present AND no row carries
83
+ * `execution_lease` AND no `integration_merge_lease` (no dangling leases).
69
84
  */
70
85
  export declare function validateWorkflowSnapshot(doc: unknown): GateResult;
86
+ /** Machine code of the v1 read-alias migration diagnostic (`readWorkflowSnapshot`). */
87
+ export declare const LEGACY_WORKTREE_PATH_CODE = "workflow.snapshot.legacy-control-worktree-path";
88
+ /**
89
+ * Result of the canonical snapshot read: the validated snapshot plus the
90
+ * non-blocking diagnostics collected while reading (currently only the
91
+ * `workflow.snapshot.legacy-control-worktree-path` migration diagnostic for
92
+ * v1-shaped documents). A read with diagnostics is NOT write permission —
93
+ * writers keep strict validation and emit only the canonical shape.
94
+ */
95
+ export type WorkflowSnapshotRead = {
96
+ snapshot: WorkflowSnapshot;
97
+ diagnostics: ValidationResult[];
98
+ };
99
+ /**
100
+ * Canonical snapshot reader (`{WORKFLOW_DIR}/<id>/snapshot.json`): reads
101
+ * `dir/snapshot.json`, validates the raw document, normalizes the single
102
+ * permitted legacy alias (`control_worktree_path` →
103
+ * `integration_worktree_path`) IN MEMORY and returns its medium migration
104
+ * diagnostic separately. Any other validation violation refuses the read
105
+ * (throw) — read acceptance extends only to the migration diagnostic, so
106
+ * this never weakens the strict writer gate. Performs no writes: the
107
+ * source file's bytes are never touched; legacy snapshots migrate on their
108
+ * next authorized read-modify-write through the canonical writer. Missing
109
+ * files, malformed JSON, and non-object documents throw.
110
+ */
111
+ export declare class WorkflowSnapshotValidationError extends Error {
112
+ readonly violations: ValidationResult[];
113
+ constructor(message: string, violations: ValidationResult[]);
114
+ }
115
+ export declare function readWorkflowSnapshot(dir: string): WorkflowSnapshotRead;
71
116
  /**
72
117
  * Write a workflow snapshot as a whole-rewrite of `dir/snapshot.json` under
73
118
  * `withStatusWriteLock(snapshotPath)` ( — the `.status-write.lockdir`
@@ -84,3 +129,13 @@ export declare function validateWorkflowSnapshot(doc: unknown): GateResult;
84
129
  * `setArtifactStore(createFsStore(root))` first.
85
130
  */
86
131
  export declare function writeWorkflowSnapshot(snapshot: WorkflowSnapshot, dir: string): Promise<void>;
132
+ /** Terminal enum predicate only; callers validate document shape separately. */
133
+ export declare function isTerminalSnapshot(doc: WorkflowSnapshot): boolean;
134
+ export type CloseWorkflowOptions = {
135
+ endedAt: string;
136
+ };
137
+ /**
138
+ * Complete the latest snapshot under its write lock. Never releases leases.
139
+ * A valid terminal snapshot is returned unchanged, including failed/stopped.
140
+ */
141
+ export declare function closeWorkflow(workflowId: string, dir: string, opts: CloseWorkflowOptions): Promise<WorkflowSnapshot>;
@@ -1,4 +1,6 @@
1
1
  import type { GateResult } from "./core.js";
2
+ import type { WorkflowLifecycleType } from "./workflow.js";
3
+ export declare function gitProbeTimeoutMs(): number;
2
4
  /** One L2 parallel implement track (mstar-branch-worktree L2 table). */
3
5
  export type WorktreeTrack = {
4
6
  /** Absolute worktree checkout path for the track. */
@@ -6,13 +8,57 @@ export type WorktreeTrack = {
6
8
  /** PM-approved Working branch checked out in that worktree. */
7
9
  workingBranch: string;
8
10
  };
11
+ /** The Git-derived main worktree of the repository containing a cwd. */
12
+ export type MainWorktreeInfo = {
13
+ /** Absolute (realpath'd) main-worktree checkout root. */
14
+ root: string;
15
+ /** Branch checked out at the main worktree (`""` when detached). */
16
+ branch: string;
17
+ };
18
+ /**
19
+ * Discover the main worktree of the repository containing `cwd` (default
20
+ * `process.cwd()`): the FIRST record of `git worktree list --porcelain -z`,
21
+ * never the first attached branch or a name-matched path. Bounded by the
22
+ * shared probe timeout; unavailable Git, a hung git, a bare repository,
23
+ * malformed output, or an inaccessible root yield `null` — callers fail
24
+ * closed (`worktree.main.unresolved`), never fall through.
25
+ */
26
+ export declare function readMainWorktree(cwd?: string): MainWorktreeInfo | null;
9
27
  /**
10
- * L1 pre-dispatch checklist input mirrors the status.json L1 fields
11
- * (`metadata.control_worktree_path` + `plans[].execution_lease`).
28
+ * Primary-residency equality primitive: the main worktree must be on the
29
+ * branch RECORDED at lifecycle start (`expectedBranch` callers transport
30
+ * the recorded value or the explicit `branch.base` fallback, never the
31
+ * branch observed at check time as its own expected value). Detached main
32
+ * (`branch: ""`) and any mismatch are `worktree.main.residency-switched`
33
+ * (high). Never switch main to satisfy this check.
34
+ */
35
+ export declare function assertMainWorktreeResidency(main: MainWorktreeInfo, expectedBranch: string): GateResult;
36
+ /**
37
+ * L1 pre-dispatch checklist input — the three-domain topology: the
38
+ * Git-derived main worktree (process-SSOT holder), the governing snapshot's
39
+ * dedicated integration checkout, and the plan's feature worktree
40
+ * (`plans[].execution_lease`). Callers carry the actual snapshot type and
41
+ * resolve the recorded residency expectation + active lifecycle branches
42
+ * from the governing snapshots.
12
43
  */
13
44
  export type L1PreDispatchInput = {
14
- /** `metadata.control_worktree_path`harness coordination SSOT checkout. */
15
- controlWorktreePath: string;
45
+ /** Governing snapshot lifecycle type `plan` standalone or `iteration`. */
46
+ workflowType: WorkflowLifecycleType;
47
+ /**
48
+ * `integration_worktree_path` — the dedicated integration checkout, on
49
+ * `branch.integration`. A standalone plan without integration passes `""`
50
+ * (both fields empty); if either field is supplied, both and the full
51
+ * checks are required.
52
+ */
53
+ integrationWorktreePath: string;
54
+ /** `branch.integration` — the branch that must be checked out at the integration worktree (`""` for a standalone plan without integration). */
55
+ integrationBranch: string;
56
+ /** Git-derived main worktree (`readMainWorktree`); `null` = unresolved — a failure, never a skipped row. */
57
+ mainWorktree: MainWorktreeInfo | null;
58
+ /** Recorded main-worktree branch (plan header) or the explicit `branch.base` fallback — never the branch observed at check time. */
59
+ expectedMainBranch: string;
60
+ /** Branches owned by ANY active lifecycle (integration/plan/track) — main must not sit on any of them, even when the recorded expectation matches. */
61
+ lifecycleBranches: readonly string[];
16
62
  /** `execution_lease.worktree_path` — the plan's feature worktree. */
17
63
  leaseWorktreePath: string;
18
64
  /** `execution_lease.working_branch` — the plan's Working branch. */
@@ -78,9 +124,13 @@ export declare function isDistinctCheckout(controlPath: string, candidatePath: s
78
124
  export declare function probeCheckoutRoot(path: string, opts?: BranchProbeOptions): string | null;
79
125
  /**
80
126
  * L1 cross-plan pre-dispatch checklist (mstar-branch-worktree L1 table +
81
- * Harness path SSOT hard rules): control path recorded, feature worktree
82
- * exists, lease worktree control path, and the branch checked out at the
83
- * feature worktree matches `execution_lease.working_branch`.
127
+ * iteration spec worktree-write-model § "Locked interfaces (P1 engine)"):
128
+ * main residency against the recorded expectation + non-ownership of any
129
+ * active lifecycle branch, integration presence/alignment (iterations
130
+ * require both integration fields; a standalone plan without integration
131
+ * checks main vs feature only), pairwise main/integration/feature
132
+ * Git-checkout identity, and the existing feature/lease checks. Null main
133
+ * discovery is a failure (`worktree.main.unresolved`), never a skipped row.
84
134
  */
85
135
  export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: BranchProbeOptions): GateResult;
86
136
  /**
@@ -94,14 +144,14 @@ export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: Bra
94
144
  */
95
145
  export declare function l2PreDispatchCheck(input: L2PreDispatchInput, opts?: BranchProbeOptions): GateResult;
96
146
  /**
97
- * L1 hard rule (Harness path SSOT): `execution_lease.worktree_path` MUST
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).
147
+ * L1 hard rule (main control root vs feature): `execution_lease.worktree_path`
148
+ * MUST be a Git checkout DISTINCT from the MAIN worktree (the process-SSOT
149
+ * control root) — the same checkout, a plain subdirectory, or a symlink
150
+ * alias of the main checkout is refused (checkout identity via the
151
+ * canonical per-worktree git dir; probe failure fails closed). Both-empty
152
+ * stays a match (nothing recorded, per the lease validator contract); one
153
+ * empty has nothing to compare and passes (the lease validator's
154
+ * absolute-path requirement owns empty lease paths).
105
155
  */
106
156
  export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string, opts?: BranchProbeOptions): GateResult;
107
157
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "3.8.3",
3
+ "version": "3.9.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": {