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
|
@@ -110,62 +110,94 @@ function selectBinding(selected, scoped) {
|
|
|
110
110
|
function argvAttributedRepositoryFiles(argv, cwd, bindings, targetRef) {
|
|
111
111
|
const result = new Set();
|
|
112
112
|
for (const argument of argv) {
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
113
|
+
const classification = classifyRootArgvToken(argument);
|
|
114
|
+
if (classification.kind === "label")
|
|
115
115
|
continue;
|
|
116
|
-
if (
|
|
117
|
-
fail("process_root_argv_unsafe", `${targetRef}:${argument}`);
|
|
118
|
-
const candidate = path.posix.join(cwd === "." ? "" : cwd, value);
|
|
116
|
+
if (classification.kind === "external_reference")
|
|
117
|
+
fail("process_root_argv_unsafe", `${targetRef}:${argument}:${classification.reason}`);
|
|
118
|
+
const candidate = path.posix.join(cwd === "." ? "" : cwd, classification.value);
|
|
119
119
|
let normalized;
|
|
120
120
|
try {
|
|
121
121
|
normalized = normalizeRepositoryFile(candidate, "process_root_argv");
|
|
122
122
|
}
|
|
123
123
|
catch {
|
|
124
|
-
fail("process_root_argv_unsafe", `${targetRef}:${argument}`);
|
|
124
|
+
fail("process_root_argv_unsafe", `${targetRef}:${argument}:parent_escape`);
|
|
125
125
|
}
|
|
126
126
|
if (bindingsContaining(bindings, normalized).length)
|
|
127
127
|
result.add(normalized);
|
|
128
128
|
}
|
|
129
129
|
return [...result].sort();
|
|
130
130
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
131
|
+
function classifyRootArgvToken(argument) {
|
|
132
|
+
if (!argument)
|
|
133
|
+
return { kind: "label" };
|
|
134
|
+
if (hasUnsafeArgvControl(argument))
|
|
135
|
+
return externalArgvReference("unsupported_compound");
|
|
136
|
+
if (argument === "--")
|
|
137
|
+
return { kind: "label" };
|
|
138
|
+
if (argument.startsWith("--")) {
|
|
139
|
+
const assignment = /^--[A-Za-z0-9][A-Za-z0-9_-]*=(.*)$/u.exec(argument);
|
|
140
|
+
if (assignment) {
|
|
141
|
+
const value = assignment[1];
|
|
142
|
+
if (!value)
|
|
143
|
+
return { kind: "label" };
|
|
144
|
+
if (value.includes("="))
|
|
145
|
+
return externalArgvReference("unsupported_compound");
|
|
146
|
+
return classifyRootArgvValue(value);
|
|
147
|
+
}
|
|
148
|
+
if (/^--[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(argument))
|
|
149
|
+
return { kind: "label" };
|
|
150
|
+
return externalArgvReference("unsupported_compound");
|
|
151
|
+
}
|
|
152
|
+
if (/^-[A-Za-z0-9?]$/u.test(argument))
|
|
153
|
+
return { kind: "label" };
|
|
154
|
+
if (/^-\d+(?:\.\d+)?$/u.test(argument))
|
|
155
|
+
return { kind: "label" };
|
|
156
|
+
if (argument.startsWith("-") || argument.startsWith("@"))
|
|
157
|
+
return externalArgvReference("unsupported_compound");
|
|
158
|
+
if (argument.includes("="))
|
|
159
|
+
return externalArgvReference("unsupported_compound");
|
|
160
|
+
return classifyRootArgvValue(argument);
|
|
161
|
+
}
|
|
162
|
+
function classifyRootArgvValue(value) {
|
|
163
|
+
if (!value)
|
|
164
|
+
return { kind: "label" };
|
|
165
|
+
if (hasUnsafeArgvControl(value))
|
|
166
|
+
return externalArgvReference("unsupported_compound");
|
|
167
|
+
if (/["']/u.test(value))
|
|
168
|
+
return externalArgvReference("quote_ambiguous");
|
|
169
|
+
if (value.includes("\\"))
|
|
170
|
+
return externalArgvReference("platform_ambiguous");
|
|
171
|
+
if (/^\/[A-Za-z?]$/u.test(value))
|
|
172
|
+
return externalArgvReference("platform_ambiguous");
|
|
173
|
+
if (value.startsWith("/"))
|
|
174
|
+
return externalArgvReference("absolute");
|
|
175
|
+
if (/^[A-Za-z]:/u.test(value))
|
|
176
|
+
return externalArgvReference("drive_prefixed");
|
|
177
|
+
if (value.startsWith("@"))
|
|
178
|
+
return externalArgvReference("unsupported_compound");
|
|
179
|
+
if (hasUnsupportedArgvCompound(value))
|
|
180
|
+
return externalArgvReference("unsupported_compound");
|
|
181
|
+
if (/^node:\d+(?:\.\d+)?$/u.test(value))
|
|
182
|
+
return { kind: "label" };
|
|
183
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value))
|
|
184
|
+
return externalArgvReference("protocol_prefixed");
|
|
185
|
+
if (value.includes(":"))
|
|
186
|
+
return /^\d{1,4}:\d{1,4}$/u.test(value)
|
|
187
|
+
? { kind: "label" }
|
|
188
|
+
: externalArgvReference("unsupported_compound");
|
|
189
|
+
return { kind: "repository_candidate", value };
|
|
190
|
+
}
|
|
191
|
+
function hasUnsafeArgvControl(value) {
|
|
192
|
+
return /[\u0000-\u001f\u007f]/u.test(value);
|
|
156
193
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return { value, quoted: false };
|
|
194
|
+
function hasUnsupportedArgvCompound(value) {
|
|
195
|
+
return (value.trim() !== value ||
|
|
196
|
+
value.startsWith("~") ||
|
|
197
|
+
!/^[\p{L}\p{N}._+,: /-]+$/u.test(value));
|
|
162
198
|
}
|
|
163
|
-
function
|
|
164
|
-
return
|
|
165
|
-
value.startsWith("/") ||
|
|
166
|
-
/^[A-Za-z]:\//u.test(value) ||
|
|
167
|
-
/^file:/iu.test(value) ||
|
|
168
|
-
/^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(value));
|
|
199
|
+
function externalArgvReference(reason) {
|
|
200
|
+
return { kind: "external_reference", reason };
|
|
169
201
|
}
|
|
170
202
|
function firstMatchingPattern(file, patterns) {
|
|
171
203
|
return patterns.find((pattern) => matchesRepoPattern(file, pattern)) ?? null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ProcessInstance {
|
|
2
|
+
pid: number;
|
|
3
|
+
parent_pid: number;
|
|
4
|
+
start_identity: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function processInstanceMatches(expected: Pick<ProcessInstance, "pid" | "start_identity">, current: Pick<ProcessInstance, "pid" | "start_identity">): boolean;
|
|
7
|
+
export declare function processSnapshot(): Promise<ProcessInstance[]>;
|
|
8
|
+
export declare function descendantsOf(rootPid: number, snapshot: readonly ProcessInstance[]): ProcessInstance[];
|
|
9
|
+
export declare function liveProcessInstances(candidates: readonly ProcessInstance[], snapshot: readonly ProcessInstance[]): ProcessInstance[];
|
|
10
|
+
export declare function rootProcessAlive(rootPid: number, rootInstance: ProcessInstance | null, snapshot: readonly ProcessInstance[]): boolean;
|
|
11
|
+
export declare function uniqueProcessInstances(instances: readonly ProcessInstance[]): ProcessInstance[];
|
|
12
|
+
export declare function processInstanceKey(instance: ProcessInstance): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const PROCESS_TABLE_OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function processInstanceMatches(expected, current) {
|
|
6
|
+
if (expected.pid !== current.pid)
|
|
7
|
+
return false;
|
|
8
|
+
if (expected.start_identity === null || current.start_identity === null)
|
|
9
|
+
return true;
|
|
10
|
+
return expected.start_identity === current.start_identity;
|
|
11
|
+
}
|
|
12
|
+
export async function processSnapshot() {
|
|
13
|
+
let stdout;
|
|
14
|
+
try {
|
|
15
|
+
const result = process.platform === "win32"
|
|
16
|
+
? await execFileAsync("powershell.exe", [
|
|
17
|
+
"-NoLogo",
|
|
18
|
+
"-NoProfile",
|
|
19
|
+
"-NonInteractive",
|
|
20
|
+
"-Command",
|
|
21
|
+
'$rows = Get-CimInstance Win32_Process; foreach ($row in $rows) { $started = if ($null -eq $row.CreationDate) { "unknown" } else { $row.CreationDate.ToUniversalTime().Ticks }; "$($row.ProcessId) $($row.ParentProcessId) $started" }',
|
|
22
|
+
], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
timeout: 5_000,
|
|
26
|
+
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
|
|
27
|
+
})
|
|
28
|
+
: await execFileAsync("ps", ["-A", "-o", "pid=,ppid="], {
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
timeout: 5_000,
|
|
31
|
+
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT,
|
|
32
|
+
});
|
|
33
|
+
stdout = result.stdout;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`process_observer_process_tree_inspection_unavailable:${message(error)}`);
|
|
37
|
+
}
|
|
38
|
+
const rows = [];
|
|
39
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
40
|
+
const [pidText, parentText, startText] = line.trim().split(/\s+/u);
|
|
41
|
+
const pid = Number.parseInt(pidText ?? "", 10);
|
|
42
|
+
const parent = Number.parseInt(parentText ?? "", 10);
|
|
43
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent))
|
|
44
|
+
continue;
|
|
45
|
+
rows.push({
|
|
46
|
+
pid,
|
|
47
|
+
parent_pid: parent,
|
|
48
|
+
start_identity: process.platform === "win32" && startText ? startText : null,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
export function descendantsOf(rootPid, snapshot) {
|
|
54
|
+
const children = new Map();
|
|
55
|
+
for (const processInstance of snapshot) {
|
|
56
|
+
const row = children.get(processInstance.parent_pid);
|
|
57
|
+
if (row)
|
|
58
|
+
row.push(processInstance);
|
|
59
|
+
else
|
|
60
|
+
children.set(processInstance.parent_pid, [processInstance]);
|
|
61
|
+
}
|
|
62
|
+
const result = [];
|
|
63
|
+
const pending = [...(children.get(rootPid) ?? [])];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
while (pending.length) {
|
|
66
|
+
const processInstance = pending.shift();
|
|
67
|
+
const key = processInstanceKey(processInstance);
|
|
68
|
+
if (seen.has(key))
|
|
69
|
+
continue;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
result.push(processInstance);
|
|
72
|
+
pending.push(...(children.get(processInstance.pid) ?? []));
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
export function liveProcessInstances(candidates, snapshot) {
|
|
77
|
+
if (process.platform !== "win32")
|
|
78
|
+
return candidates.filter((candidate) => processIdExists(candidate.pid));
|
|
79
|
+
const currentByPid = new Map(snapshot.map((entry) => [entry.pid, entry]));
|
|
80
|
+
return candidates.filter((candidate) => {
|
|
81
|
+
const current = currentByPid.get(candidate.pid);
|
|
82
|
+
return current ? processInstanceMatches(candidate, current) : false;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function rootProcessAlive(rootPid, rootInstance, snapshot) {
|
|
86
|
+
if (process.platform !== "win32")
|
|
87
|
+
return processIdExists(rootPid);
|
|
88
|
+
const current = snapshot.find((entry) => entry.pid === rootPid);
|
|
89
|
+
if (!current)
|
|
90
|
+
return false;
|
|
91
|
+
return rootInstance ? processInstanceMatches(rootInstance, current) : true;
|
|
92
|
+
}
|
|
93
|
+
export function uniqueProcessInstances(instances) {
|
|
94
|
+
return [
|
|
95
|
+
...new Map(instances.map((instance) => [processInstanceKey(instance), instance])).values(),
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
export function processInstanceKey(instance) {
|
|
99
|
+
return `${instance.pid}:${instance.start_identity ?? "pid-only"}`;
|
|
100
|
+
}
|
|
101
|
+
function processIdExists(pid) {
|
|
102
|
+
try {
|
|
103
|
+
process.kill(pid, 0);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (nodeErrorCode(error) === "ESRCH")
|
|
108
|
+
return false;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function nodeErrorCode(error) {
|
|
113
|
+
return error && typeof error === "object" && "code" in error
|
|
114
|
+
? String(error.code)
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
function message(error) {
|
|
118
|
+
return error instanceof Error ? error.message : String(error);
|
|
119
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { processInstanceMatches } from "./long-task-process-table.js";
|
|
2
|
+
export interface ProcessTreeObserver {
|
|
3
|
+
stop(): void;
|
|
4
|
+
wait(): Promise<void>;
|
|
5
|
+
terminate(force: boolean): Promise<void>;
|
|
6
|
+
forceQuiescence(): Promise<void>;
|
|
7
|
+
assertQuiescent(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createProcessTreeObserver(rootPid: number): ProcessTreeObserver;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { descendantsOf, liveProcessInstances, processInstanceKey, processInstanceMatches, processSnapshot, rootProcessAlive, uniqueProcessInstances, } from "./long-task-process-table.js";
|
|
4
|
+
export { processInstanceMatches } from "./long-task-process-table.js";
|
|
5
|
+
const PROCESS_TREE_GRACE_MS = 1_000;
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export function createProcessTreeObserver(rootPid) {
|
|
8
|
+
let stopped = false;
|
|
9
|
+
let rootInstance = null;
|
|
10
|
+
const observed = new Map();
|
|
11
|
+
const monitor = observe();
|
|
12
|
+
return {
|
|
13
|
+
stop() {
|
|
14
|
+
stopped = true;
|
|
15
|
+
},
|
|
16
|
+
wait() {
|
|
17
|
+
return monitor;
|
|
18
|
+
},
|
|
19
|
+
terminate(force) {
|
|
20
|
+
return terminateProcessTree(rootPid, rootInstance, force, [...observed.values()], true);
|
|
21
|
+
},
|
|
22
|
+
forceQuiescence() {
|
|
23
|
+
return forceProcessTreeQuiescence(rootPid, rootInstance, observed);
|
|
24
|
+
},
|
|
25
|
+
assertQuiescent() {
|
|
26
|
+
return assertProcessTreeQuiescent(rootPid, rootInstance, observed);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
async function observe() {
|
|
30
|
+
do {
|
|
31
|
+
const snapshot = await processSnapshot();
|
|
32
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
33
|
+
if (currentRoot &&
|
|
34
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))) {
|
|
35
|
+
rootInstance ??= currentRoot;
|
|
36
|
+
for (const descendant of descendantsOf(rootPid, snapshot))
|
|
37
|
+
observed.set(processInstanceKey(descendant), descendant);
|
|
38
|
+
}
|
|
39
|
+
if (stopped)
|
|
40
|
+
return;
|
|
41
|
+
await delay(process.platform === "win32" ? 100 : 25);
|
|
42
|
+
} while (!stopped);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function forceProcessTreeQuiescence(rootPid, rootInstance, observed) {
|
|
46
|
+
await terminateProcessTree(rootPid, rootInstance, true, [...observed.values()], true);
|
|
47
|
+
const deadline = Date.now() + PROCESS_TREE_GRACE_MS;
|
|
48
|
+
while (Date.now() < deadline) {
|
|
49
|
+
const snapshot = await processSnapshot();
|
|
50
|
+
const rootAlive = rootProcessAlive(rootPid, rootInstance, snapshot);
|
|
51
|
+
const descendantsAlive = liveProcessInstances([...observed.values()], snapshot).length;
|
|
52
|
+
const groupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
53
|
+
if (!rootAlive && descendantsAlive === 0 && !groupAlive)
|
|
54
|
+
return;
|
|
55
|
+
await delay(process.platform === "win32" ? 100 : 25);
|
|
56
|
+
}
|
|
57
|
+
throw new Error("process_observer_descendant_process_alive");
|
|
58
|
+
}
|
|
59
|
+
async function assertProcessTreeQuiescent(rootPid, rootInstance, observed) {
|
|
60
|
+
const snapshot = await processSnapshot();
|
|
61
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
62
|
+
const finalDescendants = currentRoot &&
|
|
63
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))
|
|
64
|
+
? descendantsOf(rootPid, snapshot)
|
|
65
|
+
: [];
|
|
66
|
+
const candidates = uniqueProcessInstances([
|
|
67
|
+
...observed.values(),
|
|
68
|
+
...finalDescendants,
|
|
69
|
+
]);
|
|
70
|
+
const descendants = liveProcessInstances(candidates, snapshot);
|
|
71
|
+
const processGroupAlive = process.platform !== "win32" && processGroupExists(rootPid);
|
|
72
|
+
if (!descendants.length && !processGroupAlive)
|
|
73
|
+
return;
|
|
74
|
+
await terminateProcessTree(rootPid, rootInstance, true, descendants, false);
|
|
75
|
+
throw new Error("process_observer_descendant_process_alive");
|
|
76
|
+
}
|
|
77
|
+
async function terminateProcessTree(rootPid, rootInstance, force, knownDescendants, includeRoot) {
|
|
78
|
+
if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
|
|
79
|
+
return;
|
|
80
|
+
if (process.platform === "win32") {
|
|
81
|
+
const snapshot = await processSnapshot();
|
|
82
|
+
const currentRoot = snapshot.find((entry) => entry.pid === rootPid);
|
|
83
|
+
const rootTarget = includeRoot &&
|
|
84
|
+
currentRoot &&
|
|
85
|
+
(!rootInstance || processInstanceMatches(rootInstance, currentRoot))
|
|
86
|
+
? currentRoot
|
|
87
|
+
: null;
|
|
88
|
+
const currentDescendants = rootTarget
|
|
89
|
+
? descendantsOf(rootPid, snapshot)
|
|
90
|
+
: [];
|
|
91
|
+
const descendants = liveProcessInstances(uniqueProcessInstances([...knownDescendants, ...currentDescendants]), snapshot).reverse();
|
|
92
|
+
const targets = rootTarget ? [...descendants, rootTarget] : descendants;
|
|
93
|
+
for (const target of targets)
|
|
94
|
+
await execFileAsync("taskkill.exe", ["/PID", String(target.pid), "/T", "/F"], {
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
timeout: PROCESS_TREE_GRACE_MS,
|
|
97
|
+
}).catch(() => undefined);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
101
|
+
if (includeRoot)
|
|
102
|
+
try {
|
|
103
|
+
process.kill(-rootPid, signal);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (nodeErrorCode(error) !== "ESRCH")
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
const snapshot = await processSnapshot();
|
|
110
|
+
const descendants = liveProcessInstances(knownDescendants, snapshot);
|
|
111
|
+
for (const descendant of descendants.reverse())
|
|
112
|
+
try {
|
|
113
|
+
process.kill(descendant.pid, signal);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (nodeErrorCode(error) !== "ESRCH")
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function processGroupExists(rootPid) {
|
|
121
|
+
try {
|
|
122
|
+
process.kill(-rootPid, 0);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (nodeErrorCode(error) === "ESRCH")
|
|
127
|
+
return false;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function nodeErrorCode(error) {
|
|
132
|
+
return error && typeof error === "object" && "code" in error
|
|
133
|
+
? String(error.code)
|
|
134
|
+
: null;
|
|
135
|
+
}
|
|
136
|
+
function delay(milliseconds) {
|
|
137
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
138
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
|
|
1
|
+
export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { assertProtectedRepositoryFile } from "./repository-path-safety.js";
|
|
1
|
+
export { assertProtectedRepositoryDirectory, assertProtectedRepositoryFile, } from "./repository-path-safety.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CompiledClaimsV2 } from "./long-task-claims.js";
|
|
2
2
|
import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
|
|
3
3
|
import { type Reporter } from "./long-task-ui-surface-validation.js";
|
|
4
|
-
|
|
4
|
+
import type { DeliveryContractStructureValidationOptions } from "./long-task-delivery-validation.js";
|
|
5
|
+
export declare function validateUiSurfaceBindings(contract: DeliveryContractV2, claims: CompiledClaimsV2, report?: Reporter, options?: DeliveryContractStructureValidationOptions): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateUiDesignBinding } from "./long-task-ui-design-policy.js";
|
|
2
2
|
import { bindingCarriers, controlClaims, expectedControlProofSurface, issue, unique, validateCarrierInputs, validateRootJourney, validateTargetLocalClaimProof, } from "./long-task-ui-surface-validation.js";
|
|
3
|
-
export function validateUiSurfaceBindings(contract, claims, report) {
|
|
3
|
+
export function validateUiSurfaceBindings(contract, claims, report, options = {}) {
|
|
4
4
|
const targets = new Map(contract.task.execution_targets.map((target) => [target.key, target]));
|
|
5
5
|
const requiredTargets = new Set(contract.task.target_profile.required_target_refs);
|
|
6
6
|
const confirmations = new Map(contract.global.acceptance.external_confirmations.map((item) => [
|
|
@@ -8,9 +8,9 @@ export function validateUiSurfaceBindings(contract, claims, report) {
|
|
|
8
8
|
item,
|
|
9
9
|
]));
|
|
10
10
|
for (const outcome of contract.outcomes)
|
|
11
|
-
validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report);
|
|
11
|
+
validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report, options);
|
|
12
12
|
}
|
|
13
|
-
function validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report) {
|
|
13
|
+
function validateOutcomeBindings(outcome, claims, targets, requiredTargets, confirmations, report, options = {}) {
|
|
14
14
|
const surfaceBindings = outcome.product.surface_bindings ?? [];
|
|
15
15
|
if (outcome.product.controls.length && !surfaceBindings.length)
|
|
16
16
|
issue(report, "ui_surface_binding_required", outcome.key);
|
|
@@ -39,7 +39,10 @@ function validateOutcomeBindings(outcome, claims, targets, requiredTargets, conf
|
|
|
39
39
|
if (!requiredTargets.has(binding.target_ref))
|
|
40
40
|
issue(report, "ui_surface_binding_required_target_required", `${label}:${binding.target_ref}`);
|
|
41
41
|
validateBoundControls(binding, controls, boundControls, boundControlTargets, label, report);
|
|
42
|
-
const carriers = bindingCarriers(binding.route_binding_ref, binding.component_binding_refs, technicalBindings, label, report
|
|
42
|
+
const carriers = bindingCarriers(binding.route_binding_ref, binding.component_binding_refs, technicalBindings, label, report, {
|
|
43
|
+
allowEmptyComponentRefs: options.allowDeferredDesignComponentBindingClosure === true &&
|
|
44
|
+
binding.design_targets.length > 0,
|
|
45
|
+
});
|
|
43
46
|
const rootCheck = checks.get(binding.root_journey_check_ref);
|
|
44
47
|
if (!rootCheck)
|
|
45
48
|
issue(report, "ui_surface_binding_root_check_unknown", `${label}:${binding.root_journey_check_ref}`);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { CompiledClaimsV2 } from "./long-task-claims.js";
|
|
2
2
|
import type { DeliveryCheckV2, DeliveryOutcomeV2, ProductClaimV2, ProofSurface } from "./long-task-delivery-types.js";
|
|
3
3
|
export type Reporter = (message: string) => void;
|
|
4
|
-
export declare function bindingCarriers(routeRef: string, componentRefs: string[], bindings: Map<string, DeliveryOutcomeV2["technical"]["bindings"][number]>, label: string, report?: Reporter
|
|
4
|
+
export declare function bindingCarriers(routeRef: string, componentRefs: string[], bindings: Map<string, DeliveryOutcomeV2["technical"]["bindings"][number]>, label: string, report?: Reporter, options?: {
|
|
5
|
+
allowEmptyComponentRefs?: boolean;
|
|
6
|
+
}): string[];
|
|
5
7
|
export declare function validateRootJourney(binding: DeliveryOutcomeV2["product"]["surface_bindings"][number], check: DeliveryCheckV2, label: string, report?: Reporter): void;
|
|
6
8
|
export declare function validateTargetLocalClaimProof(outcome: DeliveryOutcomeV2, claim: ProductClaimV2, targetRef: string, expectedSurface: ProofSurface | null, claims: CompiledClaimsV2, checks: Map<string, DeliveryCheckV2>, label: string, report?: Reporter): void;
|
|
7
9
|
export declare function expectedControlProofSurface(runtimeFamily: string | undefined): ProofSurface;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { proveRepositoryPatternSubset } from "./long-task-paths.js";
|
|
2
|
-
export function bindingCarriers(routeRef, componentRefs, bindings, label, report) {
|
|
3
|
-
if (!componentRefs.length)
|
|
2
|
+
export function bindingCarriers(routeRef, componentRefs, bindings, label, report, options = {}) {
|
|
3
|
+
if (!componentRefs.length && !options.allowEmptyComponentRefs)
|
|
4
4
|
issue(report, "ui_surface_binding_component_ref_required", label);
|
|
5
5
|
unique(componentRefs, "ui_surface_binding_component_ref_duplicate", label, report);
|
|
6
6
|
const carriers = [];
|
|
@@ -37,6 +37,8 @@ function changedVerifierFiles(previous, next) {
|
|
|
37
37
|
...Object.keys(next.bundle_files),
|
|
38
38
|
]);
|
|
39
39
|
const changed = [...files].filter((file) => previous.bundle_files[file] !== next.bundle_files[file]);
|
|
40
|
+
if (previous.bundle_sha256 !== next.bundle_sha256)
|
|
41
|
+
changed.push("<bundle>");
|
|
40
42
|
if (previous.schema_sha256 !== next.schema_sha256)
|
|
41
43
|
changed.push("<schema>");
|
|
42
44
|
if (previous.hook_sha256 !== next.hook_sha256)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export declare function captureWorkspaceFingerprint(rootInput: string, excludedPrefixes?: string[]): Promise<WorkspaceFingerprintV2>;
|
|
3
|
+
export declare function captureWorkspaceManifest(rootInput: string, workdirInput: string, _copyRoot?: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceManifestV2>;
|
|
4
|
+
export declare function changedWorkspacePaths(baseline: WorkspaceManifestV2, current: WorkspaceManifestV2): string[];
|
|
5
|
+
export declare function changedWorkspacePathsFromHead(rootInput: string, workdirInput: string, additionalExcludedWorkdirs?: string[]): Promise<string[]>;
|
|
6
|
+
export declare function workspaceFingerprintExcludedPrefixes(root: string, workdirs: string[]): string[];
|
|
7
|
+
export declare function workspaceSnapshotExcludedPrefixes(root: string, workdirs: string[]): string[];
|
|
8
|
+
export declare function workspacePathExcluded(relative: string, excluded: string[]): boolean;
|