@brainervirus/workit-cursor 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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // packages/workit-cursor/hooks/workit-hook.ts
6
- import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
7
- import path7 from "node:path";
6
+ import { existsSync as existsSync5, realpathSync as realpathSync6 } 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 expectedTaskRevision = input.expectedTaskRevision ?? input.expectedRevision;
9009
- if (!expectedTaskRevision)
9098
+ const expected = input.expectedRevision;
9099
+ if (!expected)
9010
9100
  return failure("invalid_input", "task revision is required");
9011
- if (task.data.revision !== expectedTaskRevision)
9012
- return this.conflict(expectedTaskRevision, task.data.revision);
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 createHash4 } from "node:crypto";
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 path6 from "node:path";
9686
+ import path8 from "node:path";
9596
9687
 
9597
9688
  // packages/workit-core/src/core/authority.ts
9598
- import { createHash as createHash3 } from "node:crypto";
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
- return Array.isArray(steps) && steps.every((step) => typeof step === "string") ? steps : null;
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) => createHash3("sha256").update(bytes).digest("hex");
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 = path5.resolve(root, reference.path);
9728
- if (target !== root && !target.startsWith(`${root}${path5.sep}`))
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 = path5.relative(root, target);
10206
+ const relative = path7.relative(root, target);
9736
10207
  let current = root;
