project-tiny-context-harness 0.8.13 → 0.8.17
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/README.md +53 -28
- package/assets/README.md +52 -25
- package/assets/README.zh-CN.md +55 -26
- package/assets/agents/AGENTS_CORE.md +2 -2
- package/assets/agents/long-task-implementation.toml +1 -1
- package/assets/context_templates/screen-contract.md +16 -0
- package/assets/skills/context_development_engineer/SKILL.md +16 -30
- package/assets/skills/context_development_engineer/references/engineering-design-reasoning.md +93 -0
- package/assets/skills/context_surface_contract/SKILL.md +13 -2
- package/assets/skills/context_uiux_design/SKILL.md +7 -3
- package/assets/skills/context_uiux_design/references/task-uiux-analysis.md +82 -0
- package/assets/skills/design-resource-authoring/SKILL.md +14 -8
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +18 -6
- package/assets/skills/design-resource-authoring/references/formal-selected-web-app-handoff.md +26 -8
- package/assets/skills/design-resource-authoring/references/implementation-feasibility.md +111 -0
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +45 -7
- package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +7 -1
- package/assets/skills/design-resource-authoring/references/resource-selection.md +115 -3
- package/assets/skills/long-task-workflow/SKILL.md +9 -7
- package/assets/skills/long-task-workflow/agents/openai.yaml +1 -1
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +1 -1
- package/dist/commands/design-resource.js +9 -2
- package/dist/commands/long-task-revision.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/lib/design-resource-handoff-bundle.js +5 -0
- package/dist/lib/design-resource-handoff-input-types.d.ts +1 -1
- package/dist/lib/design-resource-handoff-manifest-projection.js +1 -0
- package/dist/lib/design-resource-handoff-set-integrity.js +1 -0
- package/dist/lib/design-resource-handoff-shape.js +6 -2
- package/dist/lib/design-resource-handoff-types.d.ts +5 -0
- package/dist/lib/design-resource-handoff-validation.js +12 -0
- package/dist/lib/design-resource-implementation-feasibility-model.d.ts +35 -0
- package/dist/lib/design-resource-implementation-feasibility-model.js +62 -0
- package/dist/lib/design-resource-implementation-feasibility-shape-sections.d.ts +43 -0
- package/dist/lib/design-resource-implementation-feasibility-shape-sections.js +142 -0
- package/dist/lib/design-resource-implementation-feasibility-shape.d.ts +4 -0
- package/dist/lib/design-resource-implementation-feasibility-shape.js +154 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.d.ts +24 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision-projection.js +106 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision.d.ts +21 -0
- package/dist/lib/design-resource-implementation-feasibility-source-decision.js +75 -0
- package/dist/lib/design-resource-implementation-feasibility-types.d.ts +135 -0
- package/dist/lib/design-resource-implementation-feasibility-types.js +37 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-cells.d.ts +5 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-cells.js +125 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-document.d.ts +4 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-document.js +167 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-facts.d.ts +3 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-facts.js +76 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-realizations.d.ts +8 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-realizations.js +76 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-support.d.ts +10 -0
- package/dist/lib/design-resource-implementation-feasibility-validation-support.js +75 -0
- package/dist/lib/design-resource-implementation-feasibility-validation.d.ts +3 -0
- package/dist/lib/design-resource-implementation-feasibility-validation.js +138 -0
- package/dist/lib/design-resource-symbolic-fact-shape.js +3 -1
- package/dist/lib/design-resource-symbolic-fact-types.d.ts +5 -0
- package/dist/lib/design-resource-symbolic-fact-validation.js +13 -0
- package/dist/lib/long-task-activation-validation.js +77 -7
- package/dist/lib/long-task-authority-revision-brief.js +3 -1
- package/dist/lib/long-task-authority-revision-summary.js +6 -2
- package/dist/lib/long-task-authority-revision-types.d.ts +2 -0
- package/dist/lib/long-task-check-runner.js +3 -296
- package/dist/lib/long-task-command-process.d.ts +9 -0
- package/dist/lib/long-task-command-process.js +175 -0
- package/dist/lib/long-task-delivery-validation.d.ts +5 -2
- package/dist/lib/long-task-delivery-validation.js +4 -4
- package/dist/lib/long-task-design-feasibility-binding-owners.d.ts +6 -0
- package/dist/lib/long-task-design-feasibility-binding-owners.js +52 -0
- package/dist/lib/long-task-design-feasibility-binding.d.ts +13 -0
- package/dist/lib/long-task-design-feasibility-binding.js +114 -0
- package/dist/lib/long-task-design-feasibility-source-closure.d.ts +11 -0
- package/dist/lib/long-task-design-feasibility-source-closure.js +78 -0
- package/dist/lib/long-task-design-resource-handoff.d.ts +3 -3
- package/dist/lib/long-task-design-resource-handoff.js +39 -3
- package/dist/lib/long-task-design-resource-method-binding.js +16 -4
- package/dist/lib/long-task-freshness.js +1 -1
- package/dist/lib/long-task-git.d.ts +26 -0
- package/dist/lib/long-task-git.js +130 -0
- package/dist/lib/long-task-paths.js +1 -1
- package/dist/lib/long-task-process-runtime-closure.js +74 -42
- package/dist/lib/long-task-process-table.d.ts +12 -0
- package/dist/lib/long-task-process-table.js +119 -0
- package/dist/lib/long-task-process-tree.d.ts +9 -0
- package/dist/lib/long-task-process-tree.js +138 -0
- package/dist/lib/long-task-protected-files.d.ts +1 -1
- package/dist/lib/long-task-protected-files.js +1 -1
- package/dist/lib/long-task-ui-surface-policy.d.ts +2 -1
- package/dist/lib/long-task-ui-surface-policy.js +7 -4
- package/dist/lib/long-task-ui-surface-validation.d.ts +3 -1
- package/dist/lib/long-task-ui-surface-validation.js +2 -2
- package/dist/lib/long-task-verifier-authority.js +2 -0
- package/dist/lib/long-task-workspace-manifest.d.ts +8 -0
- package/dist/lib/long-task-workspace-manifest.js +173 -0
- package/dist/lib/long-task-workspace-snapshot.d.ts +8 -0
- package/dist/lib/long-task-workspace-snapshot.js +176 -0
- package/dist/lib/long-task-workspace.d.ts +4 -25
- package/dist/lib/long-task-workspace.js +3 -432
- package/dist/lib/repository-path-safety.d.ts +2 -0
- package/dist/lib/repository-path-safety.js +36 -0
- package/dist/long-task-hook.js +12 -3
- package/dist/public-types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
2
1
|
import { randomBytes } from "node:crypto";
|
|
3
2
|
import { access, realpath } from "node:fs/promises";
|
|
4
3
|
import net from "node:net";
|
|
5
4
|
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
5
|
import { decodeCheckEvidence, invalidEvidence, } from "./long-task-check-evidence-decoder.js";
|
|
6
|
+
import { spawnCommandOnce } from "./long-task-command-process.js";
|
|
8
7
|
import { declaredEnvironmentValues, outputContainsDeclaredEnvironmentValue, runnerEnvironment, } from "./long-task-runner-environment.js";
|
|
9
8
|
import { decodeProductObservationEnvelope } from "./long-task-process-observation.js";
|
|
10
9
|
import { resolveInsideRepository } from "./long-task-workspace.js";
|
|
11
10
|
import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
|
|
12
|
-
const OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
13
|
-
const PROCESS_TREE_GRACE_MS = 1_000;
|
|
14
|
-
const execFileAsync = promisify(execFile);
|
|
15
11
|
export async function executeCheckRunner(check, snapshotRoot, context) {
|
|
16
12
|
const started = Date.now();
|
|
17
13
|
let processAuthorities;
|
|
@@ -112,7 +108,7 @@ async function runOnce(check, snapshotRoot, processAuthorities, snapshotSha256,
|
|
|
112
108
|
: runner.executable;
|
|
113
109
|
const argv = [...runner.executable_argv_prefix, ...runner.argv];
|
|
114
110
|
if (!processAuthorities.length) {
|
|
115
|
-
const execution = await
|
|
111
|
+
const execution = await spawnCommandOnce(executable, argv, cwd, runner.timeout_ms, runnerEnvironment(check.environment_requirements), false);
|
|
116
112
|
return {
|
|
117
113
|
...execution,
|
|
118
114
|
process_observation: null,
|
|
@@ -120,7 +116,7 @@ async function runOnce(check, snapshotRoot, processAuthorities, snapshotSha256,
|
|
|
120
116
|
};
|
|
121
117
|
}
|
|
122
118
|
const executionNonce = randomBytes(32).toString("hex");
|
|
123
|
-
const execution = await
|
|
119
|
+
const execution = await spawnCommandOnce(executable, argv, cwd, runner.timeout_ms, runnerEnvironment(check.environment_requirements, "product_observation"), true);
|
|
124
120
|
const processObservation = decodeProductObservationEnvelope({
|
|
125
121
|
bytes: execution.stdout,
|
|
126
122
|
authorities: processAuthorities,
|
|
@@ -160,295 +156,6 @@ async function runOnce(check, snapshotRoot, processAuthorities, snapshotSha256,
|
|
|
160
156
|
},
|
|
161
157
|
};
|
|
162
158
|
}
|
|
163
|
-
async function spawnOnce(executable, argv, cwd, timeoutMs, environment, containProcessTree) {
|
|
164
|
-
return new Promise((resolve, reject) => {
|
|
165
|
-
const startedAt = new Date().toISOString();
|
|
166
|
-
const child = spawn(executable, argv, {
|
|
167
|
-
cwd,
|
|
168
|
-
shell: false,
|
|
169
|
-
windowsHide: true,
|
|
170
|
-
env: environment,
|
|
171
|
-
detached: containProcessTree && process.platform !== "win32",
|
|
172
|
-
});
|
|
173
|
-
const pid = child.pid ?? -1;
|
|
174
|
-
let stopTreeMonitor = false;
|
|
175
|
-
let treeMonitorError = null;
|
|
176
|
-
const observedDescendants = new Set();
|
|
177
|
-
const treeMonitor = containProcessTree && pid > 0
|
|
178
|
-
? observeDescendantProcesses(pid, observedDescendants, () => stopTreeMonitor).catch((error) => {
|
|
179
|
-
treeMonitorError = error;
|
|
180
|
-
})
|
|
181
|
-
: Promise.resolve();
|
|
182
|
-
const stdout = [];
|
|
183
|
-
const stderr = [];
|
|
184
|
-
let size = 0;
|
|
185
|
-
let settled = false;
|
|
186
|
-
let terminationReason = null;
|
|
187
|
-
let terminationTask = null;
|
|
188
|
-
let forceTimer = null;
|
|
189
|
-
let abandonTimer = null;
|
|
190
|
-
const capture = (target) => (chunk) => {
|
|
191
|
-
size += chunk.length;
|
|
192
|
-
if (size > OUTPUT_LIMIT) {
|
|
193
|
-
terminate("command_output_limit_exceeded");
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
target.push(Buffer.from(chunk));
|
|
197
|
-
};
|
|
198
|
-
child.stdout.on("data", capture(stdout));
|
|
199
|
-
child.stderr.on("data", capture(stderr));
|
|
200
|
-
child.on("error", (error) => {
|
|
201
|
-
if (!settled) {
|
|
202
|
-
settled = true;
|
|
203
|
-
stopTreeMonitor = true;
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
if (forceTimer)
|
|
206
|
-
clearTimeout(forceTimer);
|
|
207
|
-
if (abandonTimer)
|
|
208
|
-
clearTimeout(abandonTimer);
|
|
209
|
-
const spawnError = new Error(`command_spawn_error:${message(error)}`);
|
|
210
|
-
if (containProcessTree && pid > 0)
|
|
211
|
-
void terminateProcessTree(pid, true, [...observedDescendants])
|
|
212
|
-
.catch(() => undefined)
|
|
213
|
-
.finally(() => reject(spawnError));
|
|
214
|
-
else
|
|
215
|
-
reject(spawnError);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
const timer = setTimeout(() => {
|
|
219
|
-
terminate("command_timeout");
|
|
220
|
-
}, timeoutMs);
|
|
221
|
-
if (pid < 0)
|
|
222
|
-
terminate("command_spawn_pid_unavailable");
|
|
223
|
-
child.on("close", async (code) => {
|
|
224
|
-
stopTreeMonitor = true;
|
|
225
|
-
clearTimeout(timer);
|
|
226
|
-
if (forceTimer)
|
|
227
|
-
clearTimeout(forceTimer);
|
|
228
|
-
if (abandonTimer)
|
|
229
|
-
clearTimeout(abandonTimer);
|
|
230
|
-
if (settled)
|
|
231
|
-
return;
|
|
232
|
-
settled = true;
|
|
233
|
-
if (terminationReason) {
|
|
234
|
-
try {
|
|
235
|
-
await terminationTask;
|
|
236
|
-
await treeMonitor;
|
|
237
|
-
if (containProcessTree)
|
|
238
|
-
await forceProcessTreeQuiescence(pid, observedDescendants);
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// The original bounded termination reason remains the public error;
|
|
242
|
-
// cleanup is best-effort after the hard process-tree kill attempt.
|
|
243
|
-
}
|
|
244
|
-
reject(terminationReason);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (containProcessTree) {
|
|
248
|
-
try {
|
|
249
|
-
await treeMonitor;
|
|
250
|
-
if (treeMonitorError)
|
|
251
|
-
throw treeMonitorError;
|
|
252
|
-
await assertProcessTreeQuiescent(pid, observedDescendants);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
await terminateProcessTree(pid, true, [...observedDescendants]).catch(() => undefined);
|
|
256
|
-
reject(error);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
resolve({
|
|
261
|
-
exit_code: code ?? -1,
|
|
262
|
-
stdout: Buffer.concat(stdout),
|
|
263
|
-
stderr: Buffer.concat(stderr),
|
|
264
|
-
pid,
|
|
265
|
-
started_at: startedAt,
|
|
266
|
-
completed_at: new Date().toISOString(),
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
function terminate(reason) {
|
|
270
|
-
if (settled || terminationReason)
|
|
271
|
-
return;
|
|
272
|
-
terminationReason = new Error(reason);
|
|
273
|
-
stopTreeMonitor = true;
|
|
274
|
-
if (containProcessTree)
|
|
275
|
-
terminationTask = terminateProcessTree(pid, false).catch((error) => {
|
|
276
|
-
treeMonitorError ??= error;
|
|
277
|
-
});
|
|
278
|
-
else {
|
|
279
|
-
child.kill();
|
|
280
|
-
terminationTask = Promise.resolve();
|
|
281
|
-
}
|
|
282
|
-
forceTimer = setTimeout(() => {
|
|
283
|
-
if (containProcessTree)
|
|
284
|
-
terminationTask = terminateProcessTree(pid, true, [
|
|
285
|
-
...observedDescendants,
|
|
286
|
-
]).catch((error) => {
|
|
287
|
-
treeMonitorError ??= error;
|
|
288
|
-
});
|
|
289
|
-
else
|
|
290
|
-
child.kill("SIGKILL");
|
|
291
|
-
}, PROCESS_TREE_GRACE_MS);
|
|
292
|
-
forceTimer.unref();
|
|
293
|
-
abandonTimer = setTimeout(() => {
|
|
294
|
-
if (settled)
|
|
295
|
-
return;
|
|
296
|
-
settled = true;
|
|
297
|
-
child.stdout.destroy();
|
|
298
|
-
child.stderr.destroy();
|
|
299
|
-
const cleanup = containProcessTree
|
|
300
|
-
? forceProcessTreeQuiescence(pid, observedDescendants).catch(() => undefined)
|
|
301
|
-
: Promise.resolve();
|
|
302
|
-
void cleanup.finally(() => reject(terminationReason));
|
|
303
|
-
}, PROCESS_TREE_GRACE_MS * 2);
|
|
304
|
-
abandonTimer.unref();
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async function forceProcessTreeQuiescence(rootPid, observed) {
|
|
309
|
-
await terminateProcessTree(rootPid, true, [...observed]);
|
|
310
|
-
const deadline = Date.now() + PROCESS_TREE_GRACE_MS;
|
|
311
|
-
while (Date.now() < deadline) {
|
|
312
|
-
const liveObserved = [rootPid, ...observed].some(processIdExists);
|
|
313
|
-
const groupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
314
|
-
if (!liveObserved && !groupAlive)
|
|
315
|
-
return;
|
|
316
|
-
await new Promise((resolve) => setTimeout(resolve, process.platform === "win32" ? 100 : 25));
|
|
317
|
-
}
|
|
318
|
-
throw new Error("process_observer_descendant_process_alive");
|
|
319
|
-
}
|
|
320
|
-
async function observeDescendantProcesses(rootPid, observed, stopped) {
|
|
321
|
-
do {
|
|
322
|
-
for (const pid of await descendantProcessIds(rootPid))
|
|
323
|
-
observed.add(pid);
|
|
324
|
-
if (stopped())
|
|
325
|
-
return;
|
|
326
|
-
await new Promise((resolve) => setTimeout(resolve, process.platform === "win32" ? 100 : 25));
|
|
327
|
-
} while (!stopped());
|
|
328
|
-
}
|
|
329
|
-
async function assertProcessTreeQuiescent(rootPid, observed) {
|
|
330
|
-
const finalDescendants = await descendantProcessIds(rootPid);
|
|
331
|
-
const descendants = [...new Set([...observed, ...finalDescendants])].filter(processIdExists);
|
|
332
|
-
const processGroupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
333
|
-
if (!descendants.length && !processGroupAlive)
|
|
334
|
-
return;
|
|
335
|
-
await terminateProcessTree(rootPid, true, descendants);
|
|
336
|
-
throw new Error("process_observer_descendant_process_alive");
|
|
337
|
-
}
|
|
338
|
-
function processIdExists(pid) {
|
|
339
|
-
try {
|
|
340
|
-
process.kill(pid, 0);
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
catch (error) {
|
|
344
|
-
if (nodeErrorCode(error) === "ESRCH")
|
|
345
|
-
return false;
|
|
346
|
-
throw error;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
async function terminateProcessTree(rootPid, force, knownDescendants) {
|
|
350
|
-
if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
|
|
351
|
-
return;
|
|
352
|
-
if (process.platform === "win32") {
|
|
353
|
-
const targets = [
|
|
354
|
-
rootPid,
|
|
355
|
-
...[...new Set(knownDescendants ?? [])]
|
|
356
|
-
.filter((pid) => pid !== rootPid)
|
|
357
|
-
.reverse(),
|
|
358
|
-
];
|
|
359
|
-
for (const pid of targets)
|
|
360
|
-
await execFileAsync("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
|
|
361
|
-
windowsHide: true,
|
|
362
|
-
timeout: PROCESS_TREE_GRACE_MS,
|
|
363
|
-
}).catch(() => undefined);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
367
|
-
try {
|
|
368
|
-
process.kill(-rootPid, signal);
|
|
369
|
-
}
|
|
370
|
-
catch (error) {
|
|
371
|
-
if (nodeErrorCode(error) !== "ESRCH")
|
|
372
|
-
throw error;
|
|
373
|
-
}
|
|
374
|
-
const descendants = knownDescendants ?? (await descendantProcessIds(rootPid));
|
|
375
|
-
for (const pid of [...descendants].reverse())
|
|
376
|
-
try {
|
|
377
|
-
process.kill(pid, signal);
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
if (nodeErrorCode(error) !== "ESRCH")
|
|
381
|
-
throw error;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async function descendantProcessIds(rootPid) {
|
|
385
|
-
let stdout;
|
|
386
|
-
try {
|
|
387
|
-
const result = process.platform === "win32"
|
|
388
|
-
? await execFileAsync("powershell.exe", [
|
|
389
|
-
"-NoLogo",
|
|
390
|
-
"-NoProfile",
|
|
391
|
-
"-NonInteractive",
|
|
392
|
-
"-Command",
|
|
393
|
-
'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId)" }',
|
|
394
|
-
], {
|
|
395
|
-
encoding: "utf8",
|
|
396
|
-
windowsHide: true,
|
|
397
|
-
timeout: 5_000,
|
|
398
|
-
maxBuffer: OUTPUT_LIMIT,
|
|
399
|
-
})
|
|
400
|
-
: await execFileAsync("ps", ["-A", "-o", "pid=,ppid="], {
|
|
401
|
-
encoding: "utf8",
|
|
402
|
-
timeout: 5_000,
|
|
403
|
-
maxBuffer: OUTPUT_LIMIT,
|
|
404
|
-
});
|
|
405
|
-
stdout = result.stdout;
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
throw new Error(`process_observer_process_tree_inspection_unavailable:${message(error)}`);
|
|
409
|
-
}
|
|
410
|
-
const children = new Map();
|
|
411
|
-
for (const line of stdout.split(/\r?\n/u)) {
|
|
412
|
-
const [pidText, parentText] = line.trim().split(/\s+/u);
|
|
413
|
-
const pid = Number.parseInt(pidText ?? "", 10);
|
|
414
|
-
const parent = Number.parseInt(parentText ?? "", 10);
|
|
415
|
-
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent))
|
|
416
|
-
continue;
|
|
417
|
-
const row = children.get(parent);
|
|
418
|
-
if (row)
|
|
419
|
-
row.push(pid);
|
|
420
|
-
else
|
|
421
|
-
children.set(parent, [pid]);
|
|
422
|
-
}
|
|
423
|
-
const result = [];
|
|
424
|
-
const pending = [...(children.get(rootPid) ?? [])];
|
|
425
|
-
const seen = new Set();
|
|
426
|
-
while (pending.length) {
|
|
427
|
-
const pid = pending.shift();
|
|
428
|
-
if (seen.has(pid))
|
|
429
|
-
continue;
|
|
430
|
-
seen.add(pid);
|
|
431
|
-
result.push(pid);
|
|
432
|
-
pending.push(...(children.get(pid) ?? []));
|
|
433
|
-
}
|
|
434
|
-
return result;
|
|
435
|
-
}
|
|
436
|
-
function processGroupExists(rootPid) {
|
|
437
|
-
try {
|
|
438
|
-
process.kill(-rootPid, 0);
|
|
439
|
-
return true;
|
|
440
|
-
}
|
|
441
|
-
catch (error) {
|
|
442
|
-
if (nodeErrorCode(error) === "ESRCH")
|
|
443
|
-
return false;
|
|
444
|
-
throw error;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function nodeErrorCode(error) {
|
|
448
|
-
return error && typeof error === "object" && "code" in error
|
|
449
|
-
? String(error.code)
|
|
450
|
-
: null;
|
|
451
|
-
}
|
|
452
159
|
function validateProcessObserverActivation(check, snapshotRoot, authorities, context) {
|
|
453
160
|
if (!authorities.length)
|
|
454
161
|
return null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SpawnedCommandExecution {
|
|
2
|
+
exit_code: number;
|
|
3
|
+
stdout: Buffer;
|
|
4
|
+
stderr: Buffer;
|
|
5
|
+
pid: number;
|
|
6
|
+
started_at: string;
|
|
7
|
+
completed_at: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function spawnCommandOnce(executable: string, argv: string[], cwd: string, timeoutMs: number, environment: NodeJS.ProcessEnv, containProcessTree: boolean): Promise<SpawnedCommandExecution>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createProcessTreeObserver, } from "./long-task-process-tree.js";
|
|
3
|
+
const OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
4
|
+
const PROCESS_TREE_GRACE_MS = 1_000;
|
|
5
|
+
export function spawnCommandOnce(executable, argv, cwd, timeoutMs, environment, containProcessTree) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(executable, argv, {
|
|
8
|
+
cwd,
|
|
9
|
+
shell: false,
|
|
10
|
+
windowsHide: true,
|
|
11
|
+
env: environment,
|
|
12
|
+
detached: containProcessTree && process.platform !== "win32",
|
|
13
|
+
});
|
|
14
|
+
const pid = child.pid ?? -1;
|
|
15
|
+
const processTree = containProcessTree && pid > 0 ? createProcessTreeObserver(pid) : null;
|
|
16
|
+
const state = {
|
|
17
|
+
child,
|
|
18
|
+
pid,
|
|
19
|
+
processTree,
|
|
20
|
+
treeMonitor: Promise.resolve(),
|
|
21
|
+
treeMonitorError: null,
|
|
22
|
+
stdout: [],
|
|
23
|
+
stderr: [],
|
|
24
|
+
size: 0,
|
|
25
|
+
settled: false,
|
|
26
|
+
terminationReason: null,
|
|
27
|
+
terminationTask: null,
|
|
28
|
+
timer: null,
|
|
29
|
+
forceTimer: null,
|
|
30
|
+
abandonTimer: null,
|
|
31
|
+
startedAt: new Date().toISOString(),
|
|
32
|
+
resolve,
|
|
33
|
+
reject,
|
|
34
|
+
};
|
|
35
|
+
if (processTree)
|
|
36
|
+
state.treeMonitor = processTree.wait().catch((error) => {
|
|
37
|
+
state.treeMonitorError = error;
|
|
38
|
+
});
|
|
39
|
+
child.stdout.on("data", captureOutput(state, state.stdout));
|
|
40
|
+
child.stderr.on("data", captureOutput(state, state.stderr));
|
|
41
|
+
child.on("error", (error) => handleSpawnError(state, error));
|
|
42
|
+
child.on("close", (code) => void handleClose(state, code));
|
|
43
|
+
state.timer = setTimeout(() => terminateExecution(state, "command_timeout"), timeoutMs);
|
|
44
|
+
if (pid < 0)
|
|
45
|
+
terminateExecution(state, "command_spawn_pid_unavailable");
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function captureOutput(state, target) {
|
|
49
|
+
return (chunk) => {
|
|
50
|
+
state.size += chunk.length;
|
|
51
|
+
if (state.size > OUTPUT_LIMIT) {
|
|
52
|
+
terminateExecution(state, "command_output_limit_exceeded");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
target.push(Buffer.from(chunk));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function handleSpawnError(state, error) {
|
|
59
|
+
if (state.settled)
|
|
60
|
+
return;
|
|
61
|
+
state.settled = true;
|
|
62
|
+
state.processTree?.stop();
|
|
63
|
+
clearExecutionTimers(state);
|
|
64
|
+
const spawnError = new Error(`command_spawn_error:${message(error)}`);
|
|
65
|
+
if (!state.processTree) {
|
|
66
|
+
state.reject(spawnError);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
void state.processTree
|
|
70
|
+
.terminate(true)
|
|
71
|
+
.catch(() => undefined)
|
|
72
|
+
.finally(() => state.reject(spawnError));
|
|
73
|
+
}
|
|
74
|
+
async function handleClose(state, code) {
|
|
75
|
+
state.processTree?.stop();
|
|
76
|
+
clearExecutionTimers(state);
|
|
77
|
+
if (state.settled)
|
|
78
|
+
return;
|
|
79
|
+
state.settled = true;
|
|
80
|
+
if (state.terminationReason) {
|
|
81
|
+
await finishBoundedTermination(state);
|
|
82
|
+
state.reject(state.terminationReason);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!(await assertTreeClosure(state)))
|
|
86
|
+
return;
|
|
87
|
+
state.resolve({
|
|
88
|
+
exit_code: code ?? -1,
|
|
89
|
+
stdout: Buffer.concat(state.stdout),
|
|
90
|
+
stderr: Buffer.concat(state.stderr),
|
|
91
|
+
pid: state.pid,
|
|
92
|
+
started_at: state.startedAt,
|
|
93
|
+
completed_at: new Date().toISOString(),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function finishBoundedTermination(state) {
|
|
97
|
+
try {
|
|
98
|
+
await state.terminationTask;
|
|
99
|
+
await state.treeMonitor;
|
|
100
|
+
if (state.processTree)
|
|
101
|
+
await state.processTree.forceQuiescence();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Preserve the original bounded termination reason after the hard cleanup.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function assertTreeClosure(state) {
|
|
108
|
+
if (!state.processTree)
|
|
109
|
+
return true;
|
|
110
|
+
try {
|
|
111
|
+
await state.treeMonitor;
|
|
112
|
+
if (state.treeMonitorError)
|
|
113
|
+
throw state.treeMonitorError;
|
|
114
|
+
await state.processTree.assertQuiescent();
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
await state.processTree.terminate(true).catch(() => undefined);
|
|
119
|
+
state.reject(error);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function terminateExecution(state, reason) {
|
|
124
|
+
if (state.settled || state.terminationReason)
|
|
125
|
+
return;
|
|
126
|
+
state.terminationReason = new Error(reason);
|
|
127
|
+
state.processTree?.stop();
|
|
128
|
+
startGracefulTermination(state);
|
|
129
|
+
state.forceTimer = setTimeout(() => startForcedTermination(state), PROCESS_TREE_GRACE_MS);
|
|
130
|
+
state.forceTimer.unref();
|
|
131
|
+
state.abandonTimer = setTimeout(() => abandonExecution(state), PROCESS_TREE_GRACE_MS * 2);
|
|
132
|
+
state.abandonTimer.unref();
|
|
133
|
+
}
|
|
134
|
+
function startGracefulTermination(state) {
|
|
135
|
+
if (state.processTree)
|
|
136
|
+
state.terminationTask = state.processTree
|
|
137
|
+
.terminate(false)
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
state.treeMonitorError ??= error;
|
|
140
|
+
});
|
|
141
|
+
else {
|
|
142
|
+
state.child.kill();
|
|
143
|
+
state.terminationTask = Promise.resolve();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function startForcedTermination(state) {
|
|
147
|
+
if (state.processTree)
|
|
148
|
+
state.terminationTask = state.processTree.terminate(true).catch((error) => {
|
|
149
|
+
state.treeMonitorError ??= error;
|
|
150
|
+
});
|
|
151
|
+
else
|
|
152
|
+
state.child.kill("SIGKILL");
|
|
153
|
+
}
|
|
154
|
+
function abandonExecution(state) {
|
|
155
|
+
if (state.settled)
|
|
156
|
+
return;
|
|
157
|
+
state.settled = true;
|
|
158
|
+
state.child.stdout.destroy();
|
|
159
|
+
state.child.stderr.destroy();
|
|
160
|
+
const cleanup = state.processTree
|
|
161
|
+
? state.processTree.forceQuiescence().catch(() => undefined)
|
|
162
|
+
: Promise.resolve();
|
|
163
|
+
void cleanup.finally(() => state.reject(state.terminationReason));
|
|
164
|
+
}
|
|
165
|
+
function clearExecutionTimers(state) {
|
|
166
|
+
if (state.timer)
|
|
167
|
+
clearTimeout(state.timer);
|
|
168
|
+
if (state.forceTimer)
|
|
169
|
+
clearTimeout(state.forceTimer);
|
|
170
|
+
if (state.abandonTimer)
|
|
171
|
+
clearTimeout(state.abandonTimer);
|
|
172
|
+
}
|
|
173
|
+
function message(error) {
|
|
174
|
+
return error instanceof Error ? error.message : String(error);
|
|
175
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export interface DeliveryContractStructureValidationOptions {
|
|
3
|
+
allowDeferredDesignComponentBindingClosure?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function validateDeliveryContractStructure(contract: DeliveryContractV2, options?: DeliveryContractStructureValidationOptions): void;
|
|
6
|
+
export declare function deliveryContractStructureDiagnostics(contract: DeliveryContractV2, options?: DeliveryContractStructureValidationOptions): string[];
|
|
@@ -10,7 +10,7 @@ import { validateExecutionTargets, validateExternalConfirmationImpacts, } from "
|
|
|
10
10
|
import { validateUiSurfaceBindings } from "./long-task-ui-surface-policy.js";
|
|
11
11
|
import { controlFieldFacts } from "./long-task-control-fields.js";
|
|
12
12
|
import { validateSemanticAssuranceShape } from "./long-task-semantic-assurance-policy.js";
|
|
13
|
-
export function validateDeliveryContractStructure(contract) {
|
|
13
|
+
export function validateDeliveryContractStructure(contract, options = {}) {
|
|
14
14
|
validateUniqueKeys(contract);
|
|
15
15
|
validateDependencies(contract);
|
|
16
16
|
validateControlClosure(contract);
|
|
@@ -21,7 +21,7 @@ export function validateDeliveryContractStructure(contract) {
|
|
|
21
21
|
allow_uncovered: true,
|
|
22
22
|
});
|
|
23
23
|
validateSourceClaimMappings(contract, claims);
|
|
24
|
-
validateUiSurfaceBindings(contract, claims);
|
|
24
|
+
validateUiSurfaceBindings(contract, claims, undefined, options);
|
|
25
25
|
assertCompiledClaimsCovered(claims);
|
|
26
26
|
validateDeliveryStages(contract);
|
|
27
27
|
validateExecutionTargets(contract);
|
|
@@ -29,7 +29,7 @@ export function validateDeliveryContractStructure(contract) {
|
|
|
29
29
|
validateEvidenceCapabilityDeclarations(contract);
|
|
30
30
|
validateExternalConfirmationImpacts(contract, claims);
|
|
31
31
|
}
|
|
32
|
-
export function deliveryContractStructureDiagnostics(contract) {
|
|
32
|
+
export function deliveryContractStructureDiagnostics(contract, options = {}) {
|
|
33
33
|
const diagnostics = [];
|
|
34
34
|
const report = (message) => diagnostics.push(message);
|
|
35
35
|
validateUniqueKeys(contract, report);
|
|
@@ -48,7 +48,7 @@ export function deliveryContractStructureDiagnostics(contract) {
|
|
|
48
48
|
});
|
|
49
49
|
if (claims) {
|
|
50
50
|
validateSourceClaimMappings(contract, claims, report);
|
|
51
|
-
validateUiSurfaceBindings(contract, claims, report);
|
|
51
|
+
validateUiSurfaceBindings(contract, claims, report, options);
|
|
52
52
|
validateExternalConfirmationImpacts(contract, claims, report);
|
|
53
53
|
capture(diagnostics, () => assertCompiledClaimsCovered(claims));
|
|
54
54
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DesignResourceImplementationFeasibilityV1 } from "./design-resource-implementation-feasibility-types.js";
|
|
2
|
+
import type { DeliveryBindingV2 } from "./long-task-delivery-types.js";
|
|
3
|
+
type FeasibleRealization = DesignResourceImplementationFeasibilityV1["component_family_cells"][number]["feasible_realizations"][number];
|
|
4
|
+
export declare function matchingFeasibilityBindingKeys(realization: FeasibleRealization, bindings: DeliveryBindingV2[]): Set<string>;
|
|
5
|
+
export declare function validateFeasibilityBindingOwnerRoots(document: DesignResourceImplementationFeasibilityV1, componentBindings: DeliveryBindingV2[], routeBinding: DeliveryBindingV2): void;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { invalid } from "./long-task-design-resource-method-binding.js";
|
|
2
|
+
import { matchesRepoPattern, proveRepositoryPatternSubset, } from "./long-task-paths.js";
|
|
3
|
+
export function matchingFeasibilityBindingKeys(realization, bindings) {
|
|
4
|
+
const result = new Set();
|
|
5
|
+
for (const owner of realization.owner_candidates)
|
|
6
|
+
for (const binding of bindings) {
|
|
7
|
+
if (binding.existence !== owner.existence)
|
|
8
|
+
continue;
|
|
9
|
+
const matches = owner.kind === "planned_logical_owner"
|
|
10
|
+
? binding.key === owner.locator || binding.target === owner.locator
|
|
11
|
+
: (binding.kind === "file" && binding.target === owner.locator) ||
|
|
12
|
+
binding.carrier_paths.some((pattern) => matchesRepoPattern(owner.locator, pattern));
|
|
13
|
+
if (matches)
|
|
14
|
+
result.add(binding.key);
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
export function validateFeasibilityBindingOwnerRoots(document, componentBindings, routeBinding) {
|
|
19
|
+
const componentRoots = observedOwnerRoots(document, "component_owner_roots");
|
|
20
|
+
const routeRoots = observedOwnerRoots(document, "route_owner_roots");
|
|
21
|
+
if (componentBindings.length && componentRoots.length === 0)
|
|
22
|
+
invalid("feasibility_component_owner_roots_required", document.target_ref);
|
|
23
|
+
for (const binding of componentBindings) {
|
|
24
|
+
if (binding.existence === "planned" && binding.carrier_paths.length === 0)
|
|
25
|
+
invalid("feasibility_planned_binding_carrier_required", binding.key);
|
|
26
|
+
proveBindingPathUniverseWithinObservedRoots(binding, componentRoots, "feasibility_component_binding_outside_owner_roots");
|
|
27
|
+
}
|
|
28
|
+
if (routeRoots.length === 0)
|
|
29
|
+
invalid("feasibility_route_owner_roots_required", document.target_ref);
|
|
30
|
+
proveBindingPathUniverseWithinObservedRoots(routeBinding, routeRoots, "feasibility_route_binding_outside_owner_roots");
|
|
31
|
+
}
|
|
32
|
+
function proveBindingPathUniverseWithinObservedRoots(binding, roots, errorCode) {
|
|
33
|
+
for (const candidate of bindingPathUniverse(binding)) {
|
|
34
|
+
const proofs = roots.map((root) => proveRepositoryPatternSubset(candidate, `${root.replace(/\/+$/u, "")}/**`));
|
|
35
|
+
if (proofs.some((proof) => proof.status === "proven_subset"))
|
|
36
|
+
continue;
|
|
37
|
+
const proof = proofs[0];
|
|
38
|
+
invalid(errorCode, `${binding.key}:${candidate}:${proof?.status ?? "unknown"}:${proof?.reason ?? "owner_roots_empty"}:${roots.join(",")}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function observedOwnerRoots(document, kind) {
|
|
42
|
+
const observation = document.substrate_observations.find((candidate) => candidate.kind === kind);
|
|
43
|
+
return observation?.disposition === "observed" &&
|
|
44
|
+
observation.value?.kind === "repository_paths"
|
|
45
|
+
? observation.value.paths
|
|
46
|
+
: [];
|
|
47
|
+
}
|
|
48
|
+
function bindingPathUniverse(binding) {
|
|
49
|
+
return binding.kind === "verified"
|
|
50
|
+
? [...binding.carrier_paths]
|
|
51
|
+
: [binding.target, ...binding.carrier_paths];
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DesignResourceHandoffPreflightV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
import type { DesignResourceHandoffPreflightV2 } from "./design-resource-symbolic-fact-types.js";
|
|
3
|
+
import type { CompiledSourceItemV2, DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
4
|
+
import type { ContractDesignTarget } from "./long-task-design-resource-handoff.js";
|
|
5
|
+
type DesignResourcePreflight = DesignResourceHandoffPreflightV1 | DesignResourceHandoffPreflightV2;
|
|
6
|
+
export interface LongTaskDesignFeasibilityBindingValidationResult {
|
|
7
|
+
consumed_component_binding_refs: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface LongTaskDesignFeasibilityBindingValidationOptions {
|
|
10
|
+
deferComponentBindingAttribution?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function validateLongTaskDesignFeasibilityBindings(contract: DeliveryContractV2, contractTarget: ContractDesignTarget, preflight: DesignResourcePreflight, sourceItems: CompiledSourceItemV2[], options?: LongTaskDesignFeasibilityBindingValidationOptions): LongTaskDesignFeasibilityBindingValidationResult;
|
|
13
|
+
export {};
|