@deftai/directive-core 0.107.0 → 0.108.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/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dispatcher.d.ts +52 -1
- package/dist/hooks/dispatcher.js +538 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/value-feedback.js +9 -18
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +17 -7
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +1 -0
- package/dist/scm/design-critique-chip.js +22 -6
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/readiness.d.ts +2 -0
- package/dist/scm/readiness.js +28 -4
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/session/git.d.ts +24 -0
- package/dist/session/git.js +138 -2
- package/dist/session/occupancy.d.ts +238 -12
- package/dist/session/occupancy.js +815 -29
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +5 -1
- package/dist/session/session-start.js +32 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +11 -0
- package/dist/session/verify-session-ritual.js +60 -15
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +8 -0
- package/dist/swarm/launch.js +63 -43
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/package.json +7 -3
|
@@ -56,6 +56,11 @@ export interface MarkRitualStaleAfterCompactResult {
|
|
|
56
56
|
* Invalidate an existing mutation ritual after host context compaction/resume (#2113).
|
|
57
57
|
* Reuses ritual-state age semantics — no parallel policy stack.
|
|
58
58
|
* Marks rearm_needed so recovery messaging prefers session:start --rearm when cold is unnecessary (#2992).
|
|
59
|
+
*
|
|
60
|
+
* Fail-open on an unbindable actor (#3769, operator amend 2026-08-28): compact
|
|
61
|
+
* carries no acting identity and runs inside a hook budget, so a skipped write
|
|
62
|
+
* has no surface a human reliably sees. The accepted cost is that an
|
|
63
|
+
* unidentified compact re-arms whoever owns the record.
|
|
59
64
|
*/
|
|
60
65
|
export declare function markRitualStaleAfterCompact(projectRoot: string, input?: {
|
|
61
66
|
now?: Date;
|
|
@@ -209,6 +209,11 @@ export const RITUAL_STALE_EPOCH = new Date("1970-01-01T00:00:00Z");
|
|
|
209
209
|
* Invalidate an existing mutation ritual after host context compaction/resume (#2113).
|
|
210
210
|
* Reuses ritual-state age semantics — no parallel policy stack.
|
|
211
211
|
* Marks rearm_needed so recovery messaging prefers session:start --rearm when cold is unnecessary (#2992).
|
|
212
|
+
*
|
|
213
|
+
* Fail-open on an unbindable actor (#3769, operator amend 2026-08-28): compact
|
|
214
|
+
* carries no acting identity and runs inside a hook budget, so a skipped write
|
|
215
|
+
* has no surface a human reliably sees. The accepted cost is that an
|
|
216
|
+
* unidentified compact re-arms whoever owns the record.
|
|
212
217
|
*/
|
|
213
218
|
export function markRitualStaleAfterCompact(projectRoot, input = {}) {
|
|
214
219
|
const now = input.now ?? new Date();
|
|
@@ -22,6 +22,8 @@ export declare const SESSION_READY_FAILED = "failed";
|
|
|
22
22
|
export type SessionReadyPath = typeof SESSION_READY_FAST_PATH | typeof SESSION_READY_RECOVERED | typeof SESSION_READY_VERIFIED | typeof SESSION_READY_FAILED;
|
|
23
23
|
export interface SessionReadyResult {
|
|
24
24
|
readonly code: number;
|
|
25
|
+
/** One resolved owner carried through preview, verification, and claim (#3611). */
|
|
26
|
+
readonly sessionId: string;
|
|
25
27
|
readonly message: string;
|
|
26
28
|
readonly path: SessionReadyPath;
|
|
27
29
|
readonly lines: readonly string[];
|
|
@@ -40,9 +42,11 @@ export interface SessionReadyOptions {
|
|
|
40
42
|
readonly runGit?: GitRunner;
|
|
41
43
|
readonly runner?: RitualRunner;
|
|
42
44
|
readonly env?: NodeJS.ProcessEnv;
|
|
45
|
+
/** Explicit lifecycle owner resolved by the host/session bridge (#3611). */
|
|
46
|
+
readonly sessionId?: string;
|
|
43
47
|
/** When set, overrides DEFT_TRIAGE_REPO / git remote inference for cache recovery. */
|
|
44
48
|
readonly repo?: string | null;
|
|
45
|
-
readonly sessionStartOptions?: Omit<SessionStartOptions, "now" | "runGit" | "env">;
|
|
49
|
+
readonly sessionStartOptions?: Omit<SessionStartOptions, "now" | "runGit" | "env" | "sessionId">;
|
|
46
50
|
readonly inspectRitual?: (projectRoot: string, options: InspectSessionRitualOptions) => VerifyResult;
|
|
47
51
|
readonly verifyRitual?: (projectRoot: string, options: VerifySessionRitualOptions) => VerifyResult;
|
|
48
52
|
readonly runStart?: (projectRoot: string, options: SessionStartOptions) => SessionStartResult;
|
|
@@ -15,7 +15,7 @@ import { execFileSync } from "node:child_process";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { cacheFetchAll } from "../cache/fetch.js";
|
|
17
17
|
import { formatFrameworkCommand } from "../render/framework-commands.js";
|
|
18
|
-
import { applyWorktreeOccupancy, } from "./occupancy.js";
|
|
18
|
+
import { applyWorktreeOccupancy, resolveOccupancySessionId, } from "./occupancy.js";
|
|
19
19
|
import { runSessionStart, } from "./session-start.js";
|
|
20
20
|
import { inspectSessionRitual, verifySessionRitual, } from "./verify-session-ritual.js";
|
|
21
21
|
export const SESSION_READY_FAST_PATH = "fast";
|
|
@@ -91,28 +91,39 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
91
91
|
const env = options.env ?? process.env;
|
|
92
92
|
const lines = [];
|
|
93
93
|
const steps = [];
|
|
94
|
+
// #3611: resolve once before any occupancy preview or nested lifecycle call.
|
|
95
|
+
// A nested start must report the same persisted owner; adopting a different
|
|
96
|
+
// result would silently split the invocation's identity.
|
|
97
|
+
const sessionId = resolveOccupancySessionId({
|
|
98
|
+
sessionId: options.sessionId,
|
|
99
|
+
env,
|
|
100
|
+
newSessionId: options.sessionStartOptions?.newSessionId,
|
|
101
|
+
});
|
|
102
|
+
let nestedStartClaimedOccupancy = false;
|
|
94
103
|
const inspect = options.inspectRitual ?? inspectSessionRitual;
|
|
95
104
|
const verify = options.verifyRitual ?? verifySessionRitual;
|
|
96
105
|
const start = options.runStart ?? runSessionStart;
|
|
97
106
|
const fetchAll = options.fetchAll ?? cacheFetchAll;
|
|
98
107
|
const inferRepo = options.inferRepo ?? ((root) => inferSessionReadyRepo(root, env));
|
|
99
108
|
const applyOccupancy = options.applyOccupancy ?? applyWorktreeOccupancy;
|
|
100
|
-
const occupancyInput = (write) => ({
|
|
109
|
+
const occupancyInput = (write, steal) => ({
|
|
110
|
+
sessionId,
|
|
101
111
|
env,
|
|
102
112
|
now,
|
|
103
|
-
steal
|
|
113
|
+
steal,
|
|
104
114
|
confirm: options.sessionStartOptions?.confirm,
|
|
105
115
|
occupant: options.sessionStartOptions?.occupant,
|
|
106
|
-
newSessionId: options.sessionStartOptions?.newSessionId,
|
|
107
116
|
intent: options.sessionStartOptions?.occupancyIntent ?? "mutation",
|
|
108
117
|
write,
|
|
109
118
|
});
|
|
110
|
-
const
|
|
119
|
+
const requestedSteal = options.sessionStartOptions?.steal === true;
|
|
120
|
+
const previewOccupancy = applyOccupancy(projectRoot, occupancyInput(false, requestedSteal));
|
|
111
121
|
if (previewOccupancy.code !== 0) {
|
|
112
122
|
const message = previewOccupancy.message;
|
|
113
123
|
lines.push(message);
|
|
114
124
|
return {
|
|
115
125
|
code: previewOccupancy.code,
|
|
126
|
+
sessionId,
|
|
116
127
|
message,
|
|
117
128
|
path: SESSION_READY_FAILED,
|
|
118
129
|
lines,
|
|
@@ -120,13 +131,14 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
120
131
|
duration_ms: elapsedMs(started),
|
|
121
132
|
};
|
|
122
133
|
}
|
|
123
|
-
const claimOnSuccess = () => applyOccupancy(projectRoot, occupancyInput(true));
|
|
134
|
+
const claimOnSuccess = () => applyOccupancy(projectRoot, occupancyInput(true, nestedStartClaimedOccupancy ? false : requestedSteal));
|
|
124
135
|
const finishReady = (path, message) => {
|
|
125
136
|
const claimed = claimOnSuccess();
|
|
126
137
|
if (claimed.code !== 0) {
|
|
127
138
|
lines.push(claimed.message);
|
|
128
139
|
return {
|
|
129
140
|
code: claimed.code,
|
|
141
|
+
sessionId,
|
|
130
142
|
message: claimed.message,
|
|
131
143
|
path: SESSION_READY_FAILED,
|
|
132
144
|
lines,
|
|
@@ -134,9 +146,39 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
134
146
|
duration_ms: elapsedMs(started),
|
|
135
147
|
};
|
|
136
148
|
}
|
|
149
|
+
// The verified ritual can change while the final occupancy claim is being
|
|
150
|
+
// persisted. Re-read only the exact bound ritual owner after the claim;
|
|
151
|
+
// never return success with a lease/ritual split (#3611). Files remain a
|
|
152
|
+
// cooperative gate, so this narrows rather than eliminates TOCTOU.
|
|
153
|
+
const postClaimRitual = inspect(projectRoot, {
|
|
154
|
+
tier: "gated",
|
|
155
|
+
posture: "mutation",
|
|
156
|
+
now,
|
|
157
|
+
runGit: options.runGit,
|
|
158
|
+
});
|
|
159
|
+
if (postClaimRitual.code !== 0 || postClaimRitual.boundSessionId !== sessionId) {
|
|
160
|
+
const actual = postClaimRitual.boundSessionId ?? "<unbound>";
|
|
161
|
+
const detail = postClaimRitual.code === 0
|
|
162
|
+
? `post-claim ritual owner ${actual}`
|
|
163
|
+
: `post-claim ritual inspection failed: ${postClaimRitual.message}`;
|
|
164
|
+
const failure = `session:ready ${detail}, but occupancy is held by ${sessionId}. ` +
|
|
165
|
+
"Readiness remains fail-closed. Re-run session:start with the same explicit " +
|
|
166
|
+
"--session-id to align ritual and occupancy state.";
|
|
167
|
+
lines.push(failure);
|
|
168
|
+
return {
|
|
169
|
+
code: postClaimRitual.code === 0 ? 1 : postClaimRitual.code,
|
|
170
|
+
sessionId,
|
|
171
|
+
message: failure,
|
|
172
|
+
path: SESSION_READY_FAILED,
|
|
173
|
+
lines,
|
|
174
|
+
steps,
|
|
175
|
+
duration_ms: elapsedMs(started),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
137
178
|
lines.push(message);
|
|
138
179
|
return {
|
|
139
180
|
code: 0,
|
|
181
|
+
sessionId,
|
|
140
182
|
message,
|
|
141
183
|
path,
|
|
142
184
|
lines,
|
|
@@ -144,6 +186,22 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
144
186
|
duration_ms: elapsedMs(started),
|
|
145
187
|
};
|
|
146
188
|
};
|
|
189
|
+
const ownerAlignmentFailure = (actualOwner, context = "verified ritual") => {
|
|
190
|
+
const actual = actualOwner ?? "<unbound>";
|
|
191
|
+
const message = `session:ready ${context} owner ${actual}, but the resolved occupancy owner is ` +
|
|
192
|
+
`${sessionId}. Refusing to claim a mismatched lease. Re-run session:start with the ` +
|
|
193
|
+
"same explicit --session-id to align ritual and occupancy state.";
|
|
194
|
+
lines.push(message);
|
|
195
|
+
return {
|
|
196
|
+
code: 1,
|
|
197
|
+
sessionId,
|
|
198
|
+
message,
|
|
199
|
+
path: SESSION_READY_FAILED,
|
|
200
|
+
lines,
|
|
201
|
+
steps,
|
|
202
|
+
duration_ms: elapsedMs(started),
|
|
203
|
+
};
|
|
204
|
+
};
|
|
147
205
|
const verifyOpts = {
|
|
148
206
|
tier: "gated",
|
|
149
207
|
posture: "mutation",
|
|
@@ -159,7 +217,10 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
159
217
|
now,
|
|
160
218
|
runGit: options.runGit,
|
|
161
219
|
});
|
|
162
|
-
|
|
220
|
+
// A confirmed owner transition must also rewrite ritual state. Even a green
|
|
221
|
+
// legacy ritual cannot take the fast path, because stealing only in the
|
|
222
|
+
// final occupancy claim would leave lease and ritual owners mismatched.
|
|
223
|
+
if (gatedInspect.code === 0 && gatedInspect.boundSessionId === sessionId && !requestedSteal) {
|
|
163
224
|
steps.push("verify:session-ritual:gated");
|
|
164
225
|
const refreshed = verify(projectRoot, verifyOpts);
|
|
165
226
|
if (!isGatedVerifyActuallyReady(refreshed)) {
|
|
@@ -169,6 +230,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
169
230
|
lines.push(message);
|
|
170
231
|
return {
|
|
171
232
|
code: refreshed.code === 0 ? 1 : refreshed.code,
|
|
233
|
+
sessionId,
|
|
172
234
|
message,
|
|
173
235
|
path: SESSION_READY_FAILED,
|
|
174
236
|
lines,
|
|
@@ -176,6 +238,9 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
176
238
|
duration_ms: elapsedMs(started),
|
|
177
239
|
};
|
|
178
240
|
}
|
|
241
|
+
if (refreshed.boundSessionId !== sessionId) {
|
|
242
|
+
return ownerAlignmentFailure(refreshed.boundSessionId);
|
|
243
|
+
}
|
|
179
244
|
return finishReady(SESSION_READY_FAST_PATH, "OK session ready (gated ritual already fresh).");
|
|
180
245
|
}
|
|
181
246
|
// --- Ensure quick-tier ritual state when missing / stale / drifted ---
|
|
@@ -185,13 +250,14 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
185
250
|
now,
|
|
186
251
|
runGit: options.runGit,
|
|
187
252
|
});
|
|
188
|
-
if (quickInspect.code !== 0) {
|
|
253
|
+
if (quickInspect.code !== 0 || quickInspect.boundSessionId !== sessionId || requestedSteal) {
|
|
189
254
|
steps.push("session:start");
|
|
190
255
|
const startResult = start(projectRoot, {
|
|
191
256
|
...options.sessionStartOptions,
|
|
192
257
|
now,
|
|
193
258
|
runGit: options.runGit,
|
|
194
259
|
env,
|
|
260
|
+
sessionId,
|
|
195
261
|
writeHistory: options.sessionStartOptions?.writeHistory ?? false,
|
|
196
262
|
});
|
|
197
263
|
for (const line of startResult.lines) {
|
|
@@ -203,6 +269,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
203
269
|
lines.push(message);
|
|
204
270
|
return {
|
|
205
271
|
code: startResult.code,
|
|
272
|
+
sessionId,
|
|
206
273
|
message,
|
|
207
274
|
path: SESSION_READY_FAILED,
|
|
208
275
|
lines,
|
|
@@ -210,11 +277,26 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
210
277
|
duration_ms: elapsedMs(started),
|
|
211
278
|
};
|
|
212
279
|
}
|
|
280
|
+
const startedOccupancy = startResult.payload.occupancy;
|
|
281
|
+
if (startedOccupancy !== null &&
|
|
282
|
+
typeof startedOccupancy === "object" &&
|
|
283
|
+
!Array.isArray(startedOccupancy)) {
|
|
284
|
+
const persistedSessionId = startedOccupancy.session_id;
|
|
285
|
+
if (typeof persistedSessionId === "string" && persistedSessionId.trim().length > 0) {
|
|
286
|
+
if (persistedSessionId.trim() !== sessionId) {
|
|
287
|
+
return ownerAlignmentFailure(persistedSessionId.trim(), "nested session:start");
|
|
288
|
+
}
|
|
289
|
+
nestedStartClaimedOccupancy = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
213
292
|
}
|
|
214
293
|
// --- Gated verify (lazy doctor + cache_fresh) ---
|
|
215
294
|
steps.push("verify:session-ritual:gated");
|
|
216
295
|
let verifyResult = verify(projectRoot, verifyOpts);
|
|
217
296
|
if (isGatedVerifyActuallyReady(verifyResult)) {
|
|
297
|
+
if (verifyResult.boundSessionId !== sessionId) {
|
|
298
|
+
return ownerAlignmentFailure(verifyResult.boundSessionId);
|
|
299
|
+
}
|
|
218
300
|
return finishReady(SESSION_READY_VERIFIED, "OK session ready (gated ritual verified).");
|
|
219
301
|
}
|
|
220
302
|
if (verifyResult.code === 0 && verifyResult.bypassed) {
|
|
@@ -222,6 +304,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
222
304
|
lines.push(message);
|
|
223
305
|
return {
|
|
224
306
|
code: 1,
|
|
307
|
+
sessionId,
|
|
225
308
|
message,
|
|
226
309
|
path: SESSION_READY_FAILED,
|
|
227
310
|
lines,
|
|
@@ -238,6 +321,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
238
321
|
lines.push(message);
|
|
239
322
|
return {
|
|
240
323
|
code: 1,
|
|
324
|
+
sessionId,
|
|
241
325
|
message,
|
|
242
326
|
path: SESSION_READY_FAILED,
|
|
243
327
|
lines,
|
|
@@ -262,6 +346,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
262
346
|
lines.push(message);
|
|
263
347
|
return {
|
|
264
348
|
code: 1,
|
|
349
|
+
sessionId,
|
|
265
350
|
message,
|
|
266
351
|
path: SESSION_READY_FAILED,
|
|
267
352
|
lines,
|
|
@@ -272,6 +357,9 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
272
357
|
steps.push("verify:session-ritual:gated:retry");
|
|
273
358
|
verifyResult = verify(projectRoot, { ...verifyOpts, forceGatedSteps: [] });
|
|
274
359
|
if (isGatedVerifyActuallyReady(verifyResult)) {
|
|
360
|
+
if (verifyResult.boundSessionId !== sessionId) {
|
|
361
|
+
return ownerAlignmentFailure(verifyResult.boundSessionId);
|
|
362
|
+
}
|
|
275
363
|
return finishReady(SESSION_READY_RECOVERED, "OK session ready (recovered via cache refresh).");
|
|
276
364
|
}
|
|
277
365
|
if (verifyResult.code === 0 && verifyResult.bypassed) {
|
|
@@ -279,6 +367,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
279
367
|
lines.push(message);
|
|
280
368
|
return {
|
|
281
369
|
code: 1,
|
|
370
|
+
sessionId,
|
|
282
371
|
message,
|
|
283
372
|
path: SESSION_READY_FAILED,
|
|
284
373
|
lines,
|
|
@@ -293,6 +382,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
293
382
|
lines.push(message);
|
|
294
383
|
return {
|
|
295
384
|
code: verifyResult.code,
|
|
385
|
+
sessionId,
|
|
296
386
|
message,
|
|
297
387
|
path: SESSION_READY_FAILED,
|
|
298
388
|
lines,
|
|
@@ -10,7 +10,7 @@ import type { GitRunner } from "./git.js";
|
|
|
10
10
|
import { type ApplyOccupancyInput, type OccupancyDecision } from "./occupancy.js";
|
|
11
11
|
import { type OrientationBundle, type RunOrientationOptions } from "./orientation-compression.js";
|
|
12
12
|
import { type ReleaseAvailabilityProbeOptions } from "./release-availability.js";
|
|
13
|
-
import { type RitualState, ritualStatePath } from "./ritual-sentinel.js";
|
|
13
|
+
import { type RitualState, ritualStatePath, writeRitualState } from "./ritual-sentinel.js";
|
|
14
14
|
import { type ToolchainPreflightOptions, type ToolchainPreflightResult } from "./toolchain-preflight.js";
|
|
15
15
|
export declare const SESSION_POSTURES: readonly ["read-only", "mutation"];
|
|
16
16
|
export type SessionPosture = (typeof SESSION_POSTURES)[number];
|
|
@@ -76,6 +76,10 @@ export interface SessionStartOptions {
|
|
|
76
76
|
readonly now?: Date;
|
|
77
77
|
readonly writeHistory?: boolean;
|
|
78
78
|
readonly runGit?: GitRunner;
|
|
79
|
+
/** Test seam for deterministic ritual persistence failures. */
|
|
80
|
+
readonly writeRitualState?: typeof writeRitualState;
|
|
81
|
+
/** Explicit lifecycle owner resolved by the host/session bridge (#3611). */
|
|
82
|
+
readonly sessionId?: string;
|
|
79
83
|
readonly newSessionId?: () => string;
|
|
80
84
|
/** #3433: steal the worktree occupancy lease. Requires confirm + occupant. */
|
|
81
85
|
readonly steal?: boolean;
|
|
@@ -26,7 +26,7 @@ import { verifyRequiredTools } from "../verify-env/verify-tools.js";
|
|
|
26
26
|
import { ceremonyDialEvidenceToDict, collectCeremonyDialConsumerEvidence, formatCeremonyDialEvidenceLine, mergeCeremonyDialInputsWithConsumerEvidence, } from "./ceremony-dial-evidence.js";
|
|
27
27
|
import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
|
|
28
28
|
import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
|
|
29
|
-
import { applyWorktreeOccupancy, } from "./occupancy.js";
|
|
29
|
+
import { applyWorktreeOccupancy, resolveOccupancySessionId, } from "./occupancy.js";
|
|
30
30
|
import { resolveSessionCompact, runOrientationCompression, } from "./orientation-compression.js";
|
|
31
31
|
import { emitSessionStartProcessCost, formatSessionStartCeremonyCostLine } from "./process-cost.js";
|
|
32
32
|
import { probeSessionReleaseAvailability, } from "./release-availability.js";
|
|
@@ -110,6 +110,13 @@ const STEP_ALIASES = {
|
|
|
110
110
|
triage: "triage_welcome",
|
|
111
111
|
"triage-welcome": "triage_welcome",
|
|
112
112
|
};
|
|
113
|
+
function ritualPersistenceTransitionError(cause, sessionId) {
|
|
114
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
115
|
+
return new Error(`Occupancy is now held by session ${sessionId}, but ritual-state persistence failed: ${detail}. ` +
|
|
116
|
+
"Direct writes remain fail-closed until both owners align. Re-run " +
|
|
117
|
+
"`deft session:start --rearm --session-id=<same-session-id>` when re-arm is eligible; " +
|
|
118
|
+
"otherwise run `deft session:start --session-id=<same-session-id>` for a cold ceremony.");
|
|
119
|
+
}
|
|
113
120
|
/** Format preferred `session:start` recovery command for cold vs re-arm (#2992). */
|
|
114
121
|
export function formatSessionStartRecoveryCommand(tier = "cold") {
|
|
115
122
|
if (tier === "rearm") {
|
|
@@ -386,6 +393,7 @@ function resolveSessionScmReadiness(options, allowOptionalNetwork) {
|
|
|
386
393
|
authState: "unknown",
|
|
387
394
|
githubAuthMode: "host-gh",
|
|
388
395
|
runtimeMode: "local-unsandboxed",
|
|
396
|
+
runtimeModeReason: null,
|
|
389
397
|
injectedTokenPresent: false,
|
|
390
398
|
depth: allowOptionalNetwork ? "deep" : "shallow",
|
|
391
399
|
detail: "SCM readiness probe failed unexpectedly; treating SCM-dependent gates as skipped",
|
|
@@ -463,11 +471,11 @@ function resolveEffortBudget(options) {
|
|
|
463
471
|
};
|
|
464
472
|
}
|
|
465
473
|
}
|
|
466
|
-
function occupancyInput(options, now, write) {
|
|
474
|
+
function occupancyInput(options, sessionId, now, write) {
|
|
467
475
|
return {
|
|
476
|
+
sessionId,
|
|
468
477
|
env: options.env,
|
|
469
478
|
now,
|
|
470
|
-
newSessionId: options.newSessionId,
|
|
471
479
|
steal: options.steal,
|
|
472
480
|
confirm: options.confirm,
|
|
473
481
|
occupant: options.occupant,
|
|
@@ -475,9 +483,9 @@ function occupancyInput(options, now, write) {
|
|
|
475
483
|
write,
|
|
476
484
|
};
|
|
477
485
|
}
|
|
478
|
-
function runOccupancy(projectRoot, options, now, write) {
|
|
486
|
+
function runOccupancy(projectRoot, options, sessionId, now, write) {
|
|
479
487
|
const apply = options.applyOccupancy ?? applyWorktreeOccupancy;
|
|
480
|
-
return apply(projectRoot, occupancyInput(options, now, write));
|
|
488
|
+
return apply(projectRoot, occupancyInput(options, sessionId, now, write));
|
|
481
489
|
}
|
|
482
490
|
function occupancyDeniedResult(occupancy, environment) {
|
|
483
491
|
return {
|
|
@@ -497,8 +505,8 @@ function occupancyDeniedResult(occupancy, environment) {
|
|
|
497
505
|
lines: occupancy.message.split("\n"),
|
|
498
506
|
};
|
|
499
507
|
}
|
|
500
|
-
function persistOccupancyOrDeny(projectRoot, options, now, environment) {
|
|
501
|
-
const occupancy = runOccupancy(projectRoot, options, now, true);
|
|
508
|
+
function persistOccupancyOrDeny(projectRoot, options, sessionId, now, environment) {
|
|
509
|
+
const occupancy = runOccupancy(projectRoot, options, sessionId, now, true);
|
|
502
510
|
if (occupancy.code !== 0)
|
|
503
511
|
return occupancyDeniedResult(occupancy, environment);
|
|
504
512
|
return occupancy;
|
|
@@ -553,7 +561,7 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
553
561
|
};
|
|
554
562
|
return { code: 0, payload: resultPayload, lines };
|
|
555
563
|
}
|
|
556
|
-
function runSessionRearm(projectRoot, options, instant, environment) {
|
|
564
|
+
function runSessionRearm(projectRoot, options, sessionId, instant, environment) {
|
|
557
565
|
const overallStarted = performance.now();
|
|
558
566
|
const runGit = options.runGit ?? defaultGitRunner;
|
|
559
567
|
const eligibility = assessRearmEligibility(projectRoot, { runGit });
|
|
@@ -581,7 +589,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
581
589
|
lines: [formatEnvironmentContext(environment), message],
|
|
582
590
|
};
|
|
583
591
|
}
|
|
584
|
-
const plannedOccupancy = runOccupancy(projectRoot, options, instant,
|
|
592
|
+
const plannedOccupancy = runOccupancy(projectRoot, options, sessionId, instant, false);
|
|
585
593
|
if (plannedOccupancy.code !== 0) {
|
|
586
594
|
return occupancyDeniedResult(plannedOccupancy, environment);
|
|
587
595
|
}
|
|
@@ -683,7 +691,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
683
691
|
};
|
|
684
692
|
const gatedSteps = restampSteps(eligibility.state.gatedSteps, instant);
|
|
685
693
|
const writeStarted = performance.now();
|
|
686
|
-
const persistedOccupancy = persistOccupancyOrDeny(projectRoot, options, instant, environment);
|
|
694
|
+
const persistedOccupancy = persistOccupancyOrDeny(projectRoot, options, sessionId, instant, environment);
|
|
687
695
|
if ("payload" in persistedOccupancy) {
|
|
688
696
|
return persistedOccupancy;
|
|
689
697
|
}
|
|
@@ -703,7 +711,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
703
711
|
};
|
|
704
712
|
let statePath;
|
|
705
713
|
try {
|
|
706
|
-
statePath = writeRitualState(projectRoot, writePayload);
|
|
714
|
+
statePath = (options.writeRitualState ?? writeRitualState)(projectRoot, writePayload);
|
|
707
715
|
}
|
|
708
716
|
catch (cause) {
|
|
709
717
|
// #2994: still record failed attempt when ritual-state write throws.
|
|
@@ -722,7 +730,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
722
730
|
{ name: "ritual_write", duration_ms: elapsedMs(writeStarted) },
|
|
723
731
|
],
|
|
724
732
|
}, { projectRoot });
|
|
725
|
-
throw cause;
|
|
733
|
+
throw ritualPersistenceTransitionError(cause, rearmSessionId);
|
|
726
734
|
}
|
|
727
735
|
const stepTimings = [
|
|
728
736
|
{ name: "alignment", duration_ms: 0 },
|
|
@@ -878,9 +886,16 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
878
886
|
if (posture === READ_ONLY_POSTURE) {
|
|
879
887
|
return runReadOnlySessionStart(projectRoot, options, instant, environment);
|
|
880
888
|
}
|
|
889
|
+
// #3611: resolve once per mutation invocation. Every occupancy evaluation,
|
|
890
|
+
// persistence write, and ritual-state payload below receives this exact ID.
|
|
891
|
+
const sessionId = resolveOccupancySessionId({
|
|
892
|
+
sessionId: options.sessionId,
|
|
893
|
+
env: options.env,
|
|
894
|
+
newSessionId: options.newSessionId,
|
|
895
|
+
});
|
|
881
896
|
// #2992: re-arm path refreshes clock/bind without fat cold ceremony.
|
|
882
897
|
if (ceremonyTier === REARM_CEREMONY_TIER) {
|
|
883
|
-
return runSessionRearm(projectRoot, options, instant, environment);
|
|
898
|
+
return runSessionRearm(projectRoot, options, sessionId, instant, environment);
|
|
884
899
|
}
|
|
885
900
|
const overallStarted = performance.now();
|
|
886
901
|
const stepTimings = [];
|
|
@@ -937,7 +952,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
937
952
|
lines: [formatEnvironmentContext(environment), payload.message],
|
|
938
953
|
};
|
|
939
954
|
}
|
|
940
|
-
const plannedOccupancy = runOccupancy(projectRoot, options, instant,
|
|
955
|
+
const plannedOccupancy = runOccupancy(projectRoot, options, sessionId, instant, false);
|
|
941
956
|
if (plannedOccupancy.code !== 0) {
|
|
942
957
|
return occupancyDeniedResult(plannedOccupancy, environment);
|
|
943
958
|
}
|
|
@@ -1314,7 +1329,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
1314
1329
|
lines.push(consentPrompt.trimEnd());
|
|
1315
1330
|
}
|
|
1316
1331
|
const writeStarted = performance.now();
|
|
1317
|
-
const persistedOccupancy = persistOccupancyOrDeny(projectRoot, options, instant, environment);
|
|
1332
|
+
const persistedOccupancy = persistOccupancyOrDeny(projectRoot, options, sessionId, instant, environment);
|
|
1318
1333
|
if ("payload" in persistedOccupancy) {
|
|
1319
1334
|
return persistedOccupancy;
|
|
1320
1335
|
}
|
|
@@ -1348,7 +1363,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
1348
1363
|
};
|
|
1349
1364
|
let statePath;
|
|
1350
1365
|
try {
|
|
1351
|
-
statePath = writeRitualState(projectRoot, payload);
|
|
1366
|
+
statePath = (options.writeRitualState ?? writeRitualState)(projectRoot, payload);
|
|
1352
1367
|
}
|
|
1353
1368
|
catch (cause) {
|
|
1354
1369
|
// #2994: still record failed attempt when ritual-state write throws.
|
|
@@ -1361,7 +1376,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
1361
1376
|
optionalNetwork: allowOptionalNetwork,
|
|
1362
1377
|
steps: stepTimings,
|
|
1363
1378
|
}, { projectRoot });
|
|
1364
|
-
throw cause;
|
|
1379
|
+
throw ritualPersistenceTransitionError(cause, coldSessionId);
|
|
1365
1380
|
}
|
|
1366
1381
|
stepTimings.push({ name: "ritual_write", duration_ms: elapsedMs(writeStarted) });
|
|
1367
1382
|
const failed = Object.entries(quickSteps)
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
* remedy without embedding env values.
|
|
10
10
|
*/
|
|
11
11
|
import { type CheckGateSpec } from "../check/gate-lists.js";
|
|
12
|
+
import { type PackageManager, type PackageManagerResolutionSource } from "../resolution/package-manager.js";
|
|
13
|
+
/** Maintainer toolchain remains pnpm-based. Consumer tools are selected dynamically. */
|
|
12
14
|
export declare const TOOLCHAIN_PREFLIGHT_TOOLS: readonly ["task", "pnpm", "node", "git"];
|
|
13
|
-
export type ToolchainPreflightTool =
|
|
15
|
+
export type ToolchainPreflightTool = "task" | PackageManager | "node" | "git";
|
|
14
16
|
export type ToolchainPreflightStatus = "ok" | "missing" | "degraded";
|
|
15
17
|
/** How a missing tool affects check dispatch (#3335). */
|
|
16
18
|
export type ToolchainPreflightImpact = "none" | "degraded";
|
|
17
19
|
export interface ToolchainPreflightFinding {
|
|
18
|
-
readonly tool: ToolchainPreflightTool | "cli_dist";
|
|
20
|
+
readonly tool: ToolchainPreflightTool | "package_manager" | "cli_dist";
|
|
19
21
|
readonly present: boolean;
|
|
20
22
|
/** Machine-stable cause code (never embeds env values). */
|
|
21
23
|
readonly cause: string | null;
|
|
@@ -40,6 +42,10 @@ export interface ToolchainPreflightResult {
|
|
|
40
42
|
* Product-ordering of remaining gates is owned by #3284.
|
|
41
43
|
*/
|
|
42
44
|
readonly skipGateIds: readonly string[];
|
|
45
|
+
/** Closed manager selected for this project, or null on strict selection failure. */
|
|
46
|
+
readonly packageManager?: PackageManager | null;
|
|
47
|
+
/** Selection provenance; maintainer source is deliberately fixed to pnpm. */
|
|
48
|
+
readonly packageManagerSource?: PackageManagerResolutionSource | "maintainer" | null;
|
|
43
49
|
}
|
|
44
50
|
export interface ToolchainPreflightOptions {
|
|
45
51
|
readonly projectRoot?: string;
|
|
@@ -61,17 +67,21 @@ export interface ToolchainPreflightOptions {
|
|
|
61
67
|
* frameworkRoot vs projectRoot (#3335 / #3324).
|
|
62
68
|
*/
|
|
63
69
|
readonly consumerDeposit?: boolean;
|
|
70
|
+
/** Environment map used for consumer package-manager precedence. */
|
|
71
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
74
|
* Gates that specifically need a package manager / built dist.
|
|
67
75
|
* When task/node is missing, the orchestrator skips *all* scheduled gates
|
|
68
76
|
* (dynamic from gate-lists), not this static list — avoids hardcode drift.
|
|
69
77
|
*/
|
|
78
|
+
export declare const PACKAGE_MANAGER_DEPENDENT_GATE_IDS: readonly string[];
|
|
79
|
+
/** @deprecated use PACKAGE_MANAGER_DEPENDENT_GATE_IDS. */
|
|
70
80
|
export declare const PNPM_DEPENDENT_GATE_IDS: readonly string[];
|
|
71
81
|
/** Sentinel skip id meaning "every gate in the active check composition". */
|
|
72
82
|
export declare const SKIP_ALL_GATES = "*";
|
|
73
83
|
/**
|
|
74
|
-
* Run done-gate toolchain preflight.
|
|
84
|
+
* Run done-gate toolchain preflight. Read-only probe — never installs or builds.
|
|
75
85
|
*/
|
|
76
86
|
export declare function runToolchainPreflight(options?: ToolchainPreflightOptions): ToolchainPreflightResult;
|
|
77
87
|
/** Serialize preflight for run-summary / ritual-state (no env values). */
|