@frankzhang2026/opencode-android-orchestrator 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +79 -18
- package/dist/config/queue-policy.d.ts.map +1 -1
- package/dist/config/queue-policy.js +1 -2
- package/dist/config/queue-policy.js.map +1 -1
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +14 -3
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/installation.d.ts +1 -1
- package/dist/doctor/installation.d.ts.map +1 -1
- package/dist/doctor/installation.js +10 -1
- package/dist/doctor/installation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/adaptive-templates.d.ts +4 -1
- package/dist/installer/adaptive-templates.d.ts.map +1 -1
- package/dist/installer/adaptive-templates.js +49 -13
- package/dist/installer/adaptive-templates.js.map +1 -1
- package/dist/installer/android-project.d.ts +3 -1
- package/dist/installer/android-project.d.ts.map +1 -1
- package/dist/installer/android-project.js.map +1 -1
- package/dist/installer/capabilities-script.d.ts +2 -0
- package/dist/installer/capabilities-script.d.ts.map +1 -0
- package/dist/installer/capabilities-script.js +77 -0
- package/dist/installer/capabilities-script.js.map +1 -0
- package/dist/installer/gradle-verification.d.ts.map +1 -1
- package/dist/installer/gradle-verification.js +13 -6
- package/dist/installer/gradle-verification.js.map +1 -1
- package/dist/installer/init.d.ts.map +1 -1
- package/dist/installer/init.js +2 -1
- package/dist/installer/init.js.map +1 -1
- package/dist/installer/opencode-config.d.ts +3 -3
- package/dist/installer/opencode-config.js +1 -1
- package/dist/installer/project-capabilities.d.ts +35 -0
- package/dist/installer/project-capabilities.d.ts.map +1 -0
- package/dist/installer/project-capabilities.js +82 -0
- package/dist/installer/project-capabilities.js.map +1 -0
- package/dist/installer/upgrade.d.ts.map +1 -1
- package/dist/installer/upgrade.js +9 -1
- package/dist/installer/upgrade.js.map +1 -1
- package/dist/queue/cli.d.ts.map +1 -1
- package/dist/queue/cli.js +32 -5
- package/dist/queue/cli.js.map +1 -1
- package/dist/queue/executor.d.ts +1 -0
- package/dist/queue/executor.d.ts.map +1 -1
- package/dist/queue/executor.js +155 -11
- package/dist/queue/executor.js.map +1 -1
- package/dist/queue/process-ownership.d.ts +12 -0
- package/dist/queue/process-ownership.d.ts.map +1 -0
- package/dist/queue/process-ownership.js +58 -0
- package/dist/queue/process-ownership.js.map +1 -0
- package/dist/queue/queue.d.ts +25 -1
- package/dist/queue/queue.d.ts.map +1 -1
- package/dist/queue/queue.js +152 -11
- package/dist/queue/queue.js.map +1 -1
- package/dist/queue/service.d.ts.map +1 -1
- package/dist/queue/service.js +19 -2
- package/dist/queue/service.js.map +1 -1
- package/dist/queue/storage.d.ts +3 -1
- package/dist/queue/storage.d.ts.map +1 -1
- package/dist/queue/storage.js +25 -15
- package/dist/queue/storage.js.map +1 -1
- package/dist/queue/supervision.d.ts +38 -0
- package/dist/queue/supervision.d.ts.map +1 -0
- package/dist/queue/supervision.js +247 -0
- package/dist/queue/supervision.js.map +1 -0
- package/dist/queue/tools.d.ts.map +1 -1
- package/dist/queue/tools.js +4 -2
- package/dist/queue/tools.js.map +1 -1
- package/docs/MIGRATION.md +110 -9
- package/docs/QUEUE.md +360 -6
- package/docs/SECURITY.md +5 -2
- package/docs/TROUBLESHOOTING.md +82 -8
- package/package.json +1 -1
- package/templates/.opencode/agents/scheduled-coder.md +22 -0
- package/templates/.opencode/agents/scheduled-planner.md +73 -2
- package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +34 -2
- package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +65 -4
- package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +22 -0
- package/templates/AGENTS.md.fragment +10 -4
- package/templates/README.md +3 -1
- package/templates/automation/config.schema.json +212 -22
- package/templates/automation/task-contract.schema.json +617 -21
- package/templates/automation/tasks/TASK-TEMPLATE.json.example +31 -3
- package/templates/automation/verification/collect.init.gradle +98 -0
- package/templates/automation/verification/contract.cjs +93 -0
- package/templates/automation/verification/inventory.cjs +433 -0
- package/templates/automation/verification/project.cjs +172 -0
- package/templates/automation/verification/recovery.cjs +426 -0
- package/templates/scripts/automation/acceptance-report.sh +29 -0
- package/templates/scripts/automation/claim-task.sh +35 -2
- package/templates/scripts/automation/integration-scope-gate.sh +3 -3
- package/templates/scripts/automation/lib.sh +167 -17
- package/templates/scripts/automation/orchestrate-task.sh +7 -1
- package/templates/scripts/automation/preflight.sh +1 -2
- package/templates/scripts/automation/quality-gate.sh +10 -1
- package/templates/scripts/automation/record-red.sh +182 -32
- package/templates/scripts/automation/resume-task.sh +3 -0
- package/templates/scripts/automation/scope-gate.sh +4 -4
- package/templates/scripts/automation/show-acceptance-review.sh +8 -0
- package/templates/scripts/automation/status.sh +38 -0
- package/templates/scripts/automation/submit-review.sh +10 -1
- package/templates/scripts/automation/validate-contract.sh +56 -3
- package/templates/scripts/automation/verify-integration.sh +3 -0
- package/templates/scripts/automation/verify-task.sh +17 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { invariant } from "./storage.js";
|
|
3
|
+
/** Discard command/environment text immediately: only identities may enter evidence. */
|
|
4
|
+
export function parseProcessTable(output, token) {
|
|
5
|
+
invariant(/^[a-f0-9]{64}$/.test(token), "Invalid Worker ownership token");
|
|
6
|
+
return output.split("\n").filter(line => line.trim()).map(line => {
|
|
7
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\d+\s+\S+\s+\d+)\s+(\S+)\s*(.*)$/);
|
|
8
|
+
invariant(match, "Cannot parse process ownership snapshot");
|
|
9
|
+
return { pid: Number(match[1]), ppid: Number(match[2]), pgid: Number(match[3]),
|
|
10
|
+
started: match[4].trim().replace(/\s+/g, " "), zombie: match[5].startsWith("Z"),
|
|
11
|
+
owned: (` ${match[6]} `).includes(` AUTOMATION_WORKER_TOKEN=${token} `) };
|
|
12
|
+
}).filter(row => !row.zombie).map(({ zombie: _zombie, ...row }) => row);
|
|
13
|
+
}
|
|
14
|
+
export function processSnapshot(token) {
|
|
15
|
+
const result = spawnSync("ps", ["axeww", "-o", "pid=,ppid=,pgid=,lstart=,stat=,command="], { encoding: "utf8", timeout: 10000, maxBuffer: 64 * 1024 * 1024 });
|
|
16
|
+
// Do not include ps stdout/stderr: it contains unrelated process environments.
|
|
17
|
+
invariant(result.status === 0 && !result.error && result.stdout.trim(), "Cannot obtain complete process ownership snapshot");
|
|
18
|
+
return parseProcessTable(result.stdout, token);
|
|
19
|
+
}
|
|
20
|
+
export function ownedTree(rows, worker, known = []) {
|
|
21
|
+
const root = rows.find(row => row.pid === worker.pid);
|
|
22
|
+
// macOS may stop exposing an exiting process's environment before it becomes
|
|
23
|
+
// a zombie. Previously witnessed identity is the same proof used for children.
|
|
24
|
+
const witnessedRoot = root && known.some(p => p.pid === root.pid && p.started === root.started && p.pgid === root.pgid);
|
|
25
|
+
invariant(!root || (root.started === worker.started && (root.owned || witnessedRoot)), "Worker identity changed; refusing termination");
|
|
26
|
+
const selected = new Set(rows.filter(row => row.owned || known.some(p => p.pid === row.pid && p.started === row.started && p.pgid === row.pgid)).map(row => row.pid));
|
|
27
|
+
if (root)
|
|
28
|
+
selected.add(worker.pid);
|
|
29
|
+
for (let changed = true; changed;) {
|
|
30
|
+
changed = false;
|
|
31
|
+
for (const row of rows)
|
|
32
|
+
if (selected.has(row.ppid) && !selected.has(row.pid)) {
|
|
33
|
+
selected.add(row.pid);
|
|
34
|
+
changed = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
invariant(rows.filter(row => row.pgid === worker.pid).every(row => selected.has(row.pid)), "A Worker group member lacks ownership proof; retain execution slot");
|
|
38
|
+
const tree = rows.filter(row => selected.has(row.pid));
|
|
39
|
+
invariant(tree.length <= 256, "Worker process inventory exceeds the supervised limit");
|
|
40
|
+
return tree;
|
|
41
|
+
}
|
|
42
|
+
export function signalOwned(token, expected, signal, known = []) {
|
|
43
|
+
const current = processSnapshot(token).find(row => row.pid === expected.pid);
|
|
44
|
+
if (!current)
|
|
45
|
+
return false;
|
|
46
|
+
const witnessed = known.some(p => p.pid === current.pid && p.started === current.started && p.pgid === current.pgid);
|
|
47
|
+
invariant(current.started === expected.started && (current.owned || witnessed), "Process identity changed before signal; refusing termination");
|
|
48
|
+
try {
|
|
49
|
+
process.kill(expected.pid, signal);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.code === "ESRCH")
|
|
54
|
+
return false;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=process-ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-ownership.js","sourceRoot":"","sources":["../../src/queue/process-ownership.ts"],"names":[],"mappings":"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"}
|
package/dist/queue/queue.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { type Supervision } from "./supervision.js";
|
|
1
2
|
import type { ApprovalProof } from "./approvals.js";
|
|
2
3
|
import { QueueStorage, type ProcessIdentity } from "./storage.js";
|
|
3
4
|
import { type QueuePolicy } from "../config/queue-policy.js";
|
|
4
5
|
export type WorkspaceStrategy = "inPlaceExclusive" | "isolatedWorktree";
|
|
5
6
|
export type CommitPolicy = "humanApproval" | "autoCommit";
|
|
6
|
-
export type JobKind = "execute" | "integrate" | "revalidate" | "resume" | "resume-review" | "abort" | "recover";
|
|
7
|
+
export type JobKind = "execute" | "integrate" | "revalidate" | "resume" | "resume-review" | "abort" | "recover" | "retry-baseline";
|
|
7
8
|
export interface QueueConfiguration {
|
|
8
9
|
schemaVersion: number;
|
|
9
10
|
enabled: boolean;
|
|
@@ -21,9 +22,11 @@ export interface QueueConfiguration {
|
|
|
21
22
|
fullUnitTestTasks: string[];
|
|
22
23
|
focusedTestTasks: string[];
|
|
23
24
|
assembleTasks: string[];
|
|
25
|
+
deviceTestTasks?: string[];
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
export interface Draft {
|
|
29
|
+
planningInputsSha256?: string;
|
|
27
30
|
proposalApproval?: ApprovalProof;
|
|
28
31
|
key: string;
|
|
29
32
|
taskId: string;
|
|
@@ -71,6 +74,16 @@ export interface QueueItem extends Draft {
|
|
|
71
74
|
sealedRunId: string | null;
|
|
72
75
|
candidateId: string | null;
|
|
73
76
|
completedCommit: string | null;
|
|
77
|
+
baselineRecovery?: {
|
|
78
|
+
sha256: string;
|
|
79
|
+
nextRunAt: number | null;
|
|
80
|
+
deadline: number;
|
|
81
|
+
failure: Record<string, unknown> | null;
|
|
82
|
+
waitingReason: string | null;
|
|
83
|
+
};
|
|
84
|
+
stageRecovery?: Record<string, {
|
|
85
|
+
sha256: string;
|
|
86
|
+
}>;
|
|
74
87
|
}
|
|
75
88
|
export interface QueueRun {
|
|
76
89
|
id: string;
|
|
@@ -83,7 +96,9 @@ export interface QueueRun {
|
|
|
83
96
|
state: string;
|
|
84
97
|
error: string | null;
|
|
85
98
|
} | null;
|
|
99
|
+
supervision?: Supervision;
|
|
86
100
|
}
|
|
101
|
+
export declare function publicRun(run: QueueRun | null): unknown;
|
|
87
102
|
export interface QueueNotification {
|
|
88
103
|
id: string;
|
|
89
104
|
key: string;
|
|
@@ -141,6 +156,15 @@ export declare function now(): string;
|
|
|
141
156
|
export declare function matchesPath(pattern: string, path: string): boolean;
|
|
142
157
|
export declare function validateContract(contract: Record<string, unknown>, config: QueueConfiguration): void;
|
|
143
158
|
export declare function draftFromItem(item: QueueItem): Draft;
|
|
159
|
+
export interface ContinuityPolicy {
|
|
160
|
+
version: 1;
|
|
161
|
+
isolatedAutoIntegration: boolean;
|
|
162
|
+
planningRefresh: "reject" | "completedQueueTasks";
|
|
163
|
+
planningInputs: string[];
|
|
164
|
+
}
|
|
165
|
+
export declare function continuityPolicy(contract: Record<string, unknown>): ContinuityPolicy | undefined;
|
|
166
|
+
export declare function isolatedAutoIntegrationAuthorized(item: Pick<Draft, "contract" | "workspaceStrategy" | "commitPolicy">): boolean;
|
|
167
|
+
export declare function planningInputsDigest(root: string, head: string, patterns: string[]): string;
|
|
144
168
|
export declare function assertAuthorized(item: QueueItem): void;
|
|
145
169
|
export declare class TaskQueue {
|
|
146
170
|
readonly storage: QueueStorage<QueueDocument>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/queue/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/queue/queue.ts"],"names":[],"mappings":"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"}
|
package/dist/queue/queue.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { initialSupervision, supervisionEvidence } from "./supervision.js";
|
|
2
|
+
import { ownedTree, processSnapshot } from "./process-ownership.js";
|
|
1
3
|
import { randomUUID } from "node:crypto";
|
|
2
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
3
6
|
import { existsSync } from "node:fs";
|
|
4
7
|
import { join } from "node:path";
|
|
5
8
|
import { atomicJson, fileLock, git, gitBuffer, invariant, isAlive, processGroupAlive, processIdentity, readJson, sha256, QueueStorage, } from "./storage.js";
|
|
6
9
|
import { queuePolicy } from "../config/queue-policy.js";
|
|
10
|
+
export function publicRun(run) {
|
|
11
|
+
return run ? { ...run, ...(run.supervision ? { supervision: supervisionEvidence(run.supervision) } : {}) } : null;
|
|
12
|
+
}
|
|
7
13
|
const TERMINAL = new Set(["COMPLETED", "CANCELLED", "ABORTED", "SUPERSEDED"]);
|
|
8
14
|
const STOPPED = new Set(["AWAITING_HUMAN", "READY_TO_COMMIT", "BLOCKED", "TEST_FAILED", "NEEDS_HUMAN", "INTEGRATION_BLOCKED", "BASELINE_REVIEW"]);
|
|
9
15
|
const SNAPSHOT_RESPONSE_MAX_BYTES = 16 * 1024;
|
|
@@ -30,6 +36,11 @@ export function matchesPath(pattern, path) {
|
|
|
30
36
|
return new RegExp(`^${expression}${pattern.endsWith("/") ? ".*" : ""}$`).test(path);
|
|
31
37
|
}
|
|
32
38
|
export function validateContract(contract, config) {
|
|
39
|
+
if ([4, 5, 6, 7, 8].includes(Number(contract.schemaVersion))) {
|
|
40
|
+
const validator = createRequire(import.meta.url)("../../templates/automation/verification/contract.cjs");
|
|
41
|
+
validator.validateContract(contract, config);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
33
44
|
invariant(TASK_ID.test(String(contract.id)), "Invalid task ID");
|
|
34
45
|
invariant([1, 2, 3].includes(Number(contract.schemaVersion)), "Unsupported contract schema");
|
|
35
46
|
invariant(contract.designApproved === true && contract.ambiguityPolicy === "BLOCKED", "An approved, bounded design is required");
|
|
@@ -56,8 +67,65 @@ export function validateContract(contract, config) {
|
|
|
56
67
|
invariant(target && typeof target === "object" && typeof target.gradleTask === "string" && config.gradleVerification.focusedTestTasks.includes(target.gradleTask), "Focused test task is not configured");
|
|
57
68
|
invariant(typeof target.filter === "string" && /^[A-Za-z0-9_.#$*-]+$/.test(target.filter), "Unsafe test filter");
|
|
58
69
|
}
|
|
70
|
+
const targetKeys = contract.targetTests.map(target => `${target.gradleTask}\u0000${target.filter}`);
|
|
71
|
+
invariant(new Set(targetKeys).size === targetKeys.length, "Focused test targets must be unique");
|
|
59
72
|
invariant(typeof contract.deviceTestsRequired === "boolean", "Missing device-test policy");
|
|
73
|
+
invariant(!contract.deviceTestsRequired || (config.gradleVerification.deviceTestTasks?.length ?? 0) > 0, "Device tests are required by this contract but no device test tasks are available");
|
|
60
74
|
invariant(contract.testPolicy === "required" || (contract.testPolicy === "not-required" && typeof contract.testPolicyReason === "string" && contract.testPolicyReason.length >= 20), "Missing test policy");
|
|
75
|
+
if (Number(contract.schemaVersion) === 3) {
|
|
76
|
+
const verification = contract.verification;
|
|
77
|
+
invariant(verification && typeof verification === "object" && !Array.isArray(verification), "Schema V3 requires structured verification");
|
|
78
|
+
const value = verification;
|
|
79
|
+
invariant(Object.keys(value).sort().join(",") === "cases,maxPreparationFixes,version", "Invalid verification fields");
|
|
80
|
+
invariant(value.version === 1 && Number.isInteger(value.maxPreparationFixes) && Number(value.maxPreparationFixes) >= 0 && Number(value.maxPreparationFixes) <= 1, "Invalid verification policy");
|
|
81
|
+
invariant(Array.isArray(value.cases) && value.cases.length > 0, "Verification cases are required");
|
|
82
|
+
const caseIds = new Set();
|
|
83
|
+
const testIds = new Set();
|
|
84
|
+
let changedBehavior = 0;
|
|
85
|
+
for (const rawCase of value.cases) {
|
|
86
|
+
invariant(rawCase && typeof rawCase === "object" && !Array.isArray(rawCase), "Invalid verification case");
|
|
87
|
+
const testCase = rawCase;
|
|
88
|
+
const allowedFields = new Set(["id", "criterion", "intent", "before", "after", "source", "test", "expectedFailure"]);
|
|
89
|
+
invariant(Object.keys(testCase).every(key => allowedFields.has(key)), "Invalid verification case fields");
|
|
90
|
+
invariant(typeof testCase.id === "string" && /^[A-Z][A-Z0-9-]{2,63}$/.test(testCase.id) && !caseIds.has(testCase.id), "Verification case IDs must be unique and stable");
|
|
91
|
+
caseIds.add(testCase.id);
|
|
92
|
+
invariant(Number.isInteger(testCase.criterion) && Number(testCase.criterion) >= 1 && Number(testCase.criterion) <= contract.acceptanceCriteria.length, "Verification criterion reference is invalid");
|
|
93
|
+
invariant(testCase.after === "pass", "Every verification case must pass after implementation");
|
|
94
|
+
const intent = testCase.intent;
|
|
95
|
+
const before = testCase.before;
|
|
96
|
+
const source = testCase.source;
|
|
97
|
+
invariant(["preserve", "change", "observe"].includes(String(intent)), "Invalid verification intent");
|
|
98
|
+
invariant(["pass", "fail", "observe"].includes(String(before)), "Invalid pre-change expectation");
|
|
99
|
+
const test = testCase.test;
|
|
100
|
+
invariant(test && typeof test === "object" && !Array.isArray(test), "Verification test identity is required");
|
|
101
|
+
const testIdentity = test;
|
|
102
|
+
invariant(Object.keys(testIdentity).sort().join(",") === "className,name,target", "Invalid verification test fields");
|
|
103
|
+
invariant(Number.isInteger(testIdentity.target) && Number(testIdentity.target) >= 0 && Number(testIdentity.target) < contract.targetTests.length, "Verification target index is invalid");
|
|
104
|
+
invariant(typeof testIdentity.className === "string" && testIdentity.className.length > 0 && typeof testIdentity.name === "string" && testIdentity.name.length > 0, "Verification test identity is incomplete");
|
|
105
|
+
const testKey = `${testIdentity.target}\u0000${testIdentity.className}\u0000${testIdentity.name}`;
|
|
106
|
+
invariant(!testIds.has(testKey), "Verification test identities must be unique");
|
|
107
|
+
testIds.add(testKey);
|
|
108
|
+
const failure = testCase.expectedFailure;
|
|
109
|
+
if (intent === "preserve") {
|
|
110
|
+
invariant(before === "pass" && ["existingTest", "baselineCapture", "measuredFact"].includes(String(source)) && failure === undefined, "Preserved behavior must pass on a measured baseline");
|
|
111
|
+
}
|
|
112
|
+
else if (intent === "change") {
|
|
113
|
+
changedBehavior += 1;
|
|
114
|
+
invariant(before === "fail" && source === "userRequirement", "Changed behavior must fail before implementation and originate in the approved requirement");
|
|
115
|
+
invariant(failure && typeof failure === "object" && !Array.isArray(failure), "Changed behavior requires an expected failure");
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
invariant(before === "observe" && ["userRequirement", "existingTest", "baselineCapture", "measuredFact"].includes(String(source)), "Observed behavior must declare an admissible source");
|
|
119
|
+
}
|
|
120
|
+
if (failure !== undefined) {
|
|
121
|
+
const expected = failure;
|
|
122
|
+
invariant(Object.keys(expected).every(key => ["type", "messageIncludes", "origin"].includes(key)), "Invalid expected failure fields");
|
|
123
|
+
invariant(typeof expected.type === "string" && expected.type.length > 0 && typeof expected.origin === "string" && expected.origin.length >= 12, "Expected failure type and origin are required");
|
|
124
|
+
invariant(expected.messageIncludes === undefined || (typeof expected.messageIncludes === "string" && expected.messageIncludes.length >= 3), "Expected failure message fragment is invalid");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
invariant(changedBehavior > 0, "At least one changed behavior must provide genuine RED");
|
|
128
|
+
}
|
|
61
129
|
invariant(!/replace with|TASK-EXAMPLE|\btodo\b|\btbd\b|placeholder/i.test(JSON.stringify(contract)), "Contract contains placeholders");
|
|
62
130
|
}
|
|
63
131
|
function draftDigest(draft) {
|
|
@@ -65,16 +133,32 @@ function draftDigest(draft) {
|
|
|
65
133
|
return sha256(JSON.stringify(sealed));
|
|
66
134
|
}
|
|
67
135
|
export function draftFromItem(item) {
|
|
68
|
-
const { sequence: _sequence, approvedAt: _approvedAt, authorization: _authorization, state: _state, waitingReason: _waitingReason, runId: _runId, taskRoot: _taskRoot, request: _request, sealedDiff: _sealedDiff, sealedRunId: _sealedRunId, candidateId: _candidateId, completedCommit: _commit, approvedWorkspaceStrategy, queuePriority: _priority, ...draft } = item;
|
|
136
|
+
const { sequence: _sequence, approvedAt: _approvedAt, authorization: _authorization, state: _state, waitingReason: _waitingReason, runId: _runId, taskRoot: _taskRoot, request: _request, sealedDiff: _sealedDiff, sealedRunId: _sealedRunId, candidateId: _candidateId, completedCommit: _commit, baselineRecovery: _baselineRecovery, stageRecovery: _stageRecovery, approvedWorkspaceStrategy, queuePriority: _priority, ...draft } = item;
|
|
69
137
|
return { ...draft, workspaceStrategy: approvedWorkspaceStrategy };
|
|
70
138
|
}
|
|
139
|
+
export function continuityPolicy(contract) {
|
|
140
|
+
return contract.schemaVersion === 8 ? contract.continuity : undefined;
|
|
141
|
+
}
|
|
142
|
+
export function isolatedAutoIntegrationAuthorized(item) {
|
|
143
|
+
return item.workspaceStrategy === "isolatedWorktree" && item.commitPolicy === "autoCommit" &&
|
|
144
|
+
continuityPolicy(item.contract)?.isolatedAutoIntegration === true;
|
|
145
|
+
}
|
|
146
|
+
export function planningInputsDigest(root, head, patterns) {
|
|
147
|
+
const entries = gitBuffer(root, ["ls-tree", "-r", "-z", head]).toString("utf8").split("\0").filter(Boolean)
|
|
148
|
+
.filter(entry => patterns.some(pattern => matchesPath(pattern, entry.slice(entry.indexOf("\t") + 1))));
|
|
149
|
+
invariant(entries.every(entry => /^100(?:644|755) blob /.test(entry)), "Planning inputs must be regular tracked files, not symlinks or submodules");
|
|
150
|
+
// Paths, modes, object IDs and the selectors themselves bind additions,
|
|
151
|
+
// deletions and previously absent files without reading the Coder worktree.
|
|
152
|
+
return sha256(JSON.stringify({ version: 1, patterns, entries }));
|
|
153
|
+
}
|
|
71
154
|
export function assertAuthorized(item) {
|
|
72
155
|
invariant(!item.authorization.revoked, "Contract authorization was revoked");
|
|
73
156
|
invariant(item.digest === draftDigest(draftFromItem(item)) && item.digest === item.authorization.digest, "Approved contract or scheduling policy changed; fresh approval required");
|
|
74
157
|
invariant(item.authorization.commitPolicy === item.commitPolicy && item.authorization.pushAfterAcceptance === false, "Invalid sealed commit or no-push authorization");
|
|
75
158
|
invariant(sha256(item.contractText) === item.contractSha256 && sha256(item.plan) === item.planSha256, "Approved artifacts changed");
|
|
76
159
|
invariant(JSON.stringify(JSON.parse(item.contractText)) === JSON.stringify(item.contract), "Contract text and metadata disagree");
|
|
77
|
-
invariant(item.workspaceStrategy !== "isolatedWorktree" || item.commitPolicy === "humanApproval"
|
|
160
|
+
invariant(item.workspaceStrategy !== "isolatedWorktree" || item.commitPolicy === "humanApproval" ||
|
|
161
|
+
(item.approvedWorkspaceStrategy === "isolatedWorktree" && isolatedAutoIntegrationAuthorized(item)), "isolatedWorktree + autoCommit is unsupported without explicit V8 approval");
|
|
78
162
|
}
|
|
79
163
|
export class TaskQueue {
|
|
80
164
|
storage;
|
|
@@ -205,7 +289,11 @@ export class TaskQueue {
|
|
|
205
289
|
const commitPolicy = input.commitPolicy ?? config.commitPolicy ?? "humanApproval";
|
|
206
290
|
invariant(["inPlaceExclusive", "isolatedWorktree"].includes(workspaceStrategy), "Invalid workspace strategy");
|
|
207
291
|
invariant(["humanApproval", "autoCommit"].includes(commitPolicy), "Invalid commit policy");
|
|
208
|
-
invariant(workspaceStrategy === "inPlaceExclusive" || commitPolicy === "humanApproval"
|
|
292
|
+
invariant(workspaceStrategy === "inPlaceExclusive" || commitPolicy === "humanApproval" ||
|
|
293
|
+
isolatedAutoIntegrationAuthorized({ contract: input.contract, workspaceStrategy, commitPolicy }), "isolatedWorktree + autoCommit is unsupported without explicit V8 approval");
|
|
294
|
+
const continuity = continuityPolicy(input.contract);
|
|
295
|
+
invariant(!continuity?.isolatedAutoIntegration || (workspaceStrategy === "isolatedWorktree" && commitPolicy === "autoCommit"), "Isolated auto integration requires matching workspace and commit policies");
|
|
296
|
+
const planningInputsSha256 = continuity ? planningInputsDigest(this.storage.root, input.planningHead, continuity.planningInputs) : undefined;
|
|
209
297
|
const dependencies = input.dependsOn ?? [];
|
|
210
298
|
invariant(dependencies.every(id => TASK_ID.test(id) && id !== input.contract.id) && new Set(dependencies).size === dependencies.length, "Invalid task dependencies");
|
|
211
299
|
const priority = input.priority ?? 0;
|
|
@@ -216,7 +304,7 @@ export class TaskQueue {
|
|
|
216
304
|
return this.storage.transaction(document => {
|
|
217
305
|
const taskId = String(input.contract.id);
|
|
218
306
|
const version = Math.max(0, ...document.drafts.filter(draft => draft.taskId === taskId).map(draft => draft.version)) + 1;
|
|
219
|
-
const sealed = { ...(proposalApproval ? { proposalApproval } : {}), key: `${taskId}@${version}`, taskId, version, contract: input.contract, contractText,
|
|
307
|
+
const sealed = { ...(proposalApproval ? { proposalApproval } : {}), ...(planningInputsSha256 ? { planningInputsSha256 } : {}), key: `${taskId}@${version}`, taskId, version, contract: input.contract, contractText,
|
|
220
308
|
plan: input.plan, contractSha256: sha256(contractText), planSha256: sha256(input.plan),
|
|
221
309
|
planningHead: input.planningHead, targetBranch: input.targetBranch, sourceRoot: this.storage.root,
|
|
222
310
|
workspaceStrategy, commitPolicy, notBefore, dependsOn: dependencies, priority, createdAt: now() };
|
|
@@ -226,9 +314,12 @@ export class TaskQueue {
|
|
|
226
314
|
});
|
|
227
315
|
}
|
|
228
316
|
approvalText(draft) {
|
|
229
|
-
|
|
317
|
+
const text = draft.commitPolicy === "autoCommit"
|
|
230
318
|
? `批准入队 ${draft.key}:通过构建、全量单测和独立 Review 后自动本地提交并集成,不推送远程。`
|
|
231
319
|
: `批准入队 ${draft.key}:执行后等待人工确认提交,不推送远程。`;
|
|
320
|
+
const policy = continuityPolicy(draft.contract);
|
|
321
|
+
return policy ? text + (policy.isolatedAutoIntegration ? "隔离工作区自动集成已授权。" : "") +
|
|
322
|
+
(policy.planningRefresh === "completedQueueTasks" ? "仅允许已完成队列提交且声明的规划输入未变化时刷新基线。" : "规划基线变化须重新审批。") : text;
|
|
232
323
|
}
|
|
233
324
|
enqueue(key, digest, approval, proof) {
|
|
234
325
|
this.config();
|
|
@@ -292,6 +383,11 @@ export class TaskQueue {
|
|
|
292
383
|
this.storage.transaction(document => {
|
|
293
384
|
const run = document.active;
|
|
294
385
|
invariant(run, "No active execution to recover");
|
|
386
|
+
if (run.supervision) {
|
|
387
|
+
invariant(!run.supervision.owner || !isAlive(run.supervision.owner), "Worker supervisor is still alive");
|
|
388
|
+
invariant(run.worker, "Supervised Worker launch is ambiguous; preserve reservation");
|
|
389
|
+
invariant(ownedTree(processSnapshot(run.supervision.token), run.worker, run.supervision.known).length === 0, "Supervised descendants are still alive");
|
|
390
|
+
}
|
|
295
391
|
if (run.worker) {
|
|
296
392
|
invariant(!isAlive(run.worker) && !processGroupAlive(run.worker.pid), "Executor or its children are still alive; preserve the execution slot");
|
|
297
393
|
}
|
|
@@ -312,11 +408,24 @@ export class TaskQueue {
|
|
|
312
408
|
details(key) {
|
|
313
409
|
const item = this.item(key);
|
|
314
410
|
const evidence = {};
|
|
315
|
-
for (const name of ["acceptance-report", "review", "full-test-verification", "commit-transaction", "integration", "planning-baseline"
|
|
411
|
+
for (const name of ["acceptance-report", "review", "full-test-verification", "commit-transaction", "integration", "planning-baseline",
|
|
412
|
+
"baseline-inventory", "baseline-recovery", "test-preflight", "test-manifest", "green-inventory", "inventory-status"]) {
|
|
316
413
|
const path = join(this.storage.runtime, "evidence", item.taskId, `${name}.json`);
|
|
317
414
|
if (existsSync(path))
|
|
318
415
|
evidence[name] = readJson(path);
|
|
319
416
|
}
|
|
417
|
+
const document = this.storage.read();
|
|
418
|
+
const run = document.active?.key === item.key ? document.active : [...document.runs].reverse().find(candidate => candidate.key === item.key);
|
|
419
|
+
if (run?.supervision)
|
|
420
|
+
evidence.workerSupervision = supervisionEvidence(run.supervision);
|
|
421
|
+
const stages = {};
|
|
422
|
+
for (const phase of ["red", "green", "review"]) {
|
|
423
|
+
const file = join(this.storage.runtime, "evidence", item.taskId, "stage-recovery", `${phase}.json`);
|
|
424
|
+
if (existsSync(file))
|
|
425
|
+
stages[phase] = readJson(file);
|
|
426
|
+
}
|
|
427
|
+
if (Object.keys(stages).length)
|
|
428
|
+
evidence.stageRecovery = stages;
|
|
320
429
|
return { ...item, currentTargetHead: git(item.sourceRoot, ["rev-parse", `refs/heads/${item.targetBranch}`]), evidence };
|
|
321
430
|
}
|
|
322
431
|
notify(document, item, message) {
|
|
@@ -353,6 +462,7 @@ export class TaskQueue {
|
|
|
353
462
|
});
|
|
354
463
|
}
|
|
355
464
|
request(key, kind, approval, candidate, proof) {
|
|
465
|
+
invariant(kind !== "retry-baseline", "Automatic baseline recovery is reserved for the approved scheduler policy");
|
|
356
466
|
const config = this.config();
|
|
357
467
|
this.storage.transaction(document => {
|
|
358
468
|
const item = this.findItem(document, key);
|
|
@@ -405,15 +515,38 @@ export class TaskQueue {
|
|
|
405
515
|
return null;
|
|
406
516
|
}
|
|
407
517
|
const requests = document.items.filter(item => item.request).sort((a, b) => a.sequence - b.sequence);
|
|
518
|
+
const retries = document.items.filter(item => !item.request && item.state === "BLOCKED" && Number(item.contract.schemaVersion) >= 5 && item.baselineRecovery?.nextRunAt != null)
|
|
519
|
+
.sort((a, b) => a.sequence - b.sequence);
|
|
408
520
|
const pending = document.items.filter(item => item.state === "QUEUED").sort((a, b) => b.queuePriority - a.queuePriority || a.sequence - b.sequence);
|
|
409
|
-
for (const item of [...requests, ...pending]) {
|
|
521
|
+
for (const item of [...requests, ...retries, ...pending]) {
|
|
410
522
|
item.waitingReason = null;
|
|
523
|
+
const retry = !item.request && item.state === "BLOCKED" ? item.baselineRecovery : null;
|
|
524
|
+
if (retry) {
|
|
525
|
+
if (Date.now() >= retry.deadline) {
|
|
526
|
+
item.waitingReason = "Baseline recovery elapsed-time budget exhausted";
|
|
527
|
+
retry.nextRunAt = null;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (retry.nextRunAt === null || Date.now() < retry.nextRunAt) {
|
|
531
|
+
item.waitingReason = `Baseline recovery backoff until ${new Date(retry.nextRunAt).toISOString()}`;
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (item.sealedRunId !== item.runId || !item.sealedDiff) {
|
|
535
|
+
item.waitingReason = "Baseline candidate was not sealed; explicit recovery required";
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
411
539
|
if (fixed && fixed.key !== item.key) {
|
|
412
540
|
item.waitingReason = `Waiting for ${fixed.taskId}: ${fixed.state}`;
|
|
413
541
|
continue;
|
|
414
542
|
}
|
|
415
|
-
if (item.commitPolicy === "autoCommit" && config.workspaceStrategy === "isolatedWorktree"
|
|
416
|
-
item.
|
|
543
|
+
if (item.commitPolicy === "autoCommit" && config.workspaceStrategy === "isolatedWorktree" &&
|
|
544
|
+
!(item.approvedWorkspaceStrategy === "isolatedWorktree" && isolatedAutoIntegrationAuthorized(item))) {
|
|
545
|
+
item.waitingReason = "isolatedWorktree + autoCommit is unsupported without explicit V8 approval; fresh contract approval is required";
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (continuityPolicy(item.contract) && item.approvedWorkspaceStrategy !== config.workspaceStrategy) {
|
|
549
|
+
item.waitingReason = "V8 workspace strategy changed; restore the approved policy or approve a revised contract";
|
|
417
550
|
continue;
|
|
418
551
|
}
|
|
419
552
|
if (!item.request && item.notBefore && Date.parse(item.notBefore) > Date.now()) {
|
|
@@ -425,7 +558,7 @@ export class TaskQueue {
|
|
|
425
558
|
item.waitingReason = `Waiting for dependency ${dependency} to complete local integration`;
|
|
426
559
|
continue;
|
|
427
560
|
}
|
|
428
|
-
if (!item.request && config.workspaceStrategy === "isolatedWorktree" && (retained.length >= (config.queue?.maxWorkspaces ?? 3) || workspaceBytes >= (config.queue?.maxWorkspaceBytes ?? 20 * 1024 ** 3))) {
|
|
561
|
+
if (!item.request && !retry && config.workspaceStrategy === "isolatedWorktree" && (retained.length >= (config.queue?.maxWorkspaces ?? 3) || workspaceBytes >= (config.queue?.maxWorkspaceBytes ?? 20 * 1024 ** 3))) {
|
|
429
562
|
item.waitingReason = "Isolated workspace capacity or disk limit reached";
|
|
430
563
|
continue;
|
|
431
564
|
}
|
|
@@ -448,7 +581,13 @@ export class TaskQueue {
|
|
|
448
581
|
}
|
|
449
582
|
const launcher = processIdentity(process.pid);
|
|
450
583
|
invariant(launcher, "Cannot establish execution launcher identity");
|
|
451
|
-
const run = { id: randomUUID(), key: item.key, kind: item.request?.kind ?? "execute", createdAt: now(), launcher, worker: null, outcome: null };
|
|
584
|
+
const run = { id: randomUUID(), key: item.key, kind: item.request?.kind ?? (retry ? "retry-baseline" : "execute"), createdAt: now(), launcher, worker: null, outcome: null };
|
|
585
|
+
// Consume the scheduled wake before launch. A crash or rejected input
|
|
586
|
+
// must not replay it indefinitely without a new durable failed attempt.
|
|
587
|
+
if (item.baselineRecovery && (run.kind === "retry-baseline" || run.kind === "resume"))
|
|
588
|
+
item.baselineRecovery.nextRunAt = null;
|
|
589
|
+
if (Number(item.contract.schemaVersion) >= 6)
|
|
590
|
+
run.supervision = initialSupervision(item.contract.execution, Date.now());
|
|
452
591
|
document.active = run;
|
|
453
592
|
document.strategy = config.workspaceStrategy;
|
|
454
593
|
item.runId = run.id;
|
|
@@ -469,6 +608,8 @@ export class TaskQueue {
|
|
|
469
608
|
return;
|
|
470
609
|
const item = document.items.find(item => item.key === run.key);
|
|
471
610
|
invariant(item, "Active execution has no contract");
|
|
611
|
+
if (run.supervision && run.supervision.state !== "EXITED")
|
|
612
|
+
return;
|
|
472
613
|
if (!run.worker) {
|
|
473
614
|
// A crash between reservation and spawn is ambiguous, never requeue it.
|
|
474
615
|
if (Date.now() - Date.parse(run.createdAt) > 30000)
|