@brainervirus/workit-codex 1.0.11 → 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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +2343 -2055
- package/dist/workit-hook.js +731 -240
- package/package.json +3 -3
package/dist/workit-hook.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// packages/workit-codex/hooks/workit-hook.ts
|
|
6
|
-
import { existsSync as
|
|
7
|
-
import
|
|
6
|
+
import { existsSync as existsSync5, realpathSync as realpathSync6, statSync } from "node:fs";
|
|
7
|
+
import path9 from "node:path";
|
|
8
8
|
|
|
9
9
|
// packages/workit-core/src/core/task-contract.ts
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -7353,8 +7353,97 @@ var refSchema = discriminatedUnion("kind", [
|
|
|
7353
7353
|
id
|
|
7354
7354
|
}).strict(),
|
|
7355
7355
|
object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
|
|
7356
|
-
object({ kind: literal("external"), url: text.url() }).strict()
|
|
7356
|
+
object({ kind: literal("external"), url: text.url() }).strict(),
|
|
7357
|
+
object({
|
|
7358
|
+
kind: literal("standing"),
|
|
7359
|
+
workspace: nonEmpty,
|
|
7360
|
+
class: nonEmpty,
|
|
7361
|
+
configDigest: nullableDigest
|
|
7362
|
+
}).strict()
|
|
7357
7363
|
]);
|
|
7364
|
+
var rewriteUnknown = (value, map) => {
|
|
7365
|
+
if (Array.isArray(value)) {
|
|
7366
|
+
const out = [];
|
|
7367
|
+
for (const item of value) {
|
|
7368
|
+
const rewritten = rewriteUnknown(item, map);
|
|
7369
|
+
if (!rewritten.drop)
|
|
7370
|
+
out.push(rewritten.value);
|
|
7371
|
+
}
|
|
7372
|
+
return { drop: false, value: out };
|
|
7373
|
+
}
|
|
7374
|
+
if (typeof value === "object" && value !== null) {
|
|
7375
|
+
if (refSchema.safeParse(value).success) {
|
|
7376
|
+
const mapped = map(value);
|
|
7377
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7378
|
+
}
|
|
7379
|
+
const out = {};
|
|
7380
|
+
for (const [key, item] of Object.entries(value)) {
|
|
7381
|
+
const rewritten = rewriteUnknown(item, map);
|
|
7382
|
+
out[key] = rewritten.drop ? null : rewritten.value;
|
|
7383
|
+
}
|
|
7384
|
+
return { drop: false, value: out };
|
|
7385
|
+
}
|
|
7386
|
+
return { drop: false, value };
|
|
7387
|
+
};
|
|
7388
|
+
var rewriteNode = (value, schema, map) => {
|
|
7389
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && refSchema.safeParse(value).success) {
|
|
7390
|
+
const mapped = map(value);
|
|
7391
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7392
|
+
}
|
|
7393
|
+
let node = schema;
|
|
7394
|
+
while (node instanceof ZodNullable || node instanceof ZodOptional || node instanceof ZodDefault) {
|
|
7395
|
+
node = node.unwrap();
|
|
7396
|
+
}
|
|
7397
|
+
if (node === refSchema) {
|
|
7398
|
+
if (!refSchema.safeParse(value).success)
|
|
7399
|
+
return { drop: false, value };
|
|
7400
|
+
const mapped = map(value);
|
|
7401
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7402
|
+
}
|
|
7403
|
+
if (node instanceof ZodArray) {
|
|
7404
|
+
if (!Array.isArray(value))
|
|
7405
|
+
return { drop: false, value };
|
|
7406
|
+
const out = [];
|
|
7407
|
+
for (const item of value) {
|
|
7408
|
+
const rewritten = rewriteNode(item, node.element, map);
|
|
7409
|
+
if (!rewritten.drop)
|
|
7410
|
+
out.push(rewritten.value);
|
|
7411
|
+
}
|
|
7412
|
+
return { drop: false, value: out };
|
|
7413
|
+
}
|
|
7414
|
+
if (node instanceof ZodObject) {
|
|
7415
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
7416
|
+
return { drop: false, value };
|
|
7417
|
+
const shape = node.shape;
|
|
7418
|
+
const record = value;
|
|
7419
|
+
const out = {};
|
|
7420
|
+
for (const [key, item] of Object.entries(record)) {
|
|
7421
|
+
if (!(key in shape)) {
|
|
7422
|
+
const scrubbed = rewriteUnknown(item, map);
|
|
7423
|
+
out[key] = scrubbed.drop ? null : scrubbed.value;
|
|
7424
|
+
continue;
|
|
7425
|
+
}
|
|
7426
|
+
const child = shape[key];
|
|
7427
|
+
const rewritten = rewriteNode(item, child, map);
|
|
7428
|
+
if (rewritten.drop) {
|
|
7429
|
+
if (child instanceof ZodOptional || child instanceof ZodDefault)
|
|
7430
|
+
continue;
|
|
7431
|
+
if (child instanceof ZodNullable) {
|
|
7432
|
+
out[key] = null;
|
|
7433
|
+
continue;
|
|
7434
|
+
}
|
|
7435
|
+
return { drop: true };
|
|
7436
|
+
}
|
|
7437
|
+
out[key] = rewritten.value;
|
|
7438
|
+
}
|
|
7439
|
+
return { drop: false, value: out };
|
|
7440
|
+
}
|
|
7441
|
+
return { drop: false, value };
|
|
7442
|
+
};
|
|
7443
|
+
var rewriteRecordRefs = (value, schema, map) => {
|
|
7444
|
+
const rewritten = rewriteNode(value, schema, map);
|
|
7445
|
+
return rewritten.drop ? null : rewritten.value;
|
|
7446
|
+
};
|
|
7358
7447
|
var provenanceSchema = object({
|
|
7359
7448
|
kind: _enum(["host_observed", "agent_reported", "imported"]),
|
|
7360
7449
|
host: hostSchema,
|
|
@@ -7575,7 +7664,8 @@ var decisionSchema = object({
|
|
|
7575
7664
|
approvedContent: text,
|
|
7576
7665
|
displayed: text.optional(),
|
|
7577
7666
|
contentRefs: array(refSchema),
|
|
7578
|
-
statedChoice: object({ ref: text, text }).strict().optional()
|
|
7667
|
+
statedChoice: object({ ref: text, text }).strict().optional(),
|
|
7668
|
+
standing: object({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
|
|
7579
7669
|
}).strict(),
|
|
7580
7670
|
digest,
|
|
7581
7671
|
response: _enum(["approved", "rejected", "stated"]),
|
|
@@ -9005,11 +9095,11 @@ class TaskStore {
|
|
|
9005
9095
|
return workspace;
|
|
9006
9096
|
if (!workspace.data)
|
|
9007
9097
|
return failure("not_found", "workspace not found");
|
|
9008
|
-
const
|
|
9009
|
-
if (!
|
|
9098
|
+
const expected = input.expectedRevision;
|
|
9099
|
+
if (!expected)
|
|
9010
9100
|
return failure("invalid_input", "task revision is required");
|
|
9011
|
-
if (task.data.revision !==
|
|
9012
|
-
return this.conflict(
|
|
9101
|
+
if (task.data.revision !== expected)
|
|
9102
|
+
return this.conflict(expected, task.data.revision);
|
|
9013
9103
|
if (workspace.data.revision !== input.expectedWorkspaceRevision)
|
|
9014
9104
|
return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
|
|
9015
9105
|
const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
|
|
@@ -9543,6 +9633,7 @@ var same = (left, right) => {
|
|
|
9543
9633
|
};
|
|
9544
9634
|
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9545
9635
|
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
9636
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
9546
9637
|
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
9547
9638
|
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9548
9639
|
const code = char.charCodeAt(0);
|
|
@@ -9589,21 +9680,398 @@ function assertProductWriteAllowed(input) {
|
|
|
9589
9680
|
}
|
|
9590
9681
|
|
|
9591
9682
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
9592
|
-
import { createHash as
|
|
9683
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
9593
9684
|
import * as fs6 from "node:fs";
|
|
9594
9685
|
import { spawnSync } from "node:child_process";
|
|
9595
|
-
import
|
|
9686
|
+
import path8 from "node:path";
|
|
9596
9687
|
|
|
9597
9688
|
// packages/workit-core/src/core/authority.ts
|
|
9598
|
-
import { createHash as
|
|
9689
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9599
9690
|
import * as fs5 from "node:fs";
|
|
9691
|
+
import path7 from "node:path";
|
|
9692
|
+
|
|
9693
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
9694
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9695
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
9696
|
+
|
|
9697
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
9698
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
9699
|
+
import path6 from "node:path";
|
|
9700
|
+
|
|
9701
|
+
// packages/workit-core/src/core/config.ts
|
|
9702
|
+
import {
|
|
9703
|
+
copyFileSync,
|
|
9704
|
+
cpSync,
|
|
9705
|
+
existsSync as existsSync3,
|
|
9706
|
+
mkdirSync as mkdirSync2,
|
|
9707
|
+
readFileSync as readFileSync2,
|
|
9708
|
+
readdirSync as readdirSync2,
|
|
9709
|
+
writeFileSync as writeFileSync2
|
|
9710
|
+
} from "node:fs";
|
|
9711
|
+
import os from "node:os";
|
|
9600
9712
|
import path5 from "node:path";
|
|
9713
|
+
|
|
9714
|
+
// packages/workit-core/src/core/boundary.ts
|
|
9715
|
+
var EVENT = {
|
|
9716
|
+
initialization: "initialization",
|
|
9717
|
+
provenance: "provenance",
|
|
9718
|
+
assets: "assets",
|
|
9719
|
+
configurationSource: "configuration_source",
|
|
9720
|
+
hooks: "hooks",
|
|
9721
|
+
mcpConnection: "mcp_connection",
|
|
9722
|
+
migration: "migration",
|
|
9723
|
+
installSteps: "install_steps",
|
|
9724
|
+
uncaughtFailure: "uncaught_failure",
|
|
9725
|
+
toolsFailed: "tools_failed",
|
|
9726
|
+
doctor: "doctor"
|
|
9727
|
+
};
|
|
9728
|
+
var errorDetail = (err) => {
|
|
9729
|
+
if (err instanceof Error) {
|
|
9730
|
+
return { error_name: err.name, error: err.message };
|
|
9731
|
+
}
|
|
9732
|
+
return { error_name: "unknown", error: String(err) };
|
|
9733
|
+
};
|
|
9734
|
+
|
|
9735
|
+
// packages/workit-core/src/core/config.ts
|
|
9736
|
+
var diagnosticLogger;
|
|
9737
|
+
var PRESETS = {
|
|
9738
|
+
gitflow: {
|
|
9739
|
+
allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
|
|
9740
|
+
protected: ["main", "develop", "master", "prod", "production"]
|
|
9741
|
+
},
|
|
9742
|
+
"github-flow": { allowed: ["*"], protected: ["main"] },
|
|
9743
|
+
"trunk-based": { allowed: ["*"], protected: ["main"] },
|
|
9744
|
+
custom: { allowed: [], protected: [] }
|
|
9745
|
+
};
|
|
9746
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workit");
|
|
9747
|
+
var migratedDir = null;
|
|
9748
|
+
var migrationFailed = false;
|
|
9749
|
+
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
9750
|
+
if (migratedDir === dir)
|
|
9751
|
+
return dir;
|
|
9752
|
+
if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
|
|
9753
|
+
migratedDir = dir;
|
|
9754
|
+
return dir;
|
|
9755
|
+
}
|
|
9756
|
+
const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
|
|
9757
|
+
if (!existsSync3(legacy)) {
|
|
9758
|
+
migratedDir = dir;
|
|
9759
|
+
return dir;
|
|
9760
|
+
}
|
|
9761
|
+
if (!migrationFailed && existsSync3(dir)) {
|
|
9762
|
+
migratedDir = dir;
|
|
9763
|
+
return dir;
|
|
9764
|
+
}
|
|
9765
|
+
migrationFailed = false;
|
|
9766
|
+
mkdirSync2(dir, { recursive: true });
|
|
9767
|
+
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
9768
|
+
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
9769
|
+
const src = path5.join(legacy, entry.name);
|
|
9770
|
+
const dest = path5.join(dir, entry.name);
|
|
9771
|
+
if (existsSync3(dest))
|
|
9772
|
+
continue;
|
|
9773
|
+
try {
|
|
9774
|
+
if (entry.isDirectory())
|
|
9775
|
+
cpSync(src, dest, { recursive: true });
|
|
9776
|
+
else if (entry.isFile())
|
|
9777
|
+
copyFileSync(src, dest);
|
|
9778
|
+
} catch (err) {
|
|
9779
|
+
migrationFailed = true;
|
|
9780
|
+
diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
|
|
9781
|
+
console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
|
|
9782
|
+
}
|
|
9783
|
+
}
|
|
9784
|
+
if (!migrationFailed)
|
|
9785
|
+
migratedDir = dir;
|
|
9786
|
+
return dir;
|
|
9787
|
+
};
|
|
9788
|
+
var configDir = () => ensureConfigDir();
|
|
9789
|
+
var DEFAULTS = {
|
|
9790
|
+
locale: "en",
|
|
9791
|
+
localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
|
|
9792
|
+
timezone: "America/Santiago",
|
|
9793
|
+
branchPolicy: {
|
|
9794
|
+
preset: "gitflow",
|
|
9795
|
+
allowed: [...PRESETS.gitflow.allowed],
|
|
9796
|
+
protected: [...PRESETS.gitflow.protected]
|
|
9797
|
+
},
|
|
9798
|
+
commitPolicy: { preset: "conventional" }
|
|
9799
|
+
};
|
|
9800
|
+
var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9801
|
+
|
|
9802
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
9803
|
+
var readWorkspacesResult = (dir = configDir()) => {
|
|
9804
|
+
const file = path6.join(dir, "workspaces.json");
|
|
9805
|
+
let raw;
|
|
9806
|
+
try {
|
|
9807
|
+
raw = readFileSync3(file, "utf8");
|
|
9808
|
+
} catch {
|
|
9809
|
+
return { status: "missing", path: file, entries: [] };
|
|
9810
|
+
}
|
|
9811
|
+
let parsed;
|
|
9812
|
+
try {
|
|
9813
|
+
parsed = JSON.parse(raw);
|
|
9814
|
+
} catch {
|
|
9815
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
|
|
9816
|
+
}
|
|
9817
|
+
if (!isConfigObject(parsed)) {
|
|
9818
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
9819
|
+
}
|
|
9820
|
+
const list = parsed.workspaces;
|
|
9821
|
+
return {
|
|
9822
|
+
status: "valid",
|
|
9823
|
+
path: file,
|
|
9824
|
+
entries: Array.isArray(list) ? list : []
|
|
9825
|
+
};
|
|
9826
|
+
};
|
|
9827
|
+
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
9828
|
+
var globToRegExp = (glob) => {
|
|
9829
|
+
let out = "";
|
|
9830
|
+
for (let i = 0;i < glob.length; i++) {
|
|
9831
|
+
const c = glob[i];
|
|
9832
|
+
if (c === "*") {
|
|
9833
|
+
if (glob[i + 1] === "*") {
|
|
9834
|
+
if (glob[i + 2] === "/") {
|
|
9835
|
+
if (out === "")
|
|
9836
|
+
out += "/?";
|
|
9837
|
+
out += "(?:[^/]+/)*";
|
|
9838
|
+
i += 2;
|
|
9839
|
+
} else {
|
|
9840
|
+
if (i + 2 >= glob.length) {
|
|
9841
|
+
if (out.endsWith("/")) {
|
|
9842
|
+
out = out.slice(0, -1);
|
|
9843
|
+
out += "(?:/.*)?";
|
|
9844
|
+
} else {
|
|
9845
|
+
out += ".*";
|
|
9846
|
+
}
|
|
9847
|
+
} else {
|
|
9848
|
+
out += ".*";
|
|
9849
|
+
}
|
|
9850
|
+
i++;
|
|
9851
|
+
}
|
|
9852
|
+
} else {
|
|
9853
|
+
out += "[^/]*";
|
|
9854
|
+
}
|
|
9855
|
+
} else {
|
|
9856
|
+
out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
return new RegExp(`^${out}$`);
|
|
9860
|
+
};
|
|
9861
|
+
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
9862
|
+
var realpathOf = (p) => {
|
|
9863
|
+
const real = realpathSync2.native ?? realpathSync2;
|
|
9864
|
+
try {
|
|
9865
|
+
return real(p);
|
|
9866
|
+
} catch {
|
|
9867
|
+
return p;
|
|
9868
|
+
}
|
|
9869
|
+
};
|
|
9870
|
+
var canonicalGlob = (glob) => {
|
|
9871
|
+
const m = /[*?[\]{]/.exec(glob);
|
|
9872
|
+
const prefix = m ? glob.slice(0, m.index) : glob;
|
|
9873
|
+
const rest = m ? glob.slice(m.index) : "";
|
|
9874
|
+
if (!prefix)
|
|
9875
|
+
return glob;
|
|
9876
|
+
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
9877
|
+
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
9878
|
+
};
|
|
9879
|
+
var resolveWorkspaceFrom = (cwd, dir) => {
|
|
9880
|
+
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
9881
|
+
for (const entry of loadWorkspacesFrom(dir)) {
|
|
9882
|
+
if (!entry || typeof entry !== "object")
|
|
9883
|
+
continue;
|
|
9884
|
+
const ws = entry;
|
|
9885
|
+
if (typeof ws.glob !== "string" || !ws.glob)
|
|
9886
|
+
continue;
|
|
9887
|
+
const glob = ws.glob.replaceAll("\\", "/");
|
|
9888
|
+
const canonical = canonicalGlob(glob);
|
|
9889
|
+
for (const target of targets) {
|
|
9890
|
+
if (matchWorkspace(glob, target))
|
|
9891
|
+
return ws;
|
|
9892
|
+
if (canonical !== glob && matchWorkspace(canonical, target))
|
|
9893
|
+
return ws;
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
return null;
|
|
9897
|
+
};
|
|
9898
|
+
|
|
9899
|
+
// packages/workit-core/src/core/branch.ts
|
|
9900
|
+
import { execFileSync } from "node:child_process";
|
|
9901
|
+
var verifyPushIdentity = (root, provider, account) => {
|
|
9902
|
+
if (!account || typeof account !== "string")
|
|
9903
|
+
return { ok: false, error: "push identity requires a configured area account" };
|
|
9904
|
+
const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
|
|
9905
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
9906
|
+
let login;
|
|
9907
|
+
try {
|
|
9908
|
+
login = execFileSync(bin, [...probe], {
|
|
9909
|
+
cwd: root,
|
|
9910
|
+
encoding: "utf8",
|
|
9911
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9912
|
+
}).trim();
|
|
9913
|
+
} catch {
|
|
9914
|
+
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
9915
|
+
}
|
|
9916
|
+
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
9917
|
+
return {
|
|
9918
|
+
ok: false,
|
|
9919
|
+
error: `push identity ${login || "(unknown)"} does not match area account ${account}`
|
|
9920
|
+
};
|
|
9921
|
+
return { ok: true };
|
|
9922
|
+
};
|
|
9923
|
+
|
|
9924
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
9925
|
+
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
9926
|
+
var OPERATION_CLASS = {
|
|
9927
|
+
"git.branch_setup": "branch",
|
|
9928
|
+
"git.commit": "commit",
|
|
9929
|
+
"git.push": "push",
|
|
9930
|
+
"hosting.pull_request": "pr",
|
|
9931
|
+
"hosting.merge": "merge"
|
|
9932
|
+
};
|
|
9933
|
+
var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
|
|
9934
|
+
var digestOf = (configDirPath) => {
|
|
9935
|
+
try {
|
|
9936
|
+
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
9937
|
+
return createHash3("sha256").update(raw).digest("hex");
|
|
9938
|
+
} catch {
|
|
9939
|
+
return null;
|
|
9940
|
+
}
|
|
9941
|
+
};
|
|
9942
|
+
var normalizeClasses = (value) => {
|
|
9943
|
+
if (value === true)
|
|
9944
|
+
return [...AUTO_CLASSES];
|
|
9945
|
+
if (!Array.isArray(value))
|
|
9946
|
+
return [];
|
|
9947
|
+
const seen = new Set;
|
|
9948
|
+
for (const item of value) {
|
|
9949
|
+
if (typeof item === "string" && AUTO_CLASSES.includes(item))
|
|
9950
|
+
seen.add(item);
|
|
9951
|
+
}
|
|
9952
|
+
return [...seen];
|
|
9953
|
+
};
|
|
9954
|
+
var resolveAutoApproval = (root, dir = configDir()) => {
|
|
9955
|
+
let match;
|
|
9956
|
+
try {
|
|
9957
|
+
match = resolveWorkspaceFrom(root, dir);
|
|
9958
|
+
} catch {
|
|
9959
|
+
return { status: "off" };
|
|
9960
|
+
}
|
|
9961
|
+
if (!match)
|
|
9962
|
+
return { status: "off" };
|
|
9963
|
+
const classes = normalizeClasses(match.autoApprove);
|
|
9964
|
+
if (classes.length === 0)
|
|
9965
|
+
return { status: "off" };
|
|
9966
|
+
return {
|
|
9967
|
+
status: "on",
|
|
9968
|
+
workspace: match.name,
|
|
9969
|
+
classes,
|
|
9970
|
+
account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
|
|
9971
|
+
configDigest: digestOf(dir)
|
|
9972
|
+
};
|
|
9973
|
+
};
|
|
9974
|
+
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
9975
|
+
var standingApprovalLive = (root, receipt, cls, dir) => {
|
|
9976
|
+
if (!isStandingReceipt(receipt))
|
|
9977
|
+
return false;
|
|
9978
|
+
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
9979
|
+
return false;
|
|
9980
|
+
if (receipt.class !== cls)
|
|
9981
|
+
return false;
|
|
9982
|
+
const resolved = resolveAutoApproval(root, dir);
|
|
9983
|
+
if (resolved.status !== "on")
|
|
9984
|
+
return false;
|
|
9985
|
+
if (resolved.workspace !== receipt.workspace)
|
|
9986
|
+
return false;
|
|
9987
|
+
return resolved.classes.includes(cls);
|
|
9988
|
+
};
|
|
9989
|
+
var standingReceiptFor = (root, workspace, cls, dir) => {
|
|
9990
|
+
const resolved = resolveAutoApproval(root, dir);
|
|
9991
|
+
return {
|
|
9992
|
+
kind: "standing",
|
|
9993
|
+
workspace,
|
|
9994
|
+
class: cls,
|
|
9995
|
+
configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
|
|
9996
|
+
};
|
|
9997
|
+
};
|
|
9998
|
+
var verifyStandingApproval = (root, task, caller, binding) => {
|
|
9999
|
+
if (task.status !== "active")
|
|
10000
|
+
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
10001
|
+
const session = task.intent?.provenance?.session;
|
|
10002
|
+
if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
|
|
10003
|
+
return failure("permission_denied", "standing approval requires the lead session");
|
|
10004
|
+
const standing = binding.standing;
|
|
10005
|
+
if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
|
|
10006
|
+
return failure("invalid_input", "standing approval needs a workspace and class");
|
|
10007
|
+
let operation;
|
|
10008
|
+
try {
|
|
10009
|
+
operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
|
|
10010
|
+
} catch {
|
|
10011
|
+
operation = null;
|
|
10012
|
+
}
|
|
10013
|
+
const cls = operationAutoClass(operation);
|
|
10014
|
+
if (!cls || cls !== standing.class)
|
|
10015
|
+
return failure("permission_denied", "standing approval does not cover this operation");
|
|
10016
|
+
const resolved = resolveAutoApproval(root);
|
|
10017
|
+
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
10018
|
+
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
10019
|
+
if (cls === "push") {
|
|
10020
|
+
const workspace = resolveWorkspaceFrom(root, configDir());
|
|
10021
|
+
const provider = workspace?.vcs?.provider;
|
|
10022
|
+
const account = resolved.account;
|
|
10023
|
+
if (!provider || !account)
|
|
10024
|
+
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
10025
|
+
const identity = verifyPushIdentity(root, provider, account);
|
|
10026
|
+
if (!identity.ok)
|
|
10027
|
+
return failure("permission_denied", identity.error);
|
|
10028
|
+
}
|
|
10029
|
+
return success(null, null, {
|
|
10030
|
+
kind: "host_observed",
|
|
10031
|
+
host: caller.host,
|
|
10032
|
+
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
10033
|
+
workerId: null,
|
|
10034
|
+
receipts: [standingReceiptFor(root, standing.workspace, cls)]
|
|
10035
|
+
});
|
|
10036
|
+
};
|
|
10037
|
+
|
|
10038
|
+
// packages/workit-core/src/core/authority.ts
|
|
9601
10039
|
var verifiedAuthorities = new WeakMap;
|
|
9602
10040
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
9603
10041
|
const token = {};
|
|
9604
10042
|
verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
|
|
9605
10043
|
return token;
|
|
9606
10044
|
};
|
|
10045
|
+
var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
|
|
10046
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10047
|
+
var normalizeChainSteps = (steps) => {
|
|
10048
|
+
if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
|
|
10049
|
+
return null;
|
|
10050
|
+
const normalized = [];
|
|
10051
|
+
for (const step of steps) {
|
|
10052
|
+
if (typeof step === "string") {
|
|
10053
|
+
if (!step.trim())
|
|
10054
|
+
return null;
|
|
10055
|
+
normalized.push({ kind: "commit", message: step });
|
|
10056
|
+
continue;
|
|
10057
|
+
}
|
|
10058
|
+
if (!isRecord(step))
|
|
10059
|
+
return null;
|
|
10060
|
+
if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
|
|
10061
|
+
normalized.push({ kind: "branch", target: step.branch });
|
|
10062
|
+
continue;
|
|
10063
|
+
}
|
|
10064
|
+
if (step.pr === true && Object.keys(step).length === 1) {
|
|
10065
|
+
normalized.push({ kind: "pr" });
|
|
10066
|
+
continue;
|
|
10067
|
+
}
|
|
10068
|
+
return null;
|
|
10069
|
+
}
|
|
10070
|
+
const keys = normalized.map(chainStepKey);
|
|
10071
|
+
if (new Set(keys).size !== keys.length)
|
|
10072
|
+
return null;
|
|
10073
|
+
return normalized;
|
|
10074
|
+
};
|
|
9607
10075
|
var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
|
|
9608
10076
|
var parseSteps = (content) => {
|
|
9609
10077
|
let value;
|
|
@@ -9615,7 +10083,10 @@ var parseSteps = (content) => {
|
|
|
9615
10083
|
if (typeof value !== "object" || value === null || !("steps" in value))
|
|
9616
10084
|
return [];
|
|
9617
10085
|
const steps = value.steps;
|
|
9618
|
-
|
|
10086
|
+
if (!Array.isArray(steps))
|
|
10087
|
+
return null;
|
|
10088
|
+
const normalized = normalizeChainSteps(steps);
|
|
10089
|
+
return normalized;
|
|
9619
10090
|
};
|
|
9620
10091
|
var decisionMatches = (entry, purpose, binding) => {
|
|
9621
10092
|
const decision = entry.data;
|
|
@@ -9711,7 +10182,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
9711
10182
|
return [];
|
|
9712
10183
|
return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
|
|
9713
10184
|
}
|
|
9714
|
-
var digestBytes2 = (bytes) =>
|
|
10185
|
+
var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
9715
10186
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
9716
10187
|
let root;
|
|
9717
10188
|
try {
|
|
@@ -9724,23 +10195,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
9724
10195
|
continue;
|
|
9725
10196
|
if (!reference.digest)
|
|
9726
10197
|
return failure("invalid_input", "document references require a byte digest");
|
|
9727
|
-
const target =
|
|
9728
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
10198
|
+
const target = path7.resolve(root, reference.path);
|
|
10199
|
+
if (target !== root && !target.startsWith(`${root}${path7.sep}`))
|
|
9729
10200
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9730
10201
|
fields: [
|
|
9731
10202
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
9732
10203
|
]
|
|
9733
10204
|
});
|
|
9734
10205
|
try {
|
|
9735
|
-
const relative =
|
|
10206
|
+
const relative = path7.relative(root, target);
|
|
9736
10207
|
let current = root;
|
|
9737
|
-
for (const segment of relative.split(
|
|
10208
|
+
for (const segment of relative.split(path7.sep)) {
|
|
9738
10209
|
if (!segment)
|
|
9739
10210
|
continue;
|
|
9740
|
-
current =
|
|
10211
|
+
current = path7.join(current, segment);
|
|
9741
10212
|
const stat = fs5.lstatSync(current);
|
|
9742
10213
|
const real = fs5.realpathSync(current);
|
|
9743
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
10214
|
+
if (real !== root && !real.startsWith(`${root}${path7.sep}`))
|
|
9744
10215
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9745
10216
|
fields: [
|
|
9746
10217
|
{
|
|
@@ -9793,6 +10264,20 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
9793
10264
|
return failure("permission_denied", "decision was rejected");
|
|
9794
10265
|
if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
|
|
9795
10266
|
return failure("permission_denied", "action approval lacks native receipt assurance");
|
|
10267
|
+
const receipts = entry.provenance.receipts;
|
|
10268
|
+
const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
|
|
10269
|
+
const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
|
|
10270
|
+
if (!questionBacked && standing.length > 0) {
|
|
10271
|
+
let operation;
|
|
10272
|
+
try {
|
|
10273
|
+
operation = JSON.parse(decision.binding.approvedContent).operation;
|
|
10274
|
+
} catch {
|
|
10275
|
+
operation = null;
|
|
10276
|
+
}
|
|
10277
|
+
const cls = operationAutoClass(operation);
|
|
10278
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
10279
|
+
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
10280
|
+
}
|
|
9796
10281
|
if (decision.revoked)
|
|
9797
10282
|
return failure("permission_denied", "decision is revoked");
|
|
9798
10283
|
if (decision.digest !== decisionDigest(decision))
|
|
@@ -9806,14 +10291,12 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
9806
10291
|
return content;
|
|
9807
10292
|
const requestedSteps = parseSteps(decision.binding.approvedContent);
|
|
9808
10293
|
if (requestedSteps === null)
|
|
9809
|
-
return failure("invalid_input", "bounded action steps must be a
|
|
9810
|
-
if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps))
|
|
10294
|
+
return failure("invalid_input", "bounded action steps must be a unique non-empty array");
|
|
10295
|
+
if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
|
|
9811
10296
|
return failure("permission_denied", "bounded action steps do not match approved content");
|
|
9812
|
-
if (requestedSteps.some((step) => !step || typeof step !== "string") || new Set(requestedSteps).size !== requestedSteps.length)
|
|
9813
|
-
return failure("invalid_input", "bounded action steps must be unique and non-empty");
|
|
9814
10297
|
const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
|
|
9815
|
-
const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps, completed: [] };
|
|
9816
|
-
if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps))
|
|
10298
|
+
const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
|
|
10299
|
+
if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
|
|
9817
10300
|
return failure("permission_denied", "bounded action scope changed");
|
|
9818
10301
|
return success(null, null, { entry, workflow });
|
|
9819
10302
|
};
|
|
@@ -10094,15 +10577,15 @@ var bindingCovers = scopeCovers;
|
|
|
10094
10577
|
var verifyDecisionContent = verifyContentRefs;
|
|
10095
10578
|
|
|
10096
10579
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
10097
|
-
var digestBytes3 = (value) =>
|
|
10098
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10099
|
-
var relative = (root, candidate) =>
|
|
10580
|
+
var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
|
|
10581
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path8.sep}`);
|
|
10582
|
+
var relative = (root, candidate) => path8.relative(root, candidate).split(path8.sep).join("/") || ".";
|
|
10100
10583
|
var canonicalPath = (value) => {
|
|
10101
10584
|
if (typeof value !== "string" || !value)
|
|
10102
10585
|
return null;
|
|
10103
10586
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
10104
10587
|
return null;
|
|
10105
|
-
const normalized =
|
|
10588
|
+
const normalized = path8.posix.normalize(value);
|
|
10106
10589
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
10107
10590
|
return null;
|
|
10108
10591
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10150,14 +10633,14 @@ var gitPaths = (root) => {
|
|
|
10150
10633
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10151
10634
|
};
|
|
10152
10635
|
}
|
|
10153
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10636
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")) : [];
|
|
10154
10637
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10155
10638
|
if (stagedDeleted.status !== 0) {
|
|
10156
10639
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10157
10640
|
if (!/not a git repository/i.test(stderr))
|
|
10158
10641
|
return { paths, uncertain: true, git: true };
|
|
10159
10642
|
} else if (stagedDeleted.stdout) {
|
|
10160
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10643
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")));
|
|
10161
10644
|
}
|
|
10162
10645
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10163
10646
|
};
|
|
@@ -10172,7 +10655,7 @@ var walk = (root, directory, output) => {
|
|
|
10172
10655
|
for (const name of names) {
|
|
10173
10656
|
if (name === ".git" || name === ".workit")
|
|
10174
10657
|
continue;
|
|
10175
|
-
const target =
|
|
10658
|
+
const target = path8.join(directory, name);
|
|
10176
10659
|
let stat;
|
|
10177
10660
|
try {
|
|
10178
10661
|
stat = fs6.lstatSync(target);
|
|
@@ -10190,12 +10673,12 @@ var walk = (root, directory, output) => {
|
|
|
10190
10673
|
var scopeRoots = (root, scope) => {
|
|
10191
10674
|
const roots = [];
|
|
10192
10675
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10193
|
-
const target =
|
|
10676
|
+
const target = path8.resolve(root, item);
|
|
10194
10677
|
if (!inside(root, target))
|
|
10195
10678
|
return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
10196
10679
|
let ancestor = target;
|
|
10197
10680
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10198
|
-
ancestor =
|
|
10681
|
+
ancestor = path8.dirname(ancestor);
|
|
10199
10682
|
try {
|
|
10200
10683
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10201
10684
|
return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
@@ -10247,7 +10730,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10247
10730
|
for (const item of [...names].sort()) {
|
|
10248
10731
|
if (!scopeMatches(item, normalizedScope))
|
|
10249
10732
|
continue;
|
|
10250
|
-
const target =
|
|
10733
|
+
const target = path8.join(checkout, item);
|
|
10251
10734
|
try {
|
|
10252
10735
|
const stat = fs6.lstatSync(target);
|
|
10253
10736
|
if (stat.isSymbolicLink()) {
|
|
@@ -10368,13 +10851,28 @@ function evaluateEvidence(task, candidate) {
|
|
|
10368
10851
|
});
|
|
10369
10852
|
}
|
|
10370
10853
|
var scopeCovers2 = scopeCovers;
|
|
10854
|
+
var checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
10855
|
+
const pin = assignment.candidateId ?? null;
|
|
10856
|
+
if (pin === null)
|
|
10857
|
+
return true;
|
|
10858
|
+
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
10859
|
+
};
|
|
10860
|
+
var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
10861
|
+
const bound = evidence.kind === "check" || evidence.kind === "review";
|
|
10862
|
+
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
10863
|
+
return {
|
|
10864
|
+
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
10865
|
+
candidateId: evidence.candidateId ?? fallback
|
|
10866
|
+
};
|
|
10867
|
+
};
|
|
10868
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
|
|
10371
10869
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
10372
10870
|
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
10373
|
-
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10871
|
+
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
10374
10872
|
if (kind !== "passed")
|
|
10375
10873
|
return false;
|
|
10376
10874
|
if (dimension === "testing" || dimension === "verification")
|
|
10377
|
-
return evidenceKind === "check";
|
|
10875
|
+
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
10378
10876
|
if (dimension === "review")
|
|
10379
10877
|
return evidenceKind === "review";
|
|
10380
10878
|
if (dimension === "investigation" || dimension === "challenge")
|
|
@@ -10383,6 +10881,21 @@ var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
|
10383
10881
|
return evidenceKind === "artifact";
|
|
10384
10882
|
return false;
|
|
10385
10883
|
};
|
|
10884
|
+
var expectedKinds = (dimension, ruleId) => {
|
|
10885
|
+
if (dimension === "testing")
|
|
10886
|
+
return "check";
|
|
10887
|
+
if (dimension === "verification")
|
|
10888
|
+
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
10889
|
+
if (dimension === "review")
|
|
10890
|
+
return "review";
|
|
10891
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
10892
|
+
return "investigation";
|
|
10893
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
10894
|
+
return "artifact";
|
|
10895
|
+
if (dimension === "decisions")
|
|
10896
|
+
return "decision";
|
|
10897
|
+
return "worker report";
|
|
10898
|
+
};
|
|
10386
10899
|
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
10387
10900
|
const evidence = evaluateEvidence(task, candidate);
|
|
10388
10901
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
@@ -10396,7 +10909,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10396
10909
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
10397
10910
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
10398
10911
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
10399
|
-
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension))
|
|
10912
|
+
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
10400
10913
|
return false;
|
|
10401
10914
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
10402
10915
|
return false;
|
|
@@ -10404,11 +10917,12 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10404
10917
|
return true;
|
|
10405
10918
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10406
10919
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10407
|
-
const sameEvidenceSession =
|
|
10920
|
+
const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
|
|
10408
10921
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10409
10922
|
});
|
|
10410
10923
|
if (passed.length) {
|
|
10411
|
-
const
|
|
10924
|
+
const currentId = candidate?.id ?? null;
|
|
10925
|
+
const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
|
|
10412
10926
|
if (withRed.length)
|
|
10413
10927
|
return {
|
|
10414
10928
|
requirementId: requirement.id,
|
|
@@ -10457,6 +10971,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10457
10971
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
10458
10972
|
};
|
|
10459
10973
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
10974
|
+
if (!unavailable && related.length > 0 && !passed.length)
|
|
10975
|
+
return {
|
|
10976
|
+
requirementId: requirement.id,
|
|
10977
|
+
status: "unsatisfied",
|
|
10978
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
10979
|
+
decisionIds: [],
|
|
10980
|
+
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
10981
|
+
};
|
|
10460
10982
|
return {
|
|
10461
10983
|
requirementId: requirement.id,
|
|
10462
10984
|
status: unavailable ? "unavailable" : "unsatisfied",
|
|
@@ -10481,7 +11003,13 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10481
11003
|
if (entry.data.disposition === "fixed") {
|
|
10482
11004
|
const verified = entry.data.resolution?.evidenceIds.some((id) => {
|
|
10483
11005
|
const evidence = view.task.evidence.find((item) => item.id === id);
|
|
10484
|
-
|
|
11006
|
+
if (!evidence)
|
|
11007
|
+
return false;
|
|
11008
|
+
return findingVerificationPasses(entry.data.candidateId, {
|
|
11009
|
+
kind: evidence.data.kind,
|
|
11010
|
+
candidateId: evidence.data.candidateId,
|
|
11011
|
+
status: evidenceById.get(id) ?? "missing"
|
|
11012
|
+
});
|
|
10485
11013
|
});
|
|
10486
11014
|
if (!verified)
|
|
10487
11015
|
return failure("requirements_unsatisfied", "fixed findings require current verification");
|
|
@@ -10497,7 +11025,12 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10497
11025
|
}
|
|
10498
11026
|
}
|
|
10499
11027
|
}
|
|
10500
|
-
const blocking = evaluations.filter((item) =>
|
|
11028
|
+
const blocking = evaluations.filter((item) => {
|
|
11029
|
+
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
11030
|
+
return false;
|
|
11031
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
11032
|
+
return !requirement || requirement.before === "close";
|
|
11033
|
+
});
|
|
10501
11034
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10502
11035
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10503
11036
|
requirementIds: blocking.map((item) => item.requirementId),
|
|
@@ -10540,15 +11073,15 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10540
11073
|
// packages/workit-core/src/core/methods.ts
|
|
10541
11074
|
var METHODS = {
|
|
10542
11075
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10543
|
-
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10544
|
-
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
11076
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
11077
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
10545
11078
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10546
11079
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10547
|
-
"workit-debug": {
|
|
10548
|
-
"workit-handoff": {
|
|
11080
|
+
"workit-debug": {},
|
|
11081
|
+
"workit-handoff": {},
|
|
10549
11082
|
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
10550
11083
|
};
|
|
10551
|
-
var methodMatches = (
|
|
11084
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
10552
11085
|
var assuranceFor = (id, definition, capabilities) => {
|
|
10553
11086
|
const keys = new Set([
|
|
10554
11087
|
id,
|
|
@@ -10569,7 +11102,7 @@ var assuranceFor = (id, definition, capabilities) => {
|
|
|
10569
11102
|
};
|
|
10570
11103
|
function selectMethods(policy, capabilities) {
|
|
10571
11104
|
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
10572
|
-
const requirements = policy.requirements.filter((requirement) => methodMatches(
|
|
11105
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
10573
11106
|
if (requirements.length === 0)
|
|
10574
11107
|
return [];
|
|
10575
11108
|
const capability = assuranceFor(id, definition, capabilities);
|
|
@@ -10740,7 +11273,8 @@ var exportDigest = (bundle) => sha256({
|
|
|
10740
11273
|
// packages/workit-core/src/core/route-intent.ts
|
|
10741
11274
|
var GUIDANCE = {
|
|
10742
11275
|
"git.branch_setup": "use the Workit git.branch_setup action (workit_external_action in hosts, or `workit action git.branch_setup --payload '{}' --confirm --json` in the CLI)",
|
|
10743
|
-
"hosting.pull_request": "use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit"
|
|
11276
|
+
"hosting.pull_request": "use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit",
|
|
11277
|
+
"hosting.merge": "use the Workit hosting.merge action (workit_external_action in hosts, or `workit action hosting.merge --payload '{...}' --confirm --json` in the CLI)"
|
|
10744
11278
|
};
|
|
10745
11279
|
var unbalancedQuotes = (segment) => {
|
|
10746
11280
|
const text = segment.replace(/\\./gu, "");
|
|
@@ -10775,6 +11309,10 @@ function shellRouteIntent(command) {
|
|
|
10775
11309
|
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
10776
11310
|
if (head === "glab" && second === "mr" && third === "create")
|
|
10777
11311
|
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
11312
|
+
if (head === "gh" && second === "pr" && third === "merge")
|
|
11313
|
+
return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
|
|
11314
|
+
if (head === "glab" && second === "mr" && third === "merge")
|
|
11315
|
+
return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
|
|
10778
11316
|
}
|
|
10779
11317
|
return null;
|
|
10780
11318
|
}
|
|
@@ -10828,6 +11366,13 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10828
11366
|
babysit: boolean2().optional()
|
|
10829
11367
|
}).strict()
|
|
10830
11368
|
}).strict(),
|
|
11369
|
+
object({
|
|
11370
|
+
operation: literal("hosting.merge"),
|
|
11371
|
+
payload: object({
|
|
11372
|
+
target_branch: string2().min(1).optional(),
|
|
11373
|
+
source_branch: string2().min(1).optional()
|
|
11374
|
+
}).strict()
|
|
11375
|
+
}).strict(),
|
|
10831
11376
|
object({
|
|
10832
11377
|
operation: literal("youtrack.update"),
|
|
10833
11378
|
payload: object({
|
|
@@ -10888,7 +11433,7 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10888
11433
|
}).strict()
|
|
10889
11434
|
]);
|
|
10890
11435
|
// packages/workit-core/src/core/task-engine.ts
|
|
10891
|
-
import { realpathSync as
|
|
11436
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
10892
11437
|
|
|
10893
11438
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
10894
11439
|
var resolverInputSchema = object({
|
|
@@ -11332,6 +11877,8 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11332
11877
|
if (task.data.status === "paused" && (input.state === "running" || input.state === "cancelling"))
|
|
11333
11878
|
return failure("invalid_transition", "paused task cannot run a worker");
|
|
11334
11879
|
const notStarted = input.dispatch === true;
|
|
11880
|
+
if (notStarted && entry.data.state === "stopped" && input.session === null)
|
|
11881
|
+
return success(task.data.revision, workspace.data.revision, entry);
|
|
11335
11882
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
11336
11883
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
11337
11884
|
if (notStarted && entry.data.session !== null)
|
|
@@ -11363,7 +11910,7 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11363
11910
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
11364
11911
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
11365
11912
|
taskId: task.data.id,
|
|
11366
|
-
|
|
11913
|
+
expectedRevision: input.expectedRevision,
|
|
11367
11914
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
11368
11915
|
now: input.now,
|
|
11369
11916
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
@@ -11411,90 +11958,22 @@ var importedProvenance = (context) => ({
|
|
|
11411
11958
|
receipts: []
|
|
11412
11959
|
});
|
|
11413
11960
|
var portableTask = (task) => {
|
|
11414
|
-
const
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
];
|
|
11428
|
-
for (const entry of entries)
|
|
11429
|
-
entry.provenance = portableProvenance(entry.provenance);
|
|
11430
|
-
for (const worker of clone.workers)
|
|
11431
|
-
worker.data.session = null;
|
|
11432
|
-
clone.intent = {
|
|
11433
|
-
...clone.intent,
|
|
11434
|
-
data: { ...clone.intent.data, authorityRefs: portableRefs(clone.intent.data.authorityRefs) }
|
|
11435
|
-
};
|
|
11436
|
-
clone.constraints = clone.constraints.flatMap((constraint) => {
|
|
11437
|
-
const source = portableRef(constraint.source);
|
|
11438
|
-
return source ? [
|
|
11439
|
-
{
|
|
11440
|
-
...constraint,
|
|
11441
|
-
source,
|
|
11442
|
-
requires: constraint.requires.map((requirement) => ({
|
|
11443
|
-
...requirement,
|
|
11444
|
-
refs: portableRefs(requirement.refs)
|
|
11445
|
-
}))
|
|
11446
|
-
}
|
|
11447
|
-
] : [];
|
|
11448
|
-
});
|
|
11449
|
-
clone.assessments = clone.assessments.map((entry) => ({
|
|
11450
|
-
...entry,
|
|
11451
|
-
data: {
|
|
11452
|
-
...entry.data,
|
|
11453
|
-
facts: entry.data.facts.map(portableFact),
|
|
11454
|
-
signals: Object.fromEntries(Object.entries(entry.data.signals).map(([name, signal]) => [name, portableSignal(signal)])),
|
|
11455
|
-
consequences: entry.data.consequences.map((consequence) => ({
|
|
11456
|
-
...consequence,
|
|
11457
|
-
fact: portableFact(consequence.fact)
|
|
11458
|
-
})),
|
|
11459
|
-
verification: entry.data.verification.map((verification) => ({
|
|
11460
|
-
...verification,
|
|
11461
|
-
availableChecks: portableRefs(verification.availableChecks)
|
|
11462
|
-
}))
|
|
11463
|
-
}
|
|
11464
|
-
}));
|
|
11465
|
-
clone.progress = {
|
|
11466
|
-
...clone.progress,
|
|
11467
|
-
blockers: clone.progress.blockers.map((blocker) => ({
|
|
11468
|
-
...blocker,
|
|
11469
|
-
refs: portableRefs(blocker.refs)
|
|
11470
|
-
}))
|
|
11961
|
+
const stripped = rewriteRecordRefs(task, taskRecordSchema, portableRef);
|
|
11962
|
+
if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
|
|
11963
|
+
throw new TypeError("portable task rewrite produced a non-record");
|
|
11964
|
+
const clone = structuredClone(stripped);
|
|
11965
|
+
const demote = (facts, signals, consequences) => {
|
|
11966
|
+
for (const fact of facts)
|
|
11967
|
+
Object.assign(fact, portableFact(fact));
|
|
11968
|
+
for (const name of Object.keys(signals)) {
|
|
11969
|
+
const signal = signals[name];
|
|
11970
|
+
Object.assign(signal, portableSignal(signal));
|
|
11971
|
+
}
|
|
11972
|
+
for (const consequence of consequences)
|
|
11973
|
+
Object.assign(consequence.fact, portableFact(consequence.fact));
|
|
11471
11974
|
};
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
data: {
|
|
11475
|
-
...entry.data,
|
|
11476
|
-
refs: portableRefs(entry.data.refs),
|
|
11477
|
-
reviewContext: portableRef(entry.data.reviewContext)
|
|
11478
|
-
}
|
|
11479
|
-
}));
|
|
11480
|
-
clone.decisions = clone.decisions.map((entry) => ({
|
|
11481
|
-
...entry,
|
|
11482
|
-
data: {
|
|
11483
|
-
...entry.data,
|
|
11484
|
-
consumption: entry.data.consumption ? portableRef(entry.data.consumption.actionRef) ? {
|
|
11485
|
-
...entry.data.consumption,
|
|
11486
|
-
actionRef: portableRef(entry.data.consumption.actionRef)
|
|
11487
|
-
} : null : null,
|
|
11488
|
-
binding: {
|
|
11489
|
-
...entry.data.binding,
|
|
11490
|
-
contentRefs: portableRefs(entry.data.binding.contentRefs)
|
|
11491
|
-
}
|
|
11492
|
-
}
|
|
11493
|
-
}));
|
|
11494
|
-
clone.findings = clone.findings.map((entry) => ({
|
|
11495
|
-
...entry,
|
|
11496
|
-
data: { ...entry.data, refs: portableRefs(entry.data.refs) }
|
|
11497
|
-
}));
|
|
11975
|
+
for (const entry of clone.assessments)
|
|
11976
|
+
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
11498
11977
|
clone.candidates = [];
|
|
11499
11978
|
return clone;
|
|
11500
11979
|
};
|
|
@@ -11519,15 +11998,13 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11519
11998
|
allocate(entry.id);
|
|
11520
11999
|
const fresh = (id) => allocate(id);
|
|
11521
12000
|
const provenance = importedProvenance(context);
|
|
12001
|
+
const remap = (ref) => mapRef(ref, ids);
|
|
11522
12002
|
const intent = {
|
|
11523
12003
|
...source.intent,
|
|
11524
12004
|
id: fresh(source.intent.id),
|
|
11525
12005
|
recordedAt: timestamp,
|
|
11526
12006
|
provenance,
|
|
11527
|
-
data:
|
|
11528
|
-
...source.intent.data,
|
|
11529
|
-
authorityRefs: source.intent.data.authorityRefs.map((ref) => mapRef(ref, ids))
|
|
11530
|
-
}
|
|
12007
|
+
data: rewriteRecordRefs(source.intent.data, intentSchema, remap)
|
|
11531
12008
|
};
|
|
11532
12009
|
const assessments = source.assessments.map((entry) => {
|
|
11533
12010
|
const data = entry.data;
|
|
@@ -11536,28 +12013,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11536
12013
|
id: fresh(entry.id),
|
|
11537
12014
|
recordedAt: timestamp,
|
|
11538
12015
|
provenance,
|
|
11539
|
-
data:
|
|
11540
|
-
...data,
|
|
11541
|
-
facts: data.facts.map((fact) => ({
|
|
11542
|
-
...fact,
|
|
11543
|
-
refs: fact.refs.map((ref) => mapRef(ref, ids))
|
|
11544
|
-
})),
|
|
11545
|
-
signals: Object.fromEntries(Object.entries(data.signals).map(([name, signal]) => [
|
|
11546
|
-
name,
|
|
11547
|
-
{ ...signal, refs: signal.refs.map((ref) => mapRef(ref, ids)) }
|
|
11548
|
-
])),
|
|
11549
|
-
consequences: data.consequences.map((consequence) => ({
|
|
11550
|
-
...consequence,
|
|
11551
|
-
fact: {
|
|
11552
|
-
...consequence.fact,
|
|
11553
|
-
refs: consequence.fact.refs.map((ref) => mapRef(ref, ids))
|
|
11554
|
-
}
|
|
11555
|
-
})),
|
|
11556
|
-
verification: data.verification.map((verification) => ({
|
|
11557
|
-
...verification,
|
|
11558
|
-
availableChecks: verification.availableChecks.map((ref) => mapRef(ref, ids))
|
|
11559
|
-
}))
|
|
11560
|
-
}
|
|
12016
|
+
data: rewriteRecordRefs(data, assessmentSchema, remap)
|
|
11561
12017
|
};
|
|
11562
12018
|
});
|
|
11563
12019
|
const evidence = source.evidence.map((entry) => ({
|
|
@@ -11565,11 +12021,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11565
12021
|
id: fresh(entry.id),
|
|
11566
12022
|
recordedAt: timestamp,
|
|
11567
12023
|
provenance,
|
|
11568
|
-
data:
|
|
11569
|
-
...entry.data,
|
|
11570
|
-
refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
|
|
11571
|
-
reviewContext: entry.data.reviewContext ? mapRef(entry.data.reviewContext, ids) : null
|
|
11572
|
-
}
|
|
12024
|
+
data: rewriteRecordRefs(entry.data, evidenceSchema, remap)
|
|
11573
12025
|
}));
|
|
11574
12026
|
const decisions = source.decisions.map((entry) => ({
|
|
11575
12027
|
...entry,
|
|
@@ -11577,21 +12029,23 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11577
12029
|
recordedAt: timestamp,
|
|
11578
12030
|
provenance
|
|
11579
12031
|
}));
|
|
11580
|
-
const findings = source.findings.map((entry) =>
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
12032
|
+
const findings = source.findings.map((entry) => {
|
|
12033
|
+
const data = rewriteRecordRefs(entry.data, findingSchema, remap);
|
|
12034
|
+
return {
|
|
12035
|
+
...entry,
|
|
12036
|
+
id: fresh(entry.id),
|
|
12037
|
+
recordedAt: timestamp,
|
|
12038
|
+
provenance,
|
|
12039
|
+
data: {
|
|
12040
|
+
...data,
|
|
12041
|
+
resolution: data.resolution ? {
|
|
12042
|
+
...data.resolution,
|
|
12043
|
+
evidenceIds: data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
|
|
12044
|
+
decisionIds: data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
|
|
12045
|
+
} : null
|
|
12046
|
+
}
|
|
12047
|
+
};
|
|
12048
|
+
});
|
|
11595
12049
|
const workers = source.workers.map((entry) => ({
|
|
11596
12050
|
...entry,
|
|
11597
12051
|
id: fresh(entry.id),
|
|
@@ -11634,19 +12088,21 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11634
12088
|
closure: null,
|
|
11635
12089
|
assessments,
|
|
11636
12090
|
evidence,
|
|
11637
|
-
decisions: decisions.map((entry) =>
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
...entry
|
|
11641
|
-
|
|
11642
|
-
...
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
12091
|
+
decisions: decisions.map((entry) => {
|
|
12092
|
+
const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
|
|
12093
|
+
return {
|
|
12094
|
+
...entry,
|
|
12095
|
+
data: {
|
|
12096
|
+
...data,
|
|
12097
|
+
binding: {
|
|
12098
|
+
...data.binding,
|
|
12099
|
+
taskId: "",
|
|
12100
|
+
workspaceId: destinationWorkspaceId
|
|
12101
|
+
},
|
|
12102
|
+
consumption: null
|
|
12103
|
+
}
|
|
12104
|
+
};
|
|
12105
|
+
}),
|
|
11650
12106
|
...actionProgress ? { actionProgress } : {},
|
|
11651
12107
|
findings,
|
|
11652
12108
|
workers,
|
|
@@ -11684,7 +12140,7 @@ class WorkitCore {
|
|
|
11684
12140
|
}
|
|
11685
12141
|
contextRootError() {
|
|
11686
12142
|
try {
|
|
11687
|
-
if (
|
|
12143
|
+
if (realpathSync5(this.context.root) !== this.store.root)
|
|
11688
12144
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
11689
12145
|
} catch {
|
|
11690
12146
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -11729,7 +12185,7 @@ class WorkitCore {
|
|
|
11729
12185
|
activeWorkerBlocker(task, workspace) {
|
|
11730
12186
|
if (workspace.writer)
|
|
11731
12187
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11732
|
-
if (task.workers.some((entry) =>
|
|
12188
|
+
if (task.workers.some((entry) => workerBlocksTransition("close", entry.data.state)))
|
|
11733
12189
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11734
12190
|
return null;
|
|
11735
12191
|
}
|
|
@@ -11975,37 +12431,33 @@ class WorkitCore {
|
|
|
11975
12431
|
const currentCandidate = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
11976
12432
|
if (!currentCandidate.ok)
|
|
11977
12433
|
return currentCandidate;
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
evidence.candidateId ??= currentCandidate.data.id;
|
|
11981
|
-
} else {
|
|
11982
|
-
evidence.beforeCandidateId ??= null;
|
|
11983
|
-
evidence.candidateId ??= null;
|
|
11984
|
-
}
|
|
12434
|
+
const pin = helper && helper.ok ? helper.data.data.assignment.candidateId : null;
|
|
12435
|
+
const bound = resolveBinding(evidence, pin, currentCandidate.data.id);
|
|
11985
12436
|
if (helper?.ok) {
|
|
11986
12437
|
const assignment = helper.data.data.assignment;
|
|
11987
|
-
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment
|
|
12438
|
+
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || !checkPin(assignment, bound.beforeCandidateId, bound.candidateId) || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
11988
12439
|
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11989
12440
|
}
|
|
11990
12441
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11991
|
-
for (const candidateId of [
|
|
12442
|
+
for (const candidateId of [bound.beforeCandidateId, bound.candidateId]) {
|
|
11992
12443
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
11993
12444
|
return failure("invalid_input", "evidence candidate is not a captured candidate");
|
|
11994
12445
|
}
|
|
12446
|
+
const boundEvidence = { ...evidence, ...bound };
|
|
11995
12447
|
const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
|
|
11996
12448
|
const candidates = current.candidates.some((item) => item.id === currentCandidate.data.id) ? current.candidates : [...current.candidates, currentCandidate.data];
|
|
11997
12449
|
const entry = {
|
|
11998
12450
|
id: newId(),
|
|
11999
12451
|
recordedAt: mutation.now,
|
|
12000
12452
|
provenance: provenance(this.context, "agent_reported"),
|
|
12001
|
-
data:
|
|
12453
|
+
data: boundEvidence
|
|
12002
12454
|
};
|
|
12003
12455
|
return success(mutation.revision, null, {
|
|
12004
12456
|
...current,
|
|
12005
12457
|
candidates,
|
|
12006
12458
|
evidence: [...current.evidence, entry],
|
|
12007
12459
|
findings: current.findings.map((finding) => {
|
|
12008
|
-
if (finding.data.disposition === "open" ||
|
|
12460
|
+
if (finding.data.disposition === "open" || boundEvidence.result === "skipped")
|
|
12009
12461
|
return finding;
|
|
12010
12462
|
if (finding.data.disposition !== "fixed")
|
|
12011
12463
|
return finding;
|
|
@@ -12014,8 +12466,12 @@ class WorkitCore {
|
|
|
12014
12466
|
item.evidenceId,
|
|
12015
12467
|
item.status
|
|
12016
12468
|
]));
|
|
12017
|
-
const verified = withEntry.evidence.some((item) =>
|
|
12018
|
-
|
|
12469
|
+
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
12470
|
+
kind: item.data.kind,
|
|
12471
|
+
candidateId: item.data.candidateId,
|
|
12472
|
+
status: statuses.get(item.id) ?? "missing"
|
|
12473
|
+
}));
|
|
12474
|
+
const contradicted = boundEvidence.result === "failed" && finding.data.refs.some((left) => boundEvidence.refs.some((right) => sameValue(left, right)));
|
|
12019
12475
|
return verified && !contradicted ? finding : { ...finding, data: { ...finding.data, disposition: "open", resolution: null } };
|
|
12020
12476
|
})
|
|
12021
12477
|
});
|
|
@@ -12030,7 +12486,10 @@ class WorkitCore {
|
|
|
12030
12486
|
observeDecision(request, observation) {
|
|
12031
12487
|
return this.recordDecision(request, observation, true);
|
|
12032
12488
|
}
|
|
12033
|
-
|
|
12489
|
+
observeStandingDecision(request) {
|
|
12490
|
+
return this.recordDecision(request, undefined, true, true);
|
|
12491
|
+
}
|
|
12492
|
+
recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
|
|
12034
12493
|
const root = this.contextRootError();
|
|
12035
12494
|
if (!root.ok)
|
|
12036
12495
|
return root;
|
|
@@ -12045,8 +12504,10 @@ class WorkitCore {
|
|
|
12045
12504
|
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
12046
12505
|
if (stated && !input.binding?.statedChoice)
|
|
12047
12506
|
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
12048
|
-
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
12507
|
+
if (nativeRequired && nativeObservation === undefined && !stated && !standing)
|
|
12049
12508
|
return failure("permission_denied", "native decision observation is required");
|
|
12509
|
+
if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
|
|
12510
|
+
return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
|
|
12050
12511
|
const task = this.store.readTask(input.taskId);
|
|
12051
12512
|
if (!task.ok)
|
|
12052
12513
|
return task;
|
|
@@ -12078,7 +12539,10 @@ class WorkitCore {
|
|
|
12078
12539
|
consumption: null
|
|
12079
12540
|
};
|
|
12080
12541
|
const data = { ...base, digest: decisionDigest(base) };
|
|
12081
|
-
const
|
|
12542
|
+
const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
|
|
12543
|
+
if (standingApproval && !standingApproval.ok)
|
|
12544
|
+
return standingApproval;
|
|
12545
|
+
const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
12082
12546
|
observation: nativeObservation,
|
|
12083
12547
|
expected: {
|
|
12084
12548
|
taskId: task.data.id,
|
|
@@ -12109,7 +12573,7 @@ class WorkitCore {
|
|
|
12109
12573
|
const entry = {
|
|
12110
12574
|
id: newId(),
|
|
12111
12575
|
recordedAt: mutation.now,
|
|
12112
|
-
provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
12576
|
+
provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
12113
12577
|
data
|
|
12114
12578
|
};
|
|
12115
12579
|
return success(mutation.revision, null, {
|
|
@@ -12173,7 +12637,7 @@ class WorkitCore {
|
|
|
12173
12637
|
if (input.action === "record") {
|
|
12174
12638
|
if (helper?.ok) {
|
|
12175
12639
|
const assignment = helper.data.data.assignment;
|
|
12176
|
-
if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || assignment
|
|
12640
|
+
if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || !checkPin(assignment, null, input.candidateId ?? null) || !refsWithinScope(input.refs, assignment.scope) || !refsWithinScope(input.refs, task.data.intent.data.scope))
|
|
12177
12641
|
return failure("permission_denied", "finding is outside the worker assignment");
|
|
12178
12642
|
}
|
|
12179
12643
|
if (input.candidateId && !task.data.candidates.some((candidate) => candidate.id === input.candidateId))
|
|
@@ -12220,7 +12684,11 @@ class WorkitCore {
|
|
|
12220
12684
|
const evaluations = evaluateEvidence(task.data, current.data);
|
|
12221
12685
|
const verified = evidence.some((entry) => {
|
|
12222
12686
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
12223
|
-
return
|
|
12687
|
+
return findingVerificationPasses(finding.data.candidateId, {
|
|
12688
|
+
kind: entry.data.kind,
|
|
12689
|
+
candidateId: entry.data.candidateId,
|
|
12690
|
+
status: evaluation?.status ?? "missing"
|
|
12691
|
+
});
|
|
12224
12692
|
});
|
|
12225
12693
|
if (!verified)
|
|
12226
12694
|
return failure("permission_denied", "fixed findings require passing verification evidence");
|
|
@@ -12232,7 +12700,7 @@ class WorkitCore {
|
|
|
12232
12700
|
if (entry.data.result === "missing" || entry.data.result === "skipped")
|
|
12233
12701
|
return false;
|
|
12234
12702
|
const candidateMatches = finding.data.candidateId === null ? entry.data.candidateId === null : entry.data.candidateId === finding.data.candidateId || entry.data.beforeCandidateId === finding.data.candidateId;
|
|
12235
|
-
const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) =>
|
|
12703
|
+
const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => sameValue(left, right)));
|
|
12236
12704
|
return candidateMatches && (referenceMatches || entry.data.claim === finding.data.claim);
|
|
12237
12705
|
});
|
|
12238
12706
|
if (!relevant)
|
|
@@ -12310,7 +12778,7 @@ class WorkitCore {
|
|
|
12310
12778
|
const workerId = newId();
|
|
12311
12779
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12312
12780
|
taskId: task.data.id,
|
|
12313
|
-
|
|
12781
|
+
expectedRevision: input.expectedRevision,
|
|
12314
12782
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12315
12783
|
now: trustedNow(this.context),
|
|
12316
12784
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12348,7 +12816,7 @@ class WorkitCore {
|
|
|
12348
12816
|
return failure("permission_denied", "worker report is outside the worker assignment");
|
|
12349
12817
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12350
12818
|
taskId: task.data.id,
|
|
12351
|
-
|
|
12819
|
+
expectedRevision: input.expectedRevision,
|
|
12352
12820
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12353
12821
|
now: trustedNow(this.context),
|
|
12354
12822
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12372,24 +12840,25 @@ class WorkitCore {
|
|
|
12372
12840
|
return failure("invalid_transition", "closed task cannot cancel workers");
|
|
12373
12841
|
if (entry.data.state === "unknown")
|
|
12374
12842
|
return failure("recovery_required", "worker state requires recovery");
|
|
12375
|
-
if (entry.data.state === "stopped")
|
|
12376
|
-
|
|
12377
|
-
|
|
12843
|
+
if (entry.data.state === "stopped") {
|
|
12844
|
+
const stopped = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
12845
|
+
return success(task.data.revision, workspace.data.revision, stopped);
|
|
12846
|
+
}
|
|
12378
12847
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12379
12848
|
taskId: task.data.id,
|
|
12380
|
-
|
|
12849
|
+
expectedRevision: input.expectedRevision,
|
|
12381
12850
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12382
12851
|
now: trustedNow(this.context),
|
|
12383
|
-
workspace: (current, mutation) => success(mutation.revision, mutation.revision,
|
|
12852
|
+
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
12853
|
+
...current,
|
|
12854
|
+
writer: current.writer?.owner.workerId === input.workerId ? null : current.writer
|
|
12855
|
+
}),
|
|
12384
12856
|
task: (current, mutation) => success(mutation.revision, null, {
|
|
12385
12857
|
...current,
|
|
12386
12858
|
workers: current.workers.map((candidate) => candidate.id === input.workerId ? {
|
|
12387
12859
|
...candidate,
|
|
12388
12860
|
recordedAt: mutation.now,
|
|
12389
|
-
data: {
|
|
12390
|
-
...candidate.data,
|
|
12391
|
-
state: settles ? "stopped" : "cancelling"
|
|
12392
|
-
}
|
|
12861
|
+
data: { ...candidate.data, state: "stopped" }
|
|
12393
12862
|
} : candidate)
|
|
12394
12863
|
})
|
|
12395
12864
|
});
|
|
@@ -12486,7 +12955,7 @@ class WorkitCore {
|
|
|
12486
12955
|
return attested;
|
|
12487
12956
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12488
12957
|
taskId: task.data.id,
|
|
12489
|
-
|
|
12958
|
+
expectedRevision: input.expectedRevision,
|
|
12490
12959
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12491
12960
|
now: trustedNow(this.context),
|
|
12492
12961
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12652,6 +13121,12 @@ class WorkitCore {
|
|
|
12652
13121
|
if (!sameSession2(helper.data.data.session, this.context))
|
|
12653
13122
|
return failure("permission_denied", "worker session does not match the caller");
|
|
12654
13123
|
}
|
|
13124
|
+
const gate = this.view(task.data);
|
|
13125
|
+
if (gate.ok) {
|
|
13126
|
+
const writeGates = (task.data.policy?.requirements ?? []).filter((requirement) => requirement.before === "write" && gate.data.requirements.some((evaluation) => evaluation.requirementId === requirement.id && (evaluation.status === "unsatisfied" || evaluation.status === "unavailable")));
|
|
13127
|
+
if (writeGates.length)
|
|
13128
|
+
return failure("requirements_unsatisfied", "writer ownership is gated by unsatisfied requirements; record their evidence or an approved limitation waiver first", { requirementIds: writeGates.map((requirement) => requirement.id) });
|
|
13129
|
+
}
|
|
12655
13130
|
const owner = {
|
|
12656
13131
|
taskId: task.data.id,
|
|
12657
13132
|
workerId: helperId,
|
|
@@ -12663,7 +13138,7 @@ class WorkitCore {
|
|
|
12663
13138
|
};
|
|
12664
13139
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12665
13140
|
taskId: task.data.id,
|
|
12666
|
-
|
|
13141
|
+
expectedRevision: input.expectedRevision,
|
|
12667
13142
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12668
13143
|
now: trustedNow(this.context),
|
|
12669
13144
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
@@ -12694,7 +13169,7 @@ class WorkitCore {
|
|
|
12694
13169
|
}
|
|
12695
13170
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12696
13171
|
taskId: task.data.id,
|
|
12697
|
-
|
|
13172
|
+
expectedRevision: input.expectedRevision,
|
|
12698
13173
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12699
13174
|
now: trustedNow(this.context),
|
|
12700
13175
|
workspace: (currentWorkspace, mutation) => success(mutation.revision, mutation.revision, { ...currentWorkspace, writer: null }),
|
|
@@ -12926,7 +13401,7 @@ class WorkitCore {
|
|
|
12926
13401
|
if (view.task.workspaceId !== view.workspace.id)
|
|
12927
13402
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
12928
13403
|
try {
|
|
12929
|
-
if (
|
|
13404
|
+
if (realpathSync5(view.workspace.root) !== this.store.root)
|
|
12930
13405
|
return failure("invalid_input", "task view root does not match the task store");
|
|
12931
13406
|
} catch {
|
|
12932
13407
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -12943,7 +13418,15 @@ class WorkitCore {
|
|
|
12943
13418
|
if (seen.has(observation.workerId))
|
|
12944
13419
|
return failure("invalid_input", "worker observations must be unique");
|
|
12945
13420
|
seen.add(observation.workerId);
|
|
12946
|
-
|
|
13421
|
+
const freshTask = this.store.readTask(view.task.id);
|
|
13422
|
+
const freshWorkspace = this.store.readWorkspace();
|
|
13423
|
+
if (!freshTask.ok)
|
|
13424
|
+
return freshTask;
|
|
13425
|
+
if (!freshWorkspace.ok)
|
|
13426
|
+
return freshWorkspace;
|
|
13427
|
+
if (!freshWorkspace.data)
|
|
13428
|
+
return failure("not_found", "workspace not found");
|
|
13429
|
+
if (observation.taskId !== view.task.id || observation.expectedRevision !== freshTask.data.revision || observation.expectedWorkspaceRevision !== freshWorkspace.data.revision)
|
|
12947
13430
|
return failure("revision_conflict", "worker observation is stale");
|
|
12948
13431
|
if (!view.task.workers.some((entry) => entry.id === observation.workerId))
|
|
12949
13432
|
return failure("permission_denied", "worker observation is not assigned");
|
|
@@ -13029,19 +13512,26 @@ class WorkitCore {
|
|
|
13029
13512
|
return failure("permission_denied", "imported resume requires native destination approval");
|
|
13030
13513
|
resumeCandidate = current.data.candidate;
|
|
13031
13514
|
}
|
|
13032
|
-
const blocker = this.activeWorkerBlocker(task.data, workspace.data);
|
|
13515
|
+
const blocker = status === "paused" ? workspace.data.writer ? failure("recovery_required", "writer ownership must be released first") : null : this.activeWorkerBlocker(task.data, workspace.data);
|
|
13033
13516
|
if (blocker)
|
|
13034
13517
|
return blocker;
|
|
13518
|
+
let pauseCandidate = null;
|
|
13519
|
+
if (status === "paused") {
|
|
13520
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
13521
|
+
if (!captured.ok)
|
|
13522
|
+
return captured;
|
|
13523
|
+
pauseCandidate = captured.data;
|
|
13524
|
+
}
|
|
13035
13525
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
13036
13526
|
taskId: task.data.id,
|
|
13037
|
-
|
|
13527
|
+
expectedRevision: input.expectedRevision,
|
|
13038
13528
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
13039
13529
|
now: trustedNow(this.context),
|
|
13040
13530
|
workspace: (workspace, context) => success(context.revision, context.revision, workspace),
|
|
13041
13531
|
task: (current, context) => success(context.revision, null, {
|
|
13042
13532
|
...current,
|
|
13043
13533
|
status,
|
|
13044
|
-
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
13534
|
+
candidates: (resumeCandidate ?? pauseCandidate) && !current.candidates.some((candidate) => candidate.id === (resumeCandidate ?? pauseCandidate).id) ? [...current.candidates, resumeCandidate ?? pauseCandidate] : current.candidates,
|
|
13045
13535
|
progress: current.progress,
|
|
13046
13536
|
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
13047
13537
|
})
|
|
@@ -13079,12 +13569,13 @@ class WorkitCore {
|
|
|
13079
13569
|
return workspace;
|
|
13080
13570
|
if (!workspace.data)
|
|
13081
13571
|
return failure("not_found", "workspace not found");
|
|
13082
|
-
|
|
13083
|
-
|
|
13572
|
+
const revisionBlocker = this.activeWorkerBlocker(task.data, workspace.data);
|
|
13573
|
+
if (revisionBlocker)
|
|
13574
|
+
return revisionBlocker;
|
|
13084
13575
|
this.fillRevisions(input, task.data, workspace.data);
|
|
13085
13576
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
13086
13577
|
taskId: task.data.id,
|
|
13087
|
-
|
|
13578
|
+
expectedRevision: input.expectedRevision,
|
|
13088
13579
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
13089
13580
|
now: trustedNow(this.context),
|
|
13090
13581
|
workspace: (workspace, context) => success(context.revision, context.revision, workspace),
|
|
@@ -13140,7 +13631,7 @@ class WorkitCore {
|
|
|
13140
13631
|
return closure;
|
|
13141
13632
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
13142
13633
|
taskId: task.data.id,
|
|
13143
|
-
|
|
13634
|
+
expectedRevision: input.expectedRevision,
|
|
13144
13635
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
13145
13636
|
now: trustedNow(this.context),
|
|
13146
13637
|
workspace: (value, context) => success(context.revision, context.revision, value),
|
|
@@ -13300,7 +13791,7 @@ var parseCodexHookInput = (value) => {
|
|
|
13300
13791
|
return { ok: false, error: "permission_mode is invalid" };
|
|
13301
13792
|
if (!(value.transcript_path === null || nonEmpty2(value.transcript_path)))
|
|
13302
13793
|
return { ok: false, error: "transcript_path must be a string or null" };
|
|
13303
|
-
if (!nonEmpty2(value.cwd) || !
|
|
13794
|
+
if (!nonEmpty2(value.cwd) || !path9.isAbsolute(value.cwd) || !existsSync5(value.cwd))
|
|
13304
13795
|
return { ok: false, error: "cwd must be an existing absolute path" };
|
|
13305
13796
|
try {
|
|
13306
13797
|
if (!statSync(value.cwd).isDirectory())
|
|
@@ -13326,7 +13817,7 @@ var parseCodexHookInput = (value) => {
|
|
|
13326
13817
|
model: value.model,
|
|
13327
13818
|
permission_mode: value.permission_mode,
|
|
13328
13819
|
transcript_path: value.transcript_path,
|
|
13329
|
-
cwd:
|
|
13820
|
+
cwd: realpathSync6(value.cwd),
|
|
13330
13821
|
...event === "SessionStart" ? { source: value.source } : {},
|
|
13331
13822
|
...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
|
|
13332
13823
|
...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},
|