@deftai/directive-runtime 0.126.0-beta.1 → 0.126.0-beta.3
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/UPGRADING.md +4 -3
- package/commands.md +9 -6
- package/contracts/design-critique.md +39 -1
- package/contracts/human-origin-authz.md +14 -8
- package/dist/coverage-hotspots.d.ts +3 -0
- package/dist/coverage-hotspots.js +42 -2
- package/dist/doctor-diagnose.d.ts +1 -1
- package/dist/doctor-diagnose.js +29 -3
- package/dist/doctor.js +1 -1
- package/dist/occupancy-release.d.ts +5 -0
- package/dist/occupancy-release.js +11 -3
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +6 -0
- package/dist/session-start.js +28 -1
- package/dist/triage-actions.js +10 -2
- package/dist/verify-review-monitor.d.ts +7 -0
- package/dist/verify-review-monitor.js +21 -0
- package/docs/framework-gap-filing.md +85 -0
- package/docs/getting-started.md +13 -1
- package/docs/product-signal.md +1 -1
- package/package.json +1 -1
- package/packs/skills/skills-pack-0.1.json +10 -7
- package/packs/strategies/strategies-pack-0.1.json +32 -19
- package/scm/github.md +1 -1
- package/skills/deft-directive-article-review/SKILL.md +104 -24
- package/skills/deft-directive-design-critique/SKILL.md +10 -9
- package/skills/deft-directive-feedback/SKILL.md +37 -21
- package/skills/deft-directive-pre-pr/SKILL.md +14 -0
- package/skills/deft-directive-product-signal/SKILL.md +1 -1
- package/skills/deft-directive-review-cycle/SKILL.md +18 -1
- package/skills/deft-directive-setup/SKILL.md +41 -11
- package/skills/deft-directive-swarm/references/host-grok-build.md +1 -1
- package/skills/deft-directive-tutorial/SKILL.md +1 -1
- package/strategies/README.md +1 -0
- package/strategies/artifact-guards.md +2 -2
- package/strategies/design-thinking.md +182 -0
- package/strategies/discuss.md +2 -0
- package/strategies/interview.md +76 -23
- package/strategies/rapid.md +22 -12
- package/templates/agents-entry.md +4 -3
- package/templates/design-critique-brief.md +5 -1
- package/tutorial/beats.json +2 -2
- package/vendor/directive-content/UPGRADING.md +4 -3
- package/vendor/directive-content/commands.md +9 -6
- package/vendor/directive-content/contracts/design-critique.md +39 -1
- package/vendor/directive-content/contracts/human-origin-authz.md +14 -8
- package/vendor/directive-content/docs/framework-gap-filing.md +85 -0
- package/vendor/directive-content/docs/getting-started.md +13 -1
- package/vendor/directive-content/docs/product-signal.md +1 -1
- package/vendor/directive-content/packs/skills/skills-pack-0.1.json +10 -7
- package/vendor/directive-content/packs/strategies/strategies-pack-0.1.json +32 -19
- package/vendor/directive-content/scm/github.md +1 -1
- package/vendor/directive-content/skills/deft-directive-article-review/SKILL.md +104 -24
- package/vendor/directive-content/skills/deft-directive-design-critique/SKILL.md +10 -9
- package/vendor/directive-content/skills/deft-directive-feedback/SKILL.md +37 -21
- package/vendor/directive-content/skills/deft-directive-pre-pr/SKILL.md +14 -0
- package/vendor/directive-content/skills/deft-directive-product-signal/SKILL.md +1 -1
- package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +18 -1
- package/vendor/directive-content/skills/deft-directive-setup/SKILL.md +41 -11
- package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +1 -1
- package/vendor/directive-content/skills/deft-directive-tutorial/SKILL.md +1 -1
- package/vendor/directive-content/strategies/README.md +1 -0
- package/vendor/directive-content/strategies/artifact-guards.md +2 -2
- package/vendor/directive-content/strategies/design-thinking.md +182 -0
- package/vendor/directive-content/strategies/discuss.md +2 -0
- package/vendor/directive-content/strategies/interview.md +76 -23
- package/vendor/directive-content/strategies/rapid.md +22 -12
- package/vendor/directive-content/templates/agents-entry.md +4 -3
- package/vendor/directive-content/templates/design-critique-brief.md +5 -1
- package/vendor/directive-content/tutorial/beats.json +2 -2
- package/vendor/directive-core/dist/.tsbuildinfo +1 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.d.ts +11 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.d.ts.map +1 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.js +18 -5
- package/vendor/directive-core/dist/content-contracts/skills/helpers.js.map +1 -1
- package/vendor/directive-core/dist/coverage-applicability/evaluate.d.ts.map +1 -1
- package/vendor/directive-core/dist/coverage-applicability/evaluate.js +39 -0
- package/vendor/directive-core/dist/coverage-applicability/evaluate.js.map +1 -1
- package/vendor/directive-core/dist/coverage-hotspots/evaluate.d.ts +24 -5
- package/vendor/directive-core/dist/coverage-hotspots/evaluate.d.ts.map +1 -1
- package/vendor/directive-core/dist/coverage-hotspots/evaluate.js +89 -8
- package/vendor/directive-core/dist/coverage-hotspots/evaluate.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.d.ts +40 -0
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.js +116 -0
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/citation-grammar.d.ts +36 -0
- package/vendor/directive-core/dist/design-critique/citation-grammar.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/citation-grammar.js +60 -3
- package/vendor/directive-core/dist/design-critique/citation-grammar.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/completed-arc-record.d.ts +103 -3
- package/vendor/directive-core/dist/design-critique/completed-arc-record.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/completed-arc-record.js +127 -5
- package/vendor/directive-core/dist/design-critique/completed-arc-record.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/ingest-posture.d.ts +27 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.d.ts.map +1 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.js +66 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.js.map +1 -0
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.d.ts +106 -7
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.js +284 -17
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.js.map +1 -1
- package/vendor/directive-core/dist/docs/docs-impact.d.ts +13 -0
- package/vendor/directive-core/dist/docs/docs-impact.d.ts.map +1 -1
- package/vendor/directive-core/dist/docs/docs-impact.js +49 -4
- package/vendor/directive-core/dist/docs/docs-impact.js.map +1 -1
- package/vendor/directive-core/dist/doctor/main.d.ts +48 -0
- package/vendor/directive-core/dist/doctor/main.d.ts.map +1 -1
- package/vendor/directive-core/dist/doctor/main.js +221 -4
- package/vendor/directive-core/dist/doctor/main.js.map +1 -1
- package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
- package/vendor/directive-core/dist/hooks/dispatcher.js +160 -14
- package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
- package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +4 -0
- package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
- package/vendor/directive-core/dist/init-deposit/agent-hooks.js +25 -4
- package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
- package/vendor/directive-core/dist/init-deposit/skill-discovery-deposit.d.ts.map +1 -1
- package/vendor/directive-core/dist/init-deposit/skill-discovery-deposit.js +15 -0
- package/vendor/directive-core/dist/init-deposit/skill-discovery-deposit.js.map +1 -1
- package/vendor/directive-core/dist/intake/issue-ingest.d.ts +15 -0
- package/vendor/directive-core/dist/intake/issue-ingest.d.ts.map +1 -1
- package/vendor/directive-core/dist/intake/issue-ingest.js +190 -22
- package/vendor/directive-core/dist/intake/issue-ingest.js.map +1 -1
- package/vendor/directive-core/dist/pr-closing-keywords/detect.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-closing-keywords/detect.js +52 -7
- package/vendor/directive-core/dist/pr-closing-keywords/detect.js.map +1 -1
- package/vendor/directive-core/dist/pr-watch/main.d.ts +5 -0
- package/vendor/directive-core/dist/pr-watch/main.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-watch/main.js.map +1 -1
- package/vendor/directive-core/dist/pr-watch/watch.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-watch/watch.js +5 -0
- package/vendor/directive-core/dist/pr-watch/watch.js.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/controller.d.ts.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/controller.js +118 -12
- package/vendor/directive-core/dist/pre-pr-controller/controller.js.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/index.d.ts +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/index.d.ts.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/index.js +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/index.js.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts +5 -0
- package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts.map +1 -1
- package/vendor/directive-core/dist/pre-pr-controller/phases.js +10 -0
- package/vendor/directive-core/dist/pre-pr-controller/phases.js.map +1 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.d.ts.map +1 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.js +3 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.js.map +1 -1
- package/vendor/directive-core/dist/release/auto-hatch.d.ts +6 -0
- package/vendor/directive-core/dist/release/auto-hatch.d.ts.map +1 -1
- package/vendor/directive-core/dist/release/auto-hatch.js +13 -4
- package/vendor/directive-core/dist/release/auto-hatch.js.map +1 -1
- package/vendor/directive-core/dist/review-monitor/record.d.ts +11 -0
- package/vendor/directive-core/dist/review-monitor/record.d.ts.map +1 -1
- package/vendor/directive-core/dist/review-monitor/record.js +23 -0
- package/vendor/directive-core/dist/review-monitor/record.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/emit.d.ts +2 -1
- package/vendor/directive-core/dist/run-summary/emit.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/emit.js +3 -0
- package/vendor/directive-core/dist/run-summary/emit.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/index.d.ts +1 -1
- package/vendor/directive-core/dist/run-summary/index.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/index.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/types.d.ts +8 -2
- package/vendor/directive-core/dist/run-summary/types.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/types.js +1 -0
- package/vendor/directive-core/dist/run-summary/types.js.map +1 -1
- package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +11 -0
- package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
- package/vendor/directive-core/dist/scope/delivery-evidence.js +34 -0
- package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
- package/vendor/directive-core/dist/scope/promote-from-issue.d.ts +2 -1
- package/vendor/directive-core/dist/scope/promote-from-issue.d.ts.map +1 -1
- package/vendor/directive-core/dist/scope/promote-from-issue.js +17 -61
- package/vendor/directive-core/dist/scope/promote-from-issue.js.map +1 -1
- package/vendor/directive-core/dist/session/occupancy-recovery.d.ts +10 -0
- package/vendor/directive-core/dist/session/occupancy-recovery.d.ts.map +1 -1
- package/vendor/directive-core/dist/session/occupancy-recovery.js +43 -7
- package/vendor/directive-core/dist/session/occupancy-recovery.js.map +1 -1
- package/vendor/directive-core/dist/session/occupancy.d.ts +148 -1
- package/vendor/directive-core/dist/session/occupancy.d.ts.map +1 -1
- package/vendor/directive-core/dist/session/occupancy.js +516 -34
- package/vendor/directive-core/dist/session/occupancy.js.map +1 -1
- package/vendor/directive-core/dist/session/session-start.d.ts.map +1 -1
- package/vendor/directive-core/dist/session/session-start.js +6 -1
- package/vendor/directive-core/dist/session/session-start.js.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.d.ts.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.js +5 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.js.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.d.ts +71 -0
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.d.ts.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.js +125 -2
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.js.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.d.ts.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.js +9 -0
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.js.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/kinds.d.ts.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/kinds.js +2 -0
- package/vendor/directive-core/dist/telemetry-coverage/kinds.js.map +1 -1
- package/vendor/directive-core/dist/triage/actions/index.d.ts +8 -5
- package/vendor/directive-core/dist/triage/actions/index.d.ts.map +1 -1
- package/vendor/directive-core/dist/triage/actions/index.js +248 -22
- package/vendor/directive-core/dist/triage/actions/index.js.map +1 -1
- package/vendor/directive-core/dist/triage/actions/types.d.ts +16 -2
- package/vendor/directive-core/dist/triage/actions/types.d.ts.map +1 -1
- package/vendor/directive-core/dist/triage/bulk/index.d.ts +8 -1
- package/vendor/directive-core/dist/triage/bulk/index.d.ts.map +1 -1
- package/vendor/directive-core/dist/triage/bulk/index.js +30 -11
- package/vendor/directive-core/dist/triage/bulk/index.js.map +1 -1
- package/vendor/directive-core/dist/value/feedback-file.d.ts +54 -6
- package/vendor/directive-core/dist/value/feedback-file.d.ts.map +1 -1
- package/vendor/directive-core/dist/value/feedback-file.js +470 -43
- package/vendor/directive-core/dist/value/feedback-file.js.map +1 -1
|
@@ -75,13 +75,30 @@ import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
|
75
75
|
import { assertAppendLockOwned, withAppendLock } from "../slice/lock.js";
|
|
76
76
|
import { SWARM_WORKER_ROLES } from "../swarm/routing.js";
|
|
77
77
|
import { recordChildOccupancyLease } from "./child-occupancy.js";
|
|
78
|
-
import {
|
|
78
|
+
import { worktreePathOrNull } from "./git.js";
|
|
79
|
+
import { ambientHostSessionOwner, canonicalHostSessionId, claimsHostSessionIdShape, detectDeclaredIdentityHosts, HOST_IDENTITY_PROVIDERS, isUsableHostSessionId, parseCanonicalHostSessionId, printCompanionHostOwner, } from "./host-session-owner.js";
|
|
79
80
|
import { stableJson } from "./json.js";
|
|
80
81
|
import { isLinkedWorktreePath, isMainWorktreePath, listLinkedWorktreeCheckouts, mainWorktreeRoot, } from "./main-worktree.js";
|
|
81
82
|
import { readRitualState } from "./ritual-sentinel.js";
|
|
82
83
|
import { parseTimestamp, timestampIso } from "./time.js";
|
|
83
84
|
export const OCCUPANCY_SCHEMA_VERSION = 1;
|
|
84
85
|
export const OCCUPANCY_RELPATH = [".deft", "occupancy.json"];
|
|
86
|
+
/**
|
|
87
|
+
* Harbor / cell bootstrap signal (#5544 Prefer-A): a declared agent host will
|
|
88
|
+
* attach to this tree. Claimer surface is Harbor ritual applying
|
|
89
|
+
* `applyWorktreeOccupancy`. When set, last-resort UUID mint is refused so the
|
|
90
|
+
* later host can self-claim.
|
|
91
|
+
*/
|
|
92
|
+
export const DEFT_EXPECTED_DECLARED_HOST_ATTACH = "DEFT_EXPECTED_DECLARED_HOST_ATTACH";
|
|
93
|
+
/** Worktree applying-host record for multi-host PreToolUse deposits (#5676). */
|
|
94
|
+
export const APPLYING_HOST_RELPATH = [".deft", "applying-host.json"];
|
|
95
|
+
export const APPLYING_HOST_SCHEMA = "deft.applying-host.v1";
|
|
96
|
+
/**
|
|
97
|
+
* Applying-host staleness floor (#5676). Mirrors tip ritual staleness (8h) so a
|
|
98
|
+
* reconnect SessionStart / read-only session:start republish stays in the same
|
|
99
|
+
* recovery window as ceremony re-arm; UUID residual stays under #5544.
|
|
100
|
+
*/
|
|
101
|
+
export const APPLYING_HOST_STALE_MS = Number("8") * 60 * 60 * 1000;
|
|
85
102
|
/** Crash recovery TTL: 20 minutes without heartbeat (15–30 window). */
|
|
86
103
|
export const OCCUPANCY_TTL_MS = 20 * 60 * 1000;
|
|
87
104
|
/**
|
|
@@ -227,6 +244,381 @@ export const OCCUPANCY_MAX_GRANTS = 32;
|
|
|
227
244
|
export function occupancyPath(projectRoot) {
|
|
228
245
|
return join(resolve(projectRoot), ...OCCUPANCY_RELPATH);
|
|
229
246
|
}
|
|
247
|
+
export function applyingHostPath(projectRoot) {
|
|
248
|
+
return join(resolve(projectRoot), ...APPLYING_HOST_RELPATH);
|
|
249
|
+
}
|
|
250
|
+
function isHookHostIdentityProvider(value) {
|
|
251
|
+
return HOST_IDENTITY_PROVIDERS.includes(value);
|
|
252
|
+
}
|
|
253
|
+
function parseApplyingHostPayload(payload) {
|
|
254
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload))
|
|
255
|
+
return null;
|
|
256
|
+
const row = payload;
|
|
257
|
+
if (row.schema !== APPLYING_HOST_SCHEMA)
|
|
258
|
+
return null;
|
|
259
|
+
if (typeof row.provider !== "string" || !isHookHostIdentityProvider(row.provider))
|
|
260
|
+
return null;
|
|
261
|
+
if (typeof row.rawSessionId !== "string" || !isUsableHostSessionId(row.rawSessionId))
|
|
262
|
+
return null;
|
|
263
|
+
if (typeof row.applyingHostSessionId !== "string")
|
|
264
|
+
return null;
|
|
265
|
+
if (typeof row.claimed_at !== "string" || parseTimestamp(row.claimed_at) === null)
|
|
266
|
+
return null;
|
|
267
|
+
const transport = row.transport === "grok-hook-event" ? "grok-hook-event" : "native";
|
|
268
|
+
const expected = canonicalHostSessionId(row.provider, row.rawSessionId);
|
|
269
|
+
if (row.applyingHostSessionId !== expected)
|
|
270
|
+
return null;
|
|
271
|
+
return {
|
|
272
|
+
schema: APPLYING_HOST_SCHEMA,
|
|
273
|
+
provider: row.provider,
|
|
274
|
+
rawSessionId: row.rawSessionId,
|
|
275
|
+
applyingHostSessionId: expected,
|
|
276
|
+
claimed_at: row.claimed_at,
|
|
277
|
+
transport,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** Read the worktree applying-host record (#5676). */
|
|
281
|
+
export function readApplyingHost(projectRoot, now = new Date()) {
|
|
282
|
+
const path = applyingHostPath(projectRoot);
|
|
283
|
+
try {
|
|
284
|
+
if (!existsSync(path))
|
|
285
|
+
return { status: "missing", record: null };
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return { status: "missing", record: null };
|
|
289
|
+
}
|
|
290
|
+
let payload;
|
|
291
|
+
try {
|
|
292
|
+
payload = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return {
|
|
296
|
+
status: "invalid",
|
|
297
|
+
record: null,
|
|
298
|
+
message: "applying-host record is unreadable; republish via SessionStart or read-only session:start",
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const record = parseApplyingHostPayload(payload);
|
|
302
|
+
if (record === null) {
|
|
303
|
+
return {
|
|
304
|
+
status: "invalid",
|
|
305
|
+
record: null,
|
|
306
|
+
message: "applying-host record is malformed; republish via SessionStart or read-only session:start",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const claimedAt = parseTimestamp(record.claimed_at);
|
|
310
|
+
if (claimedAt === null) {
|
|
311
|
+
return {
|
|
312
|
+
status: "invalid",
|
|
313
|
+
record: null,
|
|
314
|
+
message: "applying-host record has invalid claimed_at; republish via SessionStart or read-only session:start",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (now.getTime() - claimedAt.getTime() > APPLYING_HOST_STALE_MS) {
|
|
318
|
+
return {
|
|
319
|
+
status: "stale",
|
|
320
|
+
record,
|
|
321
|
+
message: formatApplyingHostRepairDiagnostic("stale"),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return { status: "ok", record };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Supported repair when the applying-host record is missing, stale, or
|
|
328
|
+
* conflicting (#5676 Prefer-A Recut-5). Mutation session:start is reinforcement
|
|
329
|
+
* only — first publication is SessionStart or read-only session:start.
|
|
330
|
+
*/
|
|
331
|
+
export function formatApplyingHostRepairDiagnostic(reason = "missing") {
|
|
332
|
+
const subject = reason === "missing"
|
|
333
|
+
? "Applying-host record is missing"
|
|
334
|
+
: reason === "stale"
|
|
335
|
+
? "Applying-host record is stale"
|
|
336
|
+
: reason === "conflicting"
|
|
337
|
+
? "Applying-host record conflicts with this host session"
|
|
338
|
+
: "Applying-host record is invalid";
|
|
339
|
+
return (`${subject}. Republish via host SessionStart reconnect and/or ` +
|
|
340
|
+
"`deft session:start --read-only --session-id=<applyingHostSessionId>` " +
|
|
341
|
+
"before retrying mutation lifecycle. UUID / Harbor residuals stay under #5544.");
|
|
342
|
+
}
|
|
343
|
+
/** Write/replace the worktree applying-host record (#5676). */
|
|
344
|
+
export function publishApplyingHost(projectRoot, input) {
|
|
345
|
+
if (!isUsableHostSessionId(input.rawSessionId)) {
|
|
346
|
+
throw new Error("publishApplyingHost: rawSessionId is not a usable host session id");
|
|
347
|
+
}
|
|
348
|
+
const root = resolve(projectRoot);
|
|
349
|
+
const claimedAt = input.claimedAt ?? new Date();
|
|
350
|
+
const record = {
|
|
351
|
+
schema: APPLYING_HOST_SCHEMA,
|
|
352
|
+
provider: input.provider,
|
|
353
|
+
rawSessionId: input.rawSessionId,
|
|
354
|
+
applyingHostSessionId: canonicalHostSessionId(input.provider, input.rawSessionId),
|
|
355
|
+
claimed_at: timestampIso(claimedAt),
|
|
356
|
+
transport: input.transport ?? "native",
|
|
357
|
+
};
|
|
358
|
+
const target = applyingHostPath(root);
|
|
359
|
+
assertWriteTargetSafe(root, target);
|
|
360
|
+
const dir = dirname(target);
|
|
361
|
+
const tmpName = join(dir, `.applying-host.${process.pid}.json.tmp`);
|
|
362
|
+
const text = `${stableJson(record, 2)}\n`;
|
|
363
|
+
try {
|
|
364
|
+
containedWrite({ root, target: tmpName, data: text, mode: "create" });
|
|
365
|
+
renameSync(tmpName, target);
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
try {
|
|
369
|
+
rmSync(tmpName, { force: true });
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
/* best-effort cleanup of tmp only */
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return record;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Publish from a canonical `host:<provider>:v1:<base64url>` owner (#5676).
|
|
379
|
+
* Returns null when the id is not canonical (opaque UUID leftovers stay #5544).
|
|
380
|
+
*/
|
|
381
|
+
export function publishApplyingHostFromCanonicalSessionId(projectRoot, sessionId, opts = {}) {
|
|
382
|
+
const parsed = parseCanonicalHostSessionId(sessionId.trim());
|
|
383
|
+
if (parsed === null)
|
|
384
|
+
return null;
|
|
385
|
+
return publishApplyingHost(projectRoot, {
|
|
386
|
+
provider: parsed.provider,
|
|
387
|
+
rawSessionId: parsed.rawSessionId,
|
|
388
|
+
claimedAt: opts.claimedAt,
|
|
389
|
+
transport: opts.transport,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Lifecycle admission against the applying-host record (#5676).
|
|
394
|
+
*
|
|
395
|
+
* Matching deposits enforce `applyingHostSessionId`. Non-matching deposits
|
|
396
|
+
* soft-pass only when the command already names that id. Missing/stale/
|
|
397
|
+
* conflicting records name SessionStart / read-only repair. Absent record with
|
|
398
|
+
* no flip-flop shape returns `absent` so single-host flows keep prior behavior.
|
|
399
|
+
*/
|
|
400
|
+
export function evaluateApplyingHostLifecycleAdmission(input) {
|
|
401
|
+
const read = readApplyingHost(input.projectRoot, input.now ?? new Date());
|
|
402
|
+
if (read.status === "invalid") {
|
|
403
|
+
return {
|
|
404
|
+
kind: "repair",
|
|
405
|
+
record: null,
|
|
406
|
+
message: read.message,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (read.status === "stale") {
|
|
410
|
+
// Live occupancy owner cleanup when applying-host TTL elapsed but the lease
|
|
411
|
+
// is still heartbeat-fresh (#5676 Greptile P1). Soft-pass only when argv
|
|
412
|
+
// already names that live owner so bare commands keep the rewrite path.
|
|
413
|
+
const staleLive = liveOccupant(input.projectRoot, input.now ?? new Date());
|
|
414
|
+
const staleLiveId = staleLive?.sessionId ?? null;
|
|
415
|
+
const staleNamed = input.namedSessionId?.trim() || null;
|
|
416
|
+
if (staleLiveId !== null &&
|
|
417
|
+
read.record.applyingHostSessionId === staleLiveId &&
|
|
418
|
+
staleNamed === staleLiveId) {
|
|
419
|
+
return {
|
|
420
|
+
kind: "soft-pass",
|
|
421
|
+
record: read.record,
|
|
422
|
+
reason: "live-lease-owner-cleanup",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return { kind: "repair", record: read.record, message: read.message };
|
|
426
|
+
}
|
|
427
|
+
if (read.status === "missing" || read.record === null) {
|
|
428
|
+
const named = input.namedSessionId?.trim() || null;
|
|
429
|
+
const deposit = input.depositSessionId?.trim() || null;
|
|
430
|
+
if (named !== null && deposit !== null && named !== deposit) {
|
|
431
|
+
const namedParts = parseCanonicalHostSessionId(named);
|
|
432
|
+
const depositParts = parseCanonicalHostSessionId(deposit);
|
|
433
|
+
if (namedParts !== null &&
|
|
434
|
+
depositParts !== null &&
|
|
435
|
+
namedParts.rawSessionId === depositParts.rawSessionId &&
|
|
436
|
+
namedParts.provider !== depositParts.provider) {
|
|
437
|
+
return {
|
|
438
|
+
kind: "repair",
|
|
439
|
+
record: null,
|
|
440
|
+
message: formatApplyingHostRepairDiagnostic("missing"),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { kind: "absent", record: null };
|
|
445
|
+
}
|
|
446
|
+
const record = read.record;
|
|
447
|
+
// Live occupancy owner cleanup when a later SessionStart replaced applying-host
|
|
448
|
+
// (#5676 Greptile P1). Only soft-pass when argv already names that live owner so
|
|
449
|
+
// bare commands still take the rewrite path (do not skip --session-id injection).
|
|
450
|
+
const live = liveOccupant(input.projectRoot, input.now ?? new Date());
|
|
451
|
+
const liveId = live?.sessionId ?? null;
|
|
452
|
+
const named = input.namedSessionId?.trim() || null;
|
|
453
|
+
if (liveId !== null && record.applyingHostSessionId !== liveId && named === liveId) {
|
|
454
|
+
return {
|
|
455
|
+
kind: "soft-pass",
|
|
456
|
+
record,
|
|
457
|
+
reason: "live-lease-owner-cleanup",
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (isHookHostIdentityProvider(input.depositHost) &&
|
|
461
|
+
input.depositHost === record.provider &&
|
|
462
|
+
input.depositSessionId !== null &&
|
|
463
|
+
input.depositSessionId !== record.applyingHostSessionId) {
|
|
464
|
+
const depositParts = parseCanonicalHostSessionId(input.depositSessionId);
|
|
465
|
+
if (depositParts === null || depositParts.rawSessionId !== record.rawSessionId) {
|
|
466
|
+
return {
|
|
467
|
+
kind: "repair",
|
|
468
|
+
record,
|
|
469
|
+
message: formatApplyingHostRepairDiagnostic("conflicting"),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const matchingDeposit = input.depositHost === record.provider;
|
|
474
|
+
if (!matchingDeposit) {
|
|
475
|
+
// Soft-pass only when argv already names applyingHostSessionId (#5676).
|
|
476
|
+
// Bare commands must not skip the owner rewrite / explicit-owner gate.
|
|
477
|
+
if (named === record.applyingHostSessionId) {
|
|
478
|
+
return {
|
|
479
|
+
kind: "soft-pass",
|
|
480
|
+
record,
|
|
481
|
+
reason: "non-matching-deposit-names-applying-host",
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
kind: "foreign-owner",
|
|
486
|
+
record,
|
|
487
|
+
namedSessionId: named,
|
|
488
|
+
message: named === null
|
|
489
|
+
? `Directive refused bare lifecycle command; applying-host is ` +
|
|
490
|
+
`${record.applyingHostSessionId}. Re-run with ` +
|
|
491
|
+
`--session-id=${record.applyingHostSessionId}.`
|
|
492
|
+
: `Directive refused foreign owner ${named}; applying-host is ` +
|
|
493
|
+
`${record.applyingHostSessionId}. Re-run with ` +
|
|
494
|
+
`--session-id=${record.applyingHostSessionId}.`,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (named !== null && named !== record.applyingHostSessionId) {
|
|
498
|
+
return {
|
|
499
|
+
kind: "foreign-owner",
|
|
500
|
+
record,
|
|
501
|
+
namedSessionId: named,
|
|
502
|
+
message: `Directive refused foreign owner ${named}; applying-host is ` +
|
|
503
|
+
`${record.applyingHostSessionId}. Re-run with ` +
|
|
504
|
+
`--session-id=${record.applyingHostSessionId}.`,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
kind: "enforce",
|
|
509
|
+
record,
|
|
510
|
+
matchingDeposit: true,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Resolve the lease root CLI and hooks share for the same worktree (#5544 P2).
|
|
515
|
+
*
|
|
516
|
+
* Uses the Git worktree toplevel when available (same tree hooks select when
|
|
517
|
+
* payload alone gates, or when writes stay inside that worktree). Linked
|
|
518
|
+
* worktrees keep their own toplevel — never collapsed into the main checkout.
|
|
519
|
+
*/
|
|
520
|
+
const occupancyLeaseRootCache = new Map();
|
|
521
|
+
/**
|
|
522
|
+
* Process-local cache so Write-hook double probes (preview + refresh) and
|
|
523
|
+
* nested readOccupancy calls do not re-shell `git rev-parse` for the same path.
|
|
524
|
+
*/
|
|
525
|
+
export function resolveOccupancyLeaseRoot(projectRoot) {
|
|
526
|
+
const absolute = resolve(projectRoot);
|
|
527
|
+
const cached = occupancyLeaseRootCache.get(absolute);
|
|
528
|
+
if (cached !== undefined)
|
|
529
|
+
return cached;
|
|
530
|
+
const toplevel = worktreePathOrNull(absolute);
|
|
531
|
+
const root = toplevel ?? absolute;
|
|
532
|
+
occupancyLeaseRootCache.set(absolute, root);
|
|
533
|
+
occupancyLeaseRootCache.set(root, root);
|
|
534
|
+
return root;
|
|
535
|
+
}
|
|
536
|
+
/** Print the occupancy file (and optional Write-gate effectiveRoot) (#5544 P2). */
|
|
537
|
+
export function formatOccupancyLeaseViewNote(occupancyFilePath, options = {}) {
|
|
538
|
+
const parts = [`occupancyFile=${occupancyFilePath}`];
|
|
539
|
+
if (options.effectiveRoot !== undefined && options.effectiveRoot.length > 0) {
|
|
540
|
+
parts.push(`effectiveRoot=${options.effectiveRoot}`);
|
|
541
|
+
}
|
|
542
|
+
return parts.join(" ");
|
|
543
|
+
}
|
|
544
|
+
export function formatOccupancyAlreadyFree(occupancyFilePath, options = {}) {
|
|
545
|
+
return `occupancy already free (${formatOccupancyLeaseViewNote(occupancyFilePath, options)})`;
|
|
546
|
+
}
|
|
547
|
+
export function expectedDeclaredHostAttachSignaled(input = {}) {
|
|
548
|
+
if (input.expectedDeclaredHostAttach === true)
|
|
549
|
+
return true;
|
|
550
|
+
const raw = (input.env ?? process.env)[DEFT_EXPECTED_DECLARED_HOST_ATTACH]?.trim() ?? "";
|
|
551
|
+
if (raw.length === 0)
|
|
552
|
+
return false;
|
|
553
|
+
return raw === "1" || raw.toLowerCase() === "true" || raw.toLowerCase() === "yes";
|
|
554
|
+
}
|
|
555
|
+
export function formatOccupancyExpectedHostMintRefusal() {
|
|
556
|
+
return ("occupancy refuses last-resort mint: Harbor/bootstrap signaled a declared agent host will attach " +
|
|
557
|
+
`(${DEFT_EXPECTED_DECLARED_HOST_ATTACH}=1 or expectedDeclaredHostAttach). ` +
|
|
558
|
+
"Claimer surface: Harbor ritual applying applyWorktreeOccupancy. " +
|
|
559
|
+
"Default is host-self-claim after attach. Stay read-only (`session:start --read-only`) or skip mutation session:start. " +
|
|
560
|
+
"If a pre-attach mutation lease is required, bind the exact later host session id " +
|
|
561
|
+
"(`session:start --session-id=<host:provider:v1:...>`) and release successfully before agent launch — " +
|
|
562
|
+
"failed cleanup blocks launch (assertOccupancyFreeForHostLaunch).");
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Harbor launch gate (#5544 Prefer-A): failed pre-attach occupancy cleanup
|
|
566
|
+
* blocks agent launch. Exit 0 only when this lease root is free.
|
|
567
|
+
*/
|
|
568
|
+
export function assertOccupancyFreeForHostLaunch(projectRoot, input = {}) {
|
|
569
|
+
const root = resolveOccupancyLeaseRoot(projectRoot);
|
|
570
|
+
const path = occupancyPath(root);
|
|
571
|
+
const live = liveOccupant(root, input.now ?? new Date());
|
|
572
|
+
if (live === null) {
|
|
573
|
+
return {
|
|
574
|
+
action: "released",
|
|
575
|
+
sessionId: "",
|
|
576
|
+
record: null,
|
|
577
|
+
path,
|
|
578
|
+
message: formatOccupancyAlreadyFree(path),
|
|
579
|
+
code: 0,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const occupantArg = commandSessionId(live.sessionId, "<reported-session-id>");
|
|
583
|
+
return {
|
|
584
|
+
action: "denied",
|
|
585
|
+
sessionId: "",
|
|
586
|
+
record: live,
|
|
587
|
+
path,
|
|
588
|
+
message: `Harbor launch blocked: live occupancy lease still holds (${formatOccupancyLeaseViewNote(path)}). ` +
|
|
589
|
+
`session=${live.sessionId}. Failed pre-attach cleanup blocks launch. ` +
|
|
590
|
+
`Release with \`occupancy:release --session-id=${occupantArg} --project-root=${root}\` before attaching the agent host.`,
|
|
591
|
+
code: 1,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Narrow #5544 Prefer-A P3 gate: live minted lease + canonical host-shaped
|
|
596
|
+
* presenter may steal under confirm. Does not broaden steal defaults.
|
|
597
|
+
*/
|
|
598
|
+
export function occupancyMintedDeclaredHostStealAdmitted(record, presented) {
|
|
599
|
+
if (record.identityProvenance !== "minted")
|
|
600
|
+
return false;
|
|
601
|
+
const actor = presented?.trim() ?? "";
|
|
602
|
+
if (actor.length === 0)
|
|
603
|
+
return false;
|
|
604
|
+
return parseCanonicalHostSessionId(actor) !== null;
|
|
605
|
+
}
|
|
606
|
+
export function formatOccupancyMintedDeclaredHostStealRemediation(record, presented, projectRoot) {
|
|
607
|
+
const root = projectRoot !== undefined ? resolveOccupancyLeaseRoot(projectRoot) : undefined;
|
|
608
|
+
const path = root !== undefined ? occupancyPath(root) : undefined;
|
|
609
|
+
const occupantArg = commandSessionId(record.sessionId, "<reported-session-id>");
|
|
610
|
+
const hostArg = commandSessionId(presented, "<host-published-id>");
|
|
611
|
+
const rootFlag = root !== undefined ? ` --project-root=${JSON.stringify(root)}` : "";
|
|
612
|
+
const view = path !== undefined ? ` Lease view: ${formatOccupancyLeaseViewNote(path)}.` : "";
|
|
613
|
+
const rootNote = root !== undefined ? ` Target root: ${root}.` : "";
|
|
614
|
+
return (`Closed recovery for a minted lease while host-shaped presenter ${presented} is already on this tree:${view}${rootNote} ` +
|
|
615
|
+
`Run \`occupancy:steal --occupant=${occupantArg} --session-id=${hostArg} --confirm${rootFlag}\`` +
|
|
616
|
+
`. That is the permitted lease operation under the minted+declared-host gate (not a broad steal default). ` +
|
|
617
|
+
"Steal changes the lease only; then restore ceremony as the same presenter: " +
|
|
618
|
+
`\`session:start --rearm --session-id=${hostArg}${rootFlag}\` when re-arm is eligible, otherwise ` +
|
|
619
|
+
`\`session:start --session-id=${hostArg}${rootFlag}\` for a cold ceremony. ` +
|
|
620
|
+
"Owner-only UUID release then host re-claim remains available but is not the primary agent path.");
|
|
621
|
+
}
|
|
230
622
|
/**
|
|
231
623
|
* Whether a stored lease names this worktree (#3926).
|
|
232
624
|
*
|
|
@@ -430,7 +822,7 @@ function isOwnInheritedPresentation(occupantId, presented) {
|
|
|
430
822
|
}
|
|
431
823
|
/** Advisory coordination preface for occupancy denials (#3729 Prefer-A AC4). */
|
|
432
824
|
export const OCCUPANCY_ADVISORY_COORDINATION_PREFACE = "Occupancy is advisory coordination for accidental same-machine collision, not same-user authorization.";
|
|
433
|
-
export function formatOccupancyRemediation(record, now = new Date(), presented, env) {
|
|
825
|
+
export function formatOccupancyRemediation(record, now = new Date(), presented, env, projectRoot) {
|
|
434
826
|
const age = heartbeatAgeSeconds(record, now);
|
|
435
827
|
const header = `${OCCUPANCY_ADVISORY_COORDINATION_PREFACE}\n` +
|
|
436
828
|
`Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago, ` +
|
|
@@ -439,6 +831,13 @@ export function formatOccupancyRemediation(record, now = new Date(), presented,
|
|
|
439
831
|
const worktreeFirst = "Use another worktree.";
|
|
440
832
|
const occupantArg = commandSessionId(record.sessionId, "<reported-session-id>");
|
|
441
833
|
if (record.identityProvenance === "minted") {
|
|
834
|
+
// #5544 Prefer-A P3: when a canonical host-shaped id is already presenting,
|
|
835
|
+
// replace "Do not steal" with the closed steal-then-ceremony path.
|
|
836
|
+
const hostPresenter = presented?.trim() ?? "";
|
|
837
|
+
if (occupancyMintedDeclaredHostStealAdmitted(record, hostPresenter)) {
|
|
838
|
+
return (`${header}This lease was claimed under a minted owner. No later hook process can present that id.\n` +
|
|
839
|
+
`${formatOccupancyMintedDeclaredHostStealRemediation(record, hostPresenter, projectRoot)}`);
|
|
840
|
+
}
|
|
442
841
|
const hosts = detectDeclaredIdentityHosts(env ?? process.env);
|
|
443
842
|
const suggested = uniquePrintCompanion(hosts, env ?? process.env);
|
|
444
843
|
const named = suggested === null
|
|
@@ -446,7 +845,8 @@ export function formatOccupancyRemediation(record, now = new Date(), presented,
|
|
|
446
845
|
: commandSessionId(suggested, "<host-published-id>");
|
|
447
846
|
return (`${header}This lease was claimed under a minted owner. No later hook process can present that id.\n` +
|
|
448
847
|
`${formatOccupancySkipMutationClaimWhenMinted()}\n` +
|
|
449
|
-
`${worktreeFirst} Do not steal this lease
|
|
848
|
+
`${worktreeFirst} Do not steal this lease unless a declared host-shaped id is already presenting ` +
|
|
849
|
+
`(then use the minted+declared-host \`occupancy:steal\` gate). Stay read-only (\`session:start --read-only\`), or ` +
|
|
450
850
|
`release it (\`occupancy:release --session-id=${occupantArg}\` / \`session:end\`) and re-claim ` +
|
|
451
851
|
`with the host-published owner (\`session:start --session-id=${named}\`).${tail}`);
|
|
452
852
|
}
|
|
@@ -624,6 +1024,35 @@ export function formatPresentedIdentityDisagreement(identity) {
|
|
|
624
1024
|
*/
|
|
625
1025
|
export function resolveOccupancySessionClaim(input = {}) {
|
|
626
1026
|
const identity = resolvePresentedIdentity(input);
|
|
1027
|
+
// #5544 Prefer-A: when Harbor signals expected declared-host attach, the only
|
|
1028
|
+
// pre-attach mutation exception is an explicit argv --session-id that is
|
|
1029
|
+
// already host-shaped. Inherited DEFT_SESSION_ID (even host-shaped) and
|
|
1030
|
+
// opaque ids are refused so the later host is not locked out.
|
|
1031
|
+
if (expectedDeclaredHostAttachSignaled(input)) {
|
|
1032
|
+
const explicitHostBind = identity.source === "explicit" &&
|
|
1033
|
+
identity.sessionId.length > 0 &&
|
|
1034
|
+
parseCanonicalHostSessionId(identity.sessionId) !== null;
|
|
1035
|
+
if (explicitHostBind) {
|
|
1036
|
+
return {
|
|
1037
|
+
status: "ok",
|
|
1038
|
+
sessionId: identity.sessionId,
|
|
1039
|
+
provenance: "explicit",
|
|
1040
|
+
source: "explicit",
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
if (identity.sessionId.length > 0 || identity.disagreeingHostOwner !== null) {
|
|
1044
|
+
return {
|
|
1045
|
+
status: "refuse-mint",
|
|
1046
|
+
sessionId: "",
|
|
1047
|
+
provenance: null,
|
|
1048
|
+
source: "mint",
|
|
1049
|
+
hosts: [],
|
|
1050
|
+
suggestedSessionId: null,
|
|
1051
|
+
message: formatOccupancyExpectedHostMintRefusal(),
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
// No presented identity yet — fall through to declared-host / empty-mint refuse.
|
|
1055
|
+
}
|
|
627
1056
|
// #4066: host-authoritative claim. Inherited DEFT_SESSION_ID must not beat the
|
|
628
1057
|
// host-published owner -- that split is the measured steal-from-self loop.
|
|
629
1058
|
if (identity.source === "environment" && identity.disagreeingHostOwner !== null) {
|
|
@@ -658,6 +1087,20 @@ export function resolveOccupancySessionClaim(input = {}) {
|
|
|
658
1087
|
message,
|
|
659
1088
|
};
|
|
660
1089
|
}
|
|
1090
|
+
// #5544 Prefer-A P1: Harbor pre-attach empty-hosts mint window. Signal closes
|
|
1091
|
+
// last-resort mint before the declared host attaches; exact --session-id bind
|
|
1092
|
+
// is the only pre-attach mutation exception (already returned above when set).
|
|
1093
|
+
if (expectedDeclaredHostAttachSignaled(input)) {
|
|
1094
|
+
return {
|
|
1095
|
+
status: "refuse-mint",
|
|
1096
|
+
sessionId: "",
|
|
1097
|
+
provenance: null,
|
|
1098
|
+
source: "mint",
|
|
1099
|
+
hosts: [],
|
|
1100
|
+
suggestedSessionId: null,
|
|
1101
|
+
message: formatOccupancyExpectedHostMintRefusal(),
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
661
1104
|
return {
|
|
662
1105
|
status: "ok",
|
|
663
1106
|
sessionId: (input.newSessionId ?? randomUUID)(),
|
|
@@ -696,6 +1139,8 @@ function occupancyMintRefusalDecision(projectRoot, claim) {
|
|
|
696
1139
|
};
|
|
697
1140
|
}
|
|
698
1141
|
export function readOccupancy(projectRoot) {
|
|
1142
|
+
// #5544: reads share the claim/release lease-root view.
|
|
1143
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
699
1144
|
const path = occupancyPath(projectRoot);
|
|
700
1145
|
try {
|
|
701
1146
|
if (!existsSync(path))
|
|
@@ -796,6 +1241,8 @@ export const OCCUPANCY_CEREMONY_CASES = [
|
|
|
796
1241
|
* session:ready. Matches persist, including the sibling-lease primary fence.
|
|
797
1242
|
*/
|
|
798
1243
|
export function evaluateOccupancyCeremonyEligibility(projectRoot, input = {}) {
|
|
1244
|
+
// #5544: same lease-root view as claim / release / Write-gate.
|
|
1245
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
799
1246
|
const now = input.now ?? new Date();
|
|
800
1247
|
const claim = resolveOccupancySessionClaim(input);
|
|
801
1248
|
if (claim.status === "refuse-mint") {
|
|
@@ -822,7 +1269,7 @@ export function evaluateOccupancyCeremonyEligibility(projectRoot, input = {}) {
|
|
|
822
1269
|
sessionId: incoming,
|
|
823
1270
|
occupantId: live.sessionId,
|
|
824
1271
|
restrictedPrimary,
|
|
825
|
-
denialMessage: formatOccupancyRemediation(live, now, incoming, input.env),
|
|
1272
|
+
denialMessage: formatOccupancyRemediation(live, now, incoming, input.env, projectRoot),
|
|
826
1273
|
};
|
|
827
1274
|
}
|
|
828
1275
|
if (live !== null) {
|
|
@@ -830,7 +1277,7 @@ export function evaluateOccupancyCeremonyEligibility(projectRoot, input = {}) {
|
|
|
830
1277
|
if (admission === "member") {
|
|
831
1278
|
const grant = occupancyGrantFor(live, incoming, now);
|
|
832
1279
|
const memberText = grant === null
|
|
833
|
-
? formatOccupancyRemediation(live, now, incoming, input.env)
|
|
1280
|
+
? formatOccupancyRemediation(live, now, incoming, input.env, projectRoot)
|
|
834
1281
|
: formatOccupancyMemberAdministrationRefusal(live, grant, "session:start");
|
|
835
1282
|
return {
|
|
836
1283
|
admitCeremony: false,
|
|
@@ -848,7 +1295,7 @@ export function evaluateOccupancyCeremonyEligibility(projectRoot, input = {}) {
|
|
|
848
1295
|
sessionId: incoming,
|
|
849
1296
|
occupantId: live.sessionId,
|
|
850
1297
|
restrictedPrimary,
|
|
851
|
-
denialMessage: formatOccupancyRemediation(live, now, incoming, input.env),
|
|
1298
|
+
denialMessage: formatOccupancyRemediation(live, now, incoming, input.env, projectRoot),
|
|
852
1299
|
};
|
|
853
1300
|
}
|
|
854
1301
|
return {
|
|
@@ -903,6 +1350,8 @@ export function evaluateOccupancyCeremonyEligibility(projectRoot, input = {}) {
|
|
|
903
1350
|
};
|
|
904
1351
|
}
|
|
905
1352
|
export function applyWorktreeOccupancy(projectRoot, input = {}) {
|
|
1353
|
+
// #5544: claim/read/release share one lease-root view (git worktree toplevel).
|
|
1354
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
906
1355
|
const now = input.now ?? new Date();
|
|
907
1356
|
const path = occupancyPath(projectRoot);
|
|
908
1357
|
const claim = resolveOccupancySessionClaim(input);
|
|
@@ -942,7 +1391,7 @@ export function applyWorktreeOccupancy(projectRoot, input = {}) {
|
|
|
942
1391
|
// A granted child reads the member refusal rather than an offer of the
|
|
943
1392
|
// write grant it already holds (#3954 item 5): membership admits writes,
|
|
944
1393
|
// and claiming the lease stays owner-only.
|
|
945
|
-
message: membershipOwnerDenial(live, incoming, now, "session:start"),
|
|
1394
|
+
message: membershipOwnerDenial(live, incoming, now, "session:start", projectRoot),
|
|
946
1395
|
code: 1,
|
|
947
1396
|
};
|
|
948
1397
|
}
|
|
@@ -976,7 +1425,7 @@ export function applyWorktreeOccupancy(projectRoot, input = {}) {
|
|
|
976
1425
|
sessionId: incoming,
|
|
977
1426
|
record: liveLocked,
|
|
978
1427
|
path,
|
|
979
|
-
message: membershipOwnerDenial(liveLocked, incoming, now, "session:start"),
|
|
1428
|
+
message: membershipOwnerDenial(liveLocked, incoming, now, "session:start", projectRoot),
|
|
980
1429
|
code: 1,
|
|
981
1430
|
};
|
|
982
1431
|
}
|
|
@@ -1068,6 +1517,8 @@ function occupancyStealUsesMintedWriter(input, claim) {
|
|
|
1068
1517
|
return claim.status === "ok" && (claim.provenance === "minted" || claim.source === "mint");
|
|
1069
1518
|
}
|
|
1070
1519
|
export function stealOccupancy(projectRoot, input = {}) {
|
|
1520
|
+
// #5544: steal uses the same lease-root view as claim/release.
|
|
1521
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
1071
1522
|
const now = input.now ?? new Date();
|
|
1072
1523
|
const path = occupancyPath(projectRoot);
|
|
1073
1524
|
const claim = resolveOccupancySessionClaim(input);
|
|
@@ -1099,7 +1550,9 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
1099
1550
|
// Show the occupant's write recency before the steal, not only after it
|
|
1100
1551
|
// (#3599): heartbeat age alone hides an occupant that is mid-edit.
|
|
1101
1552
|
const liveCurrent = liveOccupancyOnTree(projectRoot, current, now);
|
|
1102
|
-
const occupantDetail = liveCurrent !== null
|
|
1553
|
+
const occupantDetail = liveCurrent !== null
|
|
1554
|
+
? `\n${formatOccupancyRemediation(liveCurrent, now, undefined, undefined, projectRoot)}`
|
|
1555
|
+
: "";
|
|
1103
1556
|
return {
|
|
1104
1557
|
action: "denied",
|
|
1105
1558
|
sessionId: incoming,
|
|
@@ -1145,7 +1598,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
1145
1598
|
record: live,
|
|
1146
1599
|
path,
|
|
1147
1600
|
message: `occupancy:steal named occupant ${named} does not match live occupant ${live.sessionId}.\n` +
|
|
1148
|
-
formatOccupancyRemediation(live, now),
|
|
1601
|
+
formatOccupancyRemediation(live, now, undefined, undefined, projectRoot),
|
|
1149
1602
|
code: 1,
|
|
1150
1603
|
};
|
|
1151
1604
|
}
|
|
@@ -1189,7 +1642,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
1189
1642
|
record: liveLocked,
|
|
1190
1643
|
path,
|
|
1191
1644
|
message: `occupancy:steal named occupant ${named} does not match live occupant ${liveLocked.sessionId}.\n` +
|
|
1192
|
-
formatOccupancyRemediation(liveLocked, now),
|
|
1645
|
+
formatOccupancyRemediation(liveLocked, now, undefined, undefined, projectRoot),
|
|
1193
1646
|
code: 1,
|
|
1194
1647
|
};
|
|
1195
1648
|
}
|
|
@@ -1224,16 +1677,19 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
1224
1677
|
};
|
|
1225
1678
|
}
|
|
1226
1679
|
const record = written.record;
|
|
1680
|
+
const root = resolve(projectRoot);
|
|
1227
1681
|
return {
|
|
1228
1682
|
action: "stolen",
|
|
1229
1683
|
sessionId: record.sessionId,
|
|
1230
1684
|
record,
|
|
1231
1685
|
path,
|
|
1232
1686
|
message: `occupancy stolen from ${named}${priorClock}; writer is now session ${record.sessionId}. ` +
|
|
1687
|
+
`Lease view: ${formatOccupancyLeaseViewNote(path)}. Target root: ${root}. ` +
|
|
1688
|
+
`Presenter/writer: ${record.sessionId}. Permitted lease operation completed: occupancy:steal. ` +
|
|
1233
1689
|
"This command changes the lease only; direct writes remain denied unless ritual state already names the same owner. " +
|
|
1234
|
-
"
|
|
1235
|
-
"
|
|
1236
|
-
"for a cold ceremony, using the writer ID above.",
|
|
1690
|
+
"Required ceremony: run " +
|
|
1691
|
+
"`deft session:start --rearm --session-id=<same-session-id>` when re-arm is eligible; otherwise run " +
|
|
1692
|
+
"`deft session:start --session-id=<same-session-id>` for a cold ceremony, using the writer ID above.",
|
|
1237
1693
|
code: 0,
|
|
1238
1694
|
};
|
|
1239
1695
|
}, input.lockDeps);
|
|
@@ -1254,23 +1710,26 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
1254
1710
|
*/
|
|
1255
1711
|
export function releaseOccupancy(projectRoot, input = {}) {
|
|
1256
1712
|
const now = input.now ?? new Date();
|
|
1257
|
-
|
|
1713
|
+
// #5544 P2: share the lease-root view CLI and Write hooks use for this tree.
|
|
1714
|
+
const root = resolveOccupancyLeaseRoot(projectRoot);
|
|
1715
|
+
const path = occupancyPath(root);
|
|
1258
1716
|
const identity = resolvePresentedIdentity(input);
|
|
1259
1717
|
const caller = identity.sessionId;
|
|
1260
1718
|
const split = formatPresentedIdentityDisagreement(identity);
|
|
1261
|
-
|
|
1262
|
-
|
|
1719
|
+
const leaseNote = ` ${formatOccupancyLeaseViewNote(path)}`;
|
|
1720
|
+
return withOccupancyLock(root, (fence) => {
|
|
1721
|
+
const existing = readOccupancy(root);
|
|
1263
1722
|
if (existing === null) {
|
|
1264
1723
|
return {
|
|
1265
1724
|
action: "released",
|
|
1266
1725
|
sessionId: caller,
|
|
1267
1726
|
record: null,
|
|
1268
1727
|
path,
|
|
1269
|
-
message:
|
|
1728
|
+
message: formatOccupancyAlreadyFree(path),
|
|
1270
1729
|
code: 0,
|
|
1271
1730
|
};
|
|
1272
1731
|
}
|
|
1273
|
-
const expired = liveOccupancyOnTree(
|
|
1732
|
+
const expired = liveOccupancyOnTree(root, existing, now) === null;
|
|
1274
1733
|
const owns = caller.length > 0 && caller === existing.sessionId;
|
|
1275
1734
|
if (!expired && !owns) {
|
|
1276
1735
|
return {
|
|
@@ -1278,7 +1737,9 @@ export function releaseOccupancy(projectRoot, input = {}) {
|
|
|
1278
1737
|
sessionId: caller,
|
|
1279
1738
|
record: existing,
|
|
1280
1739
|
path,
|
|
1281
|
-
message: membershipOwnerDenial(existing, caller, now, "occupancy:release") +
|
|
1740
|
+
message: membershipOwnerDenial(existing, caller, now, "occupancy:release", root) +
|
|
1741
|
+
split +
|
|
1742
|
+
leaseNote,
|
|
1282
1743
|
code: 1,
|
|
1283
1744
|
};
|
|
1284
1745
|
}
|
|
@@ -1286,6 +1747,8 @@ export function releaseOccupancy(projectRoot, input = {}) {
|
|
|
1286
1747
|
// never stamp Prefer-A marker / rewrite AGENTS.md for a denied caller (#4544 P1).
|
|
1287
1748
|
// Session exit with this-session untracked product evidence must still reach
|
|
1288
1749
|
// enforce when markWrite persist never ran (Shell-bypass / hookless).
|
|
1750
|
+
// Prefer-A #5544 residual / babysit P1: header check uses the supplied project
|
|
1751
|
+
// root (consumer AGENTS.md), not the git lease root (which may be a parent repo).
|
|
1289
1752
|
const evidenceGate = enforceConsumerHeaderPlaceholderWhenProductEvidence(projectRoot);
|
|
1290
1753
|
if (!evidenceGate.ok) {
|
|
1291
1754
|
return {
|
|
@@ -1293,19 +1756,19 @@ export function releaseOccupancy(projectRoot, input = {}) {
|
|
|
1293
1756
|
sessionId: caller,
|
|
1294
1757
|
record: existing,
|
|
1295
1758
|
path,
|
|
1296
|
-
message: evidenceGate.message,
|
|
1759
|
+
message: evidenceGate.message + leaseNote,
|
|
1297
1760
|
code: 1,
|
|
1298
1761
|
};
|
|
1299
1762
|
}
|
|
1300
1763
|
fence();
|
|
1301
|
-
const still = readOccupancy(
|
|
1764
|
+
const still = readOccupancy(root);
|
|
1302
1765
|
if (still === null) {
|
|
1303
1766
|
return {
|
|
1304
1767
|
action: "released",
|
|
1305
1768
|
sessionId: caller,
|
|
1306
1769
|
record: null,
|
|
1307
1770
|
path,
|
|
1308
|
-
message:
|
|
1771
|
+
message: formatOccupancyAlreadyFree(path),
|
|
1309
1772
|
code: 0,
|
|
1310
1773
|
};
|
|
1311
1774
|
}
|
|
@@ -1318,17 +1781,19 @@ export function releaseOccupancy(projectRoot, input = {}) {
|
|
|
1318
1781
|
sessionId: caller,
|
|
1319
1782
|
record: still,
|
|
1320
1783
|
path,
|
|
1321
|
-
message: membershipOwnerDenial(still, caller, now, "occupancy:release") +
|
|
1784
|
+
message: membershipOwnerDenial(still, caller, now, "occupancy:release", root) +
|
|
1785
|
+
split +
|
|
1786
|
+
leaseNote,
|
|
1322
1787
|
code: 1,
|
|
1323
1788
|
};
|
|
1324
1789
|
}
|
|
1325
|
-
removeOccupancyFile(
|
|
1790
|
+
removeOccupancyFile(root, fence);
|
|
1326
1791
|
return {
|
|
1327
1792
|
action: "released",
|
|
1328
1793
|
sessionId: still.sessionId,
|
|
1329
1794
|
record: null,
|
|
1330
1795
|
path,
|
|
1331
|
-
message: `occupancy released session ${still.sessionId}`,
|
|
1796
|
+
message: `occupancy released session ${still.sessionId} (${formatOccupancyLeaseViewNote(path)})`,
|
|
1332
1797
|
code: 0,
|
|
1333
1798
|
};
|
|
1334
1799
|
}, input.lockDeps);
|
|
@@ -1374,6 +1839,8 @@ function ritualAlignedAfterAcquisition(projectRoot, acquired) {
|
|
|
1374
1839
|
* this persist.
|
|
1375
1840
|
*/
|
|
1376
1841
|
export function rollbackAcquiredOccupancy(projectRoot, acquired, input = {}) {
|
|
1842
|
+
// #5544: rollback must target the same lease root claim wrote.
|
|
1843
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
1377
1844
|
const path = occupancyPath(projectRoot);
|
|
1378
1845
|
if (!isPersistTimeAcquisition(acquired.action)) {
|
|
1379
1846
|
return {
|
|
@@ -1393,7 +1860,7 @@ export function rollbackAcquiredOccupancy(projectRoot, acquired, input = {}) {
|
|
|
1393
1860
|
sessionId: acquired.sessionId,
|
|
1394
1861
|
record: null,
|
|
1395
1862
|
path,
|
|
1396
|
-
message:
|
|
1863
|
+
message: formatOccupancyAlreadyFree(path),
|
|
1397
1864
|
code: 0,
|
|
1398
1865
|
};
|
|
1399
1866
|
}
|
|
@@ -1425,7 +1892,7 @@ export function rollbackAcquiredOccupancy(projectRoot, acquired, input = {}) {
|
|
|
1425
1892
|
sessionId: acquired.sessionId,
|
|
1426
1893
|
record: null,
|
|
1427
1894
|
path,
|
|
1428
|
-
message:
|
|
1895
|
+
message: formatOccupancyAlreadyFree(path),
|
|
1429
1896
|
code: 0,
|
|
1430
1897
|
};
|
|
1431
1898
|
}
|
|
@@ -1470,10 +1937,10 @@ export function rollbackAcquiredOccupancy(projectRoot, acquired, input = {}) {
|
|
|
1470
1937
|
* have chosen explicitly, so a refusal that does not name what was presented
|
|
1471
1938
|
* leaves it guessing which identity it was refused under.
|
|
1472
1939
|
*/
|
|
1473
|
-
function membershipOwnerDenial(live, caller, now, verb) {
|
|
1940
|
+
function membershipOwnerDenial(live, caller, now, verb, projectRoot) {
|
|
1474
1941
|
const grant = occupancyGrantFor(live, caller, now);
|
|
1475
1942
|
return grant === null
|
|
1476
|
-
? formatOccupancyRemediation(live, now, caller)
|
|
1943
|
+
? formatOccupancyRemediation(live, now, caller, undefined, projectRoot)
|
|
1477
1944
|
: formatOccupancyMemberAdministrationRefusal(live, grant, verb);
|
|
1478
1945
|
}
|
|
1479
1946
|
/**
|
|
@@ -1486,6 +1953,8 @@ function membershipOwnerDenial(live, caller, now, verb) {
|
|
|
1486
1953
|
* cap — and it buys writes alone.
|
|
1487
1954
|
*/
|
|
1488
1955
|
export function grantOccupancyMembership(projectRoot, input = {}) {
|
|
1956
|
+
// #5544: membership verbs share claim/release lease-root view.
|
|
1957
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
1489
1958
|
const now = input.now ?? new Date();
|
|
1490
1959
|
const path = occupancyPath(projectRoot);
|
|
1491
1960
|
const identity = resolvePresentedIdentity(input);
|
|
@@ -1604,7 +2073,7 @@ export function grantOccupancyMembership(projectRoot, input = {}) {
|
|
|
1604
2073
|
sessionId: owner,
|
|
1605
2074
|
record: live,
|
|
1606
2075
|
path,
|
|
1607
|
-
message: membershipOwnerDenial(live, owner, now, "occupancy:grant") + split,
|
|
2076
|
+
message: membershipOwnerDenial(live, owner, now, "occupancy:grant", projectRoot) + split,
|
|
1608
2077
|
code: 1,
|
|
1609
2078
|
};
|
|
1610
2079
|
}
|
|
@@ -1695,6 +2164,8 @@ export function grantOccupancyMembership(projectRoot, input = {}) {
|
|
|
1695
2164
|
* never have been admitted, and the owner wants that true now.
|
|
1696
2165
|
*/
|
|
1697
2166
|
export function revokeOccupancyMembership(projectRoot, input = {}) {
|
|
2167
|
+
// #5544: membership verbs share claim/release lease-root view.
|
|
2168
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
1698
2169
|
const now = input.now ?? new Date();
|
|
1699
2170
|
const path = occupancyPath(projectRoot);
|
|
1700
2171
|
const identity = resolvePresentedIdentity(input);
|
|
@@ -1734,7 +2205,8 @@ export function revokeOccupancyMembership(projectRoot, input = {}) {
|
|
|
1734
2205
|
sessionId: owner,
|
|
1735
2206
|
record: live,
|
|
1736
2207
|
path,
|
|
1737
|
-
message: membershipOwnerDenial(live, owner, now, "occupancy:grant --revoke") +
|
|
2208
|
+
message: membershipOwnerDenial(live, owner, now, "occupancy:grant --revoke", projectRoot) +
|
|
2209
|
+
split,
|
|
1738
2210
|
code: 1,
|
|
1739
2211
|
};
|
|
1740
2212
|
}
|
|
@@ -1795,6 +2267,10 @@ export function revokeOccupancyMembership(projectRoot, input = {}) {
|
|
|
1795
2267
|
*/
|
|
1796
2268
|
export function evaluateOccupancyWriteGate(projectRoot, input = {}) {
|
|
1797
2269
|
const now = input.now ?? new Date();
|
|
2270
|
+
// #5544: lease identity is always the git worktree toplevel. leaseRoot is only
|
|
2271
|
+
// a preferred base path (hook effectiveRoot may be a subdirectory when writes
|
|
2272
|
+
// leave the git tree); never skip resolve, or read/match disagree (#5544 P1).
|
|
2273
|
+
projectRoot = resolveOccupancyLeaseRoot(input.leaseRoot ?? projectRoot);
|
|
1798
2274
|
const incoming = input.sessionId?.trim() || (input.env ?? process.env).DEFT_SESSION_ID?.trim() || "";
|
|
1799
2275
|
const stored = readOccupancy(projectRoot);
|
|
1800
2276
|
const record = stored !== null && occupancyWorktreeMatches(stored.worktreePath, projectRoot) ? stored : null;
|
|
@@ -1848,12 +2324,16 @@ export function evaluateOccupancyWriteGate(projectRoot, input = {}) {
|
|
|
1848
2324
|
// Two-axis unknown-identity axis (#3729 Prefer-A): live lease + stranger →
|
|
1849
2325
|
// fail closed (OCCUPANCY_UNKNOWN_IDENTITY_POLICY). Advisory coordination,
|
|
1850
2326
|
// not authorization — prefer another worktree / grant over steal-as-primary.
|
|
2327
|
+
// #5544 P2: name the occupancy file (and effectiveRoot when the Write gate
|
|
2328
|
+
// supplies this projectRoot) so CLI/hook split-brain is diagnosable.
|
|
2329
|
+
const path = occupancyPath(projectRoot);
|
|
1851
2330
|
return {
|
|
1852
2331
|
allow: false,
|
|
1853
2332
|
// The refused caller is told what identity it actually presented (#3873).
|
|
1854
2333
|
// A hook process cannot otherwise know, and the grant this message offers
|
|
1855
2334
|
// is only runnable when the occupant can name a non-empty child.
|
|
1856
|
-
message: formatOccupancyRemediation(live, now, incoming, input.env)
|
|
2335
|
+
message: `${formatOccupancyRemediation(live, now, incoming, input.env, projectRoot)} ` +
|
|
2336
|
+
formatOccupancyLeaseViewNote(path, { effectiveRoot: projectRoot }),
|
|
1857
2337
|
occupant: live,
|
|
1858
2338
|
refreshed: false,
|
|
1859
2339
|
warning: null,
|
|
@@ -2031,6 +2511,8 @@ persistProductMutationMarker = false) {
|
|
|
2031
2511
|
* heartbeat into long verbs cannot close that deadlock: this verb is owner-only.
|
|
2032
2512
|
*/
|
|
2033
2513
|
export function heartbeatOccupancy(projectRoot, input = {}) {
|
|
2514
|
+
// #5544: heartbeat shares claim/release lease-root view.
|
|
2515
|
+
projectRoot = resolveOccupancyLeaseRoot(projectRoot);
|
|
2034
2516
|
const now = input.now ?? new Date();
|
|
2035
2517
|
const path = occupancyPath(projectRoot);
|
|
2036
2518
|
const identity = resolvePresentedIdentity(input);
|
|
@@ -2072,7 +2554,7 @@ export function heartbeatOccupancy(projectRoot, input = {}) {
|
|
|
2072
2554
|
sessionId: caller,
|
|
2073
2555
|
record: live,
|
|
2074
2556
|
path,
|
|
2075
|
-
message: membershipOwnerDenial(live, caller, now, "occupancy:heartbeat") + split,
|
|
2557
|
+
message: membershipOwnerDenial(live, caller, now, "occupancy:heartbeat", projectRoot) + split,
|
|
2076
2558
|
code: 1,
|
|
2077
2559
|
};
|
|
2078
2560
|
}
|