@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.
- package/dist/coordination-write.d.ts +1 -1
- package/dist/coordination.d.ts +14 -8
- package/dist/engine.js +29 -11
- package/package.json +1 -1
|
@@ -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
|
package/dist/coordination.d.ts
CHANGED
|
@@ -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
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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
|
|
267
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|