@mstar-harness/engine 3.11.0 → 3.11.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.
@@ -1,6 +1,6 @@
1
1
  import type { ValidationResult } from "./core.js";
2
2
  /** Stable refusal codes of the scoped coordination surface (spec §C4). */
3
- export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree", "coordination.delivery-source-repair.unsupported-workflow", "coordination.delivery-source-repair.terminal", "coordination.delivery-source-repair.no-accepted-handoff", "coordination.delivery-source-repair.already-aligned", "coordination.delivery-source-repair.not-legacy-shape", "coordination.delivery-source-repair.pr-conflict"];
3
+ export declare const COORDINATION_ERROR_CODES: readonly ["coordination.harness-not-found", "coordination.workflow-not-found", "coordination.plan-not-found", "coordination.scope-mismatch", "coordination.path-mismatch", "coordination.assignment-invalid", "coordination.assignment-stale", "coordination.not-prepared", "coordination.duplicate-holder", "coordination.session-mismatch", "coordination.session-not-found", "coordination.session-role", "coordination.invalid-session-id", "coordination.version-conflict", "coordination.expected-version-required", "coordination.invalid-transition", "coordination.invalid-input", "coordination.forbidden-field", "coordination.not-in-git", "coordination.git-unavailable", "coordination.git-proof", "coordination.evidence-stale", "coordination.integration-unresolved", "coordination.integration-diverged", "coordination.local-store-required", "coordination.direct-write-refused", "coordination.scoped-writer-required", "coordination.unknown-operation", "coordination.store", "coordination.prepare-amendment.stale", "coordination.prepare-amendment.invalid-patch", "coordination.prepare-amendment.not-prepare", "coordination.prepare-amendment.execution-started", "coordination.prepare-amendment.duplicate-plan", "coordination.prepare-amendment.invalid-plan", "coordination.prepare-amendment.compass-mismatch", "coordination.prepare-amendment.invalid-worktree", "coordination.delivery-source-repair.unsupported-workflow", "coordination.delivery-source-repair.terminal", "coordination.delivery-source-repair.no-accepted-handoff", "coordination.delivery-source-repair.already-aligned", "coordination.delivery-source-repair.not-legacy-shape", "coordination.delivery-source-repair.pr-conflict"];
4
4
  export type CoordinationErrorCode = (typeof COORDINATION_ERROR_CODES)[number];
5
5
  /**
6
6
  * Stable exception of the coordination surface: `code` is the consumer
@@ -38,7 +38,7 @@ export type ResolvedPlanScope = {
38
38
  };
39
39
  /**
40
40
  * Session envelope persisted at
41
- * `{WORKFLOW_DIR}/<workflow-id>/sessions/<session-id>.json` (mode `0600`).
41
+ * `{WORKFLOW_DIR}/<workflow-id>/sessions/<role>-<session-id>.json` (mode `0600`).
42
42
  * The envelope is the durable proof of who holds the session: the snapshot
43
43
  * stores its canonical path and every later call must present the same file.
44
44
  */
@@ -51,20 +51,24 @@ export type CoordinationSession = {
51
51
  harness_root: string;
52
52
  };
53
53
  /**
54
- * Bind addressing (spec §B). A fresh bind never supplies a session path: the
55
- * engine generates the session UUID and creates
56
- * `<workflow-dir>/<workflow-id>/sessions/<session-id>.json` itself. An existing
57
- * session is reached only through its explicit `resumePath`, which resumes
58
- * read-only and never writes.
54
+ * Bind addressing (spec §B). A fresh bind generates the session UUID and
55
+ * creates `<workflow-dir>/<workflow-id>/sessions/<role>-<session-id>.json`
56
+ * itself, unless the caller supplies `sessionId` then the engine adopts that
57
+ * value as the identity after validating it as a single safe path component (it
58
+ * names the envelope's file, prefixed by the role). An existing session is
59
+ * reached only through its explicit `resumePath`, which resumes read-only,
60
+ * never writes and never re-identifies.
59
61
  */
