@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.
@@ -7492,7 +7492,87 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
7492
7492
  const covers = (path) => normOuter.paths.some((base) => base === "." || path === base || path.startsWith(`${base}/`)) && !excluded(path, normOuter);
7493
7493
  const innerExcludes = (path) => innerExclusions.some((excludedPath) => excludedPath === path || path.startsWith(`${excludedPath}/`));
7494
7494
  return innerPaths.every(covers) && !normOuter.exclusions.some((excludedPath) => innerPaths.some((base) => (base === "." || excludedPath === base || excludedPath.startsWith(`${base}/`)) && !innerExcludes(excludedPath)));
7495
- }, refSchema, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
7495
+ }, refSchema, rewriteUnknown = (value, map) => {
7496
+ if (Array.isArray(value)) {
7497
+ const out = [];
7498
+ for (const item of value) {
7499
+ const rewritten = rewriteUnknown(item, map);
7500
+ if (!rewritten.drop)
7501
+ out.push(rewritten.value);
7502
+ }
7503
+ return { drop: false, value: out };
7504
+ }
7505
+ if (typeof value === "object" && value !== null) {
7506
+ if (refSchema.safeParse(value).success) {
7507
+ const mapped = map(value);
7508
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7509
+ }
7510
+ const out = {};
7511
+ for (const [key, item] of Object.entries(value)) {
7512
+ const rewritten = rewriteUnknown(item, map);
7513
+ out[key] = rewritten.drop ? null : rewritten.value;
7514
+ }
7515
+ return { drop: false, value: out };
7516
+ }
7517
+ return { drop: false, value };
7518
+ }, rewriteNode = (value, schema, map) => {
7519
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && refSchema.safeParse(value).success) {
7520
+ const mapped = map(value);
7521
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7522
+ }
7523
+ let node = schema;
7524
+ while (node instanceof ZodNullable || node instanceof ZodOptional || node instanceof ZodDefault) {
7525
+ node = node.unwrap();
7526
+ }
7527
+ if (node === refSchema) {
7528
+ if (!refSchema.safeParse(value).success)
7529
+ return { drop: false, value };
7530
+ const mapped = map(value);
7531
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7532
+ }
7533
+ if (node instanceof ZodArray) {
7534
+ if (!Array.isArray(value))
7535
+ return { drop: false, value };
7536
+ const out = [];
7537
+ for (const item of value) {
7538
+ const rewritten = rewriteNode(item, node.element, map);
7539
+ if (!rewritten.drop)
7540
+ out.push(rewritten.value);
7541
+ }
7542
+ return { drop: false, value: out };
7543
+ }
7544
+ if (node instanceof ZodObject) {
7545
+ if (typeof value !== "object" || value === null || Array.isArray(value))
7546
+ return { drop: false, value };
7547
+ const shape = node.shape;
7548
+ const record = value;
7549
+ const out = {};
7550
+ for (const [key, item] of Object.entries(record)) {
7551
+ if (!(key in shape)) {
7552
+ const scrubbed = rewriteUnknown(item, map);
7553
+ out[key] = scrubbed.drop ? null : scrubbed.value;
7554
+ continue;
7555
+ }
7556
+ const child = shape[key];
7557
+ const rewritten = rewriteNode(item, child, map);
7558
+ if (rewritten.drop) {
7559
+ if (child instanceof ZodOptional || child instanceof ZodDefault)
7560
+ continue;
7561
+ if (child instanceof ZodNullable) {
7562
+ out[key] = null;
7563
+ continue;
7564
+ }
7565
+ return { drop: true };
7566
+ }
7567
+ out[key] = rewritten.value;
7568
+ }
7569
+ return { drop: false, value: out };
7570
+ }
7571
+ return { drop: false, value };
7572
+ }, rewriteRecordRefs = (value, schema, map) => {
7573
+ const rewritten = rewriteNode(value, schema, map);
7574
+ return rewritten.drop ? null : rewritten.value;
7575
+ }, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
7496
7576
  ok: true,
7497
7577
  schemaVersion: 1,
7498
7578
  revision: revisionValue,
@@ -7575,7 +7655,13 @@ var init_task_contract = __esm(() => {
7575
7655
  id
7576
7656
  }).strict(),
7577
7657
  object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
7578
- object({ kind: literal("external"), url: text.url() }).strict()
7658
+ object({ kind: literal("external"), url: text.url() }).strict(),
7659
+ object({
7660
+ kind: literal("standing"),
7661
+ workspace: nonEmpty,
7662
+ class: nonEmpty,
7663
+ configDigest: nullableDigest
7664
+ }).strict()
7579
7665
  ]);
