@brainervirus/workit-codex 1.0.12 → 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.
@@ -14199,7 +14199,13 @@ var refSchema = discriminatedUnion("kind", [
14199
14199
  id
14200
14200
  }).strict(),
14201
14201
  object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
14202
- object({ kind: literal("external"), url: text.url() }).strict()
14202
+ object({ kind: literal("external"), url: text.url() }).strict(),
14203
+ object({
14204
+ kind: literal("standing"),
14205
+ workspace: nonEmpty,
14206
+ class: nonEmpty,
14207
+ configDigest: nullableDigest
14208
+ }).strict()
14203
14209
  ]);
14204
14210
  var rewriteUnknown = (value, map) => {
14205
14211
  if (Array.isArray(value)) {
@@ -14504,7 +14510,8 @@ var decisionSchema = object({
14504
14510
  approvedContent: text,
14505
14511
  displayed: text.optional(),
14506
14512
  contentRefs: array(refSchema),
14507
- statedChoice: object({ ref: text, text }).strict().optional()
14513
+ statedChoice: object({ ref: text, text }).strict().optional(),
14514
+ standing: object({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
14508
14515
  }).strict(),
14509
14516
  digest,
14510
14517
  response: _enum(["approved", "rejected", "stated"]),
@@ -16580,1622 +16587,1784 @@ function assertProductWriteAllowed(input) {
16580
16587
  }
16581
16588
 
16582
16589
  // packages/workit-core/src/core/task-evaluation.ts
16590
+ import { createHash as createHash5 } from "node:crypto";
16591
+ import * as fs7 from "node:fs";
16592
+ import { spawnSync as spawnSync2 } from "node:child_process";
16593
+ import path9 from "node:path";
16594
+
16595
+ // packages/workit-core/src/core/authority.ts
16583
16596
  import { createHash as createHash4 } from "node:crypto";
16584
16597
  import * as fs6 from "node:fs";
16585
- import { spawnSync } from "node:child_process";
16586
- import path6 from "node:path";
16598
+ import path8 from "node:path";
16587
16599
 
16588
- // packages/workit-core/src/core/authority.ts
16600
+ // packages/workit-core/src/core/auto-approval.ts
16589
16601
  import { createHash as createHash3 } from "node:crypto";
16590
- import * as fs5 from "node:fs";
16602
+ import { readFileSync as readFileSync4 } from "node:fs";
16603
+
16604
+ // packages/workit-core/src/core/workspaces.ts
16605
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
16606
+ import path6 from "node:path";
16607
+
16608
+ // packages/workit-core/src/core/config.ts
16609
+ import {
16610
+ copyFileSync,
16611
+ cpSync,
16612
+ existsSync as existsSync3,
16613
+ mkdirSync as mkdirSync2,
16614
+ readFileSync as readFileSync2,
16615
+ readdirSync as readdirSync2,
16616
+ writeFileSync as writeFileSync2
16617
+ } from "node:fs";
16618
+ import os from "node:os";
16591
16619
  import path5 from "node:path";
16592
- var verifiedAuthorities = new WeakMap;
16593
- var trustedAuthority = (kind, provenance, expected, binding, caller) => {
16594
- const token = {};
16595
- verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
16596
- return token;
16620
+
16621
+ // packages/workit-core/src/core/boundary.ts
16622
+ import { statSync } from "node:fs";
16623
+ var EVENT = {
16624
+ initialization: "initialization",
16625
+ provenance: "provenance",
16626
+ assets: "assets",
16627
+ configurationSource: "configuration_source",
16628
+ hooks: "hooks",
16629
+ mcpConnection: "mcp_connection",
16630
+ migration: "migration",
16631
+ installSteps: "install_steps",
16632
+ uncaughtFailure: "uncaught_failure",
16633
+ toolsFailed: "tools_failed",
16634
+ doctor: "doctor"
16597
16635
  };
16598
- var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
16599
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16600
- var normalizeChainSteps = (steps) => {
16601
- if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
16602
- return null;
16603
- const normalized = [];
16604
- for (const step of steps) {
16605
- if (typeof step === "string") {
16606
- if (!step.trim())
16607
- return null;
16608
- normalized.push({ kind: "commit", message: step });
16609
- continue;
16610
- }
16611
- if (!isRecord(step))
16612
- return null;
16613
- if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
16614
- normalized.push({ kind: "branch", target: step.branch });
16615
- continue;
16616
- }
16617
- if (step.pr === true && Object.keys(step).length === 1) {
16618
- normalized.push({ kind: "pr" });
16636
+ var errorDetail = (err) => {
16637
+ if (err instanceof Error) {
16638
+ return { error_name: err.name, error: err.message };
16639
+ }
16640
+ return { error_name: "unknown", error: String(err) };
16641
+ };
16642
+ var markSourcesLoaded = (files, loadedAt = Date.now()) => ({
16643
+ loadedAt,
16644
+ files: [...files]
16645
+ });
16646
+ var changedSourcesSinceLoad = (marker) => {
16647
+ const changed = [];
16648
+ for (const file of marker.files) {
16649
+ try {
16650
+ if (statSync(file).mtimeMs > marker.loadedAt)
16651
+ changed.push(file);
16652
+ } catch {}
16653
+ }
16654
+ return changed;
16655
+ };
16656
+
16657
+ // packages/workit-core/src/core/config.ts
16658
+ var diagnosticLogger;
16659
+ var PRESETS = {
16660
+ gitflow: {
16661
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
16662
+ protected: ["main", "develop", "master", "prod", "production"]
16663
+ },
16664
+ "github-flow": { allowed: ["*"], protected: ["main"] },
16665
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
16666
+ custom: { allowed: [], protected: [] }
16667
+ };
16668
+ var mergePreset = (preset, input = {}, current = {
16669
+ branchPolicy: { preset, allowed: [], protected: [] }
16670
+ }) => {
16671
+ const defs = PRESETS[preset];
16672
+ return {
16673
+ preset,
16674
+ allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
16675
+ protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
16676
+ };
16677
+ };
16678
+ 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");
16679
+ var migratedDir = null;
16680
+ var migrationFailed = false;
16681
+ var ensureConfigDir = (dir = resolveConfigDir()) => {
16682
+ if (migratedDir === dir)
16683
+ return dir;
16684
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
16685
+ migratedDir = dir;
16686
+ return dir;
16687
+ }
16688
+ const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
16689
+ if (!existsSync3(legacy)) {
16690
+ migratedDir = dir;
16691
+ return dir;
16692
+ }
16693
+ if (!migrationFailed && existsSync3(dir)) {
16694
+ migratedDir = dir;
16695
+ return dir;
16696
+ }
16697
+ migrationFailed = false;
16698
+ mkdirSync2(dir, { recursive: true });
16699
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
16700
+ for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
16701
+ const src = path5.join(legacy, entry.name);
16702
+ const dest = path5.join(dir, entry.name);
16703
+ if (existsSync3(dest))
16619
16704
  continue;
16705
+ try {
16706
+ if (entry.isDirectory())
16707
+ cpSync(src, dest, { recursive: true });
16708
+ else if (entry.isFile())
16709
+ copyFileSync(src, dest);
16710
+ } catch (err) {
16711
+ migrationFailed = true;
16712
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
16713
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
16620
16714
  }
16621
- return null;
16622
16715
  }
16623
- const keys = normalized.map(chainStepKey);
16624
- if (new Set(keys).size !== keys.length)
16716
+ if (!migrationFailed)
16717
+ migratedDir = dir;
16718
+ return dir;
16719
+ };
16720
+ var configDir = () => ensureConfigDir();
16721
+ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
16722
+ var DEFAULTS = {
16723
+ locale: "en",
16724
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
16725
+ timezone: "America/Santiago",
16726
+ branchPolicy: {
16727
+ preset: "gitflow",
16728
+ allowed: [...PRESETS.gitflow.allowed],
16729
+ protected: [...PRESETS.gitflow.protected]
16730
+ },
16731
+ commitPolicy: { preset: "conventional" }
16732
+ };
16733
+ var readSafe = (p) => {
16734
+ try {
16735
+ return readFileSync2(p, "utf8");
16736
+ } catch {
16625
16737
  return null;
16626
- return normalized;
16738
+ }
16627
16739
  };
16628
- var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
16629
- var parseSteps = (content) => {
16630
- let value;
16740
+ var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16741
+ var parseConfigResult = (raw, file) => {
16742
+ if (raw === null)
16743
+ return { status: "missing", path: file };
16744
+ let parsed;
16631
16745
  try {
16632
- value = JSON.parse(content);
16746
+ parsed = JSON.parse(raw);
16633
16747
  } catch {
16634
- return [];
16748
+ return { status: "malformed", path: file, error: `${file} is not valid JSON` };
16635
16749
  }
16636
- if (typeof value !== "object" || value === null || !("steps" in value))
16637
- return [];
16638
- const steps = value.steps;
16639
- if (!Array.isArray(steps))
16640
- return null;
16641
- const normalized = normalizeChainSteps(steps);
16642
- return normalized;
16750
+ if (!isConfigObject(parsed)) {
16751
+ return { status: "malformed", path: file, error: `${file} is not a JSON object` };
16752
+ }
16753
+ const input = parsed;
16754
+ const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
16755
+ const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
16756
+ const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
16757
+ return {
16758
+ status: "valid",
16759
+ path: file,
16760
+ config: {
16761
+ locale,
16762
+ localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
16763
+ timezone: input.timezone ?? DEFAULTS.timezone,
16764
+ branchPolicy: mergePreset(preset, {
16765
+ allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
16766
+ protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
16767
+ }, DEFAULTS),
16768
+ commitPolicy: {
16769
+ preset: commitPreset,
16770
+ ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
16771
+ }
16772
+ }
16773
+ };
16643
16774
  };
16644
- var decisionMatches = (entry, purpose, binding) => {
16645
- const decision = entry.data;
16646
- 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);
16775
+ var readConfigTyped = (dir) => {
16776
+ const file = path5.join(dir ?? configDir(), "config.json");
16777
+ return parseConfigResult(readSafe(file), file);
16647
16778
  };
16648
- var sameRef = (left, right) => canonicalJson(left) === canonicalJson(right);
16649
- var validProvenance = (value, caller) => {
16650
- if (!provenanceSchema.safeParse(value).success)
16651
- return false;
16652
- const provenance = value;
16653
- return provenance.kind === "host_observed" && provenance.host === caller.host && provenance.session?.kind === "host" && provenance.session.host === caller.host && provenance.session.handle === caller.actor && provenance.receipts.some((receipt) => receipt.kind === "host" && receipt.host === caller.host);
16779
+ var readConfig = () => {
16780
+ const result = readConfigTyped();
16781
+ if (result.status === "malformed")
16782
+ throw new Error(result.error);
16783
+ return result.config ?? DEFAULTS;
16654
16784
  };
16655
- var verifyNativeDecision = (verifier, input, binding) => {
16656
- if (!verifier)
16657
- return failure("permission_denied", "native authority verifier is unavailable");
16658
- const result = verifier.verifyDecision(input);
16659
- if (!result.ok || !validProvenance(result.data, input.caller))
16660
- return failure("permission_denied", "native decision observation was not attested");
16661
- return success(null, null, trustedAuthority("decision", result.data, {
16662
- taskId: input.expected.taskId,
16663
- workspaceId: input.expected.workspaceId,
16664
- purpose: input.expected.purpose,
16665
- response: input.expected.response,
16666
- binding: input.expected.binding,
16667
- bindingBytes: input.expected.bindingBytes,
16668
- digest: input.expected.digest,
16669
- requirementIds: [...input.expected.requirementIds]
16670
- }, binding, input.caller));
16785
+ var resolveBranchPolicy = (config, workspace) => {
16786
+ const wp = workspace?.branchPolicy ?? {};
16787
+ const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
16788
+ const merged = mergePreset(preset, {
16789
+ allowed: wp.allowed,
16790
+ protectedNames: wp.protected
16791
+ }, config);
16792
+ const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
16793
+ return {
16794
+ preset,
16795
+ allowed,
16796
+ protected: new Set(merged.protected.map((p) => p.toLowerCase())),
16797
+ integration: wp.integration === "merge" ? "merge" : "pr",
16798
+ defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop"
16799
+ };
16671
16800
  };
16672
- var verifyNativeAction = (verifier, input, binding) => {
16673
- if (!verifier)
16674
- return failure("permission_denied", "native authority verifier is unavailable");
16675
- const result = verifier.verifyAction(input);
16676
- if (!result.ok || !validProvenance(result.data, input.caller))
16677
- return failure("permission_denied", "native action observation was not attested");
16678
- return success(null, null, trustedAuthority("action", result.data, {
16679
- taskId: input.expected.taskId,
16680
- workspaceId: input.expected.workspaceId,
16681
- decisionId: input.expected.decisionId,
16682
- actionRef: input.expected.actionRef,
16683
- taskRevision: input.expected.taskRevision,
16684
- workspaceRevision: input.expected.workspaceRevision,
16685
- outcome: input.expected.outcome,
16686
- purpose: input.expected.decision.purpose,
16687
- response: input.expected.decision.response,
16688
- binding: input.expected.decision.binding,
16689
- digest: input.expected.decision.digest,
16690
- requirementIds: [...input.expected.decision.requirementIds]
16691
- }, binding, input.caller));
16692
- };
16693
- var verifyNativeReconciliation = (verifier, input, binding) => {
16694
- if (!verifier?.verifyReconciliation)
16695
- return failure("permission_denied", "native reconciliation authority is unavailable");
16696
- const result = verifier.verifyReconciliation(input);
16697
- if (!result.ok || !validProvenance(result.data, input.caller))
16698
- return failure("permission_denied", "native reconciliation observation was not attested");
16699
- return success(null, null, trustedAuthority("reconciliation", result.data, {
16700
- taskId: input.expected.taskId,
16701
- workspaceId: input.expected.workspaceId,
16702
- decisionId: input.expected.decisionId,
16703
- actionRef: input.expected.actionRef,
16704
- taskRevision: input.expected.taskRevision,
16705
- workspaceRevision: input.expected.workspaceRevision,
16706
- outcome: input.expected.outcome,
16707
- evidenceDigest: input.expected.evidenceDigest,
16708
- step: input.expected.step,
16709
- purpose: input.expected.decision.purpose,
16710
- response: input.expected.decision.response,
16711
- binding: input.expected.decision.binding,
16712
- digest: input.expected.decision.digest,
16713
- requirementIds: [...input.expected.decision.requirementIds]
16714
- }, binding, input.caller));
16715
- };
16716
- var takeAuthority = (authority, binding, caller) => {
16717
- const record = verifiedAuthorities.get(authority);
16718
- verifiedAuthorities.delete(authority);
16719
- if (!record)
16720
- return null;
16721
- if (record.owner !== binding.owner || record.store !== binding.store || record.root !== binding.root || record.root !== record.store.root || canonicalJson(record.caller) !== canonicalJson(caller))
16722
- return null;
16723
- return record;
16801
+ var COMMIT_PRESETS = [
16802
+ "conventional",
16803
+ "gitmoji",
16804
+ "ticket-prefix",
16805
+ "freeform",
16806
+ "custom",
16807
+ "auto"
16808
+ ];
16809
+
16810
+ // packages/workit-core/src/core/workspaces.ts
16811
+ var readWorkspacesResult = (dir = configDir()) => {
16812
+ const file = path6.join(dir, "workspaces.json");
16813
+ let raw;
16814
+ try {
16815
+ raw = readFileSync3(file, "utf8");
16816
+ } catch {
16817
+ return { status: "missing", path: file, entries: [] };
16818
+ }
16819
+ let parsed;
16820
+ try {
16821
+ parsed = JSON.parse(raw);
16822
+ } catch {
16823
+ return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
16824
+ }
16825
+ if (!isConfigObject(parsed)) {
16826
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
16827
+ }
16828
+ const list = parsed.workspaces;
16829
+ return {
16830
+ status: "valid",
16831
+ path: file,
16832
+ entries: Array.isArray(list) ? list : []
16833
+ };
16724
16834
  };
16725
- var retireNativeAuthority = (authority) => {
16726
- const record = verifiedAuthorities.get(authority);
16727
- verifiedAuthorities.delete(authority);
16728
- return record?.provenance ?? null;
16835
+ var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
16836
+ var globToRegExp = (glob) => {
16837
+ let out = "";
16838
+ for (let i = 0;i < glob.length; i++) {
16839
+ const c = glob[i];
16840
+ if (c === "*") {
16841
+ if (glob[i + 1] === "*") {
16842
+ if (glob[i + 2] === "/") {
16843
+ if (out === "")
16844
+ out += "/?";
16845
+ out += "(?:[^/]+/)*";
16846
+ i += 2;
16847
+ } else {
16848
+ if (i + 2 >= glob.length) {
16849
+ if (out.endsWith("/")) {
16850
+ out = out.slice(0, -1);
16851
+ out += "(?:/.*)?";
16852
+ } else {
16853
+ out += ".*";
16854
+ }
16855
+ } else {
16856
+ out += ".*";
16857
+ }
16858
+ i++;
16859
+ }
16860
+ } else {
16861
+ out += "[^/]*";
16862
+ }
16863
+ } else {
16864
+ out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
16865
+ }
16866
+ }
16867
+ return new RegExp(`^${out}$`);
16729
16868
  };
16730
- var 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;
16731
- var 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);
16732
- var provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session);
16733
- function applicableDecision(task, purpose, binding, checkoutRoot) {
16734
- if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
16735
- return [];
16736
- 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);
16737
- }
16738
- var digestBytes2 = (bytes) => createHash3("sha256").update(bytes).digest("hex");
16739
- var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
16740
- let root;
16869
+ var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
16870
+ var realpathOf = (p) => {
16871
+ const real = realpathSync2.native ?? realpathSync2;
16741
16872
  try {
16742
- root = fs5.realpathSync(checkoutRoot);
16873
+ return real(p);
16743
16874
  } catch {
16744
- return failure("permission_denied", "checkout root is unavailable");
16875
+ return p;
16745
16876
  }
16746
- for (const reference of binding.contentRefs) {
16747
- if (reference.kind !== "file")
16877
+ };
16878
+ var canonicalGlob = (glob) => {
16879
+ const m = /[*?[\]{]/.exec(glob);
16880
+ const prefix = m ? glob.slice(0, m.index) : glob;
16881
+ const rest = m ? glob.slice(m.index) : "";
16882
+ if (!prefix)
16883
+ return glob;
16884
+ const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
16885
+ return real + (prefix.endsWith("/") ? "/" : "") + rest;
16886
+ };
16887
+ var resolveWorkspaceFrom = (cwd, dir) => {
16888
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
16889
+ for (const entry of loadWorkspacesFrom(dir)) {
16890
+ if (!entry || typeof entry !== "object")
16748
16891
  continue;
16749
- if (!reference.digest)
16750
- return failure("invalid_input", "document references require a byte digest");
16751
- const target = path5.resolve(root, reference.path);
16752
- if (target !== root && !target.startsWith(`${root}${path5.sep}`))
16753
- return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16754
- fields: [
16755
- { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
16756
- ]
16757
- });
16758
- try {
16759
- const relative = path5.relative(root, target);
16760
- let current = root;
16761
- for (const segment of relative.split(path5.sep)) {
16762
- if (!segment)
16763
- continue;
16764
- current = path5.join(current, segment);
16765
- const stat = fs5.lstatSync(current);
16766
- const real = fs5.realpathSync(current);
16767
- if (real !== root && !real.startsWith(`${root}${path5.sep}`))
16768
- return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16769
- fields: [
16770
- {
16771
- path: "contentRefs.path",
16772
- reason: `${reference.path} resolves outside the checkout`
16773
- }
16774
- ]
16775
- });
16776
- if (stat.isSymbolicLink())
16777
- return failure("invalid_input", "document reference uses a symlink");
16778
- if (current === target && !stat.isFile())
16779
- return failure("invalid_input", "document reference is not a regular file");
16780
- if (current !== target && !stat.isDirectory())
16781
- return failure("invalid_input", "document reference ancestor is not a directory");
16782
- }
16783
- if (digestBytes2(fs5.readFileSync(target)) !== reference.digest)
16784
- return failure("permission_denied", "approved document bytes have changed");
16785
- } catch {
16786
- return failure("invalid_input", "approved document is unavailable");
16892
+ const ws = entry;
16893
+ if (typeof ws.glob !== "string" || !ws.glob)
16894
+ continue;
16895
+ const glob = ws.glob.replaceAll("\\", "/");
16896
+ const canonical = canonicalGlob(glob);
16897
+ for (const target of targets) {
16898
+ if (matchWorkspace(glob, target))
16899
+ return ws;
16900
+ if (canonical !== glob && matchWorkspace(canonical, target))
16901
+ return ws;
16787
16902
  }
16788
16903
  }
16789
- return success(null, null, null);
16904
+ return null;
16790
16905
  };
16791
- var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
16792
- var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
16793
- var validateAction = (store, task, workspaceId, input, authority) => {
16794
- if (task.status !== "active")
16795
- return failure("invalid_transition", "only active tasks can authorize actions");
16796
- if (!refSchema.safeParse(input.actionRef).success)
16797
- return failure("invalid_input", "action reference is invalid");
16798
- const entry = task.decisions.find((candidate) => candidate.id === input.decisionId);
16799
- if (!entry)
16800
- return failure("not_found", "decision not found");
16801
- const decision = entry.data;
16802
- if (decision.purpose !== "action")
16803
- return failure("permission_denied", "decision is not an action approval");
16804
- if (!authorityMatches(authority, {
16805
- taskId: task.id,
16806
- workspaceId,
16807
- decisionId: input.decisionId,
16808
- actionRef: input.actionRef,
16809
- taskRevision: input.expectedRevision,
16810
- workspaceRevision: input.expectedWorkspaceRevision,
16811
- outcome: "reserve"
16812
- }))
16813
- return failure("permission_denied", "native reservation authority is not bound");
16814
- if (!authorityDecisionMatches(authority, decision))
16815
- return failure("permission_denied", "native reservation authority does not match decision");
16816
- if (decision.response !== "approved")
16817
- return failure("permission_denied", "decision was rejected");
16818
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
16819
- return failure("permission_denied", "action approval lacks native receipt assurance");
16820
- if (decision.revoked)
16821
- return failure("permission_denied", "decision is revoked");
16822
- if (decision.digest !== decisionDigest(decision))
16823
- return failure("permission_denied", "decision binding is invalid");
16824
- if (decision.binding.taskId !== task.id || decision.binding.workspaceId !== workspaceId)
16825
- return failure("permission_denied", "decision task or workspace binding is invalid");
16826
- if (input.binding && canonicalJson(input.binding) !== canonicalJson(decision.binding))
16827
- return failure("permission_denied", "action binding does not match the approved decision");
16828
- const content = verifyContentRefs(store, decision.binding);
16829
- if (!content.ok)
16830
- return content;
16831
- const requestedSteps = parseSteps(decision.binding.approvedContent);
16832
- if (requestedSteps === null)
16833
- return failure("invalid_input", "bounded action steps must be a unique non-empty array");
16834
- if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
16835
- return failure("permission_denied", "bounded action steps do not match approved content");
16836
- const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
16837
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
16838
- if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
16839
- return failure("permission_denied", "bounded action scope changed");
16840
- return success(null, null, { entry, workflow });
16906
+ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
16907
+
16908
+ // packages/workit-core/src/core/branch.ts
16909
+ import { execFileSync as execFileSync2 } from "node:child_process";
16910
+
16911
+ // packages/workit-core/src/core/git.ts
16912
+ import { execFileSync } from "node:child_process";
16913
+ var run = (cwd, args) => {
16914
+ try {
16915
+ const stdout = execFileSync("git", args, {
16916
+ cwd,
16917
+ encoding: "utf8",
16918
+ stdio: ["pipe", "pipe", "pipe"]
16919
+ });
16920
+ return { stdout: stdout.trimEnd(), stderr: "", exitCode: 0 };
16921
+ } catch (error) {
16922
+ const e = error;
16923
+ return {
16924
+ stdout: (e.stdout ?? "").trimEnd(),
16925
+ stderr: (e.stderr ?? "").trimEnd(),
16926
+ exitCode: e.status ?? 1
16927
+ };
16928
+ }
16841
16929
  };
16842
- function reserveAction(input) {
16843
- if (!input.store || !input.expectedRevision || !input.expectedWorkspaceRevision)
16844
- return failure("invalid_input", "action reservation preconditions are required");
16845
- const authority = takeAuthority(input.authority, {
16846
- owner: input.authorityOwner,
16847
- store: input.store,
16848
- root: input.store.root
16849
- }, input.authorityCaller);
16850
- if (!authority)
16851
- return failure("permission_denied", "native reservation authority is not verified");
16852
- const changed = input.store.mutateTask(input.taskId, input.expectedRevision, (current, mutation) => {
16853
- const workspace = input.store.readWorkspace();
16854
- if (!workspace.ok)
16855
- return workspace;
16856
- if (!workspace.data)
16857
- return failure("not_found", "workspace not found");
16858
- if (workspace.data.revision !== input.expectedWorkspaceRevision)
16859
- return failure("revision_conflict", "workspace revision does not match", {
16860
- expectedWorkspaceRevision: input.expectedWorkspaceRevision,
16861
- actualWorkspaceRevision: workspace.data.revision
16862
- });
16863
- const valid = validateAction(input.store, current, workspace.data.id, input, authority);
16864
- if (!valid.ok)
16865
- return valid;
16866
- const { entry, workflow } = valid.data;
16867
- const existing = entry.data.consumption;
16868
- if (existing?.state === "uncertain")
16869
- return failure("external_outcome_unknown", "previous action outcome is unknown", {
16870
- operation: "reserve_action",
16871
- outcome: "unknown"
16872
- });
16873
- if (existing?.state === "consumed")
16874
- return failure("permission_denied", "action approval is consumed");
16875
- if (existing?.state === "reserved" && workflow.completed.length === 0)
16876
- return failure("permission_denied", "action approval is already reserved");
16877
- const requestedStep = input.step ?? workflow.steps[workflow.completed.length];
16878
- if (workflow.steps.length && (!requestedStep || requestedStep !== workflow.steps[workflow.completed.length]))
16879
- return failure("permission_denied", "action step is outside the approved remaining workflow");
16880
- if (requestedStep && workflow.completed.includes(requestedStep))
16881
- return failure("permission_denied", "action step was already completed");
16882
- const nextDecision = {
16883
- ...entry.data,
16884
- consumption: { state: "reserved", at: mutation.now, actionRef: input.actionRef }
16885
- };
16886
- const actionProgress = workflow.steps.length ? [
16887
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
16888
- {
16889
- decisionId: input.decisionId,
16890
- steps: [...workflow.steps],
16891
- completedSteps: [...workflow.completed]
16892
- }
16893
- ] : current.actionProgress ?? [];
16894
- return success(mutation.revision, null, {
16895
- ...current,
16896
- actionProgress,
16897
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
16898
- });
16899
- }, input.native?.now);
16900
- if (!changed.ok)
16901
- return changed;
16902
- const workspace = input.store.readWorkspace();
16903
- if (!workspace.ok || !workspace.data)
16904
- return failure("recovery_required", "workspace disappeared after reservation");
16905
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
16906
- if (!entry)
16907
- return failure("recovery_required", "reserved decision disappeared");
16908
- const workflow = changed.data.actionProgress?.find((progress) => progress.decisionId === input.decisionId) ?? {
16909
- steps: [],
16910
- completedSteps: []
16911
- };
16912
- return success(changed.data.revision, workspace.data.revision, {
16913
- taskId: input.taskId,
16914
- decisionId: input.decisionId,
16915
- actionRef: input.actionRef,
16916
- taskRevision: changed.data.revision,
16917
- workspaceRevision: workspace.data.revision,
16918
- completedSteps: [...workflow.completedSteps],
16919
- remainingSteps: workflow.steps.slice(workflow.completedSteps.length)
16920
- });
16921
- }
16922
- function settleAction(input) {
16923
- if (!input.store || !input.taskRevision || !input.workspaceRevision)
16924
- return failure("invalid_input", "action settlement preconditions are required");
16925
- const authority = takeAuthority(input.authority, {
16926
- owner: input.authorityOwner,
16927
- store: input.store,
16928
- root: input.store.root
16929
- }, input.authorityCaller);
16930
- if (!authority)
16931
- return failure("permission_denied", "native settlement authority is not verified");
16932
- if (!refSchema.safeParse(input.actionRef).success)
16933
- return failure("invalid_input", "action reference is invalid");
16934
- if (!authorityMatches(authority, {
16935
- taskId: input.taskId,
16936
- workspaceId: authority.workspaceId,
16937
- decisionId: input.decisionId,
16938
- actionRef: input.actionRef,
16939
- taskRevision: input.taskRevision,
16940
- workspaceRevision: input.workspaceRevision,
16941
- outcome: input.outcome
16942
- }))
16943
- return failure("permission_denied", "native settlement authority is not verified");
16944
- let outcomeResult = null;
16945
- const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
16946
- const workspace = input.store.readWorkspace();
16947
- if (!workspace.ok)
16948
- return workspace;
16949
- if (!workspace.data)
16950
- return failure("not_found", "workspace not found");
16951
- if (!authorityMatches(authority, {
16952
- taskId: input.taskId,
16953
- workspaceId: workspace.data.id,
16954
- decisionId: input.decisionId,
16955
- actionRef: input.actionRef,
16956
- taskRevision: input.taskRevision,
16957
- workspaceRevision: input.workspaceRevision,
16958
- outcome: input.outcome
16959
- }))
16960
- return failure("permission_denied", "native settlement authority does not match context");
16961
- if (workspace.data.revision !== input.workspaceRevision)
16962
- return failure("revision_conflict", "workspace revision does not match", {
16963
- expectedWorkspaceRevision: input.workspaceRevision,
16964
- actualWorkspaceRevision: workspace.data.revision
16965
- });
16966
- const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
16967
- if (!entry)
16968
- return failure("not_found", "decision not found");
16969
- if (!authorityDecisionMatches(authority, entry.data))
16970
- return failure("permission_denied", "native settlement authority does not match decision");
16971
- if (!provenanceMatches(entry.provenance, authority.provenance))
16972
- return failure("permission_denied", "native settlement caller does not match approval");
16973
- if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
16974
- return failure("permission_denied", "decision binding is invalid");
16975
- if (entry.data.consumption?.state === "uncertain")
16976
- return failure("external_outcome_unknown", "previous action outcome is unknown", {
16977
- operation: "settle_action",
16978
- outcome: "unknown"
16979
- });
16980
- if (entry.data.consumption?.state !== "reserved")
16981
- return failure("permission_denied", "action is not reserved");
16982
- if (canonicalJson(entry.data.consumption.actionRef) !== canonicalJson(input.actionRef))
16983
- return failure("permission_denied", "settlement reference does not match reservation");
16984
- const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
16985
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: [], completed: [] };
16986
- if (entry.data.binding.approvedContent && !stored && (parseSteps(entry.data.binding.approvedContent) ?? []).length)
16987
- return failure("recovery_required", "bounded action progress is missing");
16988
- const currentStep = input.step ?? workflow.steps[workflow.completed.length];
16989
- if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
16990
- return failure("permission_denied", "settlement step is outside the approved workflow");
16991
- if (input.outcome === "unknown") {
16992
- outcomeResult = failure("external_outcome_unknown", "external action outcome is unknown", {
16993
- operation: "settle_action",
16994
- outcome: "unknown"
16995
- });
16996
- const uncertain = {
16997
- ...entry.data,
16998
- consumption: { ...entry.data.consumption, state: "uncertain", at: mutation.now }
16999
- };
17000
- return success(mutation.revision, null, {
17001
- ...current,
17002
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: uncertain } : candidate)
17003
- });
16930
+ var gitContext = (workspaceRoot, paths = []) => {
16931
+ const cwd = workspaceRoot;
16932
+ let failure;
16933
+ const runHere = (args) => {
16934
+ const result = run(cwd, args);
16935
+ if (!failure && result.exitCode !== 0) {
16936
+ failure = { stderr: result.stderr, exitCode: result.exitCode };
17004
16937
  }
17005
- if (input.outcome === "not_started") {
17006
- const released = { ...entry.data, consumption: null };
17007
- return success(mutation.revision, null, {
17008
- ...current,
17009
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: released } : candidate)
17010
- });
16938
+ return result.stdout;
16939
+ };
16940
+ const branch = runHere(["rev-parse", "--abbrev-ref", "HEAD"]) || "unknown";
16941
+ const status_short = runHere(["status", "--porcelain"]);
16942
+ const staged = [];
16943
+ const unstaged = [];
16944
+ const untracked = [];
16945
+ for (const line of status_short.split(`
16946
+ `).filter(Boolean)) {
16947
+ const code = line.slice(0, 2);
16948
+ const file = line.slice(3);
16949
+ if (code === "??") {
16950
+ untracked.push(file);
16951
+ } else {
16952
+ if (code[0] !== " " && code[0] !== "?")
16953
+ staged.push(file);
16954
+ if (code[1] !== " " && code[1] !== "?")
16955
+ unstaged.push(file);
17011
16956
  }
17012
- if (currentStep)
17013
- workflow.completed.push(currentStep);
17014
- const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
17015
- const settled = {
17016
- ...entry.data,
17017
- consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
16957
+ }
16958
+ const pathArgs = paths.length ? ["--", ...paths] : [];
16959
+ const diff_stat = runHere(["diff", "--stat", ...pathArgs]);
16960
+ const cached_stat = runHere(["diff", "--cached", "--stat", ...pathArgs]);
16961
+ const stagedSet = new Set(staged);
16962
+ const unstagedSet = new Set(unstaged);
16963
+ const partial_staged = [...stagedSet].filter((f) => unstagedSet.has(f));
16964
+ return {
16965
+ workspace_root: cwd,
16966
+ branch,
16967
+ status_short,
16968
+ staged,
16969
+ unstaged,
16970
+ untracked,
16971
+ diff_stat: [diff_stat, cached_stat].filter(Boolean).join(`
16972
+ `),
16973
+ partial_staged: partial_staged.length > 0,
16974
+ partial_staged_files: partial_staged,
16975
+ ...failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}
16976
+ };
16977
+ };
16978
+
16979
+ // packages/workit-core/src/core/vcs-config.ts
16980
+ import fs5 from "node:fs";
16981
+ import path7 from "node:path";
16982
+ import { spawnSync } from "node:child_process";
16983
+ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
16984
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
16985
+ var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
16986
+ var remoteProvider = (cwd) => {
16987
+ const r = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16988
+ if (r.status !== 0)
16989
+ return null;
16990
+ const url = (r.stdout ?? "").trim();
16991
+ if (/github\.com[:/]/.test(url))
16992
+ return "github";
16993
+ if (/gitlab\.com[:/]/.test(url))
16994
+ return "gitlab";
16995
+ return null;
16996
+ };
16997
+ var readVcsConfig = () => {
16998
+ const file = vcsConfigPath();
16999
+ let raw;
17000
+ try {
17001
+ raw = fs5.readFileSync(file, "utf8");
17002
+ } catch {
17003
+ return { status: "missing", path: file, config: {} };
17004
+ }
17005
+ let parsed;
17006
+ try {
17007
+ parsed = JSON.parse(raw);
17008
+ } catch {
17009
+ return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
17010
+ }
17011
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
17012
+ return { status: "valid", path: file, config: parsed };
17013
+ }
17014
+ return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
17015
+ };
17016
+ function vcsConfig(mode, cwd) {
17017
+ const ws = resolveWorkspace(vcsCwd(cwd));
17018
+ const wsVcs = ws?.vcs ?? {};
17019
+ const wsYt = ws?.youtrack ?? {};
17020
+ const wsIssues = ws?.issues ?? {};
17021
+ const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
17022
+ const root = vcsCwd(cwd);
17023
+ const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
17024
+ const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
17025
+ const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
17026
+ const wp = ws?.branchPolicy ?? {};
17027
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
17028
+ const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
17029
+ const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
17030
+ const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
17031
+ const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
17032
+ let issuesProvider = null;
17033
+ let linkOnPr = null;
17034
+ if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
17035
+ issuesProvider = "github";
17036
+ linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
17037
+ }
17038
+ if (mode === "resolve") {
17039
+ if (cfgStatus === "malformed")
17040
+ return { ok: false, error: cfgError, configPath: cfgPath };
17041
+ return {
17042
+ ok: true,
17043
+ workspace_name: ws?.name ?? null,
17044
+ provider,
17045
+ defaultTargetBranch: defaultTarget,
17046
+ link_issues: linkIssues,
17047
+ youtrack_base_url: youtrackBaseUrl,
17048
+ issues_provider: issuesProvider,
17049
+ link_on_pr: linkOnPr
17018
17050
  };
17019
- const actionProgress = workflow.steps.length ? [
17020
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
17021
- {
17022
- decisionId: input.decisionId,
17023
- steps: [...workflow.steps],
17024
- completedSteps: [...workflow.completed]
17025
- }
17026
- ] : current.actionProgress ?? [];
17027
- const next = {
17028
- ...current,
17029
- actionProgress,
17030
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: settled } : candidate)
17051
+ }
17052
+ if (cfgStatus === "malformed") {
17053
+ return { ok: false, error: cfgError, configPath: cfgPath };
17054
+ }
17055
+ if (cfgStatus === "missing") {
17056
+ return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
17057
+ }
17058
+ if (provider === null) {
17059
+ return {
17060
+ ok: false,
17061
+ error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
17062
+ configPath: cfgPath
17031
17063
  };
17032
- if (!complete) {
17033
- outcomeResult = success(mutation.revision, input.workspaceRevision, {
17034
- ...entry,
17035
- data: settled
17064
+ }
17065
+ const prov = cfg[provider] ?? {};
17066
+ const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
17067
+ const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
17068
+ const tokenPath = path7.resolve(tokenFile);
17069
+ let tokenOk = false;
17070
+ if (fs5.existsSync(tokenPath)) {
17071
+ const token = fs5.readFileSync(tokenPath, "utf8").trim();
17072
+ const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
17073
+ tokenOk = !placeholder;
17074
+ }
17075
+ const out = {
17076
+ ok: true,
17077
+ configPath: path7.resolve(cfgPath),
17078
+ provider,
17079
+ defaultTargetBranch: defaultTarget,
17080
+ pr: cfg.pr ?? {},
17081
+ tokenPath,
17082
+ tokenPresent: fs5.existsSync(tokenPath),
17083
+ tokenReady: tokenOk,
17084
+ workspace_name: ws?.name ?? null,
17085
+ link_issues: linkIssues,
17086
+ youtrack_base_url: youtrackBaseUrl,
17087
+ issues_provider: issuesProvider,
17088
+ link_on_pr: linkOnPr
17089
+ };
17090
+ if (provider === "gitlab") {
17091
+ out.gitlab = {
17092
+ host: prov.host ?? "gitlab.com",
17093
+ apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
17094
+ };
17095
+ } else if (provider === "github") {
17096
+ out.github = { host: prov.host ?? "github.com" };
17097
+ }
17098
+ if (mode === "summary")
17099
+ delete out.tokenPath;
17100
+ return out;
17101
+ }
17102
+ function mergedPrStyle(limit = 6, cwd) {
17103
+ const cfg = vcsConfig("load", cwd);
17104
+ if (!cfg.ok || !cfg.tokenReady)
17105
+ return { ok: false, error: "vcs not configured" };
17106
+ const provider = cfg.provider;
17107
+ const token = fs5.readFileSync(cfg.tokenPath, "utf8").trim();
17108
+ const examples = [];
17109
+ const descInfo = (desc, caseInsensitiveNotes = false) => ({
17110
+ hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
17111
+ sections: desc.split(`
17112
+ `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
17113
+ descriptionPreview: desc.slice(0, 600)
17114
+ });
17115
+ if (provider === "gitlab") {
17116
+ const remote = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
17117
+ if (remote.status !== 0)
17118
+ return { ok: false, error: "no origin remote" };
17119
+ const url = (remote.stdout ?? "").trim();
17120
+ const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
17121
+ if (!m)
17122
+ return { ok: false, error: "not a gitlab.com origin" };
17123
+ const project = m[1];
17124
+ const env = { ...process.env, GITLAB_TOKEN: token };
17125
+ const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
17126
+ let r = run([
17127
+ `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
17128
+ ]);
17129
+ if (r.status !== 0) {
17130
+ r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
17131
+ }
17132
+ if (r.status !== 0)
17133
+ return { ok: false, error: "could not list merge requests" };
17134
+ for (const mr of JSON.parse(r.stdout ?? "[]")) {
17135
+ const desc = String(mr.description ?? "").trim();
17136
+ examples.push({
17137
+ title: mr.title,
17138
+ url: mr.web_url,
17139
+ squash: mr.squash,
17140
+ ...descInfo(desc, true)
17036
17141
  });
17037
17142
  }
17038
- return success(mutation.revision, null, next);
17039
- }, input.native?.now);
17040
- if (!changed.ok)
17041
- return changed;
17042
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
17043
- if (!entry)
17044
- return failure("recovery_required", "settled decision disappeared");
17045
- if (outcomeResult)
17046
- return outcomeResult;
17047
- return success(changed.data.revision, input.workspaceRevision, entry);
17048
- }
17049
- function reconcileAction(input) {
17050
- if (!input.store || !input.taskRevision || !input.workspaceRevision || !input.evidenceDigest)
17051
- return failure("invalid_input", "action reconciliation preconditions are required");
17052
- const authority = takeAuthority(input.authority, { owner: input.authorityOwner, store: input.store, root: input.store.root }, input.authorityCaller);
17053
- if (!authority || authority.kind !== "reconciliation")
17054
- return failure("permission_denied", "native reconciliation authority is not verified");
17055
- if (authority.taskId !== input.taskId || authority.decisionId !== input.decisionId || !sameRef(authority.actionRef, input.actionRef) || authority.taskRevision !== input.taskRevision || authority.workspaceRevision !== input.workspaceRevision || authority.outcome !== input.outcome || authority.evidenceDigest !== input.evidenceDigest || authority.step !== input.step)
17056
- return failure("permission_denied", "native reconciliation authority is not bound");
17057
- const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
17058
- const workspace = input.store.readWorkspace();
17059
- if (!workspace.ok)
17060
- return workspace;
17061
- if (!workspace.data)
17062
- return failure("not_found", "workspace not found");
17063
- if (authority.workspaceId !== workspace.data.id)
17064
- return failure("permission_denied", "native reconciliation workspace does not match");
17065
- if (workspace.data.revision !== input.workspaceRevision)
17066
- return failure("revision_conflict", "workspace revision does not match");
17067
- const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
17068
- if (!entry || entry.data.purpose !== "action")
17069
- return failure("not_found", "decision not found");
17070
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
17071
- return failure("permission_denied", "native reconciliation caller does not match approval");
17072
- if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
17073
- return failure("permission_denied", "reconciliation decision binding is invalid");
17074
- if (entry.data.consumption?.state !== "uncertain")
17075
- return failure("external_outcome_unknown", "only an uncertain action can be reconciled");
17076
- if (!sameRef(entry.data.consumption.actionRef, input.actionRef))
17077
- return failure("permission_denied", "reconciliation reference does not match reservation");
17078
- const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
17079
- const workflow = stored ? { steps: [...stored.steps], completed: [...stored.completedSteps] } : { steps: [], completed: [] };
17080
- const currentStep = input.step ?? workflow.steps[workflow.completed.length];
17081
- if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
17082
- return failure("permission_denied", "reconciliation step is outside the approved workflow");
17083
- if (input.outcome === "not_started") {
17084
- return success(mutation.revision, null, {
17085
- ...current,
17086
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: { ...candidate.data, consumption: null } } : candidate)
17087
- });
17143
+ } else if (provider === "github") {
17144
+ const r = spawnSync("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
17145
+ if (r.status !== 0)
17146
+ return { ok: false, error: "could not list pull requests" };
17147
+ for (const pr of JSON.parse(r.stdout ?? "[]")) {
17148
+ const desc = String(pr.body ?? "").trim();
17149
+ examples.push({ title: pr.title, url: pr.url, ...descInfo(desc) });
17088
17150
  }
17089
- if (currentStep)
17090
- workflow.completed.push(currentStep);
17091
- const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
17092
- const nextDecision = {
17093
- ...entry.data,
17094
- consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
17095
- };
17096
- const actionProgress = workflow.steps.length ? [
17097
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
17098
- {
17099
- decisionId: input.decisionId,
17100
- steps: workflow.steps,
17101
- completedSteps: workflow.completed
17102
- }
17103
- ] : current.actionProgress ?? [];
17104
- return success(mutation.revision, null, {
17105
- ...current,
17106
- actionProgress,
17107
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
17108
- });
17109
- }, input.native?.now);
17110
- if (!changed.ok)
17111
- return changed;
17112
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
17113
- return entry ? success(changed.data.revision, input.workspaceRevision, entry) : failure("recovery_required", "reconciled decision disappeared");
17114
- }
17115
- var bindingCovers = scopeCovers;
17116
- var verifyDecisionContent = verifyContentRefs;
17117
-
17118
- // packages/workit-core/src/core/task-evaluation.ts
17119
- var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
17120
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
17121
- var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
17122
- var canonicalPath = (value) => {
17123
- if (typeof value !== "string" || !value)
17124
- return null;
17125
- if (value.includes("\\") || value.split("/").includes(".."))
17126
- return null;
17127
- const normalized = path6.posix.normalize(value);
17128
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
17129
- return null;
17130
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
17131
- };
17132
- var canonicalScope = (scope) => {
17133
- const paths = scope.paths.length ? scope.paths : ["."];
17134
- const normalizedPaths = paths.map(canonicalPath);
17135
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
17136
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
17137
- return null;
17151
+ }
17138
17152
  return {
17139
- description: scope.description,
17140
- paths: [...new Set(normalizedPaths)],
17141
- exclusions: [...new Set(normalizedExclusions)]
17153
+ ok: true,
17154
+ provider,
17155
+ count: examples.length,
17156
+ styleHints: [
17157
+ "Prefer ## Summary bullets + ## Validation or ## Test plan only",
17158
+ "Do not add ## Notes with branch names, commit counts, or diff stats",
17159
+ "Do not paste commit log or diff stat into the body"
17160
+ ],
17161
+ examples
17142
17162
  };
17143
- };
17144
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
17145
- var scopeMatches = (value, scope) => {
17146
- const normalized = canonicalScope(scope);
17147
- if (!normalized)
17148
- return false;
17149
- const paths = normalized.paths;
17150
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
17151
- };
17152
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
17153
- var compareCodeUnits2 = (left, right) => {
17154
- const length = Math.min(left.length, right.length);
17155
- for (let index = 0;index < length; index += 1) {
17156
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
17157
- if (difference !== 0)
17158
- return difference;
17163
+ }
17164
+
17165
+ // packages/workit-core/src/core/branch.ts
17166
+ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
17167
+ var verifyPushIdentity = (root, provider, account) => {
17168
+ if (!account || typeof account !== "string")
17169
+ return { ok: false, error: "push identity requires a configured area account" };
17170
+ const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
17171
+ const bin = provider === "gitlab" ? "glab" : "gh";
17172
+ let login;
17173
+ try {
17174
+ login = execFileSync2(bin, [...probe], {
17175
+ cwd: root,
17176
+ encoding: "utf8",
17177
+ stdio: ["pipe", "pipe", "pipe"]
17178
+ }).trim();
17179
+ } catch {
17180
+ return { ok: false, error: `${bin} identity could not be resolved` };
17159
17181
  }
17160
- return left.length - right.length;
17161
- };
17162
- var gitPaths = (root) => {
17163
- const result = spawnSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
17164
- cwd: root,
17165
- encoding: "buffer"
17166
- });
17167
- if (result.status !== 0) {
17168
- const stderr = result.stderr?.toString("utf8") ?? "";
17182
+ if (!login || login.toLowerCase() !== account.toLowerCase())
17169
17183
  return {
17170
- paths: [],
17171
- uncertain: !/not a git repository/i.test(stderr),
17172
- git: /not a git repository/i.test(stderr) ? false : true
17184
+ ok: false,
17185
+ error: `push identity ${login || "(unknown)"} does not match area account ${account}`
17173
17186
  };
17174
- }
17175
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
17176
- const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
17177
- if (stagedDeleted.status !== 0) {
17178
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
17179
- if (!/not a git repository/i.test(stderr))
17180
- return { paths, uncertain: true, git: true };
17181
- } else if (stagedDeleted.stdout) {
17182
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
17183
- }
17184
- return { paths: [...new Set(paths)], uncertain: false, git: true };
17187
+ return { ok: true };
17185
17188
  };
17186
- var walk = (root, directory, output) => {
17187
- let names;
17189
+
17190
+ // packages/workit-core/src/core/auto-approval.ts
17191
+ var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
17192
+ var OPERATION_CLASS = {
17193
+ "git.branch_setup": "branch",
17194
+ "git.commit": "commit",
17195
+ "git.push": "push",
17196
+ "hosting.pull_request": "pr",
17197
+ "hosting.merge": "merge"
17198
+ };
17199
+ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
17200
+ var digestOf = (configDirPath) => {
17188
17201
  try {
17189
- names = fs6.readdirSync(directory);
17202
+ const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
17203
+ return createHash3("sha256").update(raw).digest("hex");
17190
17204
  } catch {
17191
- return true;
17205
+ return null;
17192
17206
  }
17193
- let uncertain = false;
17194
- for (const name of names) {
17195
- if (name === ".git" || name === ".workit")
17196
- continue;
17197
- const target = path6.join(directory, name);
17198
- let stat;
17199
- try {
17200
- stat = fs6.lstatSync(target);
17201
- } catch {
17202
- uncertain = true;
17203
- continue;
17204
- }
17205
- const item = relative(root, target);
17206
- output.add(item);
17207
- if (stat.isDirectory() && !stat.isSymbolicLink())
17208
- uncertain = walk(root, target, output) || uncertain;
17207
+ };
17208
+ var normalizeClasses = (value) => {
17209
+ if (value === true)
17210
+ return [...AUTO_CLASSES];
17211
+ if (!Array.isArray(value))
17212
+ return [];
17213
+ const seen = new Set;
17214
+ for (const item of value) {
17215
+ if (typeof item === "string" && AUTO_CLASSES.includes(item))
17216
+ seen.add(item);
17209
17217
  }
17210
- return uncertain;
17218
+ return [...seen];
17211
17219
  };
17212
- var scopeRoots = (root, scope) => {
17213
- const roots = [];
17214
- for (const item of scope.paths.length ? scope.paths : ["."]) {
17215
- const target = path6.resolve(root, item);
17216
- if (!inside(root, target))
17217
- 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` }] });
17218
- let ancestor = target;
17219
- while (!fs6.existsSync(ancestor) && ancestor !== root)
17220
- ancestor = path6.dirname(ancestor);
17221
- try {
17222
- if (!inside(root, fs6.realpathSync(ancestor)))
17223
- 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` }] });
17224
- } catch {
17225
- return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
17226
- }
17227
- roots.push(target);
17220
+ var resolveAutoApproval = (root, dir = configDir()) => {
17221
+ let match;
17222
+ try {
17223
+ match = resolveWorkspaceFrom(root, dir);
17224
+ } catch {
17225
+ return { status: "off" };
17228
17226
  }
17229
- return success(null, null, roots);
17227
+ if (!match)
17228
+ return { status: "off" };
17229
+ const classes = normalizeClasses(match.autoApprove);
17230
+ if (classes.length === 0)
17231
+ return { status: "off" };
17232
+ return {
17233
+ status: "on",
17234
+ workspace: match.name,
17235
+ classes,
17236
+ account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
17237
+ configDigest: digestOf(dir)
17238
+ };
17230
17239
  };
17231
- function captureCandidate(root, scope, environment = []) {
17232
- let checkout;
17240
+ var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
17241
+ var standingApprovalLive = (root, receipt, cls, dir) => {
17242
+ if (!isStandingReceipt(receipt))
17243
+ return false;
17244
+ if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
17245
+ return false;
17246
+ if (receipt.class !== cls)
17247
+ return false;
17248
+ const resolved = resolveAutoApproval(root, dir);
17249
+ if (resolved.status !== "on")
17250
+ return false;
17251
+ if (resolved.workspace !== receipt.workspace)
17252
+ return false;
17253
+ return resolved.classes.includes(cls);
17254
+ };
17255
+ var standingReceiptFor = (root, workspace, cls, dir) => {
17256
+ const resolved = resolveAutoApproval(root, dir);
17257
+ return {
17258
+ kind: "standing",
17259
+ workspace,
17260
+ class: cls,
17261
+ configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
17262
+ };
17263
+ };
17264
+ var verifyStandingApproval = (root, task, caller, binding) => {
17265
+ if (task.status !== "active")
17266
+ return failure("invalid_transition", "only active tasks can authorize actions");
17267
+ const session = task.intent?.provenance?.session;
17268
+ if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
17269
+ return failure("permission_denied", "standing approval requires the lead session");
17270
+ const standing = binding.standing;
17271
+ if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
17272
+ return failure("invalid_input", "standing approval needs a workspace and class");
17273
+ let operation;
17233
17274
  try {
17234
- checkout = fs6.realpathSync(root);
17275
+ operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
17235
17276
  } catch {
17236
- return failure("invalid_input", "candidate root does not exist");
17277
+ operation = null;
17278
+ }
17279
+ const cls = operationAutoClass(operation);
17280
+ if (!cls || cls !== standing.class)
17281
+ return failure("permission_denied", "standing approval does not cover this operation");
17282
+ const resolved = resolveAutoApproval(root);
17283
+ if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
17284
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
17285
+ if (cls === "push") {
17286
+ const workspace = resolveWorkspaceFrom(root, configDir());
17287
+ const provider = workspace?.vcs?.provider;
17288
+ const account = resolved.account;
17289
+ if (!provider || !account)
17290
+ return failure("permission_denied", "auto-push requires a workspace provider and area account");
17291
+ const identity = verifyPushIdentity(root, provider, account);
17292
+ if (!identity.ok)
17293
+ return failure("permission_denied", identity.error);
17237
17294
  }
17238
- const normalizedScope = canonicalScope(scope);
17239
- if (!normalizedScope)
17240
- return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
17241
- const roots = scopeRoots(checkout, normalizedScope);
17242
- if (!roots.ok)
17243
- return roots;
17244
- const git = gitPaths(checkout);
17245
- const names = new Set;
17246
- let uncertain = git.uncertain;
17247
- for (const target of roots.data) {
17248
- const item = relative(checkout, target);
17249
- if (item === ".git" || item === ".workit")
17250
- continue;
17251
- names.add(item);
17252
- if (!fs6.existsSync(target))
17253
- continue;
17254
- let stat;
17255
- try {
17256
- stat = fs6.lstatSync(target);
17257
- } catch {
17258
- uncertain = true;
17295
+ return success(null, null, {
17296
+ kind: "host_observed",
17297
+ host: caller.host,
17298
+ session: { kind: "host", host: caller.host, handle: caller.actor },
17299
+ workerId: null,
17300
+ receipts: [standingReceiptFor(root, standing.workspace, cls)]
17301
+ });
17302
+ };
17303
+
17304
+ // packages/workit-core/src/core/authority.ts
17305
+ var verifiedAuthorities = new WeakMap;
17306
+ var trustedAuthority = (kind, provenance, expected, binding, caller) => {
17307
+ const token = {};
17308
+ verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
17309
+ return token;
17310
+ };
17311
+ var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
17312
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17313
+ var normalizeChainSteps = (steps) => {
17314
+ if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
17315
+ return null;
17316
+ const normalized = [];
17317
+ for (const step of steps) {
17318
+ if (typeof step === "string") {
17319
+ if (!step.trim())
17320
+ return null;
17321
+ normalized.push({ kind: "commit", message: step });
17259
17322
  continue;
17260
17323
  }
17261
- if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
17262
- uncertain = walk(checkout, target, names) || uncertain;
17263
- }
17264
- for (const item of git.paths)
17265
- if (scopeMatches(item, normalizedScope))
17266
- names.add(item);
17267
- const files = [];
17268
- let completeness = uncertain ? "uncertain" : "known";
17269
- for (const item of [...names].sort()) {
17270
- if (!scopeMatches(item, normalizedScope))
17324
+ if (!isRecord(step))
17325
+ return null;
17326
+ if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
17327
+ normalized.push({ kind: "branch", target: step.branch });
17328
+ continue;
17329
+ }
17330
+ if (step.pr === true && Object.keys(step).length === 1) {
17331
+ normalized.push({ kind: "pr" });
17271
17332
  continue;
17272
- const target = path6.join(checkout, item);
17273
- try {
17274
- const stat = fs6.lstatSync(target);
17275
- if (stat.isSymbolicLink()) {
17276
- const link = fs6.readlinkSync(target);
17277
- files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
17278
- } else if (stat.isFile()) {
17279
- files.push({
17280
- path: item,
17281
- kind: "file",
17282
- digest: digestBytes3(fs6.readFileSync(target)),
17283
- executable: (stat.mode & 73) !== 0
17284
- });
17285
- } else if (!stat.isDirectory()) {
17286
- completeness = "uncertain";
17287
- } else if (git.git && git.paths.includes(item)) {
17288
- completeness = "uncertain";
17289
- }
17290
- } catch (error) {
17291
- if (error?.code === "ENOENT") {
17292
- files.push({ path: item, kind: "absent", digest: null, executable: null });
17293
- } else
17294
- completeness = "uncertain";
17295
17333
  }
17334
+ return null;
17296
17335
  }
17297
- const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
17298
- name,
17299
- value: environment[name] ?? null
17300
- }));
17301
- const namesByEnvironment = supplied.map(({ name }) => name);
17302
- if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
17303
- return failure("invalid_input", "candidate environment names must be unique and non-empty");
17304
- const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
17305
- if (values.some(({ value }) => value === null))
17306
- completeness = "uncertain";
17307
- const headResult = spawnSync("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
17308
- if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
17309
- completeness = "uncertain";
17310
- const head = headResult.status === 0 ? headResult.stdout.trim() : null;
17311
- const partial = {
17312
- id: "0".repeat(64),
17313
- scope: normalizedScope,
17314
- completeness,
17315
- files,
17316
- environment: values,
17317
- head
17318
- };
17319
- const candidate = { ...partial, id: candidateDigest(partial) };
17320
- const parsed = candidateSchema.safeParse(candidate);
17321
- return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
17322
- }
17323
- var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
17324
- var changedPaths = (before, after) => {
17325
- const paths = new Set([...before.files, ...after.files].map((file) => file.path));
17326
- const left = fileMap(before);
17327
- const right = fileMap(after);
17328
- const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
17329
- if (before.head !== after.head)
17330
- changed.push(".");
17331
- if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
17332
- changed.push(".");
17333
- return changed;
17336
+ const keys = normalized.map(chainStepKey);
17337
+ if (new Set(keys).size !== keys.length)
17338
+ return null;
17339
+ return normalized;
17334
17340
  };
17335
- var sessionFromRef = (value) => {
17336
- if (typeof value !== "object" || value === null || value.kind !== "host")
17341
+ var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
17342
+ var parseSteps = (content) => {
17343
+ let value;
17344
+ try {
17345
+ value = JSON.parse(content);
17346
+ } catch {
17347
+ return [];
17348
+ }
17349
+ if (typeof value !== "object" || value === null || !("steps" in value))
17350
+ return [];
17351
+ const steps = value.steps;
17352
+ if (!Array.isArray(steps))
17337
17353
  return null;
17338
- return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
17354
+ const normalized = normalizeChainSteps(steps);
17355
+ return normalized;
17339
17356
  };
17340
- var sameSession = (left, right) => {
17341
- const other = sessionFromRef(right);
17342
- return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
17357
+ var decisionMatches = (entry, purpose, binding) => {
17358
+ const decision = entry.data;
17359
+ 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);
17343
17360
  };
17344
- var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
17345
- function evaluateEvidence(task, candidate) {
17346
- const current = candidate ?? task.candidates.at(-1) ?? null;
17347
- return task.evidence.map((entry) => {
17348
- const evidence = entry.data;
17349
- if (evidence.result === "failed")
17350
- return {
17351
- evidenceId: entry.id,
17352
- status: "failed",
17353
- reason: "failed evidence remains historical"
17354
- };
17355
- if (evidence.kind === "check" || evidence.kind === "review") {
17356
- if (!evidence.beforeCandidateId || !evidence.candidateId)
17357
- return {
17358
- evidenceId: entry.id,
17359
- status: "stale",
17360
- reason: "check or review evidence lacks start and end candidates"
17361
- };
17362
- if (evidence.beforeCandidateId !== evidence.candidateId)
17363
- return {
17364
- evidenceId: entry.id,
17365
- status: "stale",
17366
- reason: "candidate changed during the check or review"
17367
- };
17368
- }
17369
- if (!evidence.candidateId || !current)
17370
- return {
17371
- evidenceId: entry.id,
17372
- status: evidence.result,
17373
- reason: "evidence has no candidate binding"
17374
- };
17375
- const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
17376
- if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
17377
- return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
17378
- if (observed.id === current.id)
17379
- return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
17380
- const scopes = evidenceScopes(task, evidence.requirementIds);
17381
- const changed = changedPaths(observed, current);
17382
- const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
17383
- if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
17384
- return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
17385
- return {
17386
- evidenceId: entry.id,
17387
- status: evidence.result,
17388
- reason: "candidate changed outside evidence scope"
17389
- };
17390
- });
17391
- }
17392
- var scopeCovers2 = scopeCovers;
17393
- var checkPin = (assignment, beforeCandidateId, candidateId) => {
17394
- const pin = assignment.candidateId ?? null;
17395
- if (pin === null)
17396
- return true;
17397
- return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
17361
+ var sameRef = (left, right) => canonicalJson(left) === canonicalJson(right);
17362
+ var validProvenance = (value, caller) => {
17363
+ if (!provenanceSchema.safeParse(value).success)
17364
+ return false;
17365
+ const provenance = value;
17366
+ return provenance.kind === "host_observed" && provenance.host === caller.host && provenance.session?.kind === "host" && provenance.session.host === caller.host && provenance.session.handle === caller.actor && provenance.receipts.some((receipt) => receipt.kind === "host" && receipt.host === caller.host);
17398
17367
  };
17399
- var resolveBinding = (evidence, pin, currentCandidateId) => {
17400
- const bound = evidence.kind === "check" || evidence.kind === "review";
17401
- const fallback = bound ? pin ?? currentCandidateId : null;
17402
- return {
17403
- beforeCandidateId: evidence.beforeCandidateId ?? fallback,
17404
- candidateId: evidence.candidateId ?? fallback
17405
- };
17368
+ var verifyNativeDecision = (verifier, input, binding) => {
17369
+ if (!verifier)
17370
+ return failure("permission_denied", "native authority verifier is unavailable");
17371
+ const result = verifier.verifyDecision(input);
17372
+ if (!result.ok || !validProvenance(result.data, input.caller))
17373
+ return failure("permission_denied", "native decision observation was not attested");
17374
+ return success(null, null, trustedAuthority("decision", result.data, {
17375
+ taskId: input.expected.taskId,
17376
+ workspaceId: input.expected.workspaceId,
17377
+ purpose: input.expected.purpose,
17378
+ response: input.expected.response,
17379
+ binding: input.expected.binding,
17380
+ bindingBytes: input.expected.bindingBytes,
17381
+ digest: input.expected.digest,
17382
+ requirementIds: [...input.expected.requirementIds]
17383
+ }, binding, input.caller));
17406
17384
  };
17407
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
17408
- 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")));
17409
- 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 }));
17410
- var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
17411
- if (kind !== "passed")
17412
- return false;
17413
- if (dimension === "testing" || dimension === "verification")
17414
- return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
17415
- if (dimension === "review")
17416
- return evidenceKind === "review";
17417
- if (dimension === "investigation" || dimension === "challenge")
17418
- return evidenceKind === "investigation";
17419
- if (dimension === "artifacts" || dimension === "continuity")
17420
- return evidenceKind === "artifact";
17421
- return false;
17385
+ var verifyNativeAction = (verifier, input, binding) => {
17386
+ if (!verifier)
17387
+ return failure("permission_denied", "native authority verifier is unavailable");
17388
+ const result = verifier.verifyAction(input);
17389
+ if (!result.ok || !validProvenance(result.data, input.caller))
17390
+ return failure("permission_denied", "native action observation was not attested");
17391
+ return success(null, null, trustedAuthority("action", result.data, {
17392
+ taskId: input.expected.taskId,
17393
+ workspaceId: input.expected.workspaceId,
17394
+ decisionId: input.expected.decisionId,
17395
+ actionRef: input.expected.actionRef,
17396
+ taskRevision: input.expected.taskRevision,
17397
+ workspaceRevision: input.expected.workspaceRevision,
17398
+ outcome: input.expected.outcome,
17399
+ purpose: input.expected.decision.purpose,
17400
+ response: input.expected.decision.response,
17401
+ binding: input.expected.decision.binding,
17402
+ digest: input.expected.decision.digest,
17403
+ requirementIds: [...input.expected.decision.requirementIds]
17404
+ }, binding, input.caller));
17422
17405
  };
17423
- var expectedKinds = (dimension, ruleId) => {
17424
- if (dimension === "testing")
17425
- return "check";
17426
- if (dimension === "verification")
17427
- return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
17428
- if (dimension === "review")
17429
- return "review";
17430
- if (dimension === "investigation" || dimension === "challenge")
17431
- return "investigation";
17432
- if (dimension === "artifacts" || dimension === "continuity")
17433
- return "artifact";
17434
- if (dimension === "decisions")
17435
- return "decision";
17436
- return "worker report";
17406
+ var verifyNativeReconciliation = (verifier, input, binding) => {
17407
+ if (!verifier?.verifyReconciliation)
17408
+ return failure("permission_denied", "native reconciliation authority is unavailable");
17409
+ const result = verifier.verifyReconciliation(input);
17410
+ if (!result.ok || !validProvenance(result.data, input.caller))
17411
+ return failure("permission_denied", "native reconciliation observation was not attested");
17412
+ return success(null, null, trustedAuthority("reconciliation", result.data, {
17413
+ taskId: input.expected.taskId,
17414
+ workspaceId: input.expected.workspaceId,
17415
+ decisionId: input.expected.decisionId,
17416
+ actionRef: input.expected.actionRef,
17417
+ taskRevision: input.expected.taskRevision,
17418
+ workspaceRevision: input.expected.workspaceRevision,
17419
+ outcome: input.expected.outcome,
17420
+ evidenceDigest: input.expected.evidenceDigest,
17421
+ step: input.expected.step,
17422
+ purpose: input.expected.decision.purpose,
17423
+ response: input.expected.decision.response,
17424
+ binding: input.expected.decision.binding,
17425
+ digest: input.expected.decision.digest,
17426
+ requirementIds: [...input.expected.decision.requirementIds]
17427
+ }, binding, input.caller));
17437
17428
  };
17438
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
17439
- const evidence = evaluateEvidence(task, candidate);
17440
- if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
17441
- return task.policy.requirements.map((requirement) => ({
17442
- requirementId: requirement.id,
17443
- status: "unsatisfied",
17444
- evidenceIds: [],
17445
- decisionIds: [],
17446
- reason: "stored policy version is unsupported; reassessment is required"
17447
- }));
17448
- return (task.policy?.requirements ?? []).map((requirement) => {
17449
- const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
17450
- const passed = related.filter(({ entry, evaluation }) => {
17451
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
17452
- return false;
17453
- if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
17454
- return false;
17455
- if (requirement.dimension !== "review")
17456
- return true;
17457
- const reviewSession = sessionFromRef(entry.data.reviewContext);
17458
- const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
17459
- 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));
17460
- return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
17461
- });
17462
- if (passed.length) {
17463
- const currentId = candidate?.id ?? null;
17464
- 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;
17465
- if (withRed.length)
17466
- return {
17467
- requirementId: requirement.id,
17468
- status: "satisfied",
17469
- evidenceIds: withRed.map(({ entry }) => entry.id),
17470
- decisionIds: [],
17471
- reason: "fresh applicable evidence passed"
17472
- };
17473
- if (requirement.dimension === "testing")
17474
- return {
17475
- requirementId: requirement.id,
17476
- status: "unsatisfied",
17477
- evidenceIds: [],
17478
- decisionIds: [],
17479
- reason: "testing requirement has GREEN evidence but no preceding RED"
17480
- };
17481
- }
17482
- if (requirement.dimension === "delegation") {
17483
- const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
17484
- const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
17485
- if (reconciled)
17486
- return {
17487
- requirementId: requirement.id,
17488
- status: "satisfied",
17489
- evidenceIds: [],
17490
- decisionIds: [],
17491
- reason: "bounded helper completed and its result is recorded"
17492
- };
17493
- }
17494
- const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
17495
- if (decisions.length)
17496
- return {
17497
- requirementId: requirement.id,
17498
- status: "satisfied",
17499
- evidenceIds: [],
17500
- decisionIds: decisions.map((decision) => decision.id),
17501
- reason: "an applicable approved or stated decision satisfies the requirement"
17502
- };
17503
- const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
17504
- if (limitations.length)
17505
- return {
17506
- requirementId: requirement.id,
17507
- status: "accepted_limitation",
17508
- evidenceIds: [],
17509
- decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
17510
- reason: "an applicable approved limitation permits the missing evidence"
17511
- };
17512
- const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
17513
- if (!unavailable && related.length > 0 && !passed.length)
17514
- return {
17515
- requirementId: requirement.id,
17516
- status: "unsatisfied",
17517
- evidenceIds: related.map(({ entry }) => entry.id),
17518
- decisionIds: [],
17519
- reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
17520
- };
17521
- return {
17522
- requirementId: requirement.id,
17523
- status: unavailable ? "unavailable" : "unsatisfied",
17524
- evidenceIds: related.map(({ entry }) => entry.id),
17525
- decisionIds: [],
17526
- reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
17527
- };
17528
- });
17429
+ var takeAuthority = (authority, binding, caller) => {
17430
+ const record = verifiedAuthorities.get(authority);
17431
+ verifiedAuthorities.delete(authority);
17432
+ if (!record)
17433
+ return null;
17434
+ if (record.owner !== binding.owner || record.store !== binding.store || record.root !== binding.root || record.root !== record.store.root || canonicalJson(record.caller) !== canonicalJson(caller))
17435
+ return null;
17436
+ return record;
17437
+ };
17438
+ var retireNativeAuthority = (authority) => {
17439
+ const record = verifiedAuthorities.get(authority);
17440
+ verifiedAuthorities.delete(authority);
17441
+ return record?.provenance ?? null;
17442
+ };
17443
+ var 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;
17444
+ var 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);
17445
+ var provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session);
17446
+ function applicableDecision(task, purpose, binding, checkoutRoot) {
17447
+ if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
17448
+ return [];
17449
+ 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);
17529
17450
  }
17530
- function evaluateClosure(requestedOutcome, view) {
17531
- if (requestedOutcome !== "stopped" && !view.task.policy)
17532
- return failure("requirements_unsatisfied", "task has not been assessed");
17533
- if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
17534
- return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
17535
- const evaluations = view.requirements;
17536
- const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
17537
- if (requestedOutcome !== "stopped" && openFindings.length)
17538
- return failure("requirements_unsatisfied", "open findings must be resolved before closure");
17539
- if (requestedOutcome !== "stopped") {
17540
- const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
17541
- for (const entry of view.task.findings) {
17542
- if (entry.data.disposition === "fixed") {
17543
- const verified = entry.data.resolution?.evidenceIds.some((id) => {
17544
- const evidence = view.task.evidence.find((item) => item.id === id);
17545
- if (!evidence)
17546
- return false;
17547
- return findingVerificationPasses(entry.data.candidateId, {
17548
- kind: evidence.data.kind,
17549
- candidateId: evidence.data.candidateId,
17550
- status: evidenceById.get(id) ?? "missing"
17451
+ var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
17452
+ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
17453
+ let root;
17454
+ try {
17455
+ root = fs6.realpathSync(checkoutRoot);
17456
+ } catch {
17457
+ return failure("permission_denied", "checkout root is unavailable");
17458
+ }
17459
+ for (const reference of binding.contentRefs) {
17460
+ if (reference.kind !== "file")
17461
+ continue;
17462
+ if (!reference.digest)
17463
+ return failure("invalid_input", "document references require a byte digest");
17464
+ const target = path8.resolve(root, reference.path);
17465
+ if (target !== root && !target.startsWith(`${root}${path8.sep}`))
17466
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
17467
+ fields: [
17468
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
17469
+ ]
17470
+ });
17471
+ try {
17472
+ const relative = path8.relative(root, target);
17473
+ let current = root;
17474
+ for (const segment of relative.split(path8.sep)) {
17475
+ if (!segment)
17476
+ continue;
17477
+ current = path8.join(current, segment);
17478
+ const stat = fs6.lstatSync(current);
17479
+ const real = fs6.realpathSync(current);
17480
+ if (real !== root && !real.startsWith(`${root}${path8.sep}`))
17481
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
17482
+ fields: [
17483
+ {
17484
+ path: "contentRefs.path",
17485
+ reason: `${reference.path} resolves outside the checkout`
17486
+ }
17487
+ ]
17551
17488
  });
17552
- });
17553
- if (!verified)
17554
- return failure("requirements_unsatisfied", "fixed findings require current verification");
17489
+ if (stat.isSymbolicLink())
17490
+ return failure("invalid_input", "document reference uses a symlink");
17491
+ if (current === target && !stat.isFile())
17492
+ return failure("invalid_input", "document reference is not a regular file");
17493
+ if (current !== target && !stat.isDirectory())
17494
+ return failure("invalid_input", "document reference ancestor is not a directory");
17555
17495
  }
17556
- if (entry.data.disposition === "deferred") {
17557
- const valid = entry.data.resolution?.decisionIds.some((id) => {
17558
- const decisionEntry = view.task.decisions.find((item) => item.id === id);
17559
- const decision = decisionEntry?.data;
17560
- return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
17561
- });
17562
- if (!valid)
17563
- return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
17496
+ if (digestBytes2(fs6.readFileSync(target)) !== reference.digest)
17497
+ return failure("permission_denied", "approved document bytes have changed");
17498
+ } catch {
17499
+ return failure("invalid_input", "approved document is unavailable");
17500
+ }
17501
+ }
17502
+ return success(null, null, null);
17503
+ };
17504
+ var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
17505
+ var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
17506
+ var validateAction = (store, task, workspaceId, input, authority) => {
17507
+ if (task.status !== "active")
17508
+ return failure("invalid_transition", "only active tasks can authorize actions");
17509
+ if (!refSchema.safeParse(input.actionRef).success)
17510
+ return failure("invalid_input", "action reference is invalid");
17511
+ const entry = task.decisions.find((candidate) => candidate.id === input.decisionId);
17512
+ if (!entry)
17513
+ return failure("not_found", "decision not found");
17514
+ const decision = entry.data;
17515
+ if (decision.purpose !== "action")
17516
+ return failure("permission_denied", "decision is not an action approval");
17517
+ if (!authorityMatches(authority, {
17518
+ taskId: task.id,
17519
+ workspaceId,
17520
+ decisionId: input.decisionId,
17521
+ actionRef: input.actionRef,
17522
+ taskRevision: input.expectedRevision,
17523
+ workspaceRevision: input.expectedWorkspaceRevision,
17524
+ outcome: "reserve"
17525
+ }))
17526
+ return failure("permission_denied", "native reservation authority is not bound");
17527
+ if (!authorityDecisionMatches(authority, decision))
17528
+ return failure("permission_denied", "native reservation authority does not match decision");
17529
+ if (decision.response !== "approved")
17530
+ return failure("permission_denied", "decision was rejected");
17531
+ if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
17532
+ return failure("permission_denied", "action approval lacks native receipt assurance");
17533
+ const receipts = entry.provenance.receipts;
17534
+ const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
17535
+ const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
17536
+ if (!questionBacked && standing.length > 0) {
17537
+ let operation;
17538
+ try {
17539
+ operation = JSON.parse(decision.binding.approvedContent).operation;
17540
+ } catch {
17541
+ operation = null;
17542
+ }
17543
+ const cls = operationAutoClass(operation);
17544
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
17545
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
17546
+ }
17547
+ if (decision.revoked)
17548
+ return failure("permission_denied", "decision is revoked");
17549
+ if (decision.digest !== decisionDigest(decision))
17550
+ return failure("permission_denied", "decision binding is invalid");
17551
+ if (decision.binding.taskId !== task.id || decision.binding.workspaceId !== workspaceId)
17552
+ return failure("permission_denied", "decision task or workspace binding is invalid");
17553
+ if (input.binding && canonicalJson(input.binding) !== canonicalJson(decision.binding))
17554
+ return failure("permission_denied", "action binding does not match the approved decision");
17555
+ const content = verifyContentRefs(store, decision.binding);
17556
+ if (!content.ok)
17557
+ return content;
17558
+ const requestedSteps = parseSteps(decision.binding.approvedContent);
17559
+ if (requestedSteps === null)
17560
+ return failure("invalid_input", "bounded action steps must be a unique non-empty array");
17561
+ if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
17562
+ return failure("permission_denied", "bounded action steps do not match approved content");
17563
+ const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
17564
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
17565
+ if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
17566
+ return failure("permission_denied", "bounded action scope changed");
17567
+ return success(null, null, { entry, workflow });
17568
+ };
17569
+ function reserveAction(input) {
17570
+ if (!input.store || !input.expectedRevision || !input.expectedWorkspaceRevision)
17571
+ return failure("invalid_input", "action reservation preconditions are required");
17572
+ const authority = takeAuthority(input.authority, {
17573
+ owner: input.authorityOwner,
17574
+ store: input.store,
17575
+ root: input.store.root
17576
+ }, input.authorityCaller);
17577
+ if (!authority)
17578
+ return failure("permission_denied", "native reservation authority is not verified");
17579
+ const changed = input.store.mutateTask(input.taskId, input.expectedRevision, (current, mutation) => {
17580
+ const workspace = input.store.readWorkspace();
17581
+ if (!workspace.ok)
17582
+ return workspace;
17583
+ if (!workspace.data)
17584
+ return failure("not_found", "workspace not found");
17585
+ if (workspace.data.revision !== input.expectedWorkspaceRevision)
17586
+ return failure("revision_conflict", "workspace revision does not match", {
17587
+ expectedWorkspaceRevision: input.expectedWorkspaceRevision,
17588
+ actualWorkspaceRevision: workspace.data.revision
17589
+ });
17590
+ const valid = validateAction(input.store, current, workspace.data.id, input, authority);
17591
+ if (!valid.ok)
17592
+ return valid;
17593
+ const { entry, workflow } = valid.data;
17594
+ const existing = entry.data.consumption;
17595
+ if (existing?.state === "uncertain")
17596
+ return failure("external_outcome_unknown", "previous action outcome is unknown", {
17597
+ operation: "reserve_action",
17598
+ outcome: "unknown"
17599
+ });
17600
+ if (existing?.state === "consumed")
17601
+ return failure("permission_denied", "action approval is consumed");
17602
+ if (existing?.state === "reserved" && workflow.completed.length === 0)
17603
+ return failure("permission_denied", "action approval is already reserved");
17604
+ const requestedStep = input.step ?? workflow.steps[workflow.completed.length];
17605
+ if (workflow.steps.length && (!requestedStep || requestedStep !== workflow.steps[workflow.completed.length]))
17606
+ return failure("permission_denied", "action step is outside the approved remaining workflow");
17607
+ if (requestedStep && workflow.completed.includes(requestedStep))
17608
+ return failure("permission_denied", "action step was already completed");
17609
+ const nextDecision = {
17610
+ ...entry.data,
17611
+ consumption: { state: "reserved", at: mutation.now, actionRef: input.actionRef }
17612
+ };
17613
+ const actionProgress = workflow.steps.length ? [
17614
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
17615
+ {
17616
+ decisionId: input.decisionId,
17617
+ steps: [...workflow.steps],
17618
+ completedSteps: [...workflow.completed]
17564
17619
  }
17565
- }
17566
- }
17567
- const blocking = evaluations.filter((item) => {
17568
- if (item.status !== "unsatisfied" && item.status !== "unavailable")
17569
- return false;
17570
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
17571
- return !requirement || requirement.before === "close";
17572
- });
17573
- if (requestedOutcome !== "stopped" && blocking.length)
17574
- return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
17575
- requirementIds: blocking.map((item) => item.requirementId),
17576
- requirements: blocking.map((item) => {
17577
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
17578
- return {
17579
- requirementId: item.requirementId,
17580
- ruleId: requirement?.ruleId ?? "unknown",
17581
- reason: item.reason,
17582
- satisfaction: requirement?.satisfaction ?? "",
17583
- dependentAction: requirement?.dependentAction ?? null
17584
- };
17585
- })
17586
- });
17587
- const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
17588
- if (requestedOutcome === "verified" && accepted.length)
17589
- return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
17590
- requirementIds: accepted.map((item) => item.requirementId)
17591
- });
17592
- const evidenceIds = [
17593
- ...new Set([
17594
- ...evaluations.flatMap((item) => item.evidenceIds),
17595
- ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
17596
- ])
17597
- ];
17598
- const decisionIds = [
17599
- ...new Set([
17600
- ...evaluations.flatMap((item) => item.decisionIds),
17601
- ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
17602
- ])
17603
- ];
17604
- return success(null, null, {
17605
- outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
17606
- evidenceIds,
17607
- decisionIds,
17608
- requirementIds: evaluations.map((item) => item.requirementId)
17609
- });
17610
- }
17611
-
17612
- // packages/workit-core/src/core/task-context.ts
17613
- function reconcileResume(view, observations = []) {
17614
- if (observations.length > 0)
17615
- return failure("permission_denied", "worker observations require native host verification");
17616
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
17617
- if (!candidate.ok)
17618
- return candidate;
17619
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
17620
- const blockers = [...view.task.progress.blockers];
17621
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
17622
- blockers.push({
17623
- reason: "worker state requires reconciliation",
17624
- dependentAction: "resume",
17625
- refs: []
17620
+ ] : current.actionProgress ?? [];
17621
+ return success(mutation.revision, null, {
17622
+ ...current,
17623
+ actionProgress,
17624
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
17626
17625
  });
17627
- return success(null, null, {
17628
- candidate: candidate.data,
17629
- staleEvidenceIds,
17630
- workerUpdates: [],
17631
- blockers,
17632
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
17626
+ }, input.native?.now);
17627
+ if (!changed.ok)
17628
+ return changed;
17629
+ const workspace = input.store.readWorkspace();
17630
+ if (!workspace.ok || !workspace.data)
17631
+ return failure("recovery_required", "workspace disappeared after reservation");
17632
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
17633
+ if (!entry)
17634
+ return failure("recovery_required", "reserved decision disappeared");
17635
+ const workflow = changed.data.actionProgress?.find((progress) => progress.decisionId === input.decisionId) ?? {
17636
+ steps: [],
17637
+ completedSteps: []
17638
+ };
17639
+ return success(changed.data.revision, workspace.data.revision, {
17640
+ taskId: input.taskId,
17641
+ decisionId: input.decisionId,
17642
+ actionRef: input.actionRef,
17643
+ taskRevision: changed.data.revision,
17644
+ workspaceRevision: workspace.data.revision,
17645
+ completedSteps: [...workflow.completedSteps],
17646
+ remainingSteps: workflow.steps.slice(workflow.completedSteps.length)
17633
17647
  });
17634
17648
  }
17635
- var exportDigest = (bundle) => sha256({
17636
- schemaVersion: bundle.schemaVersion,
17637
- exportedAt: bundle.exportedAt,
17638
- sourceWorkspaceId: bundle.sourceWorkspaceId,
17639
- task: bundle.task
17640
- });
17641
- // packages/workit-core/src/core/git.ts
17642
- import { execFileSync } from "node:child_process";
17643
- var run = (cwd, args) => {
17644
- try {
17645
- const stdout = execFileSync("git", args, {
17646
- cwd,
17647
- encoding: "utf8",
17648
- stdio: ["pipe", "pipe", "pipe"]
17649
- });
17650
- return { stdout: stdout.trimEnd(), stderr: "", exitCode: 0 };
17651
- } catch (error) {
17652
- const e = error;
17653
- return {
17654
- stdout: (e.stdout ?? "").trimEnd(),
17655
- stderr: (e.stderr ?? "").trimEnd(),
17656
- exitCode: e.status ?? 1
17649
+ function settleAction(input) {
17650
+ if (!input.store || !input.taskRevision || !input.workspaceRevision)
17651
+ return failure("invalid_input", "action settlement preconditions are required");
17652
+ const authority = takeAuthority(input.authority, {
17653
+ owner: input.authorityOwner,
17654
+ store: input.store,
17655
+ root: input.store.root
17656
+ }, input.authorityCaller);
17657
+ if (!authority)
17658
+ return failure("permission_denied", "native settlement authority is not verified");
17659
+ if (!refSchema.safeParse(input.actionRef).success)
17660
+ return failure("invalid_input", "action reference is invalid");
17661
+ if (!authorityMatches(authority, {
17662
+ taskId: input.taskId,
17663
+ workspaceId: authority.workspaceId,
17664
+ decisionId: input.decisionId,
17665
+ actionRef: input.actionRef,
17666
+ taskRevision: input.taskRevision,
17667
+ workspaceRevision: input.workspaceRevision,
17668
+ outcome: input.outcome
17669
+ }))
17670
+ return failure("permission_denied", "native settlement authority is not verified");
17671
+ let outcomeResult = null;
17672
+ const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
17673
+ const workspace = input.store.readWorkspace();
17674
+ if (!workspace.ok)
17675
+ return workspace;
17676
+ if (!workspace.data)
17677
+ return failure("not_found", "workspace not found");
17678
+ if (!authorityMatches(authority, {
17679
+ taskId: input.taskId,
17680
+ workspaceId: workspace.data.id,
17681
+ decisionId: input.decisionId,
17682
+ actionRef: input.actionRef,
17683
+ taskRevision: input.taskRevision,
17684
+ workspaceRevision: input.workspaceRevision,
17685
+ outcome: input.outcome
17686
+ }))
17687
+ return failure("permission_denied", "native settlement authority does not match context");
17688
+ if (workspace.data.revision !== input.workspaceRevision)
17689
+ return failure("revision_conflict", "workspace revision does not match", {
17690
+ expectedWorkspaceRevision: input.workspaceRevision,
17691
+ actualWorkspaceRevision: workspace.data.revision
17692
+ });
17693
+ const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
17694
+ if (!entry)
17695
+ return failure("not_found", "decision not found");
17696
+ if (!authorityDecisionMatches(authority, entry.data))
17697
+ return failure("permission_denied", "native settlement authority does not match decision");
17698
+ if (!provenanceMatches(entry.provenance, authority.provenance))
17699
+ return failure("permission_denied", "native settlement caller does not match approval");
17700
+ if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
17701
+ return failure("permission_denied", "decision binding is invalid");
17702
+ if (entry.data.consumption?.state === "uncertain")
17703
+ return failure("external_outcome_unknown", "previous action outcome is unknown", {
17704
+ operation: "settle_action",
17705
+ outcome: "unknown"
17706
+ });
17707
+ if (entry.data.consumption?.state !== "reserved")
17708
+ return failure("permission_denied", "action is not reserved");
17709
+ if (canonicalJson(entry.data.consumption.actionRef) !== canonicalJson(input.actionRef))
17710
+ return failure("permission_denied", "settlement reference does not match reservation");
17711
+ const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
17712
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: [], completed: [] };
17713
+ if (entry.data.binding.approvedContent && !stored && (parseSteps(entry.data.binding.approvedContent) ?? []).length)
17714
+ return failure("recovery_required", "bounded action progress is missing");
17715
+ const currentStep = input.step ?? workflow.steps[workflow.completed.length];
17716
+ if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
17717
+ return failure("permission_denied", "settlement step is outside the approved workflow");
17718
+ if (input.outcome === "unknown") {
17719
+ outcomeResult = failure("external_outcome_unknown", "external action outcome is unknown", {
17720
+ operation: "settle_action",
17721
+ outcome: "unknown"
17722
+ });
17723
+ const uncertain = {
17724
+ ...entry.data,
17725
+ consumption: { ...entry.data.consumption, state: "uncertain", at: mutation.now }
17726
+ };
17727
+ return success(mutation.revision, null, {
17728
+ ...current,
17729
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: uncertain } : candidate)
17730
+ });
17731
+ }
17732
+ if (input.outcome === "not_started") {
17733
+ const released = { ...entry.data, consumption: null };
17734
+ return success(mutation.revision, null, {
17735
+ ...current,
17736
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: released } : candidate)
17737
+ });
17738
+ }
17739
+ if (currentStep)
17740
+ workflow.completed.push(currentStep);
17741
+ const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
17742
+ const settled = {
17743
+ ...entry.data,
17744
+ consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
17745
+ };
17746
+ const actionProgress = workflow.steps.length ? [
17747
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
17748
+ {
17749
+ decisionId: input.decisionId,
17750
+ steps: [...workflow.steps],
17751
+ completedSteps: [...workflow.completed]
17752
+ }
17753
+ ] : current.actionProgress ?? [];
17754
+ const next = {
17755
+ ...current,
17756
+ actionProgress,
17757
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: settled } : candidate)
17657
17758
  };
17658
- }
17659
- };
17660
- var gitContext = (workspaceRoot, paths = []) => {
17661
- const cwd = workspaceRoot;
17662
- let failure;
17663
- const runHere = (args) => {
17664
- const result = run(cwd, args);
17665
- if (!failure && result.exitCode !== 0) {
17666
- failure = { stderr: result.stderr, exitCode: result.exitCode };
17759
+ if (!complete) {
17760
+ outcomeResult = success(mutation.revision, input.workspaceRevision, {
17761
+ ...entry,
17762
+ data: settled
17763
+ });
17667
17764
  }
17668
- return result.stdout;
17669
- };
17670
- const branch = runHere(["rev-parse", "--abbrev-ref", "HEAD"]) || "unknown";
17671
- const status_short = runHere(["status", "--porcelain"]);
17672
- const staged = [];
17673
- const unstaged = [];
17674
- const untracked = [];
17675
- for (const line of status_short.split(`
17676
- `).filter(Boolean)) {
17677
- const code = line.slice(0, 2);
17678
- const file = line.slice(3);
17679
- if (code === "??") {
17680
- untracked.push(file);
17681
- } else {
17682
- if (code[0] !== " " && code[0] !== "?")
17683
- staged.push(file);
17684
- if (code[1] !== " " && code[1] !== "?")
17685
- unstaged.push(file);
17765
+ return success(mutation.revision, null, next);
17766
+ }, input.native?.now);
17767
+ if (!changed.ok)
17768
+ return changed;
17769
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
17770
+ if (!entry)
17771
+ return failure("recovery_required", "settled decision disappeared");
17772
+ if (outcomeResult)
17773
+ return outcomeResult;
17774
+ return success(changed.data.revision, input.workspaceRevision, entry);
17775
+ }
17776
+ function reconcileAction(input) {
17777
+ if (!input.store || !input.taskRevision || !input.workspaceRevision || !input.evidenceDigest)
17778
+ return failure("invalid_input", "action reconciliation preconditions are required");
17779
+ const authority = takeAuthority(input.authority, { owner: input.authorityOwner, store: input.store, root: input.store.root }, input.authorityCaller);
17780
+ if (!authority || authority.kind !== "reconciliation")
17781
+ return failure("permission_denied", "native reconciliation authority is not verified");
17782
+ if (authority.taskId !== input.taskId || authority.decisionId !== input.decisionId || !sameRef(authority.actionRef, input.actionRef) || authority.taskRevision !== input.taskRevision || authority.workspaceRevision !== input.workspaceRevision || authority.outcome !== input.outcome || authority.evidenceDigest !== input.evidenceDigest || authority.step !== input.step)
17783
+ return failure("permission_denied", "native reconciliation authority is not bound");
17784
+ const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
17785
+ const workspace = input.store.readWorkspace();
17786
+ if (!workspace.ok)
17787
+ return workspace;
17788
+ if (!workspace.data)
17789
+ return failure("not_found", "workspace not found");
17790
+ if (authority.workspaceId !== workspace.data.id)
17791
+ return failure("permission_denied", "native reconciliation workspace does not match");
17792
+ if (workspace.data.revision !== input.workspaceRevision)
17793
+ return failure("revision_conflict", "workspace revision does not match");
17794
+ const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
17795
+ if (!entry || entry.data.purpose !== "action")
17796
+ return failure("not_found", "decision not found");
17797
+ if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
17798
+ return failure("permission_denied", "native reconciliation caller does not match approval");
17799
+ if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
17800
+ return failure("permission_denied", "reconciliation decision binding is invalid");
17801
+ if (entry.data.consumption?.state !== "uncertain")
17802
+ return failure("external_outcome_unknown", "only an uncertain action can be reconciled");
17803
+ if (!sameRef(entry.data.consumption.actionRef, input.actionRef))
17804
+ return failure("permission_denied", "reconciliation reference does not match reservation");
17805
+ const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
17806
+ const workflow = stored ? { steps: [...stored.steps], completed: [...stored.completedSteps] } : { steps: [], completed: [] };
17807
+ const currentStep = input.step ?? workflow.steps[workflow.completed.length];
17808
+ if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
17809
+ return failure("permission_denied", "reconciliation step is outside the approved workflow");
17810
+ if (input.outcome === "not_started") {
17811
+ return success(mutation.revision, null, {
17812
+ ...current,
17813
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: { ...candidate.data, consumption: null } } : candidate)
17814
+ });
17686
17815
  }
17687
- }
17688
- const pathArgs = paths.length ? ["--", ...paths] : [];
17689
- const diff_stat = runHere(["diff", "--stat", ...pathArgs]);
17690
- const cached_stat = runHere(["diff", "--cached", "--stat", ...pathArgs]);
17691
- const stagedSet = new Set(staged);
17692
- const unstagedSet = new Set(unstaged);
17693
- const partial_staged = [...stagedSet].filter((f) => unstagedSet.has(f));
17694
- return {
17695
- workspace_root: cwd,
17696
- branch,
17697
- status_short,
17698
- staged,
17699
- unstaged,
17700
- untracked,
17701
- diff_stat: [diff_stat, cached_stat].filter(Boolean).join(`
17702
- `),
17703
- partial_staged: partial_staged.length > 0,
17704
- partial_staged_files: partial_staged,
17705
- ...failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}
17706
- };
17707
- };
17708
-
17709
- // packages/workit-core/src/core/config.ts
17710
- import {
17711
- copyFileSync,
17712
- cpSync,
17713
- existsSync as existsSync4,
17714
- mkdirSync as mkdirSync2,
17715
- readFileSync as readFileSync4,
17716
- readdirSync as readdirSync3,
17717
- writeFileSync as writeFileSync2
17718
- } from "node:fs";
17719
- import os from "node:os";
17720
- import path7 from "node:path";
17721
-
17722
- // packages/workit-core/src/core/boundary.ts
17723
- import { statSync } from "node:fs";
17724
- var EVENT = {
17725
- initialization: "initialization",
17726
- provenance: "provenance",
17727
- assets: "assets",
17728
- configurationSource: "configuration_source",
17729
- hooks: "hooks",
17730
- mcpConnection: "mcp_connection",
17731
- migration: "migration",
17732
- installSteps: "install_steps",
17733
- uncaughtFailure: "uncaught_failure",
17734
- toolsFailed: "tools_failed",
17735
- doctor: "doctor"
17736
- };
17737
- var errorDetail = (err) => {
17738
- if (err instanceof Error) {
17739
- return { error_name: err.name, error: err.message };
17740
- }
17741
- return { error_name: "unknown", error: String(err) };
17742
- };
17743
- var markSourcesLoaded = (files, loadedAt = Date.now()) => ({
17744
- loadedAt,
17745
- files: [...files]
17746
- });
17747
- var changedSourcesSinceLoad = (marker) => {
17748
- const changed = [];
17749
- for (const file of marker.files) {
17750
- try {
17751
- if (statSync(file).mtimeMs > marker.loadedAt)
17752
- changed.push(file);
17753
- } catch {}
17754
- }
17755
- return changed;
17756
- };
17816
+ if (currentStep)
17817
+ workflow.completed.push(currentStep);
17818
+ const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
17819
+ const nextDecision = {
17820
+ ...entry.data,
17821
+ consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
17822
+ };
17823
+ const actionProgress = workflow.steps.length ? [
17824
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
17825
+ {
17826
+ decisionId: input.decisionId,
17827
+ steps: workflow.steps,
17828
+ completedSteps: workflow.completed
17829
+ }
17830
+ ] : current.actionProgress ?? [];
17831
+ return success(mutation.revision, null, {
17832
+ ...current,
17833
+ actionProgress,
17834
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
17835
+ });
17836
+ }, input.native?.now);
17837
+ if (!changed.ok)
17838
+ return changed;
17839
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
17840
+ return entry ? success(changed.data.revision, input.workspaceRevision, entry) : failure("recovery_required", "reconciled decision disappeared");
17841
+ }
17842
+ var bindingCovers = scopeCovers;
17843
+ var verifyDecisionContent = verifyContentRefs;
17757
17844
 
17758
- // packages/workit-core/src/core/config.ts
17759
- var diagnosticLogger;
17760
- var PRESETS = {
17761
- gitflow: {
17762
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
17763
- protected: ["main", "develop", "master", "prod", "production"]
17764
- },
17765
- "github-flow": { allowed: ["*"], protected: ["main"] },
17766
- "trunk-based": { allowed: ["*"], protected: ["main"] },
17767
- custom: { allowed: [], protected: [] }
17768
- };
17769
- var mergePreset = (preset, input = {}, current = {
17770
- branchPolicy: { preset, allowed: [], protected: [] }
17771
- }) => {
17772
- const defs = PRESETS[preset];
17773
- return {
17774
- preset,
17775
- allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
17776
- protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
17777
- };
17778
- };
17779
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path7.join(process.env.XDG_CONFIG_HOME || path7.join(os.homedir(), ".config"), "workit");
17780
- var migratedDir = null;
17781
- var migrationFailed = false;
17782
- var ensureConfigDir = (dir = resolveConfigDir()) => {
17783
- if (migratedDir === dir)
17784
- return dir;
17785
- if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
17786
- migratedDir = dir;
17787
- return dir;
17788
- }
17789
- const legacy = path7.join(process.env.XDG_CONFIG_HOME || path7.join(os.homedir(), ".config"), "workflow-toolkit");
17790
- if (!existsSync4(legacy)) {
17791
- migratedDir = dir;
17792
- return dir;
17793
- }
17794
- if (!migrationFailed && existsSync4(dir)) {
17795
- migratedDir = dir;
17796
- return dir;
17797
- }
17798
- migrationFailed = false;
17799
- mkdirSync2(dir, { recursive: true });
17800
- diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
17801
- for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
17802
- const src = path7.join(legacy, entry.name);
17803
- const dest = path7.join(dir, entry.name);
17804
- if (existsSync4(dest))
17805
- continue;
17806
- try {
17807
- if (entry.isDirectory())
17808
- cpSync(src, dest, { recursive: true });
17809
- else if (entry.isFile())
17810
- copyFileSync(src, dest);
17811
- } catch (err) {
17812
- migrationFailed = true;
17813
- diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
17814
- console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
17815
- }
17816
- }
17817
- if (!migrationFailed)
17818
- migratedDir = dir;
17819
- return dir;
17820
- };
17821
- var configDir = () => ensureConfigDir();
17822
- var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
17823
- var DEFAULTS = {
17824
- locale: "en",
17825
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
17826
- timezone: "America/Santiago",
17827
- branchPolicy: {
17828
- preset: "gitflow",
17829
- allowed: [...PRESETS.gitflow.allowed],
17830
- protected: [...PRESETS.gitflow.protected]
17831
- },
17832
- commitPolicy: { preset: "conventional" }
17833
- };
17834
- var readSafe = (p) => {
17835
- try {
17836
- return readFileSync4(p, "utf8");
17837
- } catch {
17845
+ // packages/workit-core/src/core/task-evaluation.ts
17846
+ var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
17847
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
17848
+ var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
17849
+ var canonicalPath = (value) => {
17850
+ if (typeof value !== "string" || !value)
17851
+ return null;
17852
+ if (value.includes("\\") || value.split("/").includes(".."))
17853
+ return null;
17854
+ const normalized = path9.posix.normalize(value);
17855
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
17838
17856
  return null;
17839
- }
17857
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
17840
17858
  };
17841
- var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
17842
- var parseConfigResult = (raw, file) => {
17843
- if (raw === null)
17844
- return { status: "missing", path: file };
17845
- let parsed;
17846
- try {
17847
- parsed = JSON.parse(raw);
17848
- } catch {
17849
- return { status: "malformed", path: file, error: `${file} is not valid JSON` };
17850
- }
17851
- if (!isConfigObject(parsed)) {
17852
- return { status: "malformed", path: file, error: `${file} is not a JSON object` };
17853
- }
17854
- const input = parsed;
17855
- const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
17856
- const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
17857
- const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
17859
+ var canonicalScope = (scope) => {
17860
+ const paths = scope.paths.length ? scope.paths : ["."];
17861
+ const normalizedPaths = paths.map(canonicalPath);
17862
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
17863
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
17864
+ return null;
17858
17865
  return {
17859
- status: "valid",
17860
- path: file,
17861
- config: {
17862
- locale,
17863
- localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
17864
- timezone: input.timezone ?? DEFAULTS.timezone,
17865
- branchPolicy: mergePreset(preset, {
17866
- allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
17867
- protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
17868
- }, DEFAULTS),
17869
- commitPolicy: {
17870
- preset: commitPreset,
17871
- ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
17872
- }
17873
- }
17866
+ description: scope.description,
17867
+ paths: [...new Set(normalizedPaths)],
17868
+ exclusions: [...new Set(normalizedExclusions)]
17874
17869
  };
17875
17870
  };
17876
- var readConfigTyped = (dir) => {
17877
- const file = path7.join(dir ?? configDir(), "config.json");
17878
- return parseConfigResult(readSafe(file), file);
17879
- };
17880
- var readConfig = () => {
17881
- const result = readConfigTyped();
17882
- if (result.status === "malformed")
17883
- throw new Error(result.error);
17884
- return result.config ?? DEFAULTS;
17871
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
17872
+ var scopeMatches = (value, scope) => {
17873
+ const normalized = canonicalScope(scope);
17874
+ if (!normalized)
17875
+ return false;
17876
+ const paths = normalized.paths;
17877
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
17885
17878
  };
17886
- var resolveBranchPolicy = (config, workspace) => {
17887
- const wp = workspace?.branchPolicy ?? {};
17888
- const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
17889
- const merged = mergePreset(preset, {
17890
- allowed: wp.allowed,
17891
- protectedNames: wp.protected
17892
- }, config);
17893
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
17894
- return {
17895
- preset,
17896
- allowed,
17897
- protected: new Set(merged.protected.map((p) => p.toLowerCase())),
17898
- integration: wp.integration === "merge" ? "merge" : "pr",
17899
- defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop"
17900
- };
17879
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
17880
+ var compareCodeUnits2 = (left, right) => {
17881
+ const length = Math.min(left.length, right.length);
17882
+ for (let index = 0;index < length; index += 1) {
17883
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
17884
+ if (difference !== 0)
17885
+ return difference;
17886
+ }
17887
+ return left.length - right.length;
17901
17888
  };
17902
- var COMMIT_PRESETS = [
17903
- "conventional",
17904
- "gitmoji",
17905
- "ticket-prefix",
17906
- "freeform",
17907
- "custom",
17908
- "auto"
17909
- ];
17910
-
17911
- // packages/workit-core/src/core/workspaces.ts
17912
- import { readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
17913
- import path8 from "node:path";
17914
- var readWorkspacesResult = (dir = configDir()) => {
17915
- const file = path8.join(dir, "workspaces.json");
17916
- let raw;
17917
- try {
17918
- raw = readFileSync5(file, "utf8");
17919
- } catch {
17920
- return { status: "missing", path: file, entries: [] };
17889
+ var gitPaths = (root) => {
17890
+ const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
17891
+ cwd: root,
17892
+ encoding: "buffer"
17893
+ });
17894
+ if (result.status !== 0) {
17895
+ const stderr = result.stderr?.toString("utf8") ?? "";
17896
+ return {
17897
+ paths: [],
17898
+ uncertain: !/not a git repository/i.test(stderr),
17899
+ git: /not a git repository/i.test(stderr) ? false : true
17900
+ };
17921
17901
  }
17922
- let parsed;
17902
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
17903
+ const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
17904
+ if (stagedDeleted.status !== 0) {
17905
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
17906
+ if (!/not a git repository/i.test(stderr))
17907
+ return { paths, uncertain: true, git: true };
17908
+ } else if (stagedDeleted.stdout) {
17909
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
17910
+ }
17911
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
17912
+ };
17913
+ var walk = (root, directory, output) => {
17914
+ let names;
17923
17915
  try {
17924
- parsed = JSON.parse(raw);
17916
+ names = fs7.readdirSync(directory);
17925
17917
  } catch {
17926
- return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
17918
+ return true;
17927
17919
  }
17928
- if (!isConfigObject(parsed)) {
17929
- return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
17920
+ let uncertain = false;
17921
+ for (const name of names) {
17922
+ if (name === ".git" || name === ".workit")
17923
+ continue;
17924
+ const target = path9.join(directory, name);
17925
+ let stat;
17926
+ try {
17927
+ stat = fs7.lstatSync(target);
17928
+ } catch {
17929
+ uncertain = true;
17930
+ continue;
17931
+ }
17932
+ const item = relative(root, target);
17933
+ output.add(item);
17934
+ if (stat.isDirectory() && !stat.isSymbolicLink())
17935
+ uncertain = walk(root, target, output) || uncertain;
17930
17936
  }
17931
- const list = parsed.workspaces;
17932
- return {
17933
- status: "valid",
17934
- path: file,
17935
- entries: Array.isArray(list) ? list : []
17936
- };
17937
+ return uncertain;
17937
17938
  };
17938
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
17939
- var globToRegExp = (glob) => {
17940
- let out = "";
17941
- for (let i = 0;i < glob.length; i++) {
17942
- const c = glob[i];
17943
- if (c === "*") {
17944
- if (glob[i + 1] === "*") {
17945
- if (glob[i + 2] === "/") {
17946
- if (out === "")
17947
- out += "/?";
17948
- out += "(?:[^/]+/)*";
17949
- i += 2;
17950
- } else {
17951
- if (i + 2 >= glob.length) {
17952
- if (out.endsWith("/")) {
17953
- out = out.slice(0, -1);
17954
- out += "(?:/.*)?";
17955
- } else {
17956
- out += ".*";
17957
- }
17958
- } else {
17959
- out += ".*";
17960
- }
17961
- i++;
17962
- }
17963
- } else {
17964
- out += "[^/]*";
17965
- }
17966
- } else {
17967
- out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
17939
+ var scopeRoots = (root, scope) => {
17940
+ const roots = [];
17941
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
17942
+ const target = path9.resolve(root, item);
17943
+ if (!inside(root, target))
17944
+ 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` }] });
17945
+ let ancestor = target;
17946
+ while (!fs7.existsSync(ancestor) && ancestor !== root)
17947
+ ancestor = path9.dirname(ancestor);
17948
+ try {
17949
+ if (!inside(root, fs7.realpathSync(ancestor)))
17950
+ 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` }] });
17951
+ } catch {
17952
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
17968
17953
  }
17954
+ roots.push(target);
17969
17955
  }
17970
- return new RegExp(`^${out}$`);
17956
+ return success(null, null, roots);
17971
17957
  };
17972
- var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
17973
- var realpathOf = (p) => {
17974
- const real = realpathSync4.native ?? realpathSync4;
17958
+ function captureCandidate(root, scope, environment = []) {
17959
+ let checkout;
17975
17960
  try {
17976
- return real(p);
17961
+ checkout = fs7.realpathSync(root);
17977
17962
  } catch {
17978
- return p;
17963
+ return failure("invalid_input", "candidate root does not exist");
17979
17964
  }
17980
- };
17981
- var canonicalGlob = (glob) => {
17982
- const m = /[*?[\]{]/.exec(glob);
17983
- const prefix = m ? glob.slice(0, m.index) : glob;
17984
- const rest = m ? glob.slice(m.index) : "";
17985
- if (!prefix)
17986
- return glob;
17987
- const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
17988
- return real + (prefix.endsWith("/") ? "/" : "") + rest;
17989
- };
17990
- var resolveWorkspaceFrom = (cwd, dir) => {
17991
- const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
17992
- for (const entry of loadWorkspacesFrom(dir)) {
17993
- if (!entry || typeof entry !== "object")
17965
+ const normalizedScope = canonicalScope(scope);
17966
+ if (!normalizedScope)
17967
+ return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
17968
+ const roots = scopeRoots(checkout, normalizedScope);
17969
+ if (!roots.ok)
17970
+ return roots;
17971
+ const git = gitPaths(checkout);
17972
+ const names = new Set;
17973
+ let uncertain = git.uncertain;
17974
+ for (const target of roots.data) {
17975
+ const item = relative(checkout, target);
17976
+ if (item === ".git" || item === ".workit")
17994
17977
  continue;
17995
- const ws = entry;
17996
- if (typeof ws.glob !== "string" || !ws.glob)
17978
+ names.add(item);
17979
+ if (!fs7.existsSync(target))
17997
17980
  continue;
17998
- const glob = ws.glob.replaceAll("\\", "/");
17999
- const canonical = canonicalGlob(glob);
18000
- for (const target of targets) {
18001
- if (matchWorkspace(glob, target))
18002
- return ws;
18003
- if (canonical !== glob && matchWorkspace(canonical, target))
18004
- return ws;
17981
+ let stat;
17982
+ try {
17983
+ stat = fs7.lstatSync(target);
17984
+ } catch {
17985
+ uncertain = true;
17986
+ continue;
17987
+ }
17988
+ if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
17989
+ uncertain = walk(checkout, target, names) || uncertain;
17990
+ }
17991
+ for (const item of git.paths)
17992
+ if (scopeMatches(item, normalizedScope))
17993
+ names.add(item);
17994
+ const files = [];
17995
+ let completeness = uncertain ? "uncertain" : "known";
17996
+ for (const item of [...names].sort()) {
17997
+ if (!scopeMatches(item, normalizedScope))
17998
+ continue;
17999
+ const target = path9.join(checkout, item);
18000
+ try {
18001
+ const stat = fs7.lstatSync(target);
18002
+ if (stat.isSymbolicLink()) {
18003
+ const link = fs7.readlinkSync(target);
18004
+ files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
18005
+ } else if (stat.isFile()) {
18006
+ files.push({
18007
+ path: item,
18008
+ kind: "file",
18009
+ digest: digestBytes3(fs7.readFileSync(target)),
18010
+ executable: (stat.mode & 73) !== 0
18011
+ });
18012
+ } else if (!stat.isDirectory()) {
18013
+ completeness = "uncertain";
18014
+ } else if (git.git && git.paths.includes(item)) {
18015
+ completeness = "uncertain";
18016
+ }
18017
+ } catch (error) {
18018
+ if (error?.code === "ENOENT") {
18019
+ files.push({ path: item, kind: "absent", digest: null, executable: null });
18020
+ } else
18021
+ completeness = "uncertain";
18005
18022
  }
18006
18023
  }
18007
- return null;
18024
+ const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
18025
+ name,
18026
+ value: environment[name] ?? null
18027
+ }));
18028
+ const namesByEnvironment = supplied.map(({ name }) => name);
18029
+ if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
18030
+ return failure("invalid_input", "candidate environment names must be unique and non-empty");
18031
+ const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
18032
+ if (values.some(({ value }) => value === null))
18033
+ completeness = "uncertain";
18034
+ const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
18035
+ if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
18036
+ completeness = "uncertain";
18037
+ const head = headResult.status === 0 ? headResult.stdout.trim() : null;
18038
+ const partial = {
18039
+ id: "0".repeat(64),
18040
+ scope: normalizedScope,
18041
+ completeness,
18042
+ files,
18043
+ environment: values,
18044
+ head
18045
+ };
18046
+ const candidate = { ...partial, id: candidateDigest(partial) };
18047
+ const parsed = candidateSchema.safeParse(candidate);
18048
+ return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
18049
+ }
18050
+ var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
18051
+ var changedPaths = (before, after) => {
18052
+ const paths = new Set([...before.files, ...after.files].map((file) => file.path));
18053
+ const left = fileMap(before);
18054
+ const right = fileMap(after);
18055
+ const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
18056
+ if (before.head !== after.head)
18057
+ changed.push(".");
18058
+ if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
18059
+ changed.push(".");
18060
+ return changed;
18008
18061
  };
18009
- var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
18010
-
18011
- // packages/workit-core/src/core/vcs-config.ts
18012
- import fs7 from "node:fs";
18013
- import path9 from "node:path";
18014
- import { spawnSync as spawnSync2 } from "node:child_process";
18015
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
18016
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
18017
- var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
18018
- var remoteProvider = (cwd) => {
18019
- const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
18020
- if (r.status !== 0)
18062
+ var sessionFromRef = (value) => {
18063
+ if (typeof value !== "object" || value === null || value.kind !== "host")
18021
18064
  return null;
18022
- const url = (r.stdout ?? "").trim();
18023
- if (/github\.com[:/]/.test(url))
18024
- return "github";
18025
- if (/gitlab\.com[:/]/.test(url))
18026
- return "gitlab";
18027
- return null;
18065
+ return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
18028
18066
  };
18029
- var readVcsConfig = () => {
18030
- const file = vcsConfigPath();
18031
- let raw;
18032
- try {
18033
- raw = fs7.readFileSync(file, "utf8");
18034
- } catch {
18035
- return { status: "missing", path: file, config: {} };
18036
- }
18037
- let parsed;
18038
- try {
18039
- parsed = JSON.parse(raw);
18040
- } catch {
18041
- return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
18042
- }
18043
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
18044
- return { status: "valid", path: file, config: parsed };
18045
- }
18046
- return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
18067
+ var sameSession = (left, right) => {
18068
+ const other = sessionFromRef(right);
18069
+ return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
18047
18070
  };
18048
- function vcsConfig(mode, cwd) {
18049
- const ws = resolveWorkspace(vcsCwd(cwd));
18050
- const wsVcs = ws?.vcs ?? {};
18051
- const wsYt = ws?.youtrack ?? {};
18052
- const wsIssues = ws?.issues ?? {};
18053
- const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
18054
- const root = vcsCwd(cwd);
18055
- const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
18056
- const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
18057
- const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
18058
- const wp = ws?.branchPolicy ?? {};
18059
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
18060
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
18061
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
18062
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
18063
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
18064
- let issuesProvider = null;
18065
- let linkOnPr = null;
18066
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
18067
- issuesProvider = "github";
18068
- linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
18069
- }
18070
- if (mode === "resolve") {
18071
- if (cfgStatus === "malformed")
18072
- return { ok: false, error: cfgError, configPath: cfgPath };
18071
+ var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
18072
+ function evaluateEvidence(task, candidate) {
18073
+ const current = candidate ?? task.candidates.at(-1) ?? null;
18074
+ return task.evidence.map((entry) => {
18075
+ const evidence = entry.data;
18076
+ if (evidence.result === "failed")
18077
+ return {
18078
+ evidenceId: entry.id,
18079
+ status: "failed",
18080
+ reason: "failed evidence remains historical"
18081
+ };
18082
+ if (evidence.kind === "check" || evidence.kind === "review") {
18083
+ if (!evidence.beforeCandidateId || !evidence.candidateId)
18084
+ return {
18085
+ evidenceId: entry.id,
18086
+ status: "stale",
18087
+ reason: "check or review evidence lacks start and end candidates"
18088
+ };
18089
+ if (evidence.beforeCandidateId !== evidence.candidateId)
18090
+ return {
18091
+ evidenceId: entry.id,
18092
+ status: "stale",
18093
+ reason: "candidate changed during the check or review"
18094
+ };
18095
+ }
18096
+ if (!evidence.candidateId || !current)
18097
+ return {
18098
+ evidenceId: entry.id,
18099
+ status: evidence.result,
18100
+ reason: "evidence has no candidate binding"
18101
+ };
18102
+ const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
18103
+ if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
18104
+ return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
18105
+ if (observed.id === current.id)
18106
+ return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
18107
+ const scopes = evidenceScopes(task, evidence.requirementIds);
18108
+ const changed = changedPaths(observed, current);
18109
+ const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
18110
+ if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
18111
+ return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
18073
18112
  return {
18074
- ok: true,
18075
- workspace_name: ws?.name ?? null,
18076
- provider,
18077
- defaultTargetBranch: defaultTarget,
18078
- link_issues: linkIssues,
18079
- youtrack_base_url: youtrackBaseUrl,
18080
- issues_provider: issuesProvider,
18081
- link_on_pr: linkOnPr
18113
+ evidenceId: entry.id,
18114
+ status: evidence.result,
18115
+ reason: "candidate changed outside evidence scope"
18082
18116
  };
18083
- }
18084
- if (cfgStatus === "malformed") {
18085
- return { ok: false, error: cfgError, configPath: cfgPath };
18086
- }
18087
- if (cfgStatus === "missing") {
18088
- return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
18089
- }
18090
- if (provider === null) {
18117
+ });
18118
+ }
18119
+ var scopeCovers2 = scopeCovers;
18120
+ var checkPin = (assignment, beforeCandidateId, candidateId) => {
18121
+ const pin = assignment.candidateId ?? null;
18122
+ if (pin === null)
18123
+ return true;
18124
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
18125
+ };
18126
+ var resolveBinding = (evidence, pin, currentCandidateId) => {
18127
+ const bound = evidence.kind === "check" || evidence.kind === "review";
18128
+ const fallback = bound ? pin ?? currentCandidateId : null;
18129
+ return {
18130
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
18131
+ candidateId: evidence.candidateId ?? fallback
18132
+ };
18133
+ };
18134
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
18135
+ 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")));
18136
+ 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 }));
18137
+ var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
18138
+ if (kind !== "passed")
18139
+ return false;
18140
+ if (dimension === "testing" || dimension === "verification")
18141
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
18142
+ if (dimension === "review")
18143
+ return evidenceKind === "review";
18144
+ if (dimension === "investigation" || dimension === "challenge")
18145
+ return evidenceKind === "investigation";
18146
+ if (dimension === "artifacts" || dimension === "continuity")
18147
+ return evidenceKind === "artifact";
18148
+ return false;
18149
+ };
18150
+ var expectedKinds = (dimension, ruleId) => {
18151
+ if (dimension === "testing")
18152
+ return "check";
18153
+ if (dimension === "verification")
18154
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
18155
+ if (dimension === "review")
18156
+ return "review";
18157
+ if (dimension === "investigation" || dimension === "challenge")
18158
+ return "investigation";
18159
+ if (dimension === "artifacts" || dimension === "continuity")
18160
+ return "artifact";
18161
+ if (dimension === "decisions")
18162
+ return "decision";
18163
+ return "worker report";
18164
+ };
18165
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
18166
+ const evidence = evaluateEvidence(task, candidate);
18167
+ if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
18168
+ return task.policy.requirements.map((requirement) => ({
18169
+ requirementId: requirement.id,
18170
+ status: "unsatisfied",
18171
+ evidenceIds: [],
18172
+ decisionIds: [],
18173
+ reason: "stored policy version is unsupported; reassessment is required"
18174
+ }));
18175
+ return (task.policy?.requirements ?? []).map((requirement) => {
18176
+ const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
18177
+ const passed = related.filter(({ entry, evaluation }) => {
18178
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
18179
+ return false;
18180
+ if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
18181
+ return false;
18182
+ if (requirement.dimension !== "review")
18183
+ return true;
18184
+ const reviewSession = sessionFromRef(entry.data.reviewContext);
18185
+ const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
18186
+ 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));
18187
+ return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
18188
+ });
18189
+ if (passed.length) {
18190
+ const currentId = candidate?.id ?? null;
18191
+ 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;
18192
+ if (withRed.length)
18193
+ return {
18194
+ requirementId: requirement.id,
18195
+ status: "satisfied",
18196
+ evidenceIds: withRed.map(({ entry }) => entry.id),
18197
+ decisionIds: [],
18198
+ reason: "fresh applicable evidence passed"
18199
+ };
18200
+ if (requirement.dimension === "testing")
18201
+ return {
18202
+ requirementId: requirement.id,
18203
+ status: "unsatisfied",
18204
+ evidenceIds: [],
18205
+ decisionIds: [],
18206
+ reason: "testing requirement has GREEN evidence but no preceding RED"
18207
+ };
18208
+ }
18209
+ if (requirement.dimension === "delegation") {
18210
+ const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
18211
+ const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
18212
+ if (reconciled)
18213
+ return {
18214
+ requirementId: requirement.id,
18215
+ status: "satisfied",
18216
+ evidenceIds: [],
18217
+ decisionIds: [],
18218
+ reason: "bounded helper completed and its result is recorded"
18219
+ };
18220
+ }
18221
+ const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
18222
+ if (decisions.length)
18223
+ return {
18224
+ requirementId: requirement.id,
18225
+ status: "satisfied",
18226
+ evidenceIds: [],
18227
+ decisionIds: decisions.map((decision) => decision.id),
18228
+ reason: "an applicable approved or stated decision satisfies the requirement"
18229
+ };
18230
+ const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
18231
+ if (limitations.length)
18232
+ return {
18233
+ requirementId: requirement.id,
18234
+ status: "accepted_limitation",
18235
+ evidenceIds: [],
18236
+ decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
18237
+ reason: "an applicable approved limitation permits the missing evidence"
18238
+ };
18239
+ const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
18240
+ if (!unavailable && related.length > 0 && !passed.length)
18241
+ return {
18242
+ requirementId: requirement.id,
18243
+ status: "unsatisfied",
18244
+ evidenceIds: related.map(({ entry }) => entry.id),
18245
+ decisionIds: [],
18246
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
18247
+ };
18091
18248
  return {
18092
- ok: false,
18093
- error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
18094
- configPath: cfgPath
18095
- };
18096
- }
18097
- const prov = cfg[provider] ?? {};
18098
- const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
18099
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path9.join(configDir(), `${provider}.token`));
18100
- const tokenPath = path9.resolve(tokenFile);
18101
- let tokenOk = false;
18102
- if (fs7.existsSync(tokenPath)) {
18103
- const token = fs7.readFileSync(tokenPath, "utf8").trim();
18104
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
18105
- tokenOk = !placeholder;
18106
- }
18107
- const out = {
18108
- ok: true,
18109
- configPath: path9.resolve(cfgPath),
18110
- provider,
18111
- defaultTargetBranch: defaultTarget,
18112
- pr: cfg.pr ?? {},
18113
- tokenPath,
18114
- tokenPresent: fs7.existsSync(tokenPath),
18115
- tokenReady: tokenOk,
18116
- workspace_name: ws?.name ?? null,
18117
- link_issues: linkIssues,
18118
- youtrack_base_url: youtrackBaseUrl,
18119
- issues_provider: issuesProvider,
18120
- link_on_pr: linkOnPr
18121
- };
18122
- if (provider === "gitlab") {
18123
- out.gitlab = {
18124
- host: prov.host ?? "gitlab.com",
18125
- apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
18249
+ requirementId: requirement.id,
18250
+ status: unavailable ? "unavailable" : "unsatisfied",
18251
+ evidenceIds: related.map(({ entry }) => entry.id),
18252
+ decisionIds: [],
18253
+ reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
18126
18254
  };
18127
- } else if (provider === "github") {
18128
- out.github = { host: prov.host ?? "github.com" };
18129
- }
18130
- if (mode === "summary")
18131
- delete out.tokenPath;
18132
- return out;
18133
- }
18134
- function mergedPrStyle(limit = 6, cwd) {
18135
- const cfg = vcsConfig("load", cwd);
18136
- if (!cfg.ok || !cfg.tokenReady)
18137
- return { ok: false, error: "vcs not configured" };
18138
- const provider = cfg.provider;
18139
- const token = fs7.readFileSync(cfg.tokenPath, "utf8").trim();
18140
- const examples = [];
18141
- const descInfo = (desc, caseInsensitiveNotes = false) => ({
18142
- hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
18143
- sections: desc.split(`
18144
- `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
18145
- descriptionPreview: desc.slice(0, 600)
18146
18255
  });
18147
- if (provider === "gitlab") {
18148
- const remote = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
18149
- if (remote.status !== 0)
18150
- return { ok: false, error: "no origin remote" };
18151
- const url = (remote.stdout ?? "").trim();
18152
- const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
18153
- if (!m)
18154
- return { ok: false, error: "not a gitlab.com origin" };
18155
- const project = m[1];
18156
- const env = { ...process.env, GITLAB_TOKEN: token };
18157
- const run = (args) => spawnSync2("glab", ["api", ...args], { cwd, encoding: "utf8", env });
18158
- let r = run([
18159
- `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
18160
- ]);
18161
- if (r.status !== 0) {
18162
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
18163
- }
18164
- if (r.status !== 0)
18165
- return { ok: false, error: "could not list merge requests" };
18166
- for (const mr of JSON.parse(r.stdout ?? "[]")) {
18167
- const desc = String(mr.description ?? "").trim();
18168
- examples.push({
18169
- title: mr.title,
18170
- url: mr.web_url,
18171
- squash: mr.squash,
18172
- ...descInfo(desc, true)
18173
- });
18174
- }
18175
- } else if (provider === "github") {
18176
- const r = spawnSync2("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
18177
- if (r.status !== 0)
18178
- return { ok: false, error: "could not list pull requests" };
18179
- for (const pr of JSON.parse(r.stdout ?? "[]")) {
18180
- const desc = String(pr.body ?? "").trim();
18181
- examples.push({ title: pr.title, url: pr.url, ...descInfo(desc) });
18256
+ }
18257
+ function evaluateClosure(requestedOutcome, view) {
18258
+ if (requestedOutcome !== "stopped" && !view.task.policy)
18259
+ return failure("requirements_unsatisfied", "task has not been assessed");
18260
+ if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
18261
+ return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
18262
+ const evaluations = view.requirements;
18263
+ const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
18264
+ if (requestedOutcome !== "stopped" && openFindings.length)
18265
+ return failure("requirements_unsatisfied", "open findings must be resolved before closure");
18266
+ if (requestedOutcome !== "stopped") {
18267
+ const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
18268
+ for (const entry of view.task.findings) {
18269
+ if (entry.data.disposition === "fixed") {
18270
+ const verified = entry.data.resolution?.evidenceIds.some((id) => {
18271
+ const evidence = view.task.evidence.find((item) => item.id === id);
18272
+ if (!evidence)
18273
+ return false;
18274
+ return findingVerificationPasses(entry.data.candidateId, {
18275
+ kind: evidence.data.kind,
18276
+ candidateId: evidence.data.candidateId,
18277
+ status: evidenceById.get(id) ?? "missing"
18278
+ });
18279
+ });
18280
+ if (!verified)
18281
+ return failure("requirements_unsatisfied", "fixed findings require current verification");
18282
+ }
18283
+ if (entry.data.disposition === "deferred") {
18284
+ const valid = entry.data.resolution?.decisionIds.some((id) => {
18285
+ const decisionEntry = view.task.decisions.find((item) => item.id === id);
18286
+ const decision = decisionEntry?.data;
18287
+ return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
18288
+ });
18289
+ if (!valid)
18290
+ return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
18291
+ }
18182
18292
  }
18183
18293
  }
18184
- return {
18185
- ok: true,
18186
- provider,
18187
- count: examples.length,
18188
- styleHints: [
18189
- "Prefer ## Summary bullets + ## Validation or ## Test plan only",
18190
- "Do not add ## Notes with branch names, commit counts, or diff stats",
18191
- "Do not paste commit log or diff stat into the body"
18192
- ],
18193
- examples
18194
- };
18294
+ const blocking = evaluations.filter((item) => {
18295
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
18296
+ return false;
18297
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
18298
+ return !requirement || requirement.before === "close";
18299
+ });
18300
+ if (requestedOutcome !== "stopped" && blocking.length)
18301
+ return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
18302
+ requirementIds: blocking.map((item) => item.requirementId),
18303
+ requirements: blocking.map((item) => {
18304
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
18305
+ return {
18306
+ requirementId: item.requirementId,
18307
+ ruleId: requirement?.ruleId ?? "unknown",
18308
+ reason: item.reason,
18309
+ satisfaction: requirement?.satisfaction ?? "",
18310
+ dependentAction: requirement?.dependentAction ?? null
18311
+ };
18312
+ })
18313
+ });
18314
+ const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
18315
+ if (requestedOutcome === "verified" && accepted.length)
18316
+ return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
18317
+ requirementIds: accepted.map((item) => item.requirementId)
18318
+ });
18319
+ const evidenceIds = [
18320
+ ...new Set([
18321
+ ...evaluations.flatMap((item) => item.evidenceIds),
18322
+ ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
18323
+ ])
18324
+ ];
18325
+ const decisionIds = [
18326
+ ...new Set([
18327
+ ...evaluations.flatMap((item) => item.decisionIds),
18328
+ ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
18329
+ ])
18330
+ ];
18331
+ return success(null, null, {
18332
+ outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
18333
+ evidenceIds,
18334
+ decisionIds,
18335
+ requirementIds: evaluations.map((item) => item.requirementId)
18336
+ });
18195
18337
  }
