@mstar-harness/engine 3.9.3 → 3.10.0

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/index.d.ts CHANGED
@@ -45,8 +45,8 @@ export type { PlanRow, ResidualEntry, StatusDoc, StatusV2Doc, WorkflowEntry, } f
45
45
  export { normalizeSeverity, registerWorkflow, resolveCompassEnforcement, resolveMstarcEnforcement, resolveRepoEnforcement, unregisterWorkflow, validatePlanRow, validateResidual, validateStatus, validateStatusV2, validateWorkflowEntry, } from "./status.js";
46
46
  export type { ClaimLeaseFields, ExecutionLease, ExecutionLeaseLocations, IntegrationMergeLease, LeaseTransition, LeaseVerifyResult, } from "./lease.js";
47
47
  export { canSteal, claimLease, planExecutionLeaseLocations, releaseLease, sameHolderResume, validateExecutionLease, validateIntegrationMergeLease, verifyPlanExecutionLease, withStatusWriteLock, } from "./lease.js";
48
- export type { CloseWorkflowOptions, WorkflowBranchAnchors, WorkflowExecutionPolicy, WorkflowLifecycleStatus, WorkflowLifecycleType, WorkflowSnapshot, WorkflowSnapshotRead, } from "./workflow.js";
49
- 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";
48
+ export type { CloseWorkflowOptions, DeclareWorkflowDeliveryKindOptions, DeliveryRegistrationEvidence, RecordWorkflowDeliveryOptions, RecordWorkflowDeliveryResult, RegisterPlanWorkflowOptions, RegisterPlanWorkflowResult, WorkflowBranchAnchors, WorkflowCompoundOutcome, WorkflowDeliveryEvidence, WorkflowDeliveryKind, WorkflowExecutionPolicy, WorkflowLifecycleStatus, WorkflowLifecycleType, WorkflowSnapshot, WorkflowSnapshotRead, } from "./workflow.js";
49
+ export { assertDeliveryRegistrationCoherence, closeWorkflow, consultDeliveryEvidence, declareWorkflowDeliveryKind, isTerminalSnapshot, LEGACY_WORKTREE_PATH_CODE, recordWorkflowDelivery, registerPlanWorkflow, WORKFLOW_COMPOUND_OUTCOMES, WORKFLOW_DELIVERY_KINDS, WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_LIFECYCLE_TYPES, WORKFLOW_SNAPSHOT_FILE, WORKFLOW_TERMINAL_STATUSES, readWorkflowSnapshot, validateWorkflowSnapshot, writeWorkflowSnapshot, } from "./workflow.js";
50
50
  export type { CleanupDecision, CleanupFacts, CleanupTarget, CleanupTargetKind, } from "./cleanup.js";
51
51
  export { planWorktreeCleanup } from "./cleanup.js";
52
52
  export type { EvidenceArtifactFact, EvidenceAssessment, EvidenceCaptureRequest, EvidenceCoverage, EvidenceEnvironmentKey, EvidenceExpectation, EvidenceInputEntry, EvidenceInputSnapshot, EvidenceInputSpec, EvidenceLimits, EvidenceLog, EvidenceOutcome, EvidenceToolFingerprint, SddEvidenceRecord, } from "./evidence.js";
@@ -87,3 +87,5 @@ export type { ArtifactDoc, ArtifactKind, ArtifactRef, ArtifactStore } from "./st
87
87
  export { assertFsStorePath, createFsStore, getArtifactStore, loadStoreModule, resolveArtifactPath, setArtifactStore } from "./store.js";
88
88
  export { collectActiveLifecycleBranches, scanActiveLifecycleBranches, type ActiveLifecycleScan } from "./lifecycle-branches.js";
89
89
  export { WorkflowSnapshotValidationError } from "./workflow.js";