7580
7666
  provenanceSchema = object({
7581
7667
  kind: _enum(["host_observed", "agent_reported", "imported"]),
@@ -7796,7 +7882,8 @@ var init_task_contract = __esm(() => {
7796
7882
  approvedContent: text,
7797
7883
  displayed: text.optional(),
7798
7884
  contentRefs: array(refSchema),
7799
- statedChoice: object({ ref: text, text }).strict().optional()
7885
+ statedChoice: object({ ref: text, text }).strict().optional(),
7886
+ standing: object({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
7800
7887
  }).strict(),
7801
7888
  digest,
7802
7889
  response: _enum(["approved", "rejected", "stated"]),
@@ -9075,11 +9162,11 @@ class TaskStore {
9075
9162
  return workspace;
9076
9163
  if (!workspace.data)
9077
9164
  return failure("not_found", "workspace not found");
9078
- const expectedTaskRevision = input.expectedTaskRevision ?? input.expectedRevision;
9079
- if (!expectedTaskRevision)
9165
+ const expected = input.expectedRevision;
9166
+ if (!expected)
9080
9167
  return failure("invalid_input", "task revision is required");
9081
- if (task.data.revision !== expectedTaskRevision)
9082
- return this.conflict(expectedTaskRevision, task.data.revision);
9168
+ if (task.data.revision !== expected)
9169
+ return this.conflict(expected, task.data.revision);
9083
9170
  if (workspace.data.revision !== input.expectedWorkspaceRevision)
9084
9171
  return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
9085
9172
  const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
@@ -9705,7 +9792,7 @@ var same = (left, right) => {
9705
9792
  } catch {
9706
9793
  return false;
9707
9794
  }
9708
- }, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9795
+ }, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9709
9796
  const code = char.charCodeAt(0);
9710
9797
  return code < 32 || code === 127;
9711
9798
  }) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
@@ -9714,10 +9801,360 @@ var init_workers = __esm(() => {
9714
9801
  UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9715
9802
  });
9716
9803
 
9717
- // packages/workit-core/src/core/authority.ts
9804
+ // packages/workit-core/src/core/boundary.ts
9805
+ var EVENT, errorDetail = (err) => {
9806
+ if (err instanceof Error) {
9807
+ return { error_name: err.name, error: err.message };
9808
+ }
9809
+ return { error_name: "unknown", error: String(err) };
9810
+ };
9811
+ var init_boundary = __esm(() => {
9812
+ EVENT = {
9813
+ initialization: "initialization",
9814
+ provenance: "provenance",
9815
+ assets: "assets",
9816
+ configurationSource: "configuration_source",
9817
+ hooks: "hooks",
9818
+ mcpConnection: "mcp_connection",
9819
+ migration: "migration",
9820
+ installSteps: "install_steps",
9821
+ uncaughtFailure: "uncaught_failure",
9822
+ toolsFailed: "tools_failed",
9823
+ doctor: "doctor"
9824
+ };
9825
+ });
9826
+
9827
+ // packages/workit-core/src/core/config.ts
9828
+ import {
9829
+ copyFileSync,
9830
+ cpSync,
9831
+ existsSync as existsSync3,
9832
+ mkdirSync as mkdirSync2,
9833
+ readFileSync as readFileSync2,
9834
+ readdirSync as readdirSync2,
9835
+ writeFileSync as writeFileSync2
9836
+ } from "node:fs";
9837
+ import os from "node:os";
9838
+ import path5 from "node:path";
9839
+ var diagnosticLogger, PRESETS, 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"), migratedDir = null, migrationFailed = false, ensureConfigDir = (dir = resolveConfigDir()) => {
9840
+ if (migratedDir === dir)
9841
+ return dir;
9842
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
9843
+ migratedDir = dir;
9844
+ return dir;
9845
+ }
9846
+ const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
9847
+ if (!existsSync3(legacy)) {
9848
+ migratedDir = dir;
9849
+ return dir;
9850
+ }
9851
+ if (!migrationFailed && existsSync3(dir)) {
9852
+ migratedDir = dir;
9853
+ return dir;
9854
+ }
9855
+ migrationFailed = false;
9856
+ mkdirSync2(dir, { recursive: true });
9857
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
9858
+ for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
9859
+ const src = path5.join(legacy, entry.name);
9860
+ const dest = path5.join(dir, entry.name);
9861
+ if (existsSync3(dest))
9862
+ continue;
9863
+ try {
9864
+ if (entry.isDirectory())
9865
+ cpSync(src, dest, { recursive: true });
9866
+ else if (entry.isFile())
9867
+ copyFileSync(src, dest);
9868
+ } catch (err) {
9869
+ migrationFailed = true;
9870
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
9871
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
9872
+ }
9873
+ }
9874
+ if (!migrationFailed)
9875
+ migratedDir = dir;
9876
+ return dir;
9877
+ }, configDir = () => ensureConfigDir(), DEFAULTS, isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9878
+ var init_config = __esm(() => {
9879
+ init_boundary();
9880
+ PRESETS = {
9881
+ gitflow: {
9882
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
9883
+ protected: ["main", "develop", "master", "prod", "production"]
9884
+ },
9885
+ "github-flow": { allowed: ["*"], protected: ["main"] },
9886
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
9887
+ custom: { allowed: [], protected: [] }
9888
+ };
9889
+ DEFAULTS = {
9890
+ locale: "en",
9891
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
9892
+ timezone: "America/Santiago",
9893
+ branchPolicy: {
9894
+ preset: "gitflow",
9895
+ allowed: [...PRESETS.gitflow.allowed],
9896
+ protected: [...PRESETS.gitflow.protected]
9897
+ },
9898
+ commitPolicy: { preset: "conventional" }
9899
+ };
9900
+ });
9901
+
9902
+ // packages/workit-core/src/core/workspaces.ts
9903
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
9904
+ import path6 from "node:path";
9905
+ var readWorkspacesResult = (dir = configDir()) => {
9906
+ const file = path6.join(dir, "workspaces.json");
9907
+ let raw;
9908
+ try {
9909
+ raw = readFileSync3(file, "utf8");
9910
+ } catch {
9911
+ return { status: "missing", path: file, entries: [] };
9912
+ }
9913
+ let parsed;
9914
+ try {
9915
+ parsed = JSON.parse(raw);
9916
+ } catch {
9917
+ return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
9918
+ }
9919
+ if (!isConfigObject(parsed)) {
9920
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
9921
+ }
9922
+ const list = parsed.workspaces;
9923
+ return {
9924
+ status: "valid",
9925
+ path: file,
9926
+ entries: Array.isArray(list) ? list : []
9927
+ };
9928
+ }, loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries, globToRegExp = (glob) => {
9929
+ let out = "";
9930
+ for (let i = 0;i < glob.length; i++) {
9931
+ const c = glob[i];
9932
+ if (c === "*") {
9933
+ if (glob[i + 1] === "*") {
9934
+ if (glob[i + 2] === "/") {
9935
+ if (out === "")
9936
+ out += "/?";
9937
+ out += "(?:[^/]+/)*";
9938
+ i += 2;
9939
+ } else {
9940
+ if (i + 2 >= glob.length) {
9941
+ if (out.endsWith("/")) {
9942
+ out = out.slice(0, -1);
9943
+ out += "(?:/.*)?";
9944
+ } else {
9945
+ out += ".*";
9946
+ }
9947
+ } else {
9948
+ out += ".*";
9949
+ }
9950
+ i++;
9951
+ }
9952
+ } else {
9953
+ out += "[^/]*";
9954
+ }
9955
+ } else {
9956
+ out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9957
+ }
9958
+ }
9959
+ return new RegExp(`^${out}$`);
9960
+ }, matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/")), realpathOf = (p) => {
9961
+ const real = realpathSync2.native ?? realpathSync2;
9962
+ try {
9963
+ return real(p);
9964
+ } catch {
9965
+ return p;
9966
+ }
9967
+ }, canonicalGlob = (glob) => {
9968
+ const m = /[*?[\]{]/.exec(glob);
9969
+ const prefix = m ? glob.slice(0, m.index) : glob;
9970
+ const rest = m ? glob.slice(m.index) : "";
9971
+ if (!prefix)
9972
+ return glob;
9973
+ const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
9974
+ return real + (prefix.endsWith("/") ? "/" : "") + rest;
9975
+ }, resolveWorkspaceFrom = (cwd, dir) => {
9976
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
9977
+ for (const entry of loadWorkspacesFrom(dir)) {
9978
+ if (!entry || typeof entry !== "object")
9979
+ continue;
9980
+ const ws = entry;
9981
+ if (typeof ws.glob !== "string" || !ws.glob)
9982
+ continue;
9983
+ const glob = ws.glob.replaceAll("\\", "/");
9984
+ const canonical = canonicalGlob(glob);
9985
+ for (const target of targets) {
9986
+ if (matchWorkspace(glob, target))
9987
+ return ws;
9988
+ if (canonical !== glob && matchWorkspace(canonical, target))
9989
+ return ws;
9990
+ }
9991
+ }
9992
+ return null;
9993
+ };
9994
+ var init_workspaces = __esm(() => {
9995
+ init_config();
9996
+ });
9997
+
9998
+ // packages/workit-core/src/core/git.ts
9999
+ var init_git = () => {};
10000
+
10001
+ // packages/workit-core/src/core/vcs-config.ts
10002
+ var init_vcs_config = __esm(() => {
10003
+ init_config();
10004
+ init_workspaces();
10005
+ init_branch();
10006
+ });
10007
+
10008
+ // packages/workit-core/src/core/branch.ts
10009
+ import { execFileSync } from "node:child_process";
10010
+ var verifyPushIdentity = (root, provider, account) => {
10011
+ if (!account || typeof account !== "string")
10012
+ return { ok: false, error: "push identity requires a configured area account" };
10013
+ const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
10014
+ const bin = provider === "gitlab" ? "glab" : "gh";
10015
+ let login;
10016
+ try {
10017
+ login = execFileSync(bin, [...probe], {
10018
+ cwd: root,
10019
+ encoding: "utf8",
10020
+ stdio: ["pipe", "pipe", "pipe"]
10021
+ }).trim();
10022
+ } catch {
10023
+ return { ok: false, error: `${bin} identity could not be resolved` };
10024
+ }
10025
+ if (!login || login.toLowerCase() !== account.toLowerCase())
10026
+ return {
10027
+ ok: false,
10028
+ error: `push identity ${login || "(unknown)"} does not match area account ${account}`
10029
+ };
10030
+ return { ok: true };
10031
+ };
10032
+ var init_branch = __esm(() => {
10033
+ init_git();
10034
+ init_config();
10035
+ init_workspaces();
10036
+ init_vcs_config();
10037
+ });
10038
+
10039
+ // packages/workit-core/src/core/auto-approval.ts
9718
10040
  import { createHash as createHash3 } from "node:crypto";
10041
+ import { readFileSync as readFileSync4 } from "node:fs";
10042
+ var AUTO_CLASSES, OPERATION_CLASS, operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null, digestOf = (configDirPath) => {
10043
+ try {
10044
+ const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
10045
+ return createHash3("sha256").update(raw).digest("hex");
10046
+ } catch {
10047
+ return null;
10048
+ }
10049
+ }, normalizeClasses = (value) => {
10050
+ if (value === true)
10051
+ return [...AUTO_CLASSES];
10052
+ if (!Array.isArray(value))
10053
+ return [];
10054
+ const seen = new Set;
10055
+ for (const item of value) {
10056
+ if (typeof item === "string" && AUTO_CLASSES.includes(item))
10057
+ seen.add(item);
10058
+ }
10059
+ return [...seen];
10060
+ }, resolveAutoApproval = (root, dir = configDir()) => {
10061
+ let match;
10062
+ try {
10063
+ match = resolveWorkspaceFrom(root, dir);
10064
+ } catch {
10065
+ return { status: "off" };
10066
+ }
10067
+ if (!match)
10068
+ return { status: "off" };
10069
+ const classes = normalizeClasses(match.autoApprove);
10070
+ if (classes.length === 0)
10071
+ return { status: "off" };
10072
+ return {
10073
+ status: "on",
10074
+ workspace: match.name,
10075
+ classes,
10076
+ account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
10077
+ configDigest: digestOf(dir)
10078
+ };
10079
+ }, isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing", standingApprovalLive = (root, receipt, cls, dir) => {
10080
+ if (!isStandingReceipt(receipt))
10081
+ return false;
10082
+ if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
10083
+ return false;
10084
+ if (receipt.class !== cls)
10085
+ return false;
10086
+ const resolved = resolveAutoApproval(root, dir);
10087
+ if (resolved.status !== "on")
10088
+ return false;
10089
+ if (resolved.workspace !== receipt.workspace)
10090
+ return false;
10091
+ return resolved.classes.includes(cls);
10092
+ }, standingReceiptFor = (root, workspace, cls, dir) => {
10093
+ const resolved = resolveAutoApproval(root, dir);
10094
+ return {
10095
+ kind: "standing",
10096
+ workspace,
10097
+ class: cls,
10098
+ configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
10099
+ };
10100
+ }, verifyStandingApproval = (root, task, caller, binding) => {
10101
+ if (task.status !== "active")
10102
+ return failure("invalid_transition", "only active tasks can authorize actions");
10103
+ const session = task.intent?.provenance?.session;
10104
+ if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
10105
+ return failure("permission_denied", "standing approval requires the lead session");
10106
+ const standing = binding.standing;
10107
+ if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
10108
+ return failure("invalid_input", "standing approval needs a workspace and class");
10109
+ let operation;
10110
+ try {
10111
+ operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
10112
+ } catch {
10113
+ operation = null;
10114
+ }
10115
+ const cls = operationAutoClass(operation);
10116
+ if (!cls || cls !== standing.class)
10117
+ return failure("permission_denied", "standing approval does not cover this operation");
10118
+ const resolved = resolveAutoApproval(root);
10119
+ if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
10120
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
10121
+ if (cls === "push") {
10122
+ const workspace = resolveWorkspaceFrom(root, configDir());
10123
+ const provider = workspace?.vcs?.provider;
10124
+ const account = resolved.account;
10125
+ if (!provider || !account)
10126
+ return failure("permission_denied", "auto-push requires a workspace provider and area account");
10127
+ const identity = verifyPushIdentity(root, provider, account);
10128
+ if (!identity.ok)
10129
+ return failure("permission_denied", identity.error);
10130
+ }
10131
+ return success(null, null, {
10132
+ kind: "host_observed",
10133
+ host: caller.host,
10134
+ session: { kind: "host", host: caller.host, handle: caller.actor },
10135
+ workerId: null,
10136
+ receipts: [standingReceiptFor(root, standing.workspace, cls)]
10137
+ });
10138
+ };
10139
+ var init_auto_approval = __esm(() => {
10140
+ init_workspaces();
10141
+ init_config();
10142
+ init_branch();
10143
+ init_task_contract();
10144
+ AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
10145
+ OPERATION_CLASS = {
10146
+ "git.branch_setup": "branch",
10147
+ "git.commit": "commit",
10148
+ "git.push": "push",
10149
+ "hosting.pull_request": "pr",
10150
+ "hosting.merge": "merge"
10151
+ };
10152
+ });
10153
+
10154
+ // packages/workit-core/src/core/authority.ts
10155
+ import { createHash as createHash4 } from "node:crypto";
9719
10156
  import * as fs5 from "node:fs";