9737
- for (const segment of relative.split(path5.sep)) {
10208
+ for (const segment of relative.split(path7.sep)) {
9738
10209
  if (!segment)
9739
10210
  continue;
9740
- current = path5.join(current, segment);
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}${path5.sep}`))
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 string array");
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) => createHash4("sha256").update(value).digest("hex");
10098
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
10099
- var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
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 = path6.posix.normalize(value);
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(path6.sep).join("/")) : [];
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(path6.sep).join("/")));
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 = path6.join(directory, name);
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 = path6.resolve(root, item);
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 = path6.dirname(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 = path6.join(checkout, item);
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 = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
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 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)) : passed;
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
- return evidenceById.get(id) === "passed" && (evidence?.data.kind === "check" || evidence?.data.kind === "review");
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) => item.status === "unsatisfied" || item.status === "unavailable");
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": { ruleIds: ["root-cause-investigation"] },
10548
- "workit-handoff": { ruleIds: ["durable-handoff"] },
11080
+ "workit-debug": {},
11081
+ "workit-handoff": {},
10549
11082
  "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
10550
11083
  };
10551
- var methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true;
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(id, definition, requirement));
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);
@@ -10770,6 +11303,13 @@ var externalActionSchema = discriminatedUnion("operation", [
10770
11303
  babysit: boolean2().optional()
10771
11304
  }).strict()
10772
11305
  }).strict(),
11306
+ object({
11307
+ operation: literal("hosting.merge"),
11308
+ payload: object({
11309
+ target_branch: string2().min(1).optional(),
11310
+ source_branch: string2().min(1).optional()
11311
+ }).strict()
11312
+ }).strict(),
10773
11313
  object({
10774
11314
  operation: literal("youtrack.update"),
10775
11315
  payload: object({
@@ -10830,7 +11370,7 @@ var externalActionSchema = discriminatedUnion("operation", [
10830
11370
  }).strict()
10831
11371
  ]);
10832
11372
  // packages/workit-core/src/core/task-engine.ts
10833
- import { realpathSync as realpathSync4 } from "node:fs";
11373
+ import { realpathSync as realpathSync5 } from "node:fs";
10834
11374
 
10835
11375
  // packages/workit-core/src/core/policy-resolver.ts
10836
11376
  var resolverInputSchema = object({
@@ -11274,6 +11814,8 @@ var applyWorkerLifecycle = (input) => {
11274
11814
  if (task.data.status === "paused" && (input.state === "running" || input.state === "cancelling"))
11275
11815
  return failure("invalid_transition", "paused task cannot run a worker");
11276
11816
  const notStarted = input.dispatch === true;
11817
+ if (notStarted && entry.data.state === "stopped" && input.session === null)
11818
+ return success(task.data.revision, workspace.data.revision, entry);
11277
11819
  if (notStarted && (input.state !== "stopped" || input.session !== null))
11278
11820
  return failure("invalid_input", "a never-dispatched worker stops with no session");
11279
11821
  if (notStarted && entry.data.session !== null)
@@ -11305,7 +11847,7 @@ var applyWorkerLifecycle = (input) => {
11305
11847
  return success(task.data.revision, workspace.data.revision, entry);
11306
11848
  const changed = input.store.mutateTaskAndWorkspace({
11307
11849
  taskId: task.data.id,
11308
- expectedTaskRevision: input.expectedRevision,
11850
+ expectedRevision: input.expectedRevision,
11309
11851
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
11310
11852
  now: input.now,
11311
11853
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -11353,90 +11895,22 @@ var importedProvenance = (context) => ({
11353
11895
  receipts: []
11354
11896
  });
11355
11897
  var portableTask = (task) => {
11356
- const clone = structuredClone(task);
11357
- const portableProvenance = (value) => ({
11358
- ...value,
11359
- session: null,
11360
- receipts: []
11361
- });
11362
- const entries = [
11363
- clone.intent,
11364
- ...clone.assessments,
11365
- ...clone.evidence,
11366
- ...clone.decisions,
11367
- ...clone.findings,
11368
- ...clone.workers
11369
- ];
11370
- for (const entry of entries)
11371
- entry.provenance = portableProvenance(entry.provenance);
11372
- for (const worker of clone.workers)
11373
- worker.data.session = null;
11374
- clone.intent = {
11375
- ...clone.intent,
11376
- data: { ...clone.intent.data, authorityRefs: portableRefs(clone.intent.data.authorityRefs) }
11377
- };
11378
- clone.constraints = clone.constraints.flatMap((constraint) => {
11379
- const source = portableRef(constraint.source);
11380
- return source ? [
11381
- {
11382
- ...constraint,
11383
- source,
11384
- requires: constraint.requires.map((requirement) => ({
11385
- ...requirement,
11386
- refs: portableRefs(requirement.refs)
11387
- }))
11388
- }
11389
- ] : [];
11390
- });
11391
- clone.assessments = clone.assessments.map((entry) => ({
11392
- ...entry,
11393
- data: {
11394
- ...entry.data,
11395
- facts: entry.data.facts.map(portableFact),
11396
- signals: Object.fromEntries(Object.entries(entry.data.signals).map(([name, signal]) => [name, portableSignal(signal)])),
11397
- consequences: entry.data.consequences.map((consequence) => ({
11398
- ...consequence,
11399
- fact: portableFact(consequence.fact)
11400
- })),
11401
- verification: entry.data.verification.map((verification) => ({
11402
- ...verification,
11403
- availableChecks: portableRefs(verification.availableChecks)
11404
- }))
11405
- }
11406
- }));
11407
- clone.progress = {
11408
- ...clone.progress,
11409
- blockers: clone.progress.blockers.map((blocker) => ({
11410
- ...blocker,
11411
- refs: portableRefs(blocker.refs)
11412
- }))
11898
+ const stripped = rewriteRecordRefs(task, taskRecordSchema, portableRef);
11899
+ if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
11900
+ throw new TypeError("portable task rewrite produced a non-record");
11901
+ const clone = structuredClone(stripped);
11902
+ const demote = (facts, signals, consequences) => {
11903
+ for (const fact of facts)
11904
+ Object.assign(fact, portableFact(fact));
11905
+ for (const name of Object.keys(signals)) {
11906
+ const signal = signals[name];
11907
+ Object.assign(signal, portableSignal(signal));
11908
+ }
11909
+ for (const consequence of consequences)
11910
+ Object.assign(consequence.fact, portableFact(consequence.fact));
11413
11911
  };
11414
- clone.evidence = clone.evidence.map((entry) => ({
11415
- ...entry,
11416
- data: {
11417
- ...entry.data,
11418
- refs: portableRefs(entry.data.refs),
11419
- reviewContext: portableRef(entry.data.reviewContext)
11420
- }
11421
- }));
11422
- clone.decisions = clone.decisions.map((entry) => ({
11423
- ...entry,
11424
- data: {
11425
- ...entry.data,
11426
- consumption: entry.data.consumption ? portableRef(entry.data.consumption.actionRef) ? {
11427
- ...entry.data.consumption,
11428
- actionRef: portableRef(entry.data.consumption.actionRef)
11429
- } : null : null,
11430
- binding: {
11431
- ...entry.data.binding,
11432
- contentRefs: portableRefs(entry.data.binding.contentRefs)
11433
- }
11434
- }
11435
- }));
11436
- clone.findings = clone.findings.map((entry) => ({
11437
- ...entry,
11438
- data: { ...entry.data, refs: portableRefs(entry.data.refs) }
11439
- }));
11912
+ for (const entry of clone.assessments)
11913
+ demote(entry.data.facts, entry.data.signals, entry.data.consequences);
11440
11914
  clone.candidates = [];
11441
11915
  return clone;
11442
11916
  };
@@ -11461,15 +11935,13 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11461
11935
  allocate(entry.id);
11462
11936
  const fresh = (id) => allocate(id);
11463
11937
  const provenance = importedProvenance(context);
11938
+ const remap = (ref) => mapRef(ref, ids);
11464
11939
  const intent = {
11465
11940
  ...source.intent,
11466
11941
  id: fresh(source.intent.id),
11467
11942
  recordedAt: timestamp,
11468
11943
  provenance,
11469
- data: {
11470
- ...source.intent.data,
11471
- authorityRefs: source.intent.data.authorityRefs.map((ref) => mapRef(ref, ids))
11472
- }
11944
+ data: rewriteRecordRefs(source.intent.data, intentSchema, remap)
11473
11945
  };
11474
11946
  const assessments = source.assessments.map((entry) => {
11475
11947
  const data = entry.data;
@@ -11478,28 +11950,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11478
11950
  id: fresh(entry.id),
11479
11951
  recordedAt: timestamp,
11480
11952
  provenance,
11481
- data: {
11482
- ...data,
11483
- facts: data.facts.map((fact) => ({
11484
- ...fact,
11485
- refs: fact.refs.map((ref) => mapRef(ref, ids))
11486
- })),
11487
- signals: Object.fromEntries(Object.entries(data.signals).map(([name, signal]) => [
11488
- name,
11489
- { ...signal, refs: signal.refs.map((ref) => mapRef(ref, ids)) }
11490
- ])),
11491
- consequences: data.consequences.map((consequence) => ({
11492
- ...consequence,
11493
- fact: {
11494
- ...consequence.fact,
11495
- refs: consequence.fact.refs.map((ref) => mapRef(ref, ids))
11496
- }
11497
- })),
11498
- verification: data.verification.map((verification) => ({
11499
- ...verification,
11500
- availableChecks: verification.availableChecks.map((ref) => mapRef(ref, ids))
11501
- }))
11502
- }
11953
+ data: rewriteRecordRefs(data, assessmentSchema, remap)
11503
11954
  };
11504
11955
  });
11505
11956
  const evidence = source.evidence.map((entry) => ({
@@ -11507,11 +11958,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11507
11958
  id: fresh(entry.id),
11508
11959
  recordedAt: timestamp,
11509
11960
  provenance,
11510
- data: {
11511
- ...entry.data,
11512
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
11513
- reviewContext: entry.data.reviewContext ? mapRef(entry.data.reviewContext, ids) : null
11514
- }
11961
+ data: rewriteRecordRefs(entry.data, evidenceSchema, remap)
11515
11962
  }));
11516
11963
  const decisions = source.decisions.map((entry) => ({
11517
11964
  ...entry,
@@ -11519,21 +11966,23 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11519
11966
  recordedAt: timestamp,
11520
11967
  provenance
11521
11968
  }));
11522
- const findings = source.findings.map((entry) => ({
11523
- ...entry,
11524
- id: fresh(entry.id),
11525
- recordedAt: timestamp,
11526
- provenance,
11527
- data: {
11528
- ...entry.data,
11529
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
11530
- resolution: entry.data.resolution ? {
11531
- ...entry.data.resolution,
11532
- evidenceIds: entry.data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
11533
- decisionIds: entry.data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
11534
- } : null
11535
- }
11536
- }));
11969
+ const findings = source.findings.map((entry) => {
11970
+ const data = rewriteRecordRefs(entry.data, findingSchema, remap);
11971
+ return {
11972
+ ...entry,
11973
+ id: fresh(entry.id),
11974
+ recordedAt: timestamp,
11975
+ provenance,
11976
+ data: {
11977
+ ...data,
11978
+ resolution: data.resolution ? {
11979
+ ...data.resolution,
11980
+ evidenceIds: data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
11981
+ decisionIds: data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
11982
+ } : null
11983
+ }
11984
+ };
11985
+ });
11537
11986
  const workers = source.workers.map((entry) => ({
11538
11987
  ...entry,
11539
11988
  id: fresh(entry.id),
@@ -11576,19 +12025,21 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11576
12025
  closure: null,
11577
12026
  assessments,
11578
12027
  evidence,
11579
- decisions: decisions.map((entry) => ({
11580
- ...entry,
11581
- data: {
11582
- ...entry.data,
11583
- binding: {
11584
- ...entry.data.binding,
11585
- taskId: "",
11586
- workspaceId: destinationWorkspaceId,
11587
- contentRefs: entry.data.binding.contentRefs.map((ref) => mapRef(ref, ids))
11588
- },
11589
- consumption: null
11590
- }
11591
- })),
12028
+ decisions: decisions.map((entry) => {
12029
+ const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
12030
+ return {
12031
+ ...entry,
12032
+ data: {
12033
+ ...data,
12034
+ binding: {
12035
+ ...data.binding,
12036
+ taskId: "",
12037
+ workspaceId: destinationWorkspaceId
12038
+ },
12039
+ consumption: null
12040
+ }
12041
+ };
12042
+ }),
11592
12043
  ...actionProgress ? { actionProgress } : {},
11593
12044
  findings,
11594
12045
  workers,
@@ -11626,7 +12077,7 @@ class WorkitCore {
11626
12077
  }
11627
12078
  contextRootError() {
11628
12079
  try {
11629
- if (realpathSync4(this.context.root) !== this.store.root)
12080
+ if (realpathSync5(this.context.root) !== this.store.root)
11630
12081
  return failure("invalid_input", "operation context root does not match the task store root");
11631
12082
  } catch {
11632
12083
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -11671,7 +12122,7 @@ class WorkitCore {
11671
12122
  activeWorkerBlocker(task, workspace) {
11672
12123
  if (workspace.writer)
11673
12124
  return failure("recovery_required", "writer ownership must be released first");
11674
- if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
12125
+ if (task.workers.some((entry) => workerBlocksTransition("close", entry.data.state)))
11675
12126
  return failure("recovery_required", "worker state requires reconciliation");
11676
12127
  return null;
11677
12128
  }
@@ -11917,37 +12368,33 @@ class WorkitCore {
11917
12368
  const currentCandidate = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
11918
12369
  if (!currentCandidate.ok)
11919
12370
  return currentCandidate;
11920
- if (evidence.kind === "check" || evidence.kind === "review") {
11921
- evidence.beforeCandidateId ??= currentCandidate.data.id;
11922
- evidence.candidateId ??= currentCandidate.data.id;
11923
- } else {
11924
- evidence.beforeCandidateId ??= null;
11925
- evidence.candidateId ??= null;
11926
- }
12371
+ const pin = helper && helper.ok ? helper.data.data.assignment.candidateId : null;
12372
+ const bound = resolveBinding(evidence, pin, currentCandidate.data.id);
11927
12373
  if (helper?.ok) {
11928
12374
  const assignment = helper.data.data.assignment;
11929
- if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
12375
+ 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))
11930
12376
  return failure("permission_denied", "evidence is outside the worker assignment");
11931
12377
  }
11932
12378
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11933
- for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
12379
+ for (const candidateId of [bound.beforeCandidateId, bound.candidateId]) {
11934
12380
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
11935
12381
  return failure("invalid_input", "evidence candidate is not a captured candidate");
11936
12382
  }
12383
+ const boundEvidence = { ...evidence, ...bound };
11937
12384
  const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
11938
12385
  const candidates = current.candidates.some((item) => item.id === currentCandidate.data.id) ? current.candidates : [...current.candidates, currentCandidate.data];
11939
12386
  const entry = {
11940
12387
  id: newId(),
11941
12388
  recordedAt: mutation.now,
11942
12389
  provenance: provenance(this.context, "agent_reported"),
11943
- data: evidence
12390
+ data: boundEvidence
11944
12391
  };
11945
12392
  return success(mutation.revision, null, {
11946
12393
  ...current,
11947
12394
  candidates,
11948
12395
  evidence: [...current.evidence, entry],
11949
12396
  findings: current.findings.map((finding) => {
11950
- if (finding.data.disposition === "open" || evidence.result === "skipped")
12397
+ if (finding.data.disposition === "open" || boundEvidence.result === "skipped")
11951
12398
  return finding;
11952
12399
  if (finding.data.disposition !== "fixed")
11953
12400
  return finding;
@@ -11956,8 +12403,12 @@ class WorkitCore {
11956
12403
  item.evidenceId,
11957
12404
  item.status
11958
12405
  ]));
11959
- const verified = withEntry.evidence.some((item) => statuses.get(item.id) === "passed" && (item.data.kind === "check" || item.data.kind === "review") && (finding.data.candidateId === null || item.data.candidateId === finding.data.candidateId));
11960
- const contradicted = evidence.result === "failed" && finding.data.refs.some((left) => evidence.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12406
+ const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
12407
+ kind: item.data.kind,
12408
+ candidateId: item.data.candidateId,
12409
+ status: statuses.get(item.id) ?? "missing"
12410
+ }));
12411
+ const contradicted = boundEvidence.result === "failed" && finding.data.refs.some((left) => boundEvidence.refs.some((right) => sameValue(left, right)));
11961
12412
  return verified && !contradicted ? finding : { ...finding, data: { ...finding.data, disposition: "open", resolution: null } };
11962
12413
  })
11963
12414
  });
@@ -11972,7 +12423,10 @@ class WorkitCore {
11972
12423
  observeDecision(request, observation) {
11973
12424
  return this.recordDecision(request, observation, true);
11974
12425
  }
11975
- recordDecision(request, nativeObservation, nativeRequired = false) {
12426
+ observeStandingDecision(request) {
12427
+ return this.recordDecision(request, undefined, true, true);
12428
+ }
12429
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
11976
12430
  const root = this.contextRootError();
11977
12431
  if (!root.ok)
11978
12432
  return root;
@@ -11987,8 +12441,10 @@ class WorkitCore {
11987
12441
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
11988
12442
  if (stated && !input.binding?.statedChoice)
11989
12443
  return failure("invalid_input", "stated choices require binding.statedChoice");
11990
- if (nativeRequired && nativeObservation === undefined && !stated)
12444
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
11991
12445
  return failure("permission_denied", "native decision observation is required");
12446
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
12447
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
11992
12448
  const task = this.store.readTask(input.taskId);
11993
12449
  if (!task.ok)
11994
12450
  return task;
@@ -12020,7 +12476,10 @@ class WorkitCore {
12020
12476
  consumption: null
12021
12477
  };
12022
12478
  const data = { ...base, digest: decisionDigest(base) };
12023
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
12479
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
12480
+ if (standingApproval && !standingApproval.ok)
12481
+ return standingApproval;
12482
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
12024
12483
  observation: nativeObservation,
12025
12484
  expected: {
12026
12485
  taskId: task.data.id,
@@ -12051,7 +12510,7 @@ class WorkitCore {
12051
12510
  const entry = {
12052
12511
  id: newId(),
12053
12512
  recordedAt: mutation.now,
12054
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
12513
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
12055
12514
  data
12056
12515
  };
12057
12516
  return success(mutation.revision, null, {
@@ -12115,7 +12574,7 @@ class WorkitCore {
12115
12574
  if (input.action === "record") {
12116
12575
  if (helper?.ok) {
12117
12576
  const assignment = helper.data.data.assignment;
12118
- if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || assignment.candidateId !== null && input.candidateId !== assignment.candidateId || !refsWithinScope(input.refs, assignment.scope) || !refsWithinScope(input.refs, task.data.intent.data.scope))
12577
+ 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))
12119
12578
  return failure("permission_denied", "finding is outside the worker assignment");
12120
12579
  }
12121
12580
  if (input.candidateId && !task.data.candidates.some((candidate) => candidate.id === input.candidateId))
@@ -12162,7 +12621,11 @@ class WorkitCore {
12162
12621
  const evaluations = evaluateEvidence(task.data, current.data);
12163
12622
  const verified = evidence.some((entry) => {
12164
12623
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
12165
- return evaluation?.status === "passed" && (entry.data.kind === "check" || entry.data.kind === "review") && (finding.data.candidateId === null || entry.data.candidateId === finding.data.candidateId);
12624
+ return findingVerificationPasses(finding.data.candidateId, {
12625
+ kind: entry.data.kind,
12626
+ candidateId: entry.data.candidateId,
12627
+ status: evaluation?.status ?? "missing"
12628
+ });
12166
12629
  });
12167
12630
  if (!verified)
12168
12631
  return failure("permission_denied", "fixed findings require passing verification evidence");
@@ -12174,7 +12637,7 @@ class WorkitCore {
12174
12637
  if (entry.data.result === "missing" || entry.data.result === "skipped")
12175
12638
  return false;
12176
12639
  const candidateMatches = finding.data.candidateId === null ? entry.data.candidateId === null : entry.data.candidateId === finding.data.candidateId || entry.data.beforeCandidateId === finding.data.candidateId;
12177
- const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12640
+ const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => sameValue(left, right)));
12178
12641
  return candidateMatches && (referenceMatches || entry.data.claim === finding.data.claim);
12179
12642
  });
12180
12643
  if (!relevant)
@@ -12252,7 +12715,7 @@ class WorkitCore {
12252
12715
  const workerId = newId();
12253
12716
  const changed = this.store.mutateTaskAndWorkspace({
12254
12717
  taskId: task.data.id,
12255
- expectedTaskRevision: input.expectedRevision,
12718
+ expectedRevision: input.expectedRevision,
12256
12719
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12257
12720
  now: trustedNow(this.context),
12258
12721
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12290,7 +12753,7 @@ class WorkitCore {
12290
12753
  return failure("permission_denied", "worker report is outside the worker assignment");
12291
12754
  const changed = this.store.mutateTaskAndWorkspace({
12292
12755
  taskId: task.data.id,
12293
- expectedTaskRevision: input.expectedRevision,
12756
+ expectedRevision: input.expectedRevision,
12294
12757
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12295
12758
  now: trustedNow(this.context),
12296
12759
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12314,24 +12777,25 @@ class WorkitCore {
12314
12777
  return failure("invalid_transition", "closed task cannot cancel workers");
12315
12778
  if (entry.data.state === "unknown")
12316
12779
  return failure("recovery_required", "worker state requires recovery");
12317
- if (entry.data.state === "stopped")
12318
- return failure("invalid_transition", "stopped worker cannot be cancelled");
12319
- const settles = entry.data.report !== null || entry.data.state === "cancelling";
12780
+ if (entry.data.state === "stopped") {
12781
+ const stopped = task.data.workers.find((candidate) => candidate.id === input.workerId);
12782
+ return success(task.data.revision, workspace.data.revision, stopped);
12783
+ }
12320
12784
  const changed = this.store.mutateTaskAndWorkspace({
12321
12785
  taskId: task.data.id,
12322
- expectedTaskRevision: input.expectedRevision,
12786
+ expectedRevision: input.expectedRevision,
12323
12787
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12324
12788
  now: trustedNow(this.context),
12325
- workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
12789
+ workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
12790
+ ...current,
12791
+ writer: current.writer?.owner.workerId === input.workerId ? null : current.writer
12792
+ }),
12326
12793
  task: (current, mutation) => success(mutation.revision, null, {
12327
12794
  ...current,
12328
12795
  workers: current.workers.map((candidate) => candidate.id === input.workerId ? {
12329
12796
  ...candidate,
12330
12797
  recordedAt: mutation.now,
12331
- data: {
12332
- ...candidate.data,
12333
- state: settles ? "stopped" : "cancelling"
12334
- }
12798
+ data: { ...candidate.data, state: "stopped" }
12335
12799
  } : candidate)
12336
12800
  })
12337
12801
  });
@@ -12428,7 +12892,7 @@ class WorkitCore {
12428
12892
  return attested;
12429
12893
  const changed = this.store.mutateTaskAndWorkspace({
12430
12894
  taskId: task.data.id,
12431
- expectedTaskRevision: input.expectedRevision,
12895
+ expectedRevision: input.expectedRevision,
12432
12896
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12433
12897
  now: trustedNow(this.context),
12434
12898
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12594,6 +13058,12 @@ class WorkitCore {
12594
13058
  if (!sameSession2(helper.data.data.session, this.context))
12595
13059
  return failure("permission_denied", "worker session does not match the caller");
12596
13060
  }
13061
+ const gate = this.view(task.data);
13062
+ if (gate.ok) {
13063
+ 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")));
13064
+ if (writeGates.length)
13065
+ 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) });
13066
+ }
12597
13067
  const owner = {
12598
13068
  taskId: task.data.id,
12599
13069
  workerId: helperId,
@@ -12605,7 +13075,7 @@ class WorkitCore {
12605
13075
  };
12606
13076
  const changed = this.store.mutateTaskAndWorkspace({
12607
13077
  taskId: task.data.id,
12608
- expectedTaskRevision: input.expectedRevision,
13078
+ expectedRevision: input.expectedRevision,
12609
13079
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12610
13080
  now: trustedNow(this.context),
12611
13081
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -12636,7 +13106,7 @@ class WorkitCore {
12636
13106
  }
12637
13107
  const changed = this.store.mutateTaskAndWorkspace({
12638
13108
  taskId: task.data.id,
12639
- expectedTaskRevision: input.expectedRevision,
13109
+ expectedRevision: input.expectedRevision,
12640
13110
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12641
13111
  now: trustedNow(this.context),
12642
13112
  workspace: (currentWorkspace, mutation) => success(mutation.revision, mutation.revision, { ...currentWorkspace, writer: null }),
@@ -12868,7 +13338,7 @@ class WorkitCore {
12868
13338
  if (view.task.workspaceId !== view.workspace.id)
12869
13339
  return failure("invalid_input", "task and workspace bindings are invalid");
12870
13340
  try {
12871
- if (realpathSync4(view.workspace.root) !== this.store.root)
13341
+ if (realpathSync5(view.workspace.root) !== this.store.root)
12872
13342
  return failure("invalid_input", "task view root does not match the task store");
12873
13343
  } catch {
12874
13344
  return failure("invalid_input", "task view root cannot be resolved");
@@ -12885,7 +13355,15 @@ class WorkitCore {
12885
13355
  if (seen.has(observation.workerId))
12886
13356
  return failure("invalid_input", "worker observations must be unique");
12887
13357
  seen.add(observation.workerId);
12888
- if (observation.taskId !== view.task.id || observation.expectedRevision !== view.task.revision || observation.expectedWorkspaceRevision !== view.workspace.revision)
13358
+ const freshTask = this.store.readTask(view.task.id);
13359
+ const freshWorkspace = this.store.readWorkspace();
13360
+ if (!freshTask.ok)
13361
+ return freshTask;
13362
+ if (!freshWorkspace.ok)
13363
+ return freshWorkspace;
13364
+ if (!freshWorkspace.data)
13365
+ return failure("not_found", "workspace not found");
13366
+ if (observation.taskId !== view.task.id || observation.expectedRevision !== freshTask.data.revision || observation.expectedWorkspaceRevision !== freshWorkspace.data.revision)
12889
13367
  return failure("revision_conflict", "worker observation is stale");
12890
13368
  if (!view.task.workers.some((entry) => entry.id === observation.workerId))
12891
13369
  return failure("permission_denied", "worker observation is not assigned");
@@ -12971,19 +13449,26 @@ class WorkitCore {
12971
13449
  return failure("permission_denied", "imported resume requires native destination approval");
12972
13450
  resumeCandidate = current.data.candidate;
12973
13451
  }
12974
- const blocker = this.activeWorkerBlocker(task.data, workspace.data);
13452
+ const blocker = status === "paused" ? workspace.data.writer ? failure("recovery_required", "writer ownership must be released first") : null : this.activeWorkerBlocker(task.data, workspace.data);
12975
13453
  if (blocker)
12976
13454
  return blocker;
13455
+ let pauseCandidate = null;
13456
+ if (status === "paused") {
13457
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
13458
+ if (!captured.ok)
13459
+ return captured;
13460
+ pauseCandidate = captured.data;
13461
+ }
12977
13462
  const changed = this.store.mutateTaskAndWorkspace({
12978
13463
  taskId: task.data.id,
12979
- expectedTaskRevision: input.expectedRevision,
13464
+ expectedRevision: input.expectedRevision,
12980
13465
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12981
13466
  now: trustedNow(this.context),
12982
13467
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
12983
13468
  task: (current, context) => success(context.revision, null, {
12984
13469
  ...current,
12985
13470
  status,
12986
- candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
13471
+ candidates: (resumeCandidate ?? pauseCandidate) && !current.candidates.some((candidate) => candidate.id === (resumeCandidate ?? pauseCandidate).id) ? [...current.candidates, resumeCandidate ?? pauseCandidate] : current.candidates,
12987
13472
  progress: current.progress,
12988
13473
  pauseReason: status === "paused" ? input.reason ?? null : null
12989
13474
  })
@@ -13021,12 +13506,13 @@ class WorkitCore {
13021
13506
  return workspace;
13022
13507
  if (!workspace.data)
13023
13508
  return failure("not_found", "workspace not found");
13024
- if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
13025
- return failure("recovery_required", "scope revision requires worker ownership reconciliation");
13509
+ const revisionBlocker = this.activeWorkerBlocker(task.data, workspace.data);
13510
+ if (revisionBlocker)
13511
+ return revisionBlocker;
13026
13512
  this.fillRevisions(input, task.data, workspace.data);
13027
13513
  const changed = this.store.mutateTaskAndWorkspace({
13028
13514
  taskId: task.data.id,
13029
- expectedTaskRevision: input.expectedRevision,
13515
+ expectedRevision: input.expectedRevision,
13030
13516
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
13031
13517
  now: trustedNow(this.context),
13032
13518
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
@@ -13082,7 +13568,7 @@ class WorkitCore {
13082
13568
  return closure;
13083
13569
  const changed = this.store.mutateTaskAndWorkspace({
13084
13570
  taskId: task.data.id,
13085
- expectedTaskRevision: input.expectedRevision,
13571
+ expectedRevision: input.expectedRevision,
13086
13572
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
13087
13573
  now: trustedNow(this.context),
13088
13574
  workspace: (value, context) => success(context.revision, context.revision, value),
@@ -13173,7 +13659,7 @@ var cursorCapabilities = (availability = {}) => {
13173
13659
  }
13174
13660
  ];
13175
13661
  };
13176
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13662
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13177
13663
  var eventNames = new Set([
13178
13664
  "sessionStart",
13179
13665
  "preToolUse",
@@ -13184,13 +13670,13 @@ var eventNames = new Set([
13184
13670
  ]);
13185
13671
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
13186
13672
  var parseCursorHookInput = (value) => {
13187
- if (!isRecord(value) || !eventNames.has(value.hook_event_name))
13673
+ if (!isRecord2(value) || !eventNames.has(value.hook_event_name))
13188
13674
  return { ok: false, error: "hook_event_name is required" };
13189
13675
  const roots = value.workspace_roots;
13190
13676
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
13191
13677
  return { ok: false, error: "exactly one workspace root is required" };
13192
13678
  const root = roots[0];
13193
- if (!path7.isAbsolute(root) || !existsSync4(root))
13679
+ if (!path9.isAbsolute(root) || !existsSync5(root))
13194
13680
  return { ok: false, error: "workspace root must be an existing absolute path" };
13195
13681
  const event = value.hook_event_name;
13196
13682
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
@@ -13331,10 +13817,10 @@ var handleSubagentStop = () => ({});
13331
13817
  var handleCursorHook = (raw) => {
13332
13818
  const parsed = parseCursorHookInput(raw);
13333
13819
  if (!parsed.ok) {
13334
- return isRecord(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
13820
+ return isRecord2(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
13335
13821
  }
13336
13822
  const input = parsed.data;
13337
- const root = realpathSync5(input.workspace_roots[0]);
13823
+ const root = realpathSync6(input.workspace_roots[0]);
13338
13824
  if (input.hook_event_name === "sessionStart") {
13339
13825
  const actor = input.session_id ?? input.conversation_id;
13340
13826
  let compact = "";
@@ -13376,7 +13862,7 @@ var runCursorHook = async () => {
13376
13862
  const output = handleCursorHook(input);
13377
13863
  process.stdout.write(`${JSON.stringify(output)}
13378
13864
  `);
13379
- const event = isRecord(input) ? input.hook_event_name : undefined;
13865
+ const event = isRecord2(input) ? input.hook_event_name : undefined;
13380
13866
  if (output.permission === "deny" && ["preToolUse", "beforeShellExecution", "subagentStart"].includes(String(event)))
13381
13867
  process.exitCode = 2;
13382
13868
  } catch {