90
+ export { CoordinationError, amendPrepareWorkflow, bindPlanSession, mutatePlanCoordination, readCoordinatedArtifact, readPlanCoordination, readSessionEnvelope, replaceCoordinatedArtifact, resolvePlanScope, resolveProcessHarnessDir, showPrepareWorkflow, } from "./coordination.js";
91
+ export type { BindPlanSessionInput, CoordinatedReplacement, CoordinationRequest, CoordinationResult, CoordinationRole, CoordinationSession, HandoffEvidence, PlanCoordinationOperation, PlanCoordinationView, PlanScopeInput, PrepareCoordinationRequest, PreparePlanAppend, PrepareWorkflowPatch, PrepareWorkflowResult, PrepareWorkflowView, ProgressCoordinationRequest, ResidualAddCoordinationRequest, ResidualCloseCoordinationRequest, ResidualInput, ResolvedPlanScope, VersionedArtifact, } from "./coordination.js";
@@ -120,7 +120,39 @@ export declare function evaluatePhaseGate(snapshotDoc: SnapshotDoc, compassDoc:
120
120
  * `evaluatePhaseGate` / `PhaseGateResult` (Phase 2–5 exit codes stay
121
121
  * intact). Stable machine codes: `PHASE6_NOT_TERMINAL`,
122
122
  * `PHASE6_ROOT_ENTRY_PRESENT`, `PHASE6_DANGLING_LEASE`,
123
- * `PHASE6_INVALID_SNAPSHOT`, `PHASE6_INVALID_ROOT`.
123
+ * `PHASE6_INVALID_SNAPSHOT`, `PHASE6_INVALID_ROOT`,
124
+ * `PHASE6_DELIVERY_KIND_UNREGISTERED`, `PHASE6_DELIVERY_EVIDENCE_INCOMPLETE`,
125
+ * `PHASE6_PLAN_ROW_NOT_DONE`.
126
+ *
127
+ * Plan-type delivery-kind consultation (seam S3, contract §6 S3 + §4g): a
128
+ * terminal `type: plan` snapshot also consults the delivery evidence recorded
129
+ * over the lifecycle — plan rows are the owned plan (no compass input) and
130
+ * remote merge verification stays excluded by contract (§4f is the PM's
131
+ * separate check). The consultation is the shared pure function
132
+ * `consultDeliveryEvidence` (workflow.ts), which `closeWorkflow` runs before
133
+ * writing a terminal snapshot — one implementation, so this read-only gate
134
+ * and the write path can never disagree. It runs for a `completed` close ONLY:
135
+ * `failed`/`stopped` closes are never demanded delivery evidence (§5 — failure
136
+ * closes through its explicit status with a recorded reason and is never
137
+ * treated as delivered), exactly as `closeWorkflow` preserves an
138
+ * already-terminal snapshot unchanged without consulting; the type-generic
139
+ * dangling-lease probe still covers every terminal status. The kind is never
140
+ * inferred (§1): a plan workflow without a registered `delivery_kind` has no
141
+ * close-verifiable delivery evidence — for a legacy terminal snapshot this is
142
+ * outside the gate's automated recovery, because the create-only register
143
+ * cannot backfill terminal bytes, so the remediation names the owner
144
+ * snapshot-amendment path (the audit-promotion grandfather population is
145
+ * disclosed there) instead of the register verb — and a registered kind with
146
+ * incomplete delivery evidence (`development` without its registered
147
+ * source/target branches, its compound disposition, its PR identity or the
148
+ * PM's verified-merge record; `verification/report-only` without the recorded
149
+ * completion policy or its fulfilment record) refuses the same way — missing
150
+ * fields are incomplete registration, not an exempt workflow. A `completed`
151
+ * close additionally requires every owned plan row `Done` (the post-write
152
+ * mirror of `closeWorkflow`'s all-rows-Done guard, §3 terminal stage);
153
+ * `failed`/`stopped` lifecycles keep their statuses and row states (§5 —
154
+ * never rewritten as successfully completed). Every refusal is read-only: the
155
+ * workflow stays registered/resumable.
124
156
  */
125
157
  export declare function evaluatePostMergeClose(snapshotDoc: SnapshotDoc, rootDoc: unknown): GateResult;
126
158
  /**
package/dist/lease.d.ts CHANGED
@@ -42,7 +42,7 @@ export type ClaimLeaseFields = {
42
42
  export type LeaseTransition = {
43
43
  ok: boolean;
44
44
  row: PlanRow;
45
- outcome?: "claimed" | "resumed" | "released";
45
+ outcome?: "claimed" | "resumed" | "released" | "transferred";
46
46
  violations: ValidationResult[];
47
47
  };
48
48
  /**
@@ -103,6 +103,18 @@ export declare function claimLease(row: PlanRow, holder: string, fields: ClaimLe
103
103
  * protocol); the caller enforces that ordering around the locked update.
104
104
  */
105
105
  export declare function releaseLease(row: PlanRow, holder: string): LeaseTransition;
106
+ /**
107
+ * Holder transfer for the scoped coordination `accept` transition (spec §D):
108
+ * move an ACTIVE `execution_lease` to another holder verbatim — same
109
+ * `claimed_at`, `worktree_path`, `working_branch`, extra fields preserved;
110
+ * only `holder` changes. Deliberately NOT `claimLease`: there is no
111
+ * Todo/Blocked → InProgress transition and no steal path here. The locked
112
+ * coordination writer has already authenticated the receiving holder as the
113
+ * workflow's coordinator, so `from` must still match the stored holder — a
114
+ * mismatched `from` is `lease.transfer.other-holder` and leaves the row
115
+ * untouched.
116
+ */
117
+ export declare function transferLease(row: PlanRow, from: string, to: string): LeaseTransition;
106
118
  /**
107
119
  * Same-holder resume check (status-and-residuals.md § Claim-before-`InProgress`
108
120
  * #2): `true` iff the stored lease `holder` equals the session `holder`.
package/dist/migrate.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type ProjectRegisterDoc } from "./project.js";
2
2
  import { type StatusV2Doc } from "./status.js";
3
- import { type WorkflowLifecycleStatus, type WorkflowLifecycleType, type WorkflowSnapshot } from "./workflow.js";
3
+ import { type WorkflowDeliveryKind, type WorkflowLifecycleStatus, type WorkflowLifecycleType, type WorkflowSnapshot } from "./workflow.js";
4
4
  /** Root status file name inside `{HARNESS_DIR}` (v1 input / v2 output). */
5
5
  export declare const MIGRATE_STATUS_FILE = "status.json";
6
6
  /** Legacy v1 root copy written by migration (never deleted without it). */
@@ -62,6 +62,21 @@ export type MigrateOptions = {
62
62
  dryRun?: boolean;
63
63
  /** Project id for the register/roadmap home (default `_default`). */
64
64
  projectId?: string;
65
+ /**
66
+ * Delivery kind declared for the ACTIVE standalone plan snapshots this lift
67
+ * produces (contract §1/§4a). A v1 `plans[]` row becomes a `type: plan`
68
+ * lifecycle, so its delivery kind is declared at registration and never
69
+ * inferred: the CLI/API caller passes it explicitly, and a lift that would
70
+ * create an active kind-less plan snapshot without it is refused (see
71
+ * `MigratePlan.deliveryKindRequired`).
72
+ */
73
+ deliveryKind?: WorkflowDeliveryKind;
74
+ /** Delivery source branch, recorded as `branch.source`. Required for `development`. */
75
+ branchSource?: string;
76
+ /** Delivery target branch, recorded as `branch.target`. Required for `development`. */
77
+ branchTarget?: string;
78
+ /** Completion policy for `verification/report-only` lifts. Required for that kind. */
79
+ completionPolicy?: string;
65
80
  };
66
81
  /** Full migration plan: every write is described; the executor applies it. */
67
82
  export type MigratePlan = {
@@ -84,11 +99,43 @@ export type MigratePlan = {
84
99
  */
85
100
  projectDir: string;
86
101
  dryRun: boolean;
102
+ /**
103
+ * Byte version (`sha256:…`, or `"absent"`) of the source `status.json` this
104
+ * plan was derived from (spec §C3 source-version CAS). The executor
105
+ * re-reads the source under the root lock before its first write and
106
+ * refuses a root whose bytes moved after planning — a still-v1 document
107
+ * changed by another writer is never silently replaced.
108
+ */
109
+ sourceVersion: string;
87
110
  /** Root status.json already at `version: 2` -> nothing to plan/apply. */
88
111
  alreadyMigrated: boolean;
89
112
  /** Human message (no-op reason when `alreadyMigrated`). */
90
113
  message: string;
91
114
  snapshots: MigrateSnapshot[];
115
+ /**
116
+ * Ids of the ACTIVE (running/paused) standalone plan snapshots this lift
117
+ * would create WITHOUT a declared delivery kind — i.e. the v1 rows lifted
118
+ * before the operator passed `opts.deliveryKind`. Non-empty means the plan is
119
+ * NOT applicable: `applyMigratePlan` refuses it (a close path could never
120
+ * complete such a lifecycle), and the CLI reports the missing flag as usage
121
+ * (exit 2) before any write. Terminal (completed) lifted snapshots are exempt
122
+ * — a finished lifecycle declares nothing (§1; the legacy terminal dead end
123
+ * is documented, not amended here).
124
+ */
125
+ deliveryKindRequired: string[];
126
+ /**
127
+ * Ids of the ACTIVE (running/paused) standalone plan snapshots the single
128
+ * `opts.deliveryKind` declaration would be stamped onto when MORE THAN ONE
129
+ * lift qualifies. The declaration is one (kind, anchors, policy) tuple — one
130
+ * delivery identity, and the kind is per-workflow registration evidence (§1)
131
+ * — so it cannot describe several lifecycles at once. Non-empty means the
132
+ * plan is NOT applicable: `applyMigratePlan` refuses it and the CLI reports
133
+ * the ambiguity as usage (exit 2) before any write, so the operator migrates
134
+ * the tree in batches of one declared plan instead of pinning one identity
135
+ * onto several. Empty when no declaration was given or exactly one lift
136
+ * qualifies; terminal lifts are exempt like `deliveryKindRequired`.
137
+ */
138
+ deliveryKindAmbiguous: string[];
92
139
  notesFiles: MigrateNotesFile[];
93
140
  register: MigrateRegister | null;
94
141
  roadmap: MigrateRoadmap | null;
package/dist/path.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ValidationResult } from "./core.js";
1
+ import type { ValidationResult } from "./core.js";
2
2
  /**
3
3
  * Options for `resolveHarnessDir`.
4
4
  */
@@ -164,11 +164,20 @@ export declare function resolveScaffoldDirs(root: string): {
164
164
  * `.mstarc`-declared `project_dir`) with a valid `roadmap.md` + empty
165
165
  * `residuals.json` (plan-conventions § 初始化 Plan 目录;
166
166
  * mstar-project-governance § `_default` 回退). Idempotent: an existing
167
- * non-empty `status.json`, `roadmap.md`, or `residuals.json` is never
168
- * clobbered; re-running on an initialized tree only creates missing
169
- * pieces. Returns the absolute resolved harness dir.
170
- */
171
- export declare function scaffoldHarness(root: string): string;
167
+ * `roadmap.md` is never clobbered, and re-running on an initialized tree only
168
+ * creates missing pieces. Returns the absolute resolved harness dir.
169
+ *
170
+ * The two coordination documents (`status.json`, `_default/residuals.json`)
171
+ * are written create-only through the active `ArtifactStore` inside the
172
+ * private protected-write context, serialized on the target's
173
+ * `withStatusWriteLock` (spec §C4): a concurrent writer's bytes are never
174
+ * replaced, and an existing empty/malformed document fails validation instead
175
+ * of being silently reinitialized. Callers whose target root differs from the
176
+ * active store's root MUST `setArtifactStore(createFsStore(<harnessRoot>))`
177
+ * first (same contract as the other routed writers). No scoped session or
178
+ * coordination record is created here.
179
+ */
180
+ export declare function scaffoldHarness(root: string): Promise<string>;
172
181
  /**
173
182
  * Harness kind for the gitignore fence — the canonical snippet is per
174
183
  * harness layout (plan-conventions § Git 跟踪策略): `.mstar/` (default) and
package/dist/sdd.d.ts CHANGED
@@ -253,7 +253,15 @@ export type SddActionKind = "source" | "artifact" | "launch";
253
253
  * the context must then match the verified lease exactly. Without an
254
254
  * active lease (no row, or a non-InProgress row without lease), the
255
255
  * standalone branch policy applies (`assertBranchAlignment`) — an
256
- * InProgress row without lease is the orphan refusal.
256
+ * InProgress row without lease is the orphan refusal. The one admission
257
+ * exception (lifecycle contract §4b/§6 S2): on a register-governed harness
258
+ * root (readable v2 `status.json` `workflows[]`), a plan with NO active
259
+ * registered row is an unregistered normal-route plan — refused with
260
+ * `sdd.context.plan-not-registered` and the register/recovery path; branch
261
+ * alignment never substitutes for registration, and a plan id retained only
262
+ * in a terminal snapshot is refused the same way (a retained row is history,
263
+ * not registration evidence — §4b). Roots without a v2 register keep the
264
+ * standalone policy byte-for-byte.
257
265
  *
258
266
  * Throws `SddScriptError` — exit 2 when the declared context itself is
259
267
  * malformed (non-absolute path, identity/composition mismatch, missing plan
package/dist/status.d.ts CHANGED
@@ -63,6 +63,18 @@ export type PlanRow = {
63
63
  execution_lease?: unknown;
64
64
  [key: string]: unknown;
65
65
  };
66
+ /**
67
+ * Every plan id a `plans[]` row can be addressed by (status-and-residuals.md
68
+ * § Compatibility: read accepts `id` or the legacy `plan_id`; write prefers
69
+ * `id`). Both non-empty keys are reported, in canonical-first order: a row
70
+ * whose two keys disagree is ambiguous (`status.plan-row.dual-id`), and an
71
+ * ownership guard keyed on one of them would let the other escape protection.
72
+ * Empty strings never count; a non-object or a row carrying neither key has
73
+ * no address.
74
+ */
75
+ export declare function rowPlanIds(row: unknown): string[];
76
+ /** The canonical single plan id of a row — `id`, else legacy `plan_id`. */
77
+ export declare function rowPlanId(row: unknown): string | undefined;
66
78
  /**
67
79
  * Normalize a residual `severity` value for reading/rolling up
68
80
  * (status-and-residuals.md § severity 5 + rollup `norm_sev`):
@@ -156,6 +168,11 @@ export declare const validateStatus: typeof validateStatusV2;
156
168
  * an invalid entry would fail `validateStatusV2` anyway, but the explicit
157
169
  * gate keeps the pre-lock fail-fast contract of `registerWorkflow`).
158
170
  *
171
+ * Replacing an existing entry additionally takes that entry's snapshot lock
172
+ * (spec §C3 root → snapshot order, `withRegisteredSnapshotLock`) and holds it
173
+ * across the root write; callers must therefore hold the root lock only, not
174
+ * a snapshot lock.
175
+ *
159
176
  * Async-only (architect-locked 2026-08-27): the durable write goes through
160
177
  * `getArtifactStore().put({ kind: "status", key: "root", ... })` inside the
161
178
  * caller's lock — the store is the persist backend, never a second lock.
@@ -185,7 +202,10 @@ export declare function registerWorkflow(root: string, entry: WorkflowEntry): Pr
185
202
  * `withStatusWriteLock`, bumping root `updated_at` only when an entry was
186
203
  * actually removed. The final document is validated (removal-at-terminal
187
204
  * invariant included) before the write — a v1 root is refused with the
188
- * `mstar migrate` hint.
205
+ * `mstar migrate` hint. Removing a registered entry additionally takes that
206
+ * entry's snapshot lock (spec §C3 root → snapshot order) and holds it across
207
+ * the root write, so the coordination check cannot be raced by a concurrent
208
+ * bind.
189
209
  *
190
210
  * Fails loud when the active FsStore would resolve its
191
211
  * `status.json` to a path other than the caller's root — the no-op branches