9720
- import path5 from "node:path";
10157
+ import path7 from "node:path";
9721
10158
  function applicableDecision(task, purpose, binding, checkoutRoot) {
9722
10159
  if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
9723
10160
  return [];
@@ -10000,6 +10437,33 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10000
10437
  const token = {};
10001
10438
  verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
10002
10439
  return token;
10440
+ }, chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr", isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), normalizeChainSteps = (steps) => {
10441
+ if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
10442
+ return null;
10443
+ const normalized = [];
10444
+ for (const step of steps) {
10445
+ if (typeof step === "string") {
10446
+ if (!step.trim())
10447
+ return null;
10448
+ normalized.push({ kind: "commit", message: step });
10449
+ continue;
10450
+ }
10451
+ if (!isRecord(step))
10452
+ return null;
10453
+ if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
10454
+ normalized.push({ kind: "branch", target: step.branch });
10455
+ continue;
10456
+ }
10457
+ if (step.pr === true && Object.keys(step).length === 1) {
10458
+ normalized.push({ kind: "pr" });
10459
+ continue;
10460
+ }
10461
+ return null;
10462
+ }
10463
+ const keys = normalized.map(chainStepKey);
10464
+ if (new Set(keys).size !== keys.length)
10465
+ return null;
10466
+ return normalized;
10003
10467
  }, scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right), parseSteps = (content) => {
10004
10468
  let value;
10005
10469
  try {
@@ -10010,7 +10474,10 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10010
10474
  if (typeof value !== "object" || value === null || !("steps" in value))
10011
10475
  return [];
10012
10476
  const steps = value.steps;
10013
- return Array.isArray(steps) && steps.every((step) => typeof step === "string") ? steps : null;
10477
+ if (!Array.isArray(steps))
10478
+ return null;
10479
+ const normalized = normalizeChainSteps(steps);
10480
+ return normalized;
10014
10481
  }, decisionMatches = (entry, purpose, binding) => {
10015
10482
  const decision = entry.data;
10016
10483
  return binding.taskId === decision.binding.taskId && binding.workspaceId === decision.binding.workspaceId && decision.purpose === purpose && decision.response === "approved" && decision.revoked === null && (purpose !== "action" || decision.consumption === null) && decision.digest === decisionDigest(decision) && scopeEqual(decision.binding.scope, binding.scope) && canonicalJson(decision.binding) === canonicalJson(binding);
@@ -10089,7 +10556,7 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10089
10556
  const record = verifiedAuthorities.get(authority);
10090
10557
  verifiedAuthorities.delete(authority);
10091
10558
  return record?.provenance ?? null;
10092
- }, authorityMatches = (authority, expected) => authority.kind === "action" && authority.taskId === expected.taskId && authority.workspaceId === expected.workspaceId && authority.decisionId === expected.decisionId && sameRef(authority.actionRef, expected.actionRef) && authority.taskRevision === expected.taskRevision && authority.workspaceRevision === expected.workspaceRevision && authority.outcome === expected.outcome, authorityDecisionMatches = (authority, decision) => authority.purpose === decision.purpose && authority.response === decision.response && authority.digest === decision.digest && canonicalJson(authority.binding) === canonicalJson(decision.binding) && canonicalJson(authority.requirementIds) === canonicalJson(decision.requirementIds), provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session), digestBytes2 = (bytes) => createHash3("sha256").update(bytes).digest("hex"), verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
10559
+ }, authorityMatches = (authority, expected) => authority.kind === "action" && authority.taskId === expected.taskId && authority.workspaceId === expected.workspaceId && authority.decisionId === expected.decisionId && sameRef(authority.actionRef, expected.actionRef) && authority.taskRevision === expected.taskRevision && authority.workspaceRevision === expected.workspaceRevision && authority.outcome === expected.outcome, authorityDecisionMatches = (authority, decision) => authority.purpose === decision.purpose && authority.response === decision.response && authority.digest === decision.digest && canonicalJson(authority.binding) === canonicalJson(decision.binding) && canonicalJson(authority.requirementIds) === canonicalJson(decision.requirementIds), provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session), digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex"), verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
10093
10560
  let root;
10094
10561
  try {
10095
10562
  root = fs5.realpathSync(checkoutRoot);
@@ -10101,23 +10568,23 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10101
10568
  continue;
10102
10569
  if (!reference.digest)
10103
10570
  return failure("invalid_input", "document references require a byte digest");
10104
- const target = path5.resolve(root, reference.path);
10105
- if (target !== root && !target.startsWith(`${root}${path5.sep}`))
10571
+ const target = path7.resolve(root, reference.path);
10572
+ if (target !== root && !target.startsWith(`${root}${path7.sep}`))
10106
10573
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
10107
10574
  fields: [
10108
10575
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
10109
10576
  ]
10110
10577
  });
10111
10578
  try {
10112
- const relative = path5.relative(root, target);
10579
+ const relative = path7.relative(root, target);
10113
10580
  let current = root;
10114
- for (const segment of relative.split(path5.sep)) {
10581
+ for (const segment of relative.split(path7.sep)) {
10115
10582
  if (!segment)
10116
10583
  continue;
10117
- current = path5.join(current, segment);
10584
+ current = path7.join(current, segment);
10118
10585
  const stat = fs5.lstatSync(current);
10119
10586
  const real = fs5.realpathSync(current);
10120
- if (real !== root && !real.startsWith(`${root}${path5.sep}`))
10587
+ if (real !== root && !real.startsWith(`${root}${path7.sep}`))
10121
10588
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
10122
10589
  fields: [
10123
10590
  {
@@ -10167,6 +10634,20 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10167
10634
  return failure("permission_denied", "decision was rejected");
10168
10635
  if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
10169
10636
  return failure("permission_denied", "action approval lacks native receipt assurance");
10637
+ const receipts = entry.provenance.receipts;
10638
+ const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
10639
+ const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
10640
+ if (!questionBacked && standing.length > 0) {
10641
+ let operation;
10642
+ try {
10643
+ operation = JSON.parse(decision.binding.approvedContent).operation;
10644
+ } catch {
10645
+ operation = null;
10646
+ }
10647
+ const cls = operationAutoClass(operation);
10648
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
10649
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
10650
+ }
10170
10651
  if (decision.revoked)
10171
10652
  return failure("permission_denied", "decision is revoked");
10172
10653
  if (decision.digest !== decisionDigest(decision))
@@ -10180,18 +10661,17 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
10180
10661
  return content;
10181
10662
  const requestedSteps = parseSteps(decision.binding.approvedContent);
10182
10663
  if (requestedSteps === null)
10183
- return failure("invalid_input", "bounded action steps must be a string array");
10184
- if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps))
10664
+ return failure("invalid_input", "bounded action steps must be a unique non-empty array");
10665
+ if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
10185
10666
  return failure("permission_denied", "bounded action steps do not match approved content");
10186
- if (requestedSteps.some((step) => !step || typeof step !== "string") || new Set(requestedSteps).size !== requestedSteps.length)
10187
- return failure("invalid_input", "bounded action steps must be unique and non-empty");
10188
10667
  const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
10189
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps, completed: [] };
10190
- if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps))
10668
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
10669
+ if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
10191
10670
  return failure("permission_denied", "bounded action scope changed");
