@deftai/directive-core 0.108.0 → 0.109.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/operations.js +1 -1
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +7 -0
- package/dist/design-critique/citation-grammar.js +1 -1
- package/dist/design-critique/completed-arc-record.d.ts +8 -1
- package/dist/design-critique/completed-arc-record.js +98 -14
- package/dist/hooks/classify/host-session-identity.d.ts +25 -12
- package/dist/hooks/classify/host-session-identity.js +64 -49
- package/dist/hooks/classify/index.d.ts +2 -2
- package/dist/hooks/classify/index.js +2 -2
- package/dist/hooks/classify/paths.d.ts +2 -0
- package/dist/hooks/classify/paths.js +8 -4
- package/dist/hooks/classify/stdin.js +69 -3
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +22 -2
- package/dist/hooks/dispatcher.js +338 -66
- package/dist/hooks/fixtures/cases.d.ts +6 -0
- package/dist/hooks/fixtures/cases.js +53 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/owner-liveness.d.ts +92 -0
- package/dist/hooks/owner-liveness.js +103 -0
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +49 -2
- package/dist/hooks/tools.js +96 -1
- package/dist/init-deposit/agent-hooks.d.ts +10 -0
- package/dist/init-deposit/agent-hooks.js +39 -0
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/host-tool-coverage.d.ts +53 -0
- package/dist/init-deposit/host-tool-coverage.js +150 -0
- package/dist/init-deposit/index.d.ts +1 -0
- package/dist/init-deposit/index.js +1 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +21 -2
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orchestration/subagent-monitor.d.ts +6 -0
- package/dist/orchestration/subagent-monitor.js +23 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +27 -0
- package/dist/orphan-active/evaluate.js +63 -6
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.js +36 -9
- package/dist/pr-wait-mergeable/wrappers.js +3 -3
- package/dist/product-first-done-gate/evaluate.js +6 -2
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/pipeline.js +55 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +6 -0
- package/dist/review-monitor/github-lease.js +4 -3
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +41 -11
- package/dist/scm/gh-rest.d.ts +25 -12
- package/dist/scm/gh-rest.js +65 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/child-occupancy.d.ts +72 -0
- package/dist/session/child-occupancy.js +209 -0
- package/dist/session/host-session-owner.d.ts +93 -0
- package/dist/session/host-session-owner.js +148 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/occupancy.d.ts +107 -2
- package/dist/session/occupancy.js +272 -34
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/swarm/complete-cohort.js +2 -0
- package/dist/swarm/pre-dispatch.js +2 -0
- package/dist/swarm/subagent-status-dir.d.ts +2 -1
- package/dist/swarm/subagent-status-dir.js +11 -2
- package/dist/swarm/worktrees.js +2 -0
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-env/agent-hooks.d.ts +6 -1
- package/dist/verify-env/agent-hooks.js +28 -2
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +3 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-recorded child occupancy leases (#3999).
|
|
3
|
+
*
|
|
4
|
+
* A parent records the child's occupancy owner and the exact worktree root at
|
|
5
|
+
* dispatch in `.deft/child-occupancy/` — lease-gated, not `.deft-scratch/**`.
|
|
6
|
+
* The orchestration terminal transition already carries agent_id / parent_id /
|
|
7
|
+
* phase; this store is the missing occupancy-owner datum. Release reuses
|
|
8
|
+
* `releaseOccupancy` under the occupancy lock and only fires when the recorded
|
|
9
|
+
* child is still the current owner of the recorded tree.
|
|
10
|
+
*
|
|
11
|
+
* Per identity-source kind: `host-env` children are strangers and strand —
|
|
12
|
+
* that is the defect. `payload` parents share one id with their children, so
|
|
13
|
+
* the same transition is a no-op; auto-release would drop a live parent lease
|
|
14
|
+
* mid-flight. Swarm close-out of the launcher's occupancy_session_id is not
|
|
15
|
+
* the precedent and is not copied here.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
18
|
+
import { join, resolve } from "node:path";
|
|
19
|
+
import { containedRemove, containedWrite } from "../fs/contained-write.js";
|
|
20
|
+
import { hookHostIdentitySource } from "./host-session-owner.js";
|
|
21
|
+
import { stableJson } from "./json.js";
|
|
22
|
+
import { readOccupancy, releaseOccupancy } from "./occupancy.js";
|
|
23
|
+
export const CHILD_OCCUPANCY_SCHEMA_VERSION = 1;
|
|
24
|
+
export const CHILD_OCCUPANCY_DIR = [".deft", "child-occupancy"];
|
|
25
|
+
export const CHILD_OCCUPANCY_IDENTITY_SOURCE_KINDS = ["host-env", "payload"];
|
|
26
|
+
function isIdentitySourceKind(value) {
|
|
27
|
+
return CHILD_OCCUPANCY_IDENTITY_SOURCE_KINDS.includes(value);
|
|
28
|
+
}
|
|
29
|
+
/** Filename-safe agent id; the payload keeps the original. */
|
|
30
|
+
export function childOccupancyFileSegment(agentId) {
|
|
31
|
+
let cleaned = "";
|
|
32
|
+
for (const ch of agentId.trim()) {
|
|
33
|
+
if ((ch >= "A" && ch <= "Z") ||
|
|
34
|
+
(ch >= "a" && ch <= "z") ||
|
|
35
|
+
(ch >= "0" && ch <= "9") ||
|
|
36
|
+
ch === "." ||
|
|
37
|
+
ch === "_" ||
|
|
38
|
+
ch === "-") {
|
|
39
|
+
cleaned += ch;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
cleaned += "-";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let start = 0;
|
|
46
|
+
let end = cleaned.length;
|
|
47
|
+
while (start < end && (cleaned[start] === "-" || cleaned[start] === "."))
|
|
48
|
+
start += 1;
|
|
49
|
+
while (end > start && (cleaned[end - 1] === "-" || cleaned[end - 1] === "."))
|
|
50
|
+
end -= 1;
|
|
51
|
+
cleaned = cleaned.slice(start, end);
|
|
52
|
+
return cleaned.length > 0 ? cleaned : "agent";
|
|
53
|
+
}
|
|
54
|
+
export function childOccupancyRelpath(agentId) {
|
|
55
|
+
return [...CHILD_OCCUPANCY_DIR, `${childOccupancyFileSegment(agentId)}.json`];
|
|
56
|
+
}
|
|
57
|
+
export function childOccupancyPath(storeRoot, agentId) {
|
|
58
|
+
return join(resolve(storeRoot), ...childOccupancyRelpath(agentId));
|
|
59
|
+
}
|
|
60
|
+
export function childOccupancyIdentitySourceKind(host) {
|
|
61
|
+
const source = hookHostIdentitySource(host);
|
|
62
|
+
if (source === null)
|
|
63
|
+
return null;
|
|
64
|
+
return source.kind;
|
|
65
|
+
}
|
|
66
|
+
function parseChildOccupancyRecord(payload) {
|
|
67
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload))
|
|
68
|
+
return null;
|
|
69
|
+
const obj = payload;
|
|
70
|
+
const agentId = typeof obj.agent_id === "string" ? obj.agent_id.trim() : "";
|
|
71
|
+
const parentId = typeof obj.parent_id === "string" ? obj.parent_id.trim() : "";
|
|
72
|
+
const occupancyOwner = typeof obj.occupancy_owner === "string" ? obj.occupancy_owner.trim() : "";
|
|
73
|
+
const worktreePath = typeof obj.worktree_path === "string" ? obj.worktree_path.trim() : "";
|
|
74
|
+
const kindRaw = typeof obj.identity_source_kind === "string" ? obj.identity_source_kind.trim() : "";
|
|
75
|
+
if (agentId.length === 0 ||
|
|
76
|
+
parentId.length === 0 ||
|
|
77
|
+
occupancyOwner.length === 0 ||
|
|
78
|
+
worktreePath.length === 0 ||
|
|
79
|
+
!isIdentitySourceKind(kindRaw)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: typeof obj.schemaVersion === "number" ? obj.schemaVersion : CHILD_OCCUPANCY_SCHEMA_VERSION,
|
|
84
|
+
agentId,
|
|
85
|
+
parentId,
|
|
86
|
+
occupancyOwner,
|
|
87
|
+
worktreePath,
|
|
88
|
+
identitySourceKind: kindRaw,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function readChildOccupancyLease(storeRoot, agentId) {
|
|
92
|
+
const path = childOccupancyPath(storeRoot, agentId);
|
|
93
|
+
try {
|
|
94
|
+
if (!existsSync(path))
|
|
95
|
+
return null;
|
|
96
|
+
return parseChildOccupancyRecord(JSON.parse(readFileSync(path, { encoding: "utf8" })));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parent-only write at dispatch. Workers cannot author this store: `.deft/` is
|
|
104
|
+
* not assist-scratch, so a mutation write is occupancy-gated and an assist
|
|
105
|
+
* writer does not get the scratch carve-out.
|
|
106
|
+
*/
|
|
107
|
+
export function recordChildOccupancyLease(storeRoot, input) {
|
|
108
|
+
const agentId = input.agentId.trim();
|
|
109
|
+
const parentId = input.parentId.trim();
|
|
110
|
+
const occupancyOwner = input.occupancyOwner.trim();
|
|
111
|
+
const worktreePath = resolve(input.worktreePath.trim());
|
|
112
|
+
if (agentId.length === 0)
|
|
113
|
+
throw new Error("recordChildOccupancyLease needs agentId");
|
|
114
|
+
if (parentId.length === 0)
|
|
115
|
+
throw new Error("recordChildOccupancyLease needs parentId");
|
|
116
|
+
if (occupancyOwner.length === 0) {
|
|
117
|
+
throw new Error("recordChildOccupancyLease needs occupancyOwner");
|
|
118
|
+
}
|
|
119
|
+
if (input.worktreePath.trim().length === 0) {
|
|
120
|
+
throw new Error("recordChildOccupancyLease needs worktreePath");
|
|
121
|
+
}
|
|
122
|
+
const record = {
|
|
123
|
+
schemaVersion: CHILD_OCCUPANCY_SCHEMA_VERSION,
|
|
124
|
+
agentId,
|
|
125
|
+
parentId,
|
|
126
|
+
occupancyOwner,
|
|
127
|
+
worktreePath,
|
|
128
|
+
identitySourceKind: input.identitySourceKind,
|
|
129
|
+
};
|
|
130
|
+
const root = resolve(storeRoot);
|
|
131
|
+
const relpath = childOccupancyRelpath(agentId);
|
|
132
|
+
mkdirSync(join(root, ...CHILD_OCCUPANCY_DIR), { recursive: true });
|
|
133
|
+
containedWrite({
|
|
134
|
+
root,
|
|
135
|
+
target: join(...relpath),
|
|
136
|
+
data: `${stableJson({
|
|
137
|
+
schemaVersion: record.schemaVersion,
|
|
138
|
+
agent_id: record.agentId,
|
|
139
|
+
parent_id: record.parentId,
|
|
140
|
+
occupancy_owner: record.occupancyOwner,
|
|
141
|
+
worktree_path: record.worktreePath,
|
|
142
|
+
identity_source_kind: record.identitySourceKind,
|
|
143
|
+
}, 2)}\n`,
|
|
144
|
+
mode: "replace",
|
|
145
|
+
});
|
|
146
|
+
return record;
|
|
147
|
+
}
|
|
148
|
+
function removeChildOccupancyLease(storeRoot, agentId) {
|
|
149
|
+
const root = resolve(storeRoot);
|
|
150
|
+
const relpath = childOccupancyRelpath(agentId);
|
|
151
|
+
const abs = join(root, ...relpath);
|
|
152
|
+
if (!existsSync(abs))
|
|
153
|
+
return;
|
|
154
|
+
containedRemove({ root, target: join(...relpath) });
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Compare-and-release under the occupancy lock. Caller identity is the id the
|
|
158
|
+
* parent recorded at dispatch — not the occupant currently named in the lease
|
|
159
|
+
* file, and not a field on a worker-authored heartbeat.
|
|
160
|
+
*/
|
|
161
|
+
export function releaseChildOccupancyOnTerminal(storeRoot, input) {
|
|
162
|
+
const agentId = input.agentId.trim();
|
|
163
|
+
if (agentId.length === 0) {
|
|
164
|
+
return { reason: "missing-record", record: null, occupancy: null };
|
|
165
|
+
}
|
|
166
|
+
const record = readChildOccupancyLease(storeRoot, agentId);
|
|
167
|
+
if (record === null) {
|
|
168
|
+
return { reason: "missing-record", record: null, occupancy: null };
|
|
169
|
+
}
|
|
170
|
+
if (record.identitySourceKind === "payload") {
|
|
171
|
+
return { reason: "payload-skip", record, occupancy: null };
|
|
172
|
+
}
|
|
173
|
+
const tree = resolve(record.worktreePath);
|
|
174
|
+
const now = input.now ?? new Date();
|
|
175
|
+
const live = readOccupancy(tree);
|
|
176
|
+
if (live === null) {
|
|
177
|
+
removeChildOccupancyLease(storeRoot, agentId);
|
|
178
|
+
return { reason: "already-free", record, occupancy: null };
|
|
179
|
+
}
|
|
180
|
+
if (live.sessionId !== record.occupancyOwner) {
|
|
181
|
+
return { reason: "owner-changed", record, occupancy: null };
|
|
182
|
+
}
|
|
183
|
+
const occupancy = releaseOccupancy(tree, {
|
|
184
|
+
sessionId: record.occupancyOwner,
|
|
185
|
+
now,
|
|
186
|
+
env: {},
|
|
187
|
+
lockDeps: input.lockDeps,
|
|
188
|
+
});
|
|
189
|
+
if (occupancy.action === "released") {
|
|
190
|
+
removeChildOccupancyLease(storeRoot, agentId);
|
|
191
|
+
if (resolve(storeRoot) !== tree)
|
|
192
|
+
removeChildOccupancyLease(tree, agentId);
|
|
193
|
+
return { reason: "released", record, occupancy };
|
|
194
|
+
}
|
|
195
|
+
return { reason: "denied", record, occupancy };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Worktree guesses for a heartbeat file. Canonical layout is
|
|
199
|
+
* `<worktree>/.deft-scratch/subagent-status/<agent>.json`; cwd is the fallback
|
|
200
|
+
* when the scratch dir was passed as a custom path.
|
|
201
|
+
*/
|
|
202
|
+
export function worktreeCandidatesForHeartbeat(heartbeatPath, cwd) {
|
|
203
|
+
const fromHeartbeat = resolve(heartbeatPath, "..", "..", "..");
|
|
204
|
+
const fromCwd = resolve(cwd);
|
|
205
|
+
if (fromHeartbeat === fromCwd)
|
|
206
|
+
return [fromHeartbeat];
|
|
207
|
+
return [fromHeartbeat, fromCwd];
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=child-occupancy.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a host publishes the session id Directive uses as the occupancy owner
|
|
3
|
+
* (#3611 / #3873).
|
|
4
|
+
*
|
|
5
|
+
* This is payload/environment classification, not authentication: cooperative
|
|
6
|
+
* owner ids are locally forgeable by another same-user process. It lives here,
|
|
7
|
+
* outside the hook classifier, because two processes must agree on the answer.
|
|
8
|
+
* The hook resolves it from its own environment; the CLI resolves it from the
|
|
9
|
+
* shell the same host spawned. A claim made under a different id than the write
|
|
10
|
+
* gate later presents is the #3873 defect -- the session denied by its own lease.
|
|
11
|
+
*/
|
|
12
|
+
export declare const HOST_IDENTITY_PROVIDERS: readonly ["codex", "claude", "cursor", "grok"];
|
|
13
|
+
export type HookHostIdentityProvider = (typeof HOST_IDENTITY_PROVIDERS)[number];
|
|
14
|
+
export declare const MAX_HOOK_HOST_IDENTITY_UTF8_BYTES = 512;
|
|
15
|
+
/**
|
|
16
|
+
* Where a provider publishes its owner id.
|
|
17
|
+
*
|
|
18
|
+
* `payload` providers carry a verified field on every hook invocation.
|
|
19
|
+
* `host-env` providers publish a stable id in the environment of the processes
|
|
20
|
+
* the host spawns. That environment is host-set: the hook is a sibling of the
|
|
21
|
+
* agent's shell rather than a descendant, so a shell export cannot reach it.
|
|
22
|
+
*/
|
|
23
|
+
export type HookHostIdentitySource = {
|
|
24
|
+
readonly kind: "payload";
|
|
25
|
+
readonly field: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "host-env";
|
|
28
|
+
readonly variable: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Every variable a `host-env` provider publishes.
|
|
32
|
+
*
|
|
33
|
+
* Exported because the ambient step is now read by the CLI occupancy surfaces
|
|
34
|
+
* as well as the hook, so callers that must control the whole ambient identity
|
|
35
|
+
* surface — a hermetic test process, a dispatcher scrubbing a child's
|
|
36
|
+
* environment — need the list rather than the one variable its author knew
|
|
37
|
+
* about (#3954).
|
|
38
|
+
*/
|
|
39
|
+
export declare const HOST_ENV_IDENTITY_VARIABLES: readonly string[];
|
|
40
|
+
/** The identity source for a host, or null when the host has no contract. */
|
|
41
|
+
export declare function hookHostIdentitySource(host: string): HookHostIdentitySource | null;
|
|
42
|
+
/** Bound a raw host id by UTF-8 bytes, control characters and surrogate pairing. */
|
|
43
|
+
export declare function isUsableHostSessionId(raw: string): boolean;
|
|
44
|
+
export declare function canonicalHostSessionId(provider: HookHostIdentityProvider, rawSessionId: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* The canonical owner form, derived from the provider list so every surface
|
|
47
|
+
* that checks it moves when a provider is added: the lifecycle-rewrite bridge
|
|
48
|
+
* and grant-time child validation both read this one pattern (#3873 / #3954).
|
|
49
|
+
* Provider ids are lowercase ASCII words, so the alternation needs no escaping.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CANONICAL_OWNER_PATTERN: RegExp;
|
|
52
|
+
/**
|
|
53
|
+
* True when a value claims the canonical owner shape, well-formed or not.
|
|
54
|
+
*
|
|
55
|
+
* The `host:` prefix is reserved for host-published identity, so a value under
|
|
56
|
+
* it that is not canonical is a malformed owner rather than an opaque id some
|
|
57
|
+
* session could present (#3954).
|
|
58
|
+
*/
|
|
59
|
+
export declare function claimsHostSessionIdShape(value: string): boolean;
|
|
60
|
+
export interface HostSessionIdParts {
|
|
61
|
+
readonly provider: HookHostIdentityProvider;
|
|
62
|
+
readonly rawSessionId: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Split a canonical owner back into the provider and the raw id the host
|
|
66
|
+
* published, or null when the value is not canonical.
|
|
67
|
+
*
|
|
68
|
+
* The round-trip check is the point: `host:grok:v1:Z3Jvay1zZXNzaW9uLWF` decodes
|
|
69
|
+
* to the same raw id as `...LWE` does, so without it one session would have two
|
|
70
|
+
* canonical strings and a grant could name the one it never presents (#3954).
|
|
71
|
+
* The raw id is held to the same bound the identity surface applies, so a
|
|
72
|
+
* payload the host could not have published is not accepted here either.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseCanonicalHostSessionId(value: string): HostSessionIdParts | null;
|
|
75
|
+
export type HostEnvIdentityStatus = "ok" | "missing" | "invalid";
|
|
76
|
+
export type HostEnvIdentityResolution = {
|
|
77
|
+
readonly status: "ok";
|
|
78
|
+
readonly rawSessionId: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly status: "missing" | "invalid";
|
|
81
|
+
readonly rawSessionId: null;
|
|
82
|
+
};
|
|
83
|
+
/** Read one `host-env` provider's variable out of a process environment. */
|
|
84
|
+
export declare function readHostEnvIdentity(environ: NodeJS.ProcessEnv, variable: string): HostEnvIdentityResolution;
|
|
85
|
+
/**
|
|
86
|
+
* The canonical owner the running host published to this process, or null.
|
|
87
|
+
*
|
|
88
|
+
* Used by the CLI claim path so `session:start` binds the lease to the same id
|
|
89
|
+
* the write hook will present. Two live host variables are ambiguous and
|
|
90
|
+
* resolve to null: picking one would bind a claim to a guess.
|
|
91
|
+
*/
|
|
92
|
+
export declare function ambientHostSessionOwner(environ?: NodeJS.ProcessEnv): string | null;
|
|
93
|
+
//# sourceMappingURL=host-session-owner.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a host publishes the session id Directive uses as the occupancy owner
|
|
3
|
+
* (#3611 / #3873).
|
|
4
|
+
*
|
|
5
|
+
* This is payload/environment classification, not authentication: cooperative
|
|
6
|
+
* owner ids are locally forgeable by another same-user process. It lives here,
|
|
7
|
+
* outside the hook classifier, because two processes must agree on the answer.
|
|
8
|
+
* The hook resolves it from its own environment; the CLI resolves it from the
|
|
9
|
+
* shell the same host spawned. A claim made under a different id than the write
|
|
10
|
+
* gate later presents is the #3873 defect -- the session denied by its own lease.
|
|
11
|
+
*/
|
|
12
|
+
export const HOST_IDENTITY_PROVIDERS = ["codex", "claude", "cursor", "grok"];
|
|
13
|
+
export const MAX_HOOK_HOST_IDENTITY_UTF8_BYTES = 512;
|
|
14
|
+
const HOST_IDENTITY_SOURCES = {
|
|
15
|
+
codex: { kind: "payload", field: "session_id" },
|
|
16
|
+
claude: { kind: "payload", field: "session_id" },
|
|
17
|
+
cursor: { kind: "payload", field: "conversation_id" },
|
|
18
|
+
grok: { kind: "host-env", variable: "GROK_SESSION_ID" },
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The `host-env` half of the table above, resolved once.
|
|
22
|
+
*
|
|
23
|
+
* `ambientHostSessionOwner` reads it to build the owner list, and
|
|
24
|
+
* `HOST_ENV_IDENTITY_VARIABLES` projects the variable names out of the same
|
|
25
|
+
* entries, so the production scan and the surface a caller scrubs cannot name
|
|
26
|
+
* different providers (#3954).
|
|
27
|
+
*/
|
|
28
|
+
const HOST_ENV_IDENTITY_ENTRIES = HOST_IDENTITY_PROVIDERS.flatMap((provider) => {
|
|
29
|
+
const source = HOST_IDENTITY_SOURCES[provider];
|
|
30
|
+
return source.kind === "host-env" ? [{ provider, variable: source.variable }] : [];
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Every variable a `host-env` provider publishes.
|
|
34
|
+
*
|
|
35
|
+
* Exported because the ambient step is now read by the CLI occupancy surfaces
|
|
36
|
+
* as well as the hook, so callers that must control the whole ambient identity
|
|
37
|
+
* surface — a hermetic test process, a dispatcher scrubbing a child's
|
|
38
|
+
* environment — need the list rather than the one variable its author knew
|
|
39
|
+
* about (#3954).
|
|
40
|
+
*/
|
|
41
|
+
export const HOST_ENV_IDENTITY_VARIABLES = HOST_ENV_IDENTITY_ENTRIES.map((entry) => entry.variable);
|
|
42
|
+
/** The identity source for a host, or null when the host has no contract. */
|
|
43
|
+
export function hookHostIdentitySource(host) {
|
|
44
|
+
return (HOST_IDENTITY_SOURCES[host] ?? null);
|
|
45
|
+
}
|
|
46
|
+
function hasControlCharacter(value) {
|
|
47
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
48
|
+
const unit = value.charCodeAt(index);
|
|
49
|
+
if (unit <= 0x1f || (unit >= 0x7f && unit <= 0x9f))
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function hasUnpairedUtf16Surrogate(value) {
|
|
55
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
56
|
+
const unit = value.charCodeAt(index);
|
|
57
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
58
|
+
const next = value.charCodeAt(index + 1);
|
|
59
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
60
|
+
return true;
|
|
61
|
+
index += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (unit >= 0xdc00 && unit <= 0xdfff)
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
/** Bound a raw host id by UTF-8 bytes, control characters and surrogate pairing. */
|
|
70
|
+
export function isUsableHostSessionId(raw) {
|
|
71
|
+
return (raw.length > 0 &&
|
|
72
|
+
raw === raw.trim() &&
|
|
73
|
+
!hasControlCharacter(raw) &&
|
|
74
|
+
!hasUnpairedUtf16Surrogate(raw) &&
|
|
75
|
+
Buffer.byteLength(raw, "utf8") <= MAX_HOOK_HOST_IDENTITY_UTF8_BYTES);
|
|
76
|
+
}
|
|
77
|
+
export function canonicalHostSessionId(provider, rawSessionId) {
|
|
78
|
+
const encoded = Buffer.from(rawSessionId, "utf8").toString("base64url");
|
|
79
|
+
return `host:${provider}:v1:${encoded}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The canonical owner form, derived from the provider list so every surface
|
|
83
|
+
* that checks it moves when a provider is added: the lifecycle-rewrite bridge
|
|
84
|
+
* and grant-time child validation both read this one pattern (#3873 / #3954).
|
|
85
|
+
* Provider ids are lowercase ASCII words, so the alternation needs no escaping.
|
|
86
|
+
*/
|
|
87
|
+
export const CANONICAL_OWNER_PATTERN = new RegExp(`^host:(?:${HOST_IDENTITY_PROVIDERS.join("|")}):v1:[A-Za-z0-9_-]+$`);
|
|
88
|
+
/**
|
|
89
|
+
* True when a value claims the canonical owner shape, well-formed or not.
|
|
90
|
+
*
|
|
91
|
+
* The `host:` prefix is reserved for host-published identity, so a value under
|
|
92
|
+
* it that is not canonical is a malformed owner rather than an opaque id some
|
|
93
|
+
* session could present (#3954).
|
|
94
|
+
*/
|
|
95
|
+
export function claimsHostSessionIdShape(value) {
|
|
96
|
+
return value.startsWith("host:");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Split a canonical owner back into the provider and the raw id the host
|
|
100
|
+
* published, or null when the value is not canonical.
|
|
101
|
+
*
|
|
102
|
+
* The round-trip check is the point: `host:grok:v1:Z3Jvay1zZXNzaW9uLWF` decodes
|
|
103
|
+
* to the same raw id as `...LWE` does, so without it one session would have two
|
|
104
|
+
* canonical strings and a grant could name the one it never presents (#3954).
|
|
105
|
+
* The raw id is held to the same bound the identity surface applies, so a
|
|
106
|
+
* payload the host could not have published is not accepted here either.
|
|
107
|
+
*/
|
|
108
|
+
export function parseCanonicalHostSessionId(value) {
|
|
109
|
+
if (!CANONICAL_OWNER_PATTERN.test(value))
|
|
110
|
+
return null;
|
|
111
|
+
const segments = value.split(":");
|
|
112
|
+
const provider = segments[1];
|
|
113
|
+
const encoded = segments[3] ?? "";
|
|
114
|
+
const rawSessionId = Buffer.from(encoded, "base64url").toString("utf8");
|
|
115
|
+
if (!isUsableHostSessionId(rawSessionId))
|
|
116
|
+
return null;
|
|
117
|
+
if (canonicalHostSessionId(provider, rawSessionId) !== value)
|
|
118
|
+
return null;
|
|
119
|
+
return { provider, rawSessionId };
|
|
120
|
+
}
|
|
121
|
+
/** Read one `host-env` provider's variable out of a process environment. */
|
|
122
|
+
export function readHostEnvIdentity(environ, variable) {
|
|
123
|
+
const raw = environ[variable];
|
|
124
|
+
// An unset variable and an empty one are one state to a host: `set X=` on
|
|
125
|
+
// Windows deletes the entry, so absence must not read as a malformed value.
|
|
126
|
+
if (raw === undefined || raw.length === 0)
|
|
127
|
+
return { status: "missing", rawSessionId: null };
|
|
128
|
+
return isUsableHostSessionId(raw)
|
|
129
|
+
? { status: "ok", rawSessionId: raw }
|
|
130
|
+
: { status: "invalid", rawSessionId: null };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The canonical owner the running host published to this process, or null.
|
|
134
|
+
*
|
|
135
|
+
* Used by the CLI claim path so `session:start` binds the lease to the same id
|
|
136
|
+
* the write hook will present. Two live host variables are ambiguous and
|
|
137
|
+
* resolve to null: picking one would bind a claim to a guess.
|
|
138
|
+
*/
|
|
139
|
+
export function ambientHostSessionOwner(environ = process.env) {
|
|
140
|
+
const resolved = [];
|
|
141
|
+
for (const { provider, variable } of HOST_ENV_IDENTITY_ENTRIES) {
|
|
142
|
+
const value = readHostEnvIdentity(environ, variable);
|
|
143
|
+
if (value.status === "ok")
|
|
144
|
+
resolved.push(canonicalHostSessionId(provider, value.rawSessionId));
|
|
145
|
+
}
|
|
146
|
+
return resolved.length === 1 ? resolved[0] : null;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=host-session-owner.js.map
|
package/dist/session/index.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ export * from "./ac-pass-banking.js";
|
|
|
2
2
|
export * from "./ac-pass-reuse.js";
|
|
3
3
|
export * from "./active-cli.js";
|
|
4
4
|
export * from "./ceremony-dial-evidence.js";
|
|
5
|
+
export * from "./child-occupancy.js";
|
|
5
6
|
export * from "./compact-ritual.js";
|
|
6
7
|
export * from "./deposit-sha.js";
|
|
7
8
|
export * from "./effort-budget.js";
|
|
8
9
|
export * from "./git.js";
|
|
10
|
+
export * from "./host-session-owner.js";
|
|
9
11
|
export * from "./json.js";
|
|
10
12
|
export * from "./occupancy.js";
|
|
11
13
|
export * from "./openclaw-soft-rebind-deposit.js";
|
package/dist/session/index.js
CHANGED
|
@@ -2,10 +2,12 @@ export * from "./ac-pass-banking.js";
|
|
|
2
2
|
export * from "./ac-pass-reuse.js";
|
|
3
3
|
export * from "./active-cli.js";
|
|
4
4
|
export * from "./ceremony-dial-evidence.js";
|
|
5
|
+
export * from "./child-occupancy.js";
|
|
5
6
|
export * from "./compact-ritual.js";
|
|
6
7
|
export * from "./deposit-sha.js";
|
|
7
8
|
export * from "./effort-budget.js";
|
|
8
9
|
export * from "./git.js";
|
|
10
|
+
export * from "./host-session-owner.js";
|
|
9
11
|
export * from "./json.js";
|
|
10
12
|
export * from "./occupancy.js";
|
|
11
13
|
export * from "./openclaw-soft-rebind-deposit.js";
|
|
@@ -19,6 +19,25 @@
|
|
|
19
19
|
* composite hook write gate measures the tree's verified ritual owner against
|
|
20
20
|
* the occupant that issued the grant, not against the writer.
|
|
21
21
|
*
|
|
22
|
+
* Parent and child, answered per identity-source kind (#3954, and it does not
|
|
23
|
+
* have one answer). On a `host-env` host the parent and its dispatched children
|
|
24
|
+
* are different actors, because the host publishes a different id into each
|
|
25
|
+
* agent session. The answer there is identity, not automatic membership: each
|
|
26
|
+
* side resolves its own owner through the shared lookup chain below and claims
|
|
27
|
+
* its own worktree, which is where the dispatch envelope already puts it.
|
|
28
|
+
* Membership stays explicit and owner-issued for the deliberate same-tree case,
|
|
29
|
+
* and it stays affordable only that way -- 32 grants at a four-hour TTL against
|
|
30
|
+
* a twenty-minute lease means granting on every dispatch exhausts a busy
|
|
31
|
+
* parent's lease inside a day. The revocation trigger is therefore the owner's
|
|
32
|
+
* own `occupancy:grant --revoke`, or expiry; releasing a child's lease on its
|
|
33
|
+
* terminal event is dispatcher lifecycle in `child-occupancy.ts` (#3999).
|
|
34
|
+
* On a `payload` host parent and subagents share one id, so there is no foreign
|
|
35
|
+
* child lease to admit and nothing to grant -- and the live consequence is the
|
|
36
|
+
* inverse one: `owns` is true for both, so a parent's `occupancy:release`
|
|
37
|
+
* removes a working child's lease mid-flight with no denial. That is a property
|
|
38
|
+
* of shared host identity, not of this module; a bearer boundary cannot
|
|
39
|
+
* distinguish two processes presenting one string.
|
|
40
|
+
*
|
|
22
41
|
* Concurrency model:
|
|
23
42
|
* - Assumptions: local filesystem; cooperating processes on one machine.
|
|
24
43
|
* - Guarantees: mutual exclusion under crash-free operation; detect-and-abort
|
|
@@ -218,7 +237,18 @@ export declare function formatOccupancyStaleWarning(record: OccupancyRecord, now
|
|
|
218
237
|
* lease is gone rather than merely quiet, so the answer is to re-claim.
|
|
219
238
|
*/
|
|
220
239
|
export declare function formatOccupancyAgeCapRemediation(record: OccupancyRecord, now?: Date, maxLeaseMs?: number): string;
|
|
221
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Tell a refused caller who holds the lease and what it can actually run.
|
|
242
|
+
*
|
|
243
|
+
* `presented` is the id the refused caller offered (#3873). Without it the
|
|
244
|
+
* message can only print `<your-session-id>` placeholders, which is fine for a
|
|
245
|
+
* CLI caller that passed its own `--session-id` and useless to a hook process,
|
|
246
|
+
* which does not know what identity it presented. Passing it also keeps the
|
|
247
|
+
* message honest when there is none: a grant cannot name an empty child --
|
|
248
|
+
* `occupancy:grant --child-session-id=` is refused at parse and at membership --
|
|
249
|
+
* so that remediation is not printed to a caller who could never run it.
|
|
250
|
+
*/
|
|
251
|
+
export declare function formatOccupancyRemediation(record: OccupancyRecord, now?: Date, presented?: string): string;
|
|
222
252
|
/**
|
|
223
253
|
* Refuse an administrative verb to a granted child (#3755). Named apart from
|
|
224
254
|
* the stranger refusal because the answer differs: this caller is admitted, and
|
|
@@ -226,11 +256,78 @@ export declare function formatOccupancyRemediation(record: OccupancyRecord, now?
|
|
|
226
256
|
* derives from. A grant admits writes; the lease has one owner.
|
|
227
257
|
*/
|
|
228
258
|
export declare function formatOccupancyMemberAdministrationRefusal(record: OccupancyRecord, grant: OccupancyGrant, verb: string): string;
|
|
259
|
+
/** Which step of the shared lookup chain produced the actor (#3954). */
|
|
260
|
+
export type PresentedIdentitySource = "explicit" | "environment" | "host" | "none";
|
|
261
|
+
export interface PresentedIdentity {
|
|
262
|
+
/** The id this surface acts under; empty when nothing was presented. */
|
|
263
|
+
readonly sessionId: string;
|
|
264
|
+
readonly source: PresentedIdentitySource;
|
|
265
|
+
/**
|
|
266
|
+
* The owner the running host published, when it names a different session
|
|
267
|
+
* than `sessionId` does; otherwise null. This is the claimer-versus-presenter
|
|
268
|
+
* split itself: the id a session claims under and the id its hook process
|
|
269
|
+
* presents are two different sessions (#3954).
|
|
270
|
+
*/
|
|
271
|
+
readonly disagreeingHostOwner: string | null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The one lookup order every occupancy surface shares (#3954): an explicit
|
|
275
|
+
* `--session-id`, then `DEFT_SESSION_ID`, then the owner the running host
|
|
276
|
+
* published.
|
|
277
|
+
*
|
|
278
|
+
* The terminal is the caller's, not this function's. Claim mints, because
|
|
279
|
+
* claiming establishes an identity where none exists. Release, heartbeat and
|
|
280
|
+
* grant/revoke are proving one, so they take the empty string and keep the
|
|
281
|
+
* diagnosis written for it -- a shared mint would replace "you presented
|
|
282
|
+
* nothing" with a plausible id no later hook will ever present, on every host
|
|
283
|
+
* that publishes no owner of its own.
|
|
284
|
+
*
|
|
285
|
+
* Disagreement is reported, not resolved. The order stands, so an explicit id
|
|
286
|
+
* beats the environment and the environment beats the host; what changes is
|
|
287
|
+
* that a refused caller is told the host names someone else, which is the state
|
|
288
|
+
* a stale inherited `DEFT_SESSION_ID` produces and the one an operator cannot
|
|
289
|
+
* otherwise see.
|
|
290
|
+
*/
|
|
291
|
+
export declare function resolvePresentedIdentity(input?: {
|
|
292
|
+
readonly sessionId?: string;
|
|
293
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
294
|
+
}): PresentedIdentity;
|
|
295
|
+
/**
|
|
296
|
+
* Name a claimer-versus-presenter split on a refusal, or return "" (#3954).
|
|
297
|
+
*
|
|
298
|
+
* Appended only to denials: while the caller is admitted the split costs it
|
|
299
|
+
* nothing, and on a refusal it is the one fact that explains why an id the
|
|
300
|
+
* operator believes is theirs is being treated as a stranger's.
|
|
301
|
+
*/
|
|
302
|
+
export declare function formatPresentedIdentityDisagreement(identity: PresentedIdentity): string;
|
|
303
|
+
/**
|
|
304
|
+
* The owner a claim is made under: the shared lookup chain, then a mint.
|
|
305
|
+
*
|
|
306
|
+
* The host step is what makes an identified host's claim reachable (#3873).
|
|
307
|
+
* Minting instead binds the lease to an id no later hook process can present,
|
|
308
|
+
* so the session that claimed the worktree is refused by its own lease. The
|
|
309
|
+
* mint stays as the last resort for hosts that publish nothing, and it is the
|
|
310
|
+
* one terminal the prove-surfaces deliberately do not share (#3954).
|
|
311
|
+
*/
|
|
229
312
|
export declare function resolveOccupancySessionId(input?: ApplyOccupancyInput): string;
|
|
230
313
|
export declare function readOccupancy(projectRoot: string): OccupancyRecord | null;
|
|
231
314
|
export declare function liveOccupant(projectRoot: string, now?: Date, ttlMs?: number, maxLeaseMs?: number): OccupancyRecord | null;
|
|
232
315
|
export declare function applyWorktreeOccupancy(projectRoot: string, input?: ApplyOccupancyInput): OccupancyDecision;
|
|
233
316
|
export declare function stealOccupancy(projectRoot: string, input?: ApplyOccupancyInput): OccupancyDecision;
|
|
317
|
+
/**
|
|
318
|
+
* Release the caller's own lease.
|
|
319
|
+
*
|
|
320
|
+
* Owner-only, deliberately (#3954 item 4, answering the open question the
|
|
321
|
+
* design-critique arc left for the builder). Letting an unidentified caller
|
|
322
|
+
* release the occupant the lease file itself records would make possession of
|
|
323
|
+
* that file path into authority to delete a live lease, which is exactly what
|
|
324
|
+
* the `!expired && !owns` refusal exists to prevent -- and the cooperative
|
|
325
|
+
* bearer model (#3755) has no second check behind it. The unreachable printed
|
|
326
|
+
* recovery is fixed by the shared lookup chain instead: on a host that
|
|
327
|
+
* publishes an owner, the occupant now resolves itself and a bare
|
|
328
|
+
* `occupancy:release` is the occupant, so the message the deny prints is one
|
|
329
|
+
* the party it addresses can actually run.
|
|
330
|
+
*/
|
|
234
331
|
export declare function releaseOccupancy(projectRoot: string, input?: {
|
|
235
332
|
readonly sessionId?: string;
|
|
236
333
|
readonly env?: NodeJS.ProcessEnv;
|
|
@@ -313,7 +410,15 @@ export declare function evaluateOccupancyWriteGate(projectRoot: string, input?:
|
|
|
313
410
|
* Never claims and never mints an owner — an unheld or foreign lease is denied.
|
|
314
411
|
*/
|
|
315
412
|
export declare function heartbeatOccupancy(projectRoot: string, input?: ApplyOccupancyInput): OccupancyDecision;
|
|
316
|
-
/**
|
|
413
|
+
/**
|
|
414
|
+
* Close-out identity comes from the launch manifest, `DEFT_SESSION_ID`, or the
|
|
415
|
+
* owner the running host published — never occupancy.json (#3954).
|
|
416
|
+
*
|
|
417
|
+
* Reading the lease for identity would be the anonymous recorded-occupant
|
|
418
|
+
* release refused in `releaseOccupancy`; the host step is the same shared
|
|
419
|
+
* lookup chain every other occupancy surface uses, so a cohort launched on a
|
|
420
|
+
* host that publishes an owner can close out without an explicit id.
|
|
421
|
+
*/
|
|
317
422
|
export declare function releaseSwarmOccupancy(projectRoot: string, input?: {
|
|
318
423
|
readonly sessionId?: string;
|
|
319
424
|
readonly env?: NodeJS.ProcessEnv;
|