18196
18338
 
18197
- // packages/workit-core/src/core/branch.ts
18198
- var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
18339
+ // packages/workit-core/src/core/task-context.ts
18340
+ function reconcileResume(view, observations = []) {
18341
+ if (observations.length > 0)
18342
+ return failure("permission_denied", "worker observations require native host verification");
18343
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
18344
+ if (!candidate.ok)
18345
+ return candidate;
18346
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
18347
+ const blockers = [...view.task.progress.blockers];
18348
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
18349
+ blockers.push({
18350
+ reason: "worker state requires reconciliation",
18351
+ dependentAction: "resume",
18352
+ refs: []
18353
+ });
18354
+ return success(null, null, {
18355
+ candidate: candidate.data,
18356
+ staleEvidenceIds,
18357
+ workerUpdates: [],
18358
+ blockers,
18359
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
18360
+ });
18361
+ }
18362
+ var exportDigest = (bundle) => sha256({
18363
+ schemaVersion: bundle.schemaVersion,
18364
+ exportedAt: bundle.exportedAt,
18365
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
18366
+ task: bundle.task
18367
+ });
18199
18368
  // packages/workit-core/src/core/external-action.ts
18200
18369
  var externalActionSchema = discriminatedUnion("operation", [
18201
18370
  object({
@@ -18229,6 +18398,13 @@ var externalActionSchema = discriminatedUnion("operation", [
18229
18398
  babysit: boolean2().optional()
18230
18399
  }).strict()
18231
18400
  }).strict(),
18401
+ object({
18402
+ operation: literal("hosting.merge"),
18403
+ payload: object({
18404
+ target_branch: string2().min(1).optional(),
18405
+ source_branch: string2().min(1).optional()
18406
+ }).strict()
18407
+ }).strict(),
18232
18408
  object({
18233
18409
  operation: literal("youtrack.update"),
18234
18410
  payload: object({
@@ -19342,7 +19518,10 @@ class WorkitCore {
19342
19518
  observeDecision(request, observation) {
19343
19519
  return this.recordDecision(request, observation, true);
19344
19520
  }
19345
- recordDecision(request, nativeObservation, nativeRequired = false) {
19521
+ observeStandingDecision(request) {
19522
+ return this.recordDecision(request, undefined, true, true);
19523
+ }
19524
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
19346
19525
  const root = this.contextRootError();
19347
19526
  if (!root.ok)
19348
19527
  return root;
@@ -19357,8 +19536,10 @@ class WorkitCore {
19357
19536
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
19358
19537
  if (stated && !input.binding?.statedChoice)
19359
19538
  return failure("invalid_input", "stated choices require binding.statedChoice");
19360
- if (nativeRequired && nativeObservation === undefined && !stated)
19539
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
19361
19540
  return failure("permission_denied", "native decision observation is required");
19541
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
19542
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
19362
19543
  const task = this.store.readTask(input.taskId);
19363
19544
  if (!task.ok)
19364
19545
  return task;
@@ -19390,7 +19571,10 @@ class WorkitCore {
19390
19571
  consumption: null
19391
19572
  };
19392
19573
  const data = { ...base, digest: decisionDigest(base) };
19393
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
19574
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
19575
+ if (standingApproval && !standingApproval.ok)
19576
+ return standingApproval;
19577
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
19394
19578
  observation: nativeObservation,
19395
19579
  expected: {
19396
19580
  taskId: task.data.id,
@@ -19421,7 +19605,7 @@ class WorkitCore {
19421
19605
  const entry = {
19422
19606
  id: newId(),
19423
19607
  recordedAt: mutation.now,
19424
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
19608
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
19425
19609
  data
19426
19610
  };
19427
19611
  return success(mutation.revision, null, {
@@ -20567,7 +20751,7 @@ var SUPPORT_MATRIX = {
20567
20751
  };
20568
20752
 
20569
20753
  // packages/workit-mcp/src/server.ts
20570
- import { existsSync as existsSync7, readFileSync as readFileSync7, realpathSync as realpathSync7 } from "node:fs";
20754
+ import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync7 } from "node:fs";
20571
20755
  import path14 from "node:path";
20572
20756
 
20573
20757
  // node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
@@ -26502,7 +26686,7 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
26502
26686
 
26503
26687
  // packages/workit-core/src/core/repo-context.ts
26504
26688
  import { spawnSync as spawnSync4 } from "node:child_process";
26505
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26689
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26506
26690
  import path12 from "node:path";
26507
26691
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26508
26692
  try {
@@ -26672,7 +26856,7 @@ var packageScripts = (cwd, keys) => {
26672
26856
  if (!existsSync6(pkgPath))
26673
26857
  return [];
26674
26858
  try {
26675
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
26859
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
26676
26860
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
26677
26861
  } catch {
26678
26862
  return [];
@@ -26706,7 +26890,7 @@ var documentationFiles = (cwd) => {
26706
26890
  var readTrimmed = (file, maxLines) => {
26707
26891
  if (!existsSync6(file))
26708
26892
  return "";
26709
- const lines = readFileSync6(file, "utf8").split(`
26893
+ const lines = readFileSync7(file, "utf8").split(`
26710
26894
  `);
26711
26895
  return lines.slice(0, maxLines).join(`
26712
26896
  `);
@@ -26894,7 +27078,7 @@ function docsRefreshContext(root, range) {
26894
27078
  const pkgPath = path12.join(cwd, "package.json");
26895
27079
  if (existsSync6(pkgPath)) {
26896
27080
  try {
26897
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
27081
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
26898
27082
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
26899
27083
  `;
26900
27084
  } catch {}
@@ -27592,7 +27776,7 @@ var TOOL_CAPABILITIES = [
27592
27776
  ];
27593
27777
  var VERSION = (() => {
27594
27778
  try {
27595
- const packageJson = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8"));
27779
+ const packageJson = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8"));
27596
27780
  return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
27597
27781
  } catch {
27598
27782
  return "0.0.0";