10192
10671
  return success(null, null, { entry, workflow });
10193
10672
  }, bindingCovers, verifyDecisionContent;
10194
10673
  var init_authority = __esm(() => {
10674
+ init_auto_approval();
10195
10675
  init_task_contract();
10196
10676
  verifiedAuthorities = new WeakMap;
10197
10677
  bindingCovers = scopeCovers;
@@ -10199,10 +10679,10 @@ var init_authority = __esm(() => {
10199
10679
  });
10200
10680
 
10201
10681
  // packages/workit-core/src/core/task-evaluation.ts
10202
- import { createHash as createHash4 } from "node:crypto";
10682
+ import { createHash as createHash5 } from "node:crypto";
10203
10683
  import * as fs6 from "node:fs";
10204
10684
  import { spawnSync } from "node:child_process";
10205
- import path6 from "node:path";
10685
+ import path8 from "node:path";
10206
10686
  function captureCandidate(root, scope, environment = []) {
10207
10687
  let checkout;
10208
10688
  try {
@@ -10244,7 +10724,7 @@ function captureCandidate(root, scope, environment = []) {
10244
10724
  for (const item of [...names].sort()) {
10245
10725
  if (!scopeMatches(item, normalizedScope))
10246
10726
  continue;
10247
- const target = path6.join(checkout, item);
10727
+ const target = path8.join(checkout, item);
10248
10728
  try {
10249
10729
  const stat = fs6.lstatSync(target);
10250
10730
  if (stat.isSymbolicLink()) {
@@ -10355,7 +10835,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10355
10835
  return (task.policy?.requirements ?? []).map((requirement) => {
10356
10836
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
10357
10837
  const passed = related.filter(({ entry, evaluation }) => {
10358
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension))
10838
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
10359
10839
  return false;
10360
10840
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
10361
10841
  return false;
@@ -10363,11 +10843,12 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10363
10843
  return true;
10364
10844
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10365
10845
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10366
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10846
+ 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));
10367
10847
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10368
10848
  });
10369
10849
  if (passed.length) {
10370
- 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;
10850
+ const currentId = candidate?.id ?? null;
10851
+ 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;
10371
10852
  if (withRed.length)
10372
10853
  return {
10373
10854
  requirementId: requirement.id,
@@ -10416,6 +10897,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10416
10897
  reason: "an applicable approved limitation permits the missing evidence"
10417
10898
  };
10418
10899
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
10900
+ if (!unavailable && related.length > 0 && !passed.length)
10901
+ return {
10902
+ requirementId: requirement.id,
10903
+ status: "unsatisfied",
10904
+ evidenceIds: related.map(({ entry }) => entry.id),
10905
+ decisionIds: [],
10906
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
10907
+ };
10419
10908
  return {
10420
10909
  requirementId: requirement.id,
10421
10910
  status: unavailable ? "unavailable" : "unsatisfied",
@@ -10440,7 +10929,13 @@ function evaluateClosure(requestedOutcome, view) {
10440
10929
  if (entry.data.disposition === "fixed") {
10441
10930
  const verified = entry.data.resolution?.evidenceIds.some((id) => {
10442
10931
  const evidence = view.task.evidence.find((item) => item.id === id);
10443
- return evidenceById.get(id) === "passed" && (evidence?.data.kind === "check" || evidence?.data.kind === "review");
10932
+ if (!evidence)
10933
+ return false;
10934
+ return findingVerificationPasses(entry.data.candidateId, {
10935
+ kind: evidence.data.kind,
10936
+ candidateId: evidence.data.candidateId,
10937
+ status: evidenceById.get(id) ?? "missing"
10938
+ });
10444
10939
  });
10445
10940
  if (!verified)
10446
10941
  return failure("requirements_unsatisfied", "fixed findings require current verification");
@@ -10456,7 +10951,12 @@ function evaluateClosure(requestedOutcome, view) {
10456
10951
  }
10457
10952
  }
10458
10953
  }
10459
- const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10954
+ const blocking = evaluations.filter((item) => {
10955
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
10956
+ return false;
10957
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10958
+ return !requirement || requirement.before === "close";
10959
+ });
10460
10960
  if (requestedOutcome !== "stopped" && blocking.length)
10461
10961
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10462
10962
  requirementIds: blocking.map((item) => item.requirementId),
@@ -10495,12 +10995,12 @@ function evaluateClosure(requestedOutcome, view) {
10495
10995
  requirementIds: evaluations.map((item) => item.requirementId)
10496
10996
  });
10497
10997
  }
10498
- var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`), relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".", canonicalPath = (value) => {
10998
+ var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path8.sep}`), relative = (root, candidate) => path8.relative(root, candidate).split(path8.sep).join("/") || ".", canonicalPath = (value) => {
10499
10999
  if (typeof value !== "string" || !value)
10500
11000
  return null;
10501
11001
  if (value.includes("\\") || value.split("/").includes(".."))
10502
11002
  return null;
10503
- const normalized = path6.posix.normalize(value);
11003
+ const normalized = path8.posix.normalize(value);
10504
11004
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
10505
11005
  return null;
10506
11006
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10542,14 +11042,14 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10542
11042
  git: /not a git repository/i.test(stderr) ? false : true
10543
11043
  };
10544
11044
  }
10545
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
11045
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")) : [];
10546
11046
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10547
11047
  if (stagedDeleted.status !== 0) {
10548
11048
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10549
11049
  if (!/not a git repository/i.test(stderr))
10550
11050
  return { paths, uncertain: true, git: true };
10551
11051
  } else if (stagedDeleted.stdout) {
10552
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
11052
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")));
10553
11053
  }
10554
11054
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10555
11055
  }, walk = (root, directory, output) => {
@@ -10563,7 +11063,7 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10563
11063
  for (const name of names) {
10564
11064
  if (name === ".git" || name === ".workit")
10565
11065
  continue;
10566
- const target = path6.join(directory, name);
11066
+ const target = path8.join(directory, name);
10567
11067
  let stat;
10568
11068
  try {
10569
11069
  stat = fs6.lstatSync(target);
@@ -10580,12 +11080,12 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10580
11080
  }, scopeRoots = (root, scope) => {
10581
11081
  const roots = [];
10582
11082
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10583
- const target = path6.resolve(root, item);
11083
+ const target = path8.resolve(root, item);
10584
11084
  if (!inside(root, target))
10585
11085
  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` }] });
10586
11086
  let ancestor = target;
10587
11087
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10588
- ancestor = path6.dirname(ancestor);
11088
+ ancestor = path8.dirname(ancestor);
10589
11089
  try {
10590
11090
  if (!inside(root, fs6.realpathSync(ancestor)))
10591
11091
  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` }] });
@@ -10612,11 +11112,23 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10612
11112
  }, sameSession = (left, right) => {
10613
11113
  const other = sessionFromRef(right);
10614
11114
  return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
10615
- }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, 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"))), 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 })), evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
11115
+ }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
11116
+ const pin = assignment.candidateId ?? null;
11117
+ if (pin === null)
11118
+ return true;
11119
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
11120
+ }, resolveBinding = (evidence, pin, currentCandidateId) => {
11121
+ const bound = evidence.kind === "check" || evidence.kind === "review";
11122
+ const fallback = bound ? pin ?? currentCandidateId : null;
11123
+ return {
11124
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
11125
+ candidateId: evidence.candidateId ?? fallback
11126
+ };
11127
+ }, findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId), 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"))), 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 })), evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
10616
11128
  if (kind !== "passed")
10617
11129
  return false;
10618
11130
  if (dimension === "testing" || dimension === "verification")
10619
- return evidenceKind === "check";
11131
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
10620
11132
  if (dimension === "review")
10621
11133
  return evidenceKind === "review";
10622
11134
  if (dimension === "investigation" || dimension === "challenge")