60
62
  export type BindPlanSessionInput = {
61
63
  scope: PlanScopeInput;
62
64
  cwd: string;
65
+ sessionId?: string;
63
66
  } | {
64
67
  coordinator: true;
65
68
  workflowId: string;
66
69
  harnessDir?: string;
67
70
  cwd: string;
71
+ sessionId?: string;
68
72
  } | {
69
73
  resumePath: string;
70
74
  cwd: string;
@@ -263,8 +267,10 @@ export declare function readCoordinatedArtifact(harnessRoot: string, ref: Artifa
263
267
  */
264
268
  /**
265
269
  * Bind a session (spec §B, §C2). Fresh addressing never supplies a session
266
- * path: the engine generates the UUID and creates the envelope. `resumePath`
267
- * names an existing envelope and resumes read-only.
270
+ * path: the engine generates the UUID or adopts the caller-supplied
271
+ * `sessionId`, refused unless it is a single safe path component — and creates
272
+ * the envelope. `resumePath` names an existing envelope and resumes read-only,
273
+ * so it takes no identity input at all.
268
274
  */
269
275
  export declare function bindPlanSession(input: BindPlanSessionInput): Promise<CoordinationResult>;
270
276
  /**
package/dist/engine.js CHANGED
@@ -10376,8 +10376,8 @@ function reportedBranchesOf(row) {
10376
10376
  function snapshotPathOf(harnessRoot, workflowId) {
10377
10377
  return join20(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), workflowId, SNAPSHOT_FILE2);
10378
10378
  }
10379
- function sessionFilePath(harnessRoot, workflowId, sessionId) {
10380
- return join20(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), workflowId, SESSION_DIR, `${sessionId}.json`);
10379
+ function sessionFilePath(harnessRoot, workflowId, role, sessionId) {
10380
+ return join20(resolveWorkflowDir(harnessRoot, { harnessDir: harnessRoot }), workflowId, SESSION_DIR, `${role}-${sessionId}.json`);
10381
10381
  }
10382
10382
  function localStore(harnessRoot) {
10383
10383
  let store;
@@ -10538,6 +10538,22 @@ function safePlanId(planId, where) {
10538
10538
  }
10539
10539
  return planId;
10540
10540
  }
10541
+ var SESSION_ID_MAX_LENGTH = 128;
10542
+ function safeSessionId(value) {
10543
+ if (value === undefined)
10544
+ return;
10545
+ if (typeof value !== "string")
10546
+ throw invalidInput("sessionId must be a string");
10547
+ if (value.length > SESSION_ID_MAX_LENGTH) {
10548
+ throw new CoordinationError("coordination.invalid-session-id", `session id is longer than ${SESSION_ID_MAX_LENGTH} characters: ${JSON.stringify(value)}`, { session_id: value, max_length: SESSION_ID_MAX_LENGTH });
10549
+ }
10550
+ try {
10551
+ assertSafePathComponent(value, "session id");
10552
+ } catch (error) {
10553
+ throw new CoordinationError("coordination.invalid-session-id", `session id ${JSON.stringify(value)} is not a safe path component: ${errorMessage(error)}`, { session_id: value });
10554
+ }
10555
+ return value;
10556
+ }
10541
10557
  function readSnapshot(dir) {
10542
10558
  const snapshotPath = join20(dir, SNAPSHOT_FILE2);
10543
10559
  if (!existsSync15(snapshotPath)) {
@@ -10721,7 +10737,7 @@ function readSessionEnvelope(sessionPath) {
10721
10737
  return session;
10722
10738
  }
10723
10739
  function createSessionEnvelope(session) {
10724
- const path = sessionFilePath(session.harness_root, session.workflow_id, session.session_id);
10740
+ const path = sessionFilePath(session.harness_root, session.workflow_id, session.role, session.session_id);
10725
10741
  mkdirSync9(dirname12(path), { recursive: true });
10726
10742
  try {
10727
10743
  writeFileSync7(path, `${JSON.stringify(session, null, 2)}
@@ -11026,7 +11042,7 @@ function assertCoordinatorResidency(cwd, snapshot) {
11026
11042
  }
11027
11043
  return main;
11028
11044
  }
11029
- async function bindCoordinatorSession(cwd, workflowId, harnessDir) {
11045
+ async function bindCoordinatorSession(cwd, workflowId, harnessDir, sessionId) {
11030
11046
  const harnessRoot = requireProcessRoot(cwd, harnessDir);
11031
11047
  safePlanId(workflowId, "workflowId");
11032
11048
  const snapshotPath = snapshotPathOf(harnessRoot, workflowId);
@@ -11035,7 +11051,7 @@ async function bindCoordinatorSession(cwd, workflowId, harnessDir) {
11035
11051
  const session = {
11036
11052
  schema_version: 1,
11037
11053
  role: "coordinator",
11038
- session_id: randomUUID2(),
11054
+ session_id: sessionId ?? randomUUID2(),
11039
11055
  workflow_id: workflowId,
11040
11056
  harness_root: harnessRoot
11041
11057
  };
@@ -11075,7 +11091,7 @@ function requireProcessRoot(cwd, harnessDir) {
11075
11091
  }
11076
11092
  return root;
11077
11093
  }
11078
- async function bindPlanSessionForPlan(scope) {
11094
+ async function bindPlanSessionForPlan(scope, sessionId) {
11079
11095
  const { harnessRoot, workflowId, planId } = scope;
11080
11096
  const snapshotPath = snapshotPathOf(harnessRoot, workflowId);
11081
11097
  assertSnapshotPath(harnessRoot, workflowId, snapshotPath);
@@ -11098,7 +11114,7 @@ async function bindPlanSessionForPlan(scope) {
11098
11114
  const session = {
11099
11115
  schema_version: 1,
11100
11116
  role: "plan-pm",
11101
- session_id: randomUUID2(),
11117
+ session_id: sessionId ?? randomUUID2(),
11102
11118
  workflow_id: workflowId,
11103
11119
  plan_id: planId,
11104
11120
  harness_root: harnessRoot
@@ -11165,19 +11181,21 @@ async function bindPlanSession(input) {
11165
11181
  return resumeBoundSession(input.resumePath);
11166
11182
  }
11167
11183
  if ("coordinator" in input) {
11168
- assertExactKeys(input, ["coordinator", "workflowId", "harnessDir", "cwd"], "bind coordinator input");
11184
+ assertExactKeys(input, ["coordinator", "workflowId", "harnessDir", "cwd", "sessionId"], "bind coordinator input");
11169
11185
  if (input.coordinator !== true)
11170
11186
  throw invalidInput("`coordinator` is only meaningful as true");
11171
11187
  if (!isNonEmptyString(input.workflowId))
11172
11188
  throw invalidInput("workflowId is required");
11173
11189
  requireCwd(input.cwd);
11174
- return bindCoordinatorSession(input.cwd, input.workflowId, input.harnessDir);
11190
+ const sessionId2 = safeSessionId(input.sessionId);
11191
+ return bindCoordinatorSession(input.cwd, input.workflowId, input.harnessDir, sessionId2);
11175
11192
  }
11176
- assertExactKeys(input, ["scope", "cwd"], "bind plan input");
11193
+ assertExactKeys(input, ["scope", "cwd", "sessionId"], "bind plan input");
11177
11194
  if (!isPlainObject2(input.scope))
11178
11195
  throw invalidInput("a plan bind requires a scope");
11179
11196
  requireCwd(input.cwd);
11180
- return bindPlanSessionForPlan(await resolvePlanScope(input.scope, input.cwd));
11197
+ const sessionId = safeSessionId(input.sessionId);
11198
+ return bindPlanSessionForPlan(await resolvePlanScope(input.scope, input.cwd), sessionId);
11181
11199
  }
11182
11200
  function requireCwd(cwd) {
11183
11201
  if (!isNonEmptyString(cwd) || !isAbsolute12(cwd))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "3.11.0",
3
+ "version": "3.11.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": {