@@ -10624,6 +11136,20 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10624
11136
  if (dimension === "artifacts" || dimension === "continuity")
10625
11137
  return evidenceKind === "artifact";
10626
11138
  return false;
11139
+ }, expectedKinds = (dimension, ruleId) => {
11140
+ if (dimension === "testing")
11141
+ return "check";
11142
+ if (dimension === "verification")
11143
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
11144
+ if (dimension === "review")
11145
+ return "review";
11146
+ if (dimension === "investigation" || dimension === "challenge")
11147
+ return "investigation";
11148
+ if (dimension === "artifacts" || dimension === "continuity")
11149
+ return "artifact";
11150
+ if (dimension === "decisions")
11151
+ return "decision";
11152
+ return "worker report";
10627
11153
  };
10628
11154
  var init_task_evaluation = __esm(() => {
10629
11155
  init_task_contract();
@@ -10634,7 +11160,7 @@ var init_task_evaluation = __esm(() => {
10634
11160
  // packages/workit-core/src/core/methods.ts
10635
11161
  function selectMethods(policy, capabilities) {
10636
11162
  return Object.entries(METHODS).flatMap(([id, definition]) => {
10637
- const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
11163
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
10638
11164
  if (requirements.length === 0)
10639
11165
  return [];
10640
11166
  const capability = assuranceFor(id, definition, capabilities);
@@ -10647,7 +11173,7 @@ function selectMethods(policy, capabilities) {
10647
11173
  return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
10648
11174
  });
10649
11175
  }
10650
- var METHODS, methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
11176
+ var METHODS, methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
10651
11177
  const keys = new Set([
10652
11178
  id,
10653
11179
  ...definition.dimensions ?? [],
@@ -10721,12 +11247,12 @@ act from memory of it.
10721
11247
  var init_methods = __esm(() => {
10722
11248
  METHODS = {
10723
11249
  "workit-challenge": { dimensions: ["challenge", "decisions"] },
10724
- "workit-behavioral-tdd": { dimensions: ["testing"] },
10725
- "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
11250
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
11251
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
10726
11252
  "workit-plan": { dimensions: ["artifacts", "continuity"] },
10727
11253
  "workit-implement": { dimensions: ["delegation"] },
10728
- "workit-debug": { ruleIds: ["root-cause-investigation"] },
10729
- "workit-handoff": { ruleIds: ["durable-handoff"] },
11254
+ "workit-debug": {},
11255
+ "workit-handoff": {},
10730
11256
  "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
10731
11257
  };
10732
11258
  });
@@ -10837,10 +11363,14 @@ var init_route_intent = __esm(() => {
10837
11363
  init_task_store();
10838
11364
  });
10839
11365
 
11366
+ // packages/workit-core/src/core/runtime-identity.ts
11367
+ var init_runtime_identity = () => {};
11368
+
10840
11369
  // packages/workit-core/src/core/external-action.ts
10841
11370
  var externalActionSchema;
10842
11371
  var init_external_action = __esm(() => {
10843
11372
  init_task_contract();
11373
+ init_authority();
10844
11374
  init_zod();
10845
11375
  externalActionSchema = discriminatedUnion("operation", [
10846
11376
  object({
@@ -10874,6 +11404,13 @@ var init_external_action = __esm(() => {
10874
11404
  babysit: boolean2().optional()
10875
11405
  }).strict()
10876
11406
  }).strict(),
11407
+ object({
11408
+ operation: literal("hosting.merge"),
11409
+ payload: object({
11410
+ target_branch: string2().min(1).optional(),
11411
+ source_branch: string2().min(1).optional()
11412
+ }).strict()
11413
+ }).strict(),
10877
11414
  object({
10878
11415
  operation: literal("youtrack.update"),
10879
11416
  payload: object({
@@ -11254,7 +11791,7 @@ var init_policy_resolver = __esm(() => {
11254
11791
  });
11255
11792
 
11256
11793
  // packages/workit-core/src/core/task-engine.ts
11257
- import { realpathSync as realpathSync4 } from "node:fs";
11794
+ import { realpathSync as realpathSync5 } from "node:fs";
11258
11795
 
11259
11796
  class WorkitCore {
11260
11797
  store;
@@ -11269,7 +11806,7 @@ class WorkitCore {
11269
11806
  }
11270
11807
  contextRootError() {
11271
11808
  try {
11272
- if (realpathSync4(this.context.root) !== this.store.root)
11809
+ if (realpathSync5(this.context.root) !== this.store.root)
11273
11810
  return failure("invalid_input", "operation context root does not match the task store root");
11274
11811
  } catch {
11275
11812
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -11314,7 +11851,7 @@ class WorkitCore {
11314
11851
  activeWorkerBlocker(task, workspace) {
11315
11852
  if (workspace.writer)
11316
11853
  return failure("recovery_required", "writer ownership must be released first");
11317
- if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11854
+ if (task.workers.some((entry) => workerBlocksTransition("close", entry.data.state)))
11318
11855
  return failure("recovery_required", "worker state requires reconciliation");
11319
11856
  return null;
11320
11857
  }
@@ -11560,37 +12097,33 @@ class WorkitCore {
11560
12097
  const currentCandidate = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
11561
12098
  if (!currentCandidate.ok)
11562
12099
  return currentCandidate;
11563
- if (evidence.kind === "check" || evidence.kind === "review") {
11564
- evidence.beforeCandidateId ??= currentCandidate.data.id;
11565
- evidence.candidateId ??= currentCandidate.data.id;
11566
- } else {
11567
- evidence.beforeCandidateId ??= null;
11568
- evidence.candidateId ??= null;
11569
- }
12100
+ const pin = helper && helper.ok ? helper.data.data.assignment.candidateId : null;
12101
+ const bound = resolveBinding(evidence, pin, currentCandidate.data.id);
11570
12102
  if (helper?.ok) {
11571
12103
  const assignment = helper.data.data.assignment;
11572
- 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))
12104
+ 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))
11573
12105
  return failure("permission_denied", "evidence is outside the worker assignment");
11574
12106
  }
11575
12107
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11576
- for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
12108
+ for (const candidateId of [bound.beforeCandidateId, bound.candidateId]) {
11577
12109
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
11578
12110
  return failure("invalid_input", "evidence candidate is not a captured candidate");
11579
12111
  }
12112
+ const boundEvidence = { ...evidence, ...bound };
11580
12113
  const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
11581
12114
  const candidates = current.candidates.some((item) => item.id === currentCandidate.data.id) ? current.candidates : [...current.candidates, currentCandidate.data];
11582
12115
  const entry = {
11583
12116
  id: newId(),
11584
12117
  recordedAt: mutation.now,
11585
12118
  provenance: provenance(this.context, "agent_reported"),
11586
- data: evidence
12119
+ data: boundEvidence
11587
12120
  };
11588
12121
  return success(mutation.revision, null, {
11589
12122
  ...current,
11590
12123
  candidates,
11591
12124
  evidence: [...current.evidence, entry],
11592
12125
  findings: current.findings.map((finding) => {
11593
- if (finding.data.disposition === "open" || evidence.result === "skipped")
12126
+ if (finding.data.disposition === "open" || boundEvidence.result === "skipped")
11594
12127
  return finding;
11595
12128
  if (finding.data.disposition !== "fixed")
11596
12129
  return finding;
@@ -11599,8 +12132,12 @@ class WorkitCore {
11599
12132
  item.evidenceId,
11600
12133
  item.status
11601
12134
  ]));
11602
- 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));
11603
- const contradicted = evidence.result === "failed" && finding.data.refs.some((left) => evidence.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12135
+ const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
12136
+ kind: item.data.kind,
12137
+ candidateId: item.data.candidateId,
12138
+ status: statuses.get(item.id) ?? "missing"
12139
+ }));
12140
+ const contradicted = boundEvidence.result === "failed" && finding.data.refs.some((left) => boundEvidence.refs.some((right) => sameValue(left, right)));
11604
12141
  return verified && !contradicted ? finding : { ...finding, data: { ...finding.data, disposition: "open", resolution: null } };
11605
12142
  })
11606
12143
  });
@@ -11615,7 +12152,10 @@ class WorkitCore {
11615
12152
  observeDecision(request, observation) {
11616
12153
  return this.recordDecision(request, observation, true);
11617
12154
  }
11618
- recordDecision(request, nativeObservation, nativeRequired = false) {
12155
+ observeStandingDecision(request) {
12156
+ return this.recordDecision(request, undefined, true, true);
12157
+ }
12158
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
11619
12159
  const root = this.contextRootError();
11620
12160
  if (!root.ok)
11621
12161
  return root;
@@ -11630,8 +12170,10 @@ class WorkitCore {
11630
12170
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
11631
12171
  if (stated && !input.binding?.statedChoice)
11632
12172
  return failure("invalid_input", "stated choices require binding.statedChoice");
11633
- if (nativeRequired && nativeObservation === undefined && !stated)
12173
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
11634
12174
  return failure("permission_denied", "native decision observation is required");
12175
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
12176
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
11635
12177
  const task = this.store.readTask(input.taskId);
11636
12178
  if (!task.ok)
11637
12179
  return task;
@@ -11663,7 +12205,10 @@ class WorkitCore {
11663
12205
  consumption: null
11664
12206
  };
11665
12207
  const data = { ...base, digest: decisionDigest(base) };
11666
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
12208
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
12209
+ if (standingApproval && !standingApproval.ok)
12210
+ return standingApproval;
12211
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
11667
12212
  observation: nativeObservation,
11668
12213
  expected: {
11669
12214
  taskId: task.data.id,
@@ -11694,7 +12239,7 @@ class WorkitCore {
11694
12239
  const entry = {
11695
12240
  id: newId(),
11696
12241
  recordedAt: mutation.now,
11697
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
12242
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
11698
12243
  data
11699
12244
  };
11700
12245
  return success(mutation.revision, null, {
@@ -11758,7 +12303,7 @@ class WorkitCore {
11758
12303
  if (input.action === "record") {
11759
12304
  if (helper?.ok) {
11760
12305
  const assignment = helper.data.data.assignment;
11761
- 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))
12306
+ 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))
11762
12307
  return failure("permission_denied", "finding is outside the worker assignment");
11763
12308
  }
11764
12309
  if (input.candidateId && !task.data.candidates.some((candidate) => candidate.id === input.candidateId))
@@ -11805,7 +12350,11 @@ class WorkitCore {
11805
12350
  const evaluations = evaluateEvidence(task.data, current.data);
11806
12351
  const verified = evidence.some((entry) => {
11807
12352
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
11808
- return evaluation?.status === "passed" && (entry.data.kind === "check" || entry.data.kind === "review") && (finding.data.candidateId === null || entry.data.candidateId === finding.data.candidateId);
12353
+ return findingVerificationPasses(finding.data.candidateId, {
12354
+ kind: entry.data.kind,
12355
+ candidateId: entry.data.candidateId,
12356
+ status: evaluation?.status ?? "missing"
12357
+ });
11809
12358
  });
11810
12359
  if (!verified)
11811
12360
  return failure("permission_denied", "fixed findings require passing verification evidence");
@@ -11817,7 +12366,7 @@ class WorkitCore {
11817
12366
  if (entry.data.result === "missing" || entry.data.result === "skipped")
11818
12367
  return false;
11819
12368
  const candidateMatches = finding.data.candidateId === null ? entry.data.candidateId === null : entry.data.candidateId === finding.data.candidateId || entry.data.beforeCandidateId === finding.data.candidateId;
11820
- const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12369
+ const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => sameValue(left, right)));
11821
12370
  return candidateMatches && (referenceMatches || entry.data.claim === finding.data.claim);
11822
12371
  });
11823
12372
  if (!relevant)
@@ -11895,7 +12444,7 @@ class WorkitCore {
11895
12444
  const workerId = newId();
11896
12445
  const changed = this.store.mutateTaskAndWorkspace({
11897
12446
  taskId: task.data.id,
11898
- expectedTaskRevision: input.expectedRevision,
12447
+ expectedRevision: input.expectedRevision,
11899
12448
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
11900
12449
  now: trustedNow(this.context),
11901
12450
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -11933,7 +12482,7 @@ class WorkitCore {
11933
12482
  return failure("permission_denied", "worker report is outside the worker assignment");
11934
12483
  const changed = this.store.mutateTaskAndWorkspace({
11935
12484
  taskId: task.data.id,
11936
- expectedTaskRevision: input.expectedRevision,
12485
+ expectedRevision: input.expectedRevision,
11937
12486
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
11938
12487
  now: trustedNow(this.context),
11939
12488
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -11957,24 +12506,25 @@ class WorkitCore {
11957
12506
  return failure("invalid_transition", "closed task cannot cancel workers");
11958
12507
  if (entry.data.state === "unknown")
11959
12508
  return failure("recovery_required", "worker state requires recovery");
11960
- if (entry.data.state === "stopped")
11961
- return failure("invalid_transition", "stopped worker cannot be cancelled");
11962
- const settles = entry.data.report !== null || entry.data.state === "cancelling";
12509
+ if (entry.data.state === "stopped") {
12510
+ const stopped = task.data.workers.find((candidate) => candidate.id === input.workerId);
12511
+ return success(task.data.revision, workspace.data.revision, stopped);
12512
+ }
11963
12513
  const changed = this.store.mutateTaskAndWorkspace({
11964
12514
  taskId: task.data.id,
11965
- expectedTaskRevision: input.expectedRevision,
12515
+ expectedRevision: input.expectedRevision,
11966
12516
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
11967
12517
  now: trustedNow(this.context),
11968
- workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
12518
+ workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
12519
+ ...current,
12520
+ writer: current.writer?.owner.workerId === input.workerId ? null : current.writer
12521
+ }),
11969
12522
  task: (current, mutation) => success(mutation.revision, null, {
11970
12523
  ...current,
11971
12524
  workers: current.workers.map((candidate) => candidate.id === input.workerId ? {
11972
12525
  ...candidate,
11973
12526
  recordedAt: mutation.now,
11974
- data: {
11975
- ...candidate.data,
11976
- state: settles ? "stopped" : "cancelling"
11977
- }
12527
+ data: { ...candidate.data, state: "stopped" }
11978
12528
  } : candidate)
11979
12529
  })
11980
12530
  });
@@ -12071,7 +12621,7 @@ class WorkitCore {
12071
12621
  return attested;
12072
12622
  const changed = this.store.mutateTaskAndWorkspace({
12073
12623
  taskId: task.data.id,
12074
- expectedTaskRevision: input.expectedRevision,
12624
+ expectedRevision: input.expectedRevision,
12075
12625
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12076
12626
  now: trustedNow(this.context),
12077
12627
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12237,6 +12787,12 @@ class WorkitCore {
12237
12787
  if (!sameSession2(helper.data.data.session, this.context))
12238
12788
  return failure("permission_denied", "worker session does not match the caller");
12239
12789
  }
12790
+ const gate = this.view(task.data);
12791
+ if (gate.ok) {
12792
+ 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")));
12793
+ if (writeGates.length)
12794
+ 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) });
12795
+ }
12240
12796
  const owner = {
12241
12797
  taskId: task.data.id,
12242
12798
  workerId: helperId,
@@ -12248,7 +12804,7 @@ class WorkitCore {
12248
12804
  };
12249
12805
  const changed = this.store.mutateTaskAndWorkspace({
12250
12806
  taskId: task.data.id,
12251
- expectedTaskRevision: input.expectedRevision,
12807
+ expectedRevision: input.expectedRevision,
12252
12808
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12253
12809
  now: trustedNow(this.context),
12254
12810
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -12279,7 +12835,7 @@ class WorkitCore {
12279
12835
  }
12280
12836
  const changed = this.store.mutateTaskAndWorkspace({
12281
12837
  taskId: task.data.id,
12282
- expectedTaskRevision: input.expectedRevision,
12838
+ expectedRevision: input.expectedRevision,
12283
12839
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12284
12840
  now: trustedNow(this.context),
12285
12841
  workspace: (currentWorkspace, mutation) => success(mutation.revision, mutation.revision, { ...currentWorkspace, writer: null }),
@@ -12511,7 +13067,7 @@ class WorkitCore {
12511
13067
  if (view.task.workspaceId !== view.workspace.id)
12512
13068
  return failure("invalid_input", "task and workspace bindings are invalid");
12513
13069
  try {
12514
- if (realpathSync4(view.workspace.root) !== this.store.root)
13070
+ if (realpathSync5(view.workspace.root) !== this.store.root)
12515
13071
  return failure("invalid_input", "task view root does not match the task store");
12516
13072
  } catch {
12517
13073
  return failure("invalid_input", "task view root cannot be resolved");
@@ -12528,7 +13084,15 @@ class WorkitCore {
12528
13084
  if (seen.has(observation.workerId))
12529
13085
  return failure("invalid_input", "worker observations must be unique");
12530
13086
  seen.add(observation.workerId);
12531
- if (observation.taskId !== view.task.id || observation.expectedRevision !== view.task.revision || observation.expectedWorkspaceRevision !== view.workspace.revision)
13087
+ const freshTask = this.store.readTask(view.task.id);
13088
+ const freshWorkspace = this.store.readWorkspace();
13089
+ if (!freshTask.ok)
13090
+ return freshTask;
13091
+ if (!freshWorkspace.ok)
13092
+ return freshWorkspace;
13093
+ if (!freshWorkspace.data)
13094
+ return failure("not_found", "workspace not found");
13095
+ if (observation.taskId !== view.task.id || observation.expectedRevision !== freshTask.data.revision || observation.expectedWorkspaceRevision !== freshWorkspace.data.revision)
12532
13096
  return failure("revision_conflict", "worker observation is stale");
12533
13097
  if (!view.task.workers.some((entry) => entry.id === observation.workerId))
12534
13098
  return failure("permission_denied", "worker observation is not assigned");
@@ -12614,19 +13178,26 @@ class WorkitCore {
12614
13178
  return failure("permission_denied", "imported resume requires native destination approval");
12615
13179
  resumeCandidate = current.data.candidate;
12616
13180
  }
12617
- const blocker = this.activeWorkerBlocker(task.data, workspace.data);
13181
+ const blocker = status === "paused" ? workspace.data.writer ? failure("recovery_required", "writer ownership must be released first") : null : this.activeWorkerBlocker(task.data, workspace.data);
12618
13182
  if (blocker)
12619
13183
  return blocker;
13184
+ let pauseCandidate = null;
13185
+ if (status === "paused") {
13186
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
13187
+ if (!captured.ok)
13188
+ return captured;
13189
+ pauseCandidate = captured.data;
13190
+ }
12620
13191
  const changed = this.store.mutateTaskAndWorkspace({
12621
13192
  taskId: task.data.id,
12622
- expectedTaskRevision: input.expectedRevision,
13193
+ expectedRevision: input.expectedRevision,
12623
13194
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12624
13195
  now: trustedNow(this.context),
12625
13196
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
12626
13197
  task: (current, context) => success(context.revision, null, {
12627
13198
  ...current,
12628
13199
  status,
12629
- candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
13200
+ candidates: (resumeCandidate ?? pauseCandidate) && !current.candidates.some((candidate) => candidate.id === (resumeCandidate ?? pauseCandidate).id) ? [...current.candidates, resumeCandidate ?? pauseCandidate] : current.candidates,
12630
13201
  progress: current.progress,
12631
13202
  pauseReason: status === "paused" ? input.reason ?? null : null
12632
13203
  })
@@ -12664,12 +13235,13 @@ class WorkitCore {
12664
13235
  return workspace;
12665
13236
  if (!workspace.data)
12666
13237
  return failure("not_found", "workspace not found");
12667
- if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
12668
- return failure("recovery_required", "scope revision requires worker ownership reconciliation");
13238
+ const revisionBlocker = this.activeWorkerBlocker(task.data, workspace.data);
13239
+ if (revisionBlocker)
13240
+ return revisionBlocker;
12669
13241
  this.fillRevisions(input, task.data, workspace.data);
12670
13242
  const changed = this.store.mutateTaskAndWorkspace({
12671
13243
  taskId: task.data.id,
12672
- expectedTaskRevision: input.expectedRevision,
13244
+ expectedRevision: input.expectedRevision,
12673
13245
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12674
13246
  now: trustedNow(this.context),
12675
13247
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
@@ -12725,7 +13297,7 @@ class WorkitCore {
12725
13297
  return closure;
12726
13298
  const changed = this.store.mutateTaskAndWorkspace({
12727
13299
  taskId: task.data.id,
12728
- expectedTaskRevision: input.expectedRevision,
13300
+ expectedRevision: input.expectedRevision,
12729
13301
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12730
13302
  now: trustedNow(this.context),
12731
13303
  workspace: (value, context) => success(context.revision, context.revision, value),
@@ -12850,6 +13422,8 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
12850
13422
  if (task.data.status === "paused" && (input.state === "running" || input.state === "cancelling"))
12851
13423
  return failure("invalid_transition", "paused task cannot run a worker");
12852
13424
  const notStarted = input.dispatch === true;
13425
+ if (notStarted && entry.data.state === "stopped" && input.session === null)
13426
+ return success(task.data.revision, workspace.data.revision, entry);
12853
13427
  if (notStarted && (input.state !== "stopped" || input.session !== null))
12854
13428
  return failure("invalid_input", "a never-dispatched worker stops with no session");
12855
13429
  if (notStarted && entry.data.session !== null)
@@ -12881,7 +13455,7 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
12881
13455
  return success(task.data.revision, workspace.data.revision, entry);
12882
13456
  const changed = input.store.mutateTaskAndWorkspace({
12883
13457
  taskId: task.data.id,
12884
- expectedTaskRevision: input.expectedRevision,
13458
+ expectedRevision: input.expectedRevision,
12885
13459
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12886
13460
  now: input.now,
12887
13461
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -12920,90 +13494,22 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
12920
13494
  workerId: null,
12921
13495
  receipts: []
12922
13496
  }), portableTask = (task) => {
12923
- const clone = structuredClone(task);
12924
- const portableProvenance = (value) => ({
12925
- ...value,
12926
- session: null,
12927
- receipts: []
12928
- });
12929
- const entries = [
12930
- clone.intent,
12931
- ...clone.assessments,
12932
- ...clone.evidence,
12933
- ...clone.decisions,
12934
- ...clone.findings,
12935
- ...clone.workers
12936
- ];
12937
- for (const entry of entries)
12938
- entry.provenance = portableProvenance(entry.provenance);
12939
- for (const worker of clone.workers)
12940
- worker.data.session = null;
12941
- clone.intent = {
12942
- ...clone.intent,
12943
- data: { ...clone.intent.data, authorityRefs: portableRefs(clone.intent.data.authorityRefs) }
13497
+ const stripped = rewriteRecordRefs(task, taskRecordSchema, portableRef);
13498
+ if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
13499
+ throw new TypeError("portable task rewrite produced a non-record");
13500
+ const clone = structuredClone(stripped);
13501
+ const demote = (facts, signals, consequences) => {
13502
+ for (const fact of facts)
13503
+ Object.assign(fact, portableFact(fact));
13504
+ for (const name of Object.keys(signals)) {
13505
+ const signal = signals[name];
13506
+ Object.assign(signal, portableSignal(signal));
13507
+ }
13508
+ for (const consequence of consequences)
13509
+ Object.assign(consequence.fact, portableFact(consequence.fact));
12944
13510
  };
12945
- clone.constraints = clone.constraints.flatMap((constraint) => {
12946
- const source = portableRef(constraint.source);
12947
- return source ? [
12948
- {
12949
- ...constraint,
12950
- source,
12951
- requires: constraint.requires.map((requirement) => ({
12952
- ...requirement,
12953
- refs: portableRefs(requirement.refs)
12954
- }))
12955
- }
12956
- ] : [];
12957
- });
12958
- clone.assessments = clone.assessments.map((entry) => ({
12959
- ...entry,
12960
- data: {
12961
- ...entry.data,
12962
- facts: entry.data.facts.map(portableFact),
12963
- signals: Object.fromEntries(Object.entries(entry.data.signals).map(([name, signal]) => [name, portableSignal(signal)])),
12964
- consequences: entry.data.consequences.map((consequence) => ({
12965
- ...consequence,
12966
- fact: portableFact(consequence.fact)
12967
- })),
12968
- verification: entry.data.verification.map((verification) => ({
12969
- ...verification,
12970
- availableChecks: portableRefs(verification.availableChecks)
12971
- }))
12972
- }
12973
- }));
12974
- clone.progress = {
12975
- ...clone.progress,
12976
- blockers: clone.progress.blockers.map((blocker) => ({
12977
- ...blocker,
12978
- refs: portableRefs(blocker.refs)
12979
- }))
12980
- };
12981
- clone.evidence = clone.evidence.map((entry) => ({
12982
- ...entry,
12983
- data: {
12984
- ...entry.data,
12985
- refs: portableRefs(entry.data.refs),
12986
- reviewContext: portableRef(entry.data.reviewContext)
12987
- }
12988
- }));
12989
- clone.decisions = clone.decisions.map((entry) => ({
12990
- ...entry,
12991
- data: {
12992
- ...entry.data,
12993
- consumption: entry.data.consumption ? portableRef(entry.data.consumption.actionRef) ? {
12994
- ...entry.data.consumption,
12995
- actionRef: portableRef(entry.data.consumption.actionRef)
12996
- } : null : null,
12997
- binding: {
12998
- ...entry.data.binding,
12999
- contentRefs: portableRefs(entry.data.binding.contentRefs)
13000
- }
13001
- }
13002
- }));
13003
- clone.findings = clone.findings.map((entry) => ({
13004
- ...entry,
13005
- data: { ...entry.data, refs: portableRefs(entry.data.refs) }
13006
- }));
13511
+ for (const entry of clone.assessments)
13512
+ demote(entry.data.facts, entry.data.signals, entry.data.consequences);
13007
13513
  clone.candidates = [];
13008
13514
  return clone;
13009
13515
  }, importedTask = (source, bundle, destinationWorkspaceId, context, timestamp) => {
@@ -13027,15 +13533,13 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
13027
13533
  allocate(entry.id);
13028
13534
  const fresh = (id) => allocate(id);
13029
13535
  const provenance = importedProvenance(context);
13536
+ const remap = (ref) => mapRef(ref, ids);
13030
13537
  const intent = {
13031
13538
  ...source.intent,
13032
13539
  id: fresh(source.intent.id),
13033
13540
  recordedAt: timestamp,
13034
13541
  provenance,
13035
- data: {
13036
- ...source.intent.data,
13037
- authorityRefs: source.intent.data.authorityRefs.map((ref) => mapRef(ref, ids))
13038
- }
13542
+ data: rewriteRecordRefs(source.intent.data, intentSchema, remap)
13039
13543
  };
13040
13544
  const assessments = source.assessments.map((entry) => {
13041
13545
  const data = entry.data;
@@ -13044,28 +13548,7 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
13044
13548
  id: fresh(entry.id),
13045
13549
  recordedAt: timestamp,
13046
13550
  provenance,
13047
- data: {
13048
- ...data,
13049
- facts: data.facts.map((fact) => ({
13050
- ...fact,
13051
- refs: fact.refs.map((ref) => mapRef(ref, ids))
13052
- })),
13053
- signals: Object.fromEntries(Object.entries(data.signals).map(([name, signal]) => [
13054
- name,
13055
- { ...signal, refs: signal.refs.map((ref) => mapRef(ref, ids)) }
13056
- ])),
13057
- consequences: data.consequences.map((consequence) => ({
13058
- ...consequence,
13059
- fact: {
13060
- ...consequence.fact,
13061
- refs: consequence.fact.refs.map((ref) => mapRef(ref, ids))
13062
- }
13063
- })),
13064
- verification: data.verification.map((verification) => ({
13065
- ...verification,
13066
- availableChecks: verification.availableChecks.map((ref) => mapRef(ref, ids))
13067
- }))
13068
- }
13551
+ data: rewriteRecordRefs(data, assessmentSchema, remap)
13069
13552
  };
13070
13553
  });
13071
13554
  const evidence = source.evidence.map((entry) => ({
@@ -13073,11 +13556,7 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
13073
13556
  id: fresh(entry.id),
13074
13557
  recordedAt: timestamp,
13075
13558
  provenance,
13076
- data: {
13077
- ...entry.data,
13078
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
13079
- reviewContext: entry.data.reviewContext ? mapRef(entry.data.reviewContext, ids) : null
13080
- }
13559
+ data: rewriteRecordRefs(entry.data, evidenceSchema, remap)
13081
13560
  }));
13082
13561
  const decisions = source.decisions.map((entry) => ({
13083
13562
  ...entry,
@@ -13085,21 +13564,23 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
13085
13564
  recordedAt: timestamp,
13086
13565
  provenance
13087
13566
  }));
13088
- const findings = source.findings.map((entry) => ({
13089
- ...entry,
13090
- id: fresh(entry.id),
13091
- recordedAt: timestamp,
13092
- provenance,
13093
- data: {
13094
- ...entry.data,
13095
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
13096
- resolution: entry.data.resolution ? {
13097
- ...entry.data.resolution,
13098
- evidenceIds: entry.data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
13099
- decisionIds: entry.data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
13100
- } : null
13101
- }
13102
- }));
13567
+ const findings = source.findings.map((entry) => {
13568
+ const data = rewriteRecordRefs(entry.data, findingSchema, remap);
13569
+ return {
13570
+ ...entry,
13571
+ id: fresh(entry.id),
13572
+ recordedAt: timestamp,
13573
+ provenance,
13574
+ data: {
13575
+ ...data,
13576
+ resolution: data.resolution ? {
13577
+ ...data.resolution,
13578
+ evidenceIds: data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
13579
+ decisionIds: data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
13580
+ } : null
13581
+ }
13582
+ };
13583
+ });
13103
13584
  const workers = source.workers.map((entry) => ({
13104
13585
  ...entry,
13105
13586
  id: fresh(entry.id),
@@ -13142,19 +13623,21 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
13142
13623
  closure: null,
13143
13624
  assessments,
13144
13625
  evidence,
13145
- decisions: decisions.map((entry) => ({
13146
- ...entry,
13147
- data: {
13148
- ...entry.data,
13149
- binding: {
13150
- ...entry.data.binding,
13151
- taskId: "",
13152
- workspaceId: destinationWorkspaceId,
13153
- contentRefs: entry.data.binding.contentRefs.map((ref) => mapRef(ref, ids))
13154
- },
13155
- consumption: null
13156
- }
13157
- })),
13626
+ decisions: decisions.map((entry) => {
13627
+ const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
13628
+ return {
13629
+ ...entry,
13630
+ data: {
13631
+ ...data,
13632
+ binding: {
13633
+ ...data.binding,
13634
+ taskId: "",
13635
+ workspaceId: destinationWorkspaceId
13636
+ },
13637
+ consumption: null
13638
+ }
13639
+ };
13640
+ }),
13158
13641
  ...actionProgress ? { actionProgress } : {},
13159
13642
  findings,
13160
13643
  workers,
@@ -13182,6 +13665,7 @@ var init_task_engine = __esm(() => {
13182
13665
  init_task_contract();
13183
13666
  init_authority();
13184
13667
  init_policy_resolver();
13668
+ init_auto_approval();
13185
13669
  init_task_context();
13186
13670
  init_workers();
13187
13671
  init_task_evaluation();
@@ -13196,6 +13680,10 @@ var init_core3 = __esm(() => {
13196
13680
  init_task_context();
13197
13681
  init_methods();
13198
13682
  init_route_intent();
13683
+ init_branch();
13684
+ init_auto_approval();
13685
+ init_runtime_identity();
13686
+ init_authority();
13199
13687
  init_authority();
13200
13688
  init_external_action();
13201
13689
  init_external_action();
@@ -13205,8 +13693,8 @@ var init_core3 = __esm(() => {
13205
13693
  });
13206
13694
 
13207
13695
  // packages/workit-cursor/hooks/workit-hook.ts
13208
- import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
13209
- import path7 from "node:path";
13696
+ import { existsSync as existsSync5, realpathSync as realpathSync6 } from "node:fs";
13697
+ import path9 from "node:path";
13210
13698
  var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapabilities = (availability = {}) => {
13211
13699
  const has = (name) => availability[name] === true;
13212
13700
  return [
@@ -13260,14 +13748,14 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
13260
13748
  refs: [hostRef("sessionStart"), hostRef("preCompact")]
13261
13749
  }
13262
13750
  ];
13263
- }, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), eventNames, nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", parseCursorHookInput = (value) => {
13264
- if (!isRecord(value) || !eventNames.has(value.hook_event_name))
13751
+ }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), eventNames, nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", parseCursorHookInput = (value) => {
13752
+ if (!isRecord2(value) || !eventNames.has(value.hook_event_name))
13265
13753
  return { ok: false, error: "hook_event_name is required" };
13266
13754
  const roots = value.workspace_roots;
13267
13755
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
13268
13756
  return { ok: false, error: "exactly one workspace root is required" };
13269
13757
  const root = roots[0];
13270
- if (!path7.isAbsolute(root) || !existsSync4(root))
13758
+ if (!path9.isAbsolute(root) || !existsSync5(root))
13271
13759
  return { ok: false, error: "workspace root must be an existing absolute path" };
13272
13760
  const event = value.hook_event_name;
13273
13761
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
@@ -13399,10 +13887,10 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
13399
13887
  }, handleSubagentStop = () => ({}), handleCursorHook = (raw) => {
13400
13888
  const parsed = parseCursorHookInput(raw);
13401
13889
  if (!parsed.ok) {
13402
- return isRecord(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
13890
+ return isRecord2(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
13403
13891
  }
13404
13892
  const input = parsed.data;
13405
- const root = realpathSync5(input.workspace_roots[0]);
13893
+ const root = realpathSync6(input.workspace_roots[0]);
13406
13894
  if (input.hook_event_name === "sessionStart") {
13407
13895
  const actor = input.session_id ?? input.conversation_id;
13408
13896
  let compact = "";
@@ -13443,7 +13931,7 @@ ${invariantBootstrap()}${compact}
13443
13931
  const output = handleCursorHook(input);
13444
13932
  process.stdout.write(`${JSON.stringify(output)}
13445
13933
  `);
13446
- const event = isRecord(input) ? input.hook_event_name : undefined;
13934
+ const event = isRecord2(input) ? input.hook_event_name : undefined;
13447
13935
  if (output.permission === "deny" && ["preToolUse", "beforeShellExecution", "subagentStart"].includes(String(event)))
13448
13936
  process.exitCode = 2;
13449
13937
  } catch {