@brainervirus/workit-codex 1.0.11 → 1.0.12

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.
@@ -7355,6 +7355,89 @@ var refSchema = discriminatedUnion("kind", [
7355
7355
  object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
7356
7356
  object({ kind: literal("external"), url: text.url() }).strict()
7357
7357
  ]);
7358
+ var rewriteUnknown = (value, map) => {
7359
+ if (Array.isArray(value)) {
7360
+ const out = [];
7361
+ for (const item of value) {
7362
+ const rewritten = rewriteUnknown(item, map);
7363
+ if (!rewritten.drop)
7364
+ out.push(rewritten.value);
7365
+ }
7366
+ return { drop: false, value: out };
7367
+ }
7368
+ if (typeof value === "object" && value !== null) {
7369
+ if (refSchema.safeParse(value).success) {
7370
+ const mapped = map(value);
7371
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7372
+ }
7373
+ const out = {};
7374
+ for (const [key, item] of Object.entries(value)) {
7375
+ const rewritten = rewriteUnknown(item, map);
7376
+ out[key] = rewritten.drop ? null : rewritten.value;
7377
+ }
7378
+ return { drop: false, value: out };
7379
+ }
7380
+ return { drop: false, value };
7381
+ };
7382
+ var rewriteNode = (value, schema, map) => {
7383
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && refSchema.safeParse(value).success) {
7384
+ const mapped = map(value);
7385
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7386
+ }
7387
+ let node = schema;
7388
+ while (node instanceof ZodNullable || node instanceof ZodOptional || node instanceof ZodDefault) {
7389
+ node = node.unwrap();
7390
+ }
7391
+ if (node === refSchema) {
7392
+ if (!refSchema.safeParse(value).success)
7393
+ return { drop: false, value };
7394
+ const mapped = map(value);
7395
+ return mapped === null ? { drop: true } : { drop: false, value: mapped };
7396
+ }
7397
+ if (node instanceof ZodArray) {
7398
+ if (!Array.isArray(value))
7399
+ return { drop: false, value };
7400
+ const out = [];
7401
+ for (const item of value) {
7402
+ const rewritten = rewriteNode(item, node.element, map);
7403
+ if (!rewritten.drop)
7404
+ out.push(rewritten.value);
7405
+ }
7406
+ return { drop: false, value: out };
7407
+ }
7408
+ if (node instanceof ZodObject) {
7409
+ if (typeof value !== "object" || value === null || Array.isArray(value))
7410
+ return { drop: false, value };
7411
+ const shape = node.shape;
7412
+ const record = value;
7413
+ const out = {};
7414
+ for (const [key, item] of Object.entries(record)) {
7415
+ if (!(key in shape)) {
7416
+ const scrubbed = rewriteUnknown(item, map);
7417
+ out[key] = scrubbed.drop ? null : scrubbed.value;
7418
+ continue;
7419
+ }
7420
+ const child = shape[key];
7421
+ const rewritten = rewriteNode(item, child, map);
7422
+ if (rewritten.drop) {
7423
+ if (child instanceof ZodOptional || child instanceof ZodDefault)
7424
+ continue;
7425
+ if (child instanceof ZodNullable) {
7426
+ out[key] = null;
7427
+ continue;
7428
+ }
7429
+ return { drop: true };
7430
+ }
7431
+ out[key] = rewritten.value;
7432
+ }
7433
+ return { drop: false, value: out };
7434
+ }
7435
+ return { drop: false, value };
7436
+ };
7437
+ var rewriteRecordRefs = (value, schema, map) => {
7438
+ const rewritten = rewriteNode(value, schema, map);
7439
+ return rewritten.drop ? null : rewritten.value;
7440
+ };
7358
7441
  var provenanceSchema = object({
7359
7442
  kind: _enum(["host_observed", "agent_reported", "imported"]),
7360
7443
  host: hostSchema,
@@ -9005,11 +9088,11 @@ class TaskStore {
9005
9088
  return workspace;
9006
9089
  if (!workspace.data)
9007
9090
  return failure("not_found", "workspace not found");
9008
- const expectedTaskRevision = input.expectedTaskRevision ?? input.expectedRevision;
9009
- if (!expectedTaskRevision)
9091
+ const expected = input.expectedRevision;
9092
+ if (!expected)
9010
9093
  return failure("invalid_input", "task revision is required");
9011
- if (task.data.revision !== expectedTaskRevision)
9012
- return this.conflict(expectedTaskRevision, task.data.revision);
9094
+ if (task.data.revision !== expected)
9095
+ return this.conflict(expected, task.data.revision);
9013
9096
  if (workspace.data.revision !== input.expectedWorkspaceRevision)
9014
9097
  return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
9015
9098
  const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
@@ -9543,6 +9626,7 @@ var same = (left, right) => {
9543
9626
  };
9544
9627
  var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9545
9628
  var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
9629
+ var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
9546
9630
  var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
9547
9631
  var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9548
9632
  const code = char.charCodeAt(0);
@@ -9604,6 +9688,36 @@ var trustedAuthority = (kind, provenance, expected, binding, caller) => {
9604
9688
  verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
9605
9689
  return token;
9606
9690
  };
9691
+ var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
9692
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9693
+ var normalizeChainSteps = (steps) => {
9694
+ if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
9695
+ return null;
9696
+ const normalized = [];
9697
+ for (const step of steps) {
9698
+ if (typeof step === "string") {
9699
+ if (!step.trim())
9700
+ return null;
9701
+ normalized.push({ kind: "commit", message: step });
9702
+ continue;
9703
+ }
9704
+ if (!isRecord(step))
9705
+ return null;
9706
+ if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
9707
+ normalized.push({ kind: "branch", target: step.branch });
9708
+ continue;
9709
+ }
9710
+ if (step.pr === true && Object.keys(step).length === 1) {
9711
+ normalized.push({ kind: "pr" });
9712
+ continue;
9713
+ }
9714
+ return null;
9715
+ }
9716
+ const keys = normalized.map(chainStepKey);
9717
+ if (new Set(keys).size !== keys.length)
9718
+ return null;
9719
+ return normalized;
9720
+ };
9607
9721
  var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
9608
9722
  var parseSteps = (content) => {
9609
9723
  let value;
@@ -9615,7 +9729,10 @@ var parseSteps = (content) => {
9615
9729
  if (typeof value !== "object" || value === null || !("steps" in value))
9616
9730
  return [];
9617
9731
  const steps = value.steps;
9618
- return Array.isArray(steps) && steps.every((step) => typeof step === "string") ? steps : null;
9732
+ if (!Array.isArray(steps))
9733
+ return null;
9734
+ const normalized = normalizeChainSteps(steps);
9735
+ return normalized;
9619
9736
  };
9620
9737
  var decisionMatches = (entry, purpose, binding) => {
9621
9738
  const decision = entry.data;
@@ -9806,14 +9923,12 @@ var validateAction = (store, task, workspaceId, input, authority) => {
9806
9923
  return content;
9807
9924
  const requestedSteps = parseSteps(decision.binding.approvedContent);
9808
9925
  if (requestedSteps === null)
9809
- return failure("invalid_input", "bounded action steps must be a string array");
9810
- if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps))
9926
+ return failure("invalid_input", "bounded action steps must be a unique non-empty array");
9927
+ if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
9811
9928
  return failure("permission_denied", "bounded action steps do not match approved content");
9812
- if (requestedSteps.some((step) => !step || typeof step !== "string") || new Set(requestedSteps).size !== requestedSteps.length)
9813
- return failure("invalid_input", "bounded action steps must be unique and non-empty");
9814
9929
  const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
9815
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps, completed: [] };
9816
- if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps))
9930
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
9931
+ if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
9817
9932
  return failure("permission_denied", "bounded action scope changed");
9818
9933
  return success(null, null, { entry, workflow });
9819
9934
  };
@@ -10368,13 +10483,28 @@ function evaluateEvidence(task, candidate) {
10368
10483
  });
10369
10484
  }
10370
10485
  var scopeCovers2 = scopeCovers;
10486
+ var checkPin = (assignment, beforeCandidateId, candidateId) => {
10487
+ const pin = assignment.candidateId ?? null;
10488
+ if (pin === null)
10489
+ return true;
10490
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
10491
+ };
10492
+ var resolveBinding = (evidence, pin, currentCandidateId) => {
10493
+ const bound = evidence.kind === "check" || evidence.kind === "review";
10494
+ const fallback = bound ? pin ?? currentCandidateId : null;
10495
+ return {
10496
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
10497
+ candidateId: evidence.candidateId ?? fallback
10498
+ };
10499
+ };
10500
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
10371
10501
  var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
10372
10502
  var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
10373
- var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
10503
+ var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
10374
10504
  if (kind !== "passed")
10375
10505
  return false;
10376
10506
  if (dimension === "testing" || dimension === "verification")
10377
- return evidenceKind === "check";
10507
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
10378
10508
  if (dimension === "review")
10379
10509
  return evidenceKind === "review";
10380
10510
  if (dimension === "investigation" || dimension === "challenge")
@@ -10383,6 +10513,21 @@ var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
10383
10513
  return evidenceKind === "artifact";
10384
10514
  return false;
10385
10515
  };
10516
+ var expectedKinds = (dimension, ruleId) => {
10517
+ if (dimension === "testing")
10518
+ return "check";
10519
+ if (dimension === "verification")
10520
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
10521
+ if (dimension === "review")
10522
+ return "review";
10523
+ if (dimension === "investigation" || dimension === "challenge")
10524
+ return "investigation";
10525
+ if (dimension === "artifacts" || dimension === "continuity")
10526
+ return "artifact";
10527
+ if (dimension === "decisions")
10528
+ return "decision";
10529
+ return "worker report";
10530
+ };
10386
10531
  function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
10387
10532
  const evidence = evaluateEvidence(task, candidate);
10388
10533
  if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
@@ -10396,7 +10541,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10396
10541
  return (task.policy?.requirements ?? []).map((requirement) => {
10397
10542
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
10398
10543
  const passed = related.filter(({ entry, evaluation }) => {
10399
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension))
10544
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
10400
10545
  return false;
10401
10546
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
10402
10547
  return false;
@@ -10404,11 +10549,12 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10404
10549
  return true;
10405
10550
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10406
10551
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10407
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10552
+ const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
10408
10553
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10409
10554
  });
10410
10555
  if (passed.length) {
10411
- const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt)) : passed;
10556
+ const currentId = candidate?.id ?? null;
10557
+ const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
10412
10558
  if (withRed.length)
10413
10559
  return {
10414
10560
  requirementId: requirement.id,
@@ -10457,6 +10603,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10457
10603
  reason: "an applicable approved limitation permits the missing evidence"
10458
10604
  };
10459
10605
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
10606
+ if (!unavailable && related.length > 0 && !passed.length)
10607
+ return {
10608
+ requirementId: requirement.id,
10609
+ status: "unsatisfied",
10610
+ evidenceIds: related.map(({ entry }) => entry.id),
10611
+ decisionIds: [],
10612
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
10613
+ };
10460
10614
  return {
10461
10615
  requirementId: requirement.id,
10462
10616
  status: unavailable ? "unavailable" : "unsatisfied",
@@ -10481,7 +10635,13 @@ function evaluateClosure(requestedOutcome, view) {
10481
10635
  if (entry.data.disposition === "fixed") {
10482
10636
  const verified = entry.data.resolution?.evidenceIds.some((id) => {
10483
10637
  const evidence = view.task.evidence.find((item) => item.id === id);
10484
- return evidenceById.get(id) === "passed" && (evidence?.data.kind === "check" || evidence?.data.kind === "review");
10638
+ if (!evidence)
10639
+ return false;
10640
+ return findingVerificationPasses(entry.data.candidateId, {
10641
+ kind: evidence.data.kind,
10642
+ candidateId: evidence.data.candidateId,
10643
+ status: evidenceById.get(id) ?? "missing"
10644
+ });
10485
10645
  });
10486
10646
  if (!verified)
10487
10647
  return failure("requirements_unsatisfied", "fixed findings require current verification");
@@ -10497,7 +10657,12 @@ function evaluateClosure(requestedOutcome, view) {
10497
10657
  }
10498
10658
  }
10499
10659
  }
10500
- const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10660
+ const blocking = evaluations.filter((item) => {
10661
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
10662
+ return false;
10663
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10664
+ return !requirement || requirement.before === "close";
10665
+ });
10501
10666
  if (requestedOutcome !== "stopped" && blocking.length)
10502
10667
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10503
10668
  requirementIds: blocking.map((item) => item.requirementId),
@@ -10540,15 +10705,15 @@ function evaluateClosure(requestedOutcome, view) {
10540
10705
  // packages/workit-core/src/core/methods.ts
10541
10706
  var METHODS = {
10542
10707
  "workit-challenge": { dimensions: ["challenge", "decisions"] },
10543
- "workit-behavioral-tdd": { dimensions: ["testing"] },
10544
- "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
10708
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
10709
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
10545
10710
  "workit-plan": { dimensions: ["artifacts", "continuity"] },
10546
10711
  "workit-implement": { dimensions: ["delegation"] },
10547
- "workit-debug": { ruleIds: ["root-cause-investigation"] },
10548
- "workit-handoff": { ruleIds: ["durable-handoff"] },
10712
+ "workit-debug": {},
10713
+ "workit-handoff": {},
10549
10714
  "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
10550
10715
  };
10551
- var methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true;
10716
+ var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
10552
10717
  var assuranceFor = (id, definition, capabilities) => {
10553
10718
  const keys = new Set([
10554
10719
  id,
@@ -10569,7 +10734,7 @@ var assuranceFor = (id, definition, capabilities) => {
10569
10734
  };
10570
10735
  function selectMethods(policy, capabilities) {
10571
10736
  return Object.entries(METHODS).flatMap(([id, definition]) => {
10572
- const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
10737
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
10573
10738
  if (requirements.length === 0)
10574
10739
  return [];
10575
10740
  const capability = assuranceFor(id, definition, capabilities);
@@ -10795,6 +10960,27 @@ function shouldDenyShellRoute(dir, command) {
10795
10960
  return null;
10796
10961
  return shellRouteIntent(command);
10797
10962
  }
10963
+ // packages/workit-core/src/core/config.ts
10964
+ var PRESETS = {
10965
+ gitflow: {
10966
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
10967
+ protected: ["main", "develop", "master", "prod", "production"]
10968
+ },
10969
+ "github-flow": { allowed: ["*"], protected: ["main"] },
10970
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
10971
+ custom: { allowed: [], protected: [] }
10972
+ };
10973
+ var DEFAULTS = {
10974
+ locale: "en",
10975
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
10976
+ timezone: "America/Santiago",
10977
+ branchPolicy: {
10978
+ preset: "gitflow",
10979
+ allowed: [...PRESETS.gitflow.allowed],
10980
+ protected: [...PRESETS.gitflow.protected]
10981
+ },
10982
+ commitPolicy: { preset: "conventional" }
10983
+ };
10798
10984
  // packages/workit-core/src/core/external-action.ts
10799
10985
  var externalActionSchema = discriminatedUnion("operation", [
10800
10986
  object({
@@ -11332,6 +11518,8 @@ var applyWorkerLifecycle = (input) => {
11332
11518
  if (task.data.status === "paused" && (input.state === "running" || input.state === "cancelling"))
11333
11519
  return failure("invalid_transition", "paused task cannot run a worker");
11334
11520
  const notStarted = input.dispatch === true;
11521
+ if (notStarted && entry.data.state === "stopped" && input.session === null)
11522
+ return success(task.data.revision, workspace.data.revision, entry);
11335
11523
  if (notStarted && (input.state !== "stopped" || input.session !== null))
11336
11524
  return failure("invalid_input", "a never-dispatched worker stops with no session");
11337
11525
  if (notStarted && entry.data.session !== null)
@@ -11363,7 +11551,7 @@ var applyWorkerLifecycle = (input) => {
11363
11551
  return success(task.data.revision, workspace.data.revision, entry);
11364
11552
  const changed = input.store.mutateTaskAndWorkspace({
11365
11553
  taskId: task.data.id,
11366
- expectedTaskRevision: input.expectedRevision,
11554
+ expectedRevision: input.expectedRevision,
11367
11555
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
11368
11556
  now: input.now,
11369
11557
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -11411,90 +11599,22 @@ var importedProvenance = (context) => ({
11411
11599
  receipts: []
11412
11600
  });
11413
11601
  var portableTask = (task) => {
11414
- const clone = structuredClone(task);
11415
- const portableProvenance = (value) => ({
11416
- ...value,
11417
- session: null,
11418
- receipts: []
11419
- });
11420
- const entries = [
11421
- clone.intent,
11422
- ...clone.assessments,
11423
- ...clone.evidence,
11424
- ...clone.decisions,
11425
- ...clone.findings,
11426
- ...clone.workers
11427
- ];
11428
- for (const entry of entries)
11429
- entry.provenance = portableProvenance(entry.provenance);
11430
- for (const worker of clone.workers)
11431
- worker.data.session = null;
11432
- clone.intent = {
11433
- ...clone.intent,
11434
- data: { ...clone.intent.data, authorityRefs: portableRefs(clone.intent.data.authorityRefs) }
11602
+ const stripped = rewriteRecordRefs(task, taskRecordSchema, portableRef);
11603
+ if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
11604
+ throw new TypeError("portable task rewrite produced a non-record");
11605
+ const clone = structuredClone(stripped);
11606
+ const demote = (facts, signals, consequences) => {
11607
+ for (const fact of facts)
11608
+ Object.assign(fact, portableFact(fact));
11609
+ for (const name of Object.keys(signals)) {
11610
+ const signal = signals[name];
11611
+ Object.assign(signal, portableSignal(signal));
11612
+ }
11613
+ for (const consequence of consequences)
11614
+ Object.assign(consequence.fact, portableFact(consequence.fact));
11435
11615
  };
11436
- clone.constraints = clone.constraints.flatMap((constraint) => {
11437
- const source = portableRef(constraint.source);
11438
- return source ? [
11439
- {
11440
- ...constraint,
11441
- source,
11442
- requires: constraint.requires.map((requirement) => ({
11443
- ...requirement,
11444
- refs: portableRefs(requirement.refs)
11445
- }))
11446
- }
11447
- ] : [];
11448
- });
11449
- clone.assessments = clone.assessments.map((entry) => ({
11450
- ...entry,
11451
- data: {
11452
- ...entry.data,
11453
- facts: entry.data.facts.map(portableFact),
11454
- signals: Object.fromEntries(Object.entries(entry.data.signals).map(([name, signal]) => [name, portableSignal(signal)])),
11455
- consequences: entry.data.consequences.map((consequence) => ({
11456
- ...consequence,
11457
- fact: portableFact(consequence.fact)
11458
- })),
11459
- verification: entry.data.verification.map((verification) => ({
11460
- ...verification,
11461
- availableChecks: portableRefs(verification.availableChecks)
11462
- }))
11463
- }
11464
- }));
11465
- clone.progress = {
11466
- ...clone.progress,
11467
- blockers: clone.progress.blockers.map((blocker) => ({
11468
- ...blocker,
11469
- refs: portableRefs(blocker.refs)
11470
- }))
11471
- };
11472
- clone.evidence = clone.evidence.map((entry) => ({
11473
- ...entry,
11474
- data: {
11475
- ...entry.data,
11476
- refs: portableRefs(entry.data.refs),
11477
- reviewContext: portableRef(entry.data.reviewContext)
11478
- }
11479
- }));
11480
- clone.decisions = clone.decisions.map((entry) => ({
11481
- ...entry,
11482
- data: {
11483
- ...entry.data,
11484
- consumption: entry.data.consumption ? portableRef(entry.data.consumption.actionRef) ? {
11485
- ...entry.data.consumption,
11486
- actionRef: portableRef(entry.data.consumption.actionRef)
11487
- } : null : null,
11488
- binding: {
11489
- ...entry.data.binding,
11490
- contentRefs: portableRefs(entry.data.binding.contentRefs)
11491
- }
11492
- }
11493
- }));
11494
- clone.findings = clone.findings.map((entry) => ({
11495
- ...entry,
11496
- data: { ...entry.data, refs: portableRefs(entry.data.refs) }
11497
- }));
11616
+ for (const entry of clone.assessments)
11617
+ demote(entry.data.facts, entry.data.signals, entry.data.consequences);
11498
11618
  clone.candidates = [];
11499
11619
  return clone;
11500
11620
  };
@@ -11519,15 +11639,13 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11519
11639
  allocate(entry.id);
11520
11640
  const fresh = (id) => allocate(id);
11521
11641
  const provenance = importedProvenance(context);
11642
+ const remap = (ref) => mapRef(ref, ids);
11522
11643
  const intent = {
11523
11644
  ...source.intent,
11524
11645
  id: fresh(source.intent.id),
11525
11646
  recordedAt: timestamp,
11526
11647
  provenance,
11527
- data: {
11528
- ...source.intent.data,
11529
- authorityRefs: source.intent.data.authorityRefs.map((ref) => mapRef(ref, ids))
11530
- }
11648
+ data: rewriteRecordRefs(source.intent.data, intentSchema, remap)
11531
11649
  };
11532
11650
  const assessments = source.assessments.map((entry) => {
11533
11651
  const data = entry.data;
@@ -11536,28 +11654,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11536
11654
  id: fresh(entry.id),
11537
11655
  recordedAt: timestamp,
11538
11656
  provenance,
11539
- data: {
11540
- ...data,
11541
- facts: data.facts.map((fact) => ({
11542
- ...fact,
11543
- refs: fact.refs.map((ref) => mapRef(ref, ids))
11544
- })),
11545
- signals: Object.fromEntries(Object.entries(data.signals).map(([name, signal]) => [
11546
- name,
11547
- { ...signal, refs: signal.refs.map((ref) => mapRef(ref, ids)) }
11548
- ])),
11549
- consequences: data.consequences.map((consequence) => ({
11550
- ...consequence,
11551
- fact: {
11552
- ...consequence.fact,
11553
- refs: consequence.fact.refs.map((ref) => mapRef(ref, ids))
11554
- }
11555
- })),
11556
- verification: data.verification.map((verification) => ({
11557
- ...verification,
11558
- availableChecks: verification.availableChecks.map((ref) => mapRef(ref, ids))
11559
- }))
11560
- }
11657
+ data: rewriteRecordRefs(data, assessmentSchema, remap)
11561
11658
  };
11562
11659
  });
11563
11660
  const evidence = source.evidence.map((entry) => ({
@@ -11565,11 +11662,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11565
11662
  id: fresh(entry.id),
11566
11663
  recordedAt: timestamp,
11567
11664
  provenance,
11568
- data: {
11569
- ...entry.data,
11570
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
11571
- reviewContext: entry.data.reviewContext ? mapRef(entry.data.reviewContext, ids) : null
11572
- }
11665
+ data: rewriteRecordRefs(entry.data, evidenceSchema, remap)
11573
11666
  }));
11574
11667
  const decisions = source.decisions.map((entry) => ({
11575
11668
  ...entry,
@@ -11577,21 +11670,23 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11577
11670
  recordedAt: timestamp,
11578
11671
  provenance
11579
11672
  }));
11580
- const findings = source.findings.map((entry) => ({
11581
- ...entry,
11582
- id: fresh(entry.id),
11583
- recordedAt: timestamp,
11584
- provenance,
11585
- data: {
11586
- ...entry.data,
11587
- refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
11588
- resolution: entry.data.resolution ? {
11589
- ...entry.data.resolution,
11590
- evidenceIds: entry.data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
11591
- decisionIds: entry.data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
11592
- } : null
11593
- }
11594
- }));
11673
+ const findings = source.findings.map((entry) => {
11674
+ const data = rewriteRecordRefs(entry.data, findingSchema, remap);
11675
+ return {
11676
+ ...entry,
11677
+ id: fresh(entry.id),
11678
+ recordedAt: timestamp,
11679
+ provenance,
11680
+ data: {
11681
+ ...data,
11682
+ resolution: data.resolution ? {
11683
+ ...data.resolution,
11684
+ evidenceIds: data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
11685
+ decisionIds: data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
11686
+ } : null
11687
+ }
11688
+ };
11689
+ });
11595
11690
  const workers = source.workers.map((entry) => ({
11596
11691
  ...entry,
11597
11692
  id: fresh(entry.id),
@@ -11634,19 +11729,21 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
11634
11729
  closure: null,
11635
11730
  assessments,
11636
11731
  evidence,
11637
- decisions: decisions.map((entry) => ({
11638
- ...entry,
11639
- data: {
11640
- ...entry.data,
11641
- binding: {
11642
- ...entry.data.binding,
11643
- taskId: "",
11644
- workspaceId: destinationWorkspaceId,
11645
- contentRefs: entry.data.binding.contentRefs.map((ref) => mapRef(ref, ids))
11646
- },
11647
- consumption: null
11648
- }
11649
- })),
11732
+ decisions: decisions.map((entry) => {
11733
+ const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
11734
+ return {
11735
+ ...entry,
11736
+ data: {
11737
+ ...data,
11738
+ binding: {
11739
+ ...data.binding,
11740
+ taskId: "",
11741
+ workspaceId: destinationWorkspaceId
11742
+ },
11743
+ consumption: null
11744
+ }
11745
+ };
11746
+ }),
11650
11747
  ...actionProgress ? { actionProgress } : {},
11651
11748
  findings,
11652
11749
  workers,
@@ -11729,7 +11826,7 @@ class WorkitCore {
11729
11826
  activeWorkerBlocker(task, workspace) {
11730
11827
  if (workspace.writer)
11731
11828
  return failure("recovery_required", "writer ownership must be released first");
11732
- if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11829
+ if (task.workers.some((entry) => workerBlocksTransition("close", entry.data.state)))
11733
11830
  return failure("recovery_required", "worker state requires reconciliation");
11734
11831
  return null;
11735
11832
  }
@@ -11975,37 +12072,33 @@ class WorkitCore {
11975
12072
  const currentCandidate = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
11976
12073
  if (!currentCandidate.ok)
11977
12074
  return currentCandidate;
11978
- if (evidence.kind === "check" || evidence.kind === "review") {
11979
- evidence.beforeCandidateId ??= currentCandidate.data.id;
11980
- evidence.candidateId ??= currentCandidate.data.id;
11981
- } else {
11982
- evidence.beforeCandidateId ??= null;
11983
- evidence.candidateId ??= null;
11984
- }
12075
+ const pin = helper && helper.ok ? helper.data.data.assignment.candidateId : null;
12076
+ const bound = resolveBinding(evidence, pin, currentCandidate.data.id);
11985
12077
  if (helper?.ok) {
11986
12078
  const assignment = helper.data.data.assignment;
11987
- if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
12079
+ if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || !checkPin(assignment, bound.beforeCandidateId, bound.candidateId) || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
11988
12080
  return failure("permission_denied", "evidence is outside the worker assignment");
11989
12081
  }
11990
12082
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11991
- for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
12083
+ for (const candidateId of [bound.beforeCandidateId, bound.candidateId]) {
11992
12084
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
11993
12085
  return failure("invalid_input", "evidence candidate is not a captured candidate");
11994
12086
  }
12087
+ const boundEvidence = { ...evidence, ...bound };
11995
12088
  const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
11996
12089
  const candidates = current.candidates.some((item) => item.id === currentCandidate.data.id) ? current.candidates : [...current.candidates, currentCandidate.data];
11997
12090
  const entry = {
11998
12091
  id: newId(),
11999
12092
  recordedAt: mutation.now,
12000
12093
  provenance: provenance(this.context, "agent_reported"),
12001
- data: evidence
12094
+ data: boundEvidence
12002
12095
  };
12003
12096
  return success(mutation.revision, null, {
12004
12097
  ...current,
12005
12098
  candidates,
12006
12099
  evidence: [...current.evidence, entry],
12007
12100
  findings: current.findings.map((finding) => {
12008
- if (finding.data.disposition === "open" || evidence.result === "skipped")
12101
+ if (finding.data.disposition === "open" || boundEvidence.result === "skipped")
12009
12102
  return finding;
12010
12103
  if (finding.data.disposition !== "fixed")
12011
12104
  return finding;
@@ -12014,8 +12107,12 @@ class WorkitCore {
12014
12107
  item.evidenceId,
12015
12108
  item.status
12016
12109
  ]));
12017
- const verified = withEntry.evidence.some((item) => statuses.get(item.id) === "passed" && (item.data.kind === "check" || item.data.kind === "review") && (finding.data.candidateId === null || item.data.candidateId === finding.data.candidateId));
12018
- const contradicted = evidence.result === "failed" && finding.data.refs.some((left) => evidence.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12110
+ const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
12111
+ kind: item.data.kind,
12112
+ candidateId: item.data.candidateId,
12113
+ status: statuses.get(item.id) ?? "missing"
12114
+ }));
12115
+ const contradicted = boundEvidence.result === "failed" && finding.data.refs.some((left) => boundEvidence.refs.some((right) => sameValue(left, right)));
12019
12116
  return verified && !contradicted ? finding : { ...finding, data: { ...finding.data, disposition: "open", resolution: null } };
12020
12117
  })
12021
12118
  });
@@ -12173,7 +12270,7 @@ class WorkitCore {
12173
12270
  if (input.action === "record") {
12174
12271
  if (helper?.ok) {
12175
12272
  const assignment = helper.data.data.assignment;
12176
- if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || assignment.candidateId !== null && input.candidateId !== assignment.candidateId || !refsWithinScope(input.refs, assignment.scope) || !refsWithinScope(input.refs, task.data.intent.data.scope))
12273
+ if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || !checkPin(assignment, null, input.candidateId ?? null) || !refsWithinScope(input.refs, assignment.scope) || !refsWithinScope(input.refs, task.data.intent.data.scope))
12177
12274
  return failure("permission_denied", "finding is outside the worker assignment");
12178
12275
  }
12179
12276
  if (input.candidateId && !task.data.candidates.some((candidate) => candidate.id === input.candidateId))
@@ -12220,7 +12317,11 @@ class WorkitCore {
12220
12317
  const evaluations = evaluateEvidence(task.data, current.data);
12221
12318
  const verified = evidence.some((entry) => {
12222
12319
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
12223
- return evaluation?.status === "passed" && (entry.data.kind === "check" || entry.data.kind === "review") && (finding.data.candidateId === null || entry.data.candidateId === finding.data.candidateId);
12320
+ return findingVerificationPasses(finding.data.candidateId, {
12321
+ kind: entry.data.kind,
12322
+ candidateId: entry.data.candidateId,
12323
+ status: evaluation?.status ?? "missing"
12324
+ });
12224
12325
  });
12225
12326
  if (!verified)
12226
12327
  return failure("permission_denied", "fixed findings require passing verification evidence");
@@ -12232,7 +12333,7 @@ class WorkitCore {
12232
12333
  if (entry.data.result === "missing" || entry.data.result === "skipped")
12233
12334
  return false;
12234
12335
  const candidateMatches = finding.data.candidateId === null ? entry.data.candidateId === null : entry.data.candidateId === finding.data.candidateId || entry.data.beforeCandidateId === finding.data.candidateId;
12235
- const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => JSON.stringify(left) === JSON.stringify(right)));
12336
+ const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => sameValue(left, right)));
12236
12337
  return candidateMatches && (referenceMatches || entry.data.claim === finding.data.claim);
12237
12338
  });
12238
12339
  if (!relevant)
@@ -12310,7 +12411,7 @@ class WorkitCore {
12310
12411
  const workerId = newId();
12311
12412
  const changed = this.store.mutateTaskAndWorkspace({
12312
12413
  taskId: task.data.id,
12313
- expectedTaskRevision: input.expectedRevision,
12414
+ expectedRevision: input.expectedRevision,
12314
12415
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12315
12416
  now: trustedNow(this.context),
12316
12417
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12348,7 +12449,7 @@ class WorkitCore {
12348
12449
  return failure("permission_denied", "worker report is outside the worker assignment");
12349
12450
  const changed = this.store.mutateTaskAndWorkspace({
12350
12451
  taskId: task.data.id,
12351
- expectedTaskRevision: input.expectedRevision,
12452
+ expectedRevision: input.expectedRevision,
12352
12453
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12353
12454
  now: trustedNow(this.context),
12354
12455
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12372,24 +12473,25 @@ class WorkitCore {
12372
12473
  return failure("invalid_transition", "closed task cannot cancel workers");
12373
12474
  if (entry.data.state === "unknown")
12374
12475
  return failure("recovery_required", "worker state requires recovery");
12375
- if (entry.data.state === "stopped")
12376
- return failure("invalid_transition", "stopped worker cannot be cancelled");
12377
- const settles = entry.data.report !== null || entry.data.state === "cancelling";
12476
+ if (entry.data.state === "stopped") {
12477
+ const stopped = task.data.workers.find((candidate) => candidate.id === input.workerId);
12478
+ return success(task.data.revision, workspace.data.revision, stopped);
12479
+ }
12378
12480
  const changed = this.store.mutateTaskAndWorkspace({
12379
12481
  taskId: task.data.id,
12380
- expectedTaskRevision: input.expectedRevision,
12482
+ expectedRevision: input.expectedRevision,
12381
12483
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12382
12484
  now: trustedNow(this.context),
12383
- workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
12485
+ workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
12486
+ ...current,
12487
+ writer: current.writer?.owner.workerId === input.workerId ? null : current.writer
12488
+ }),
12384
12489
  task: (current, mutation) => success(mutation.revision, null, {
12385
12490
  ...current,
12386
12491
  workers: current.workers.map((candidate) => candidate.id === input.workerId ? {
12387
12492
  ...candidate,
12388
12493
  recordedAt: mutation.now,
12389
- data: {
12390
- ...candidate.data,
12391
- state: settles ? "stopped" : "cancelling"
12392
- }
12494
+ data: { ...candidate.data, state: "stopped" }
12393
12495
  } : candidate)
12394
12496
  })
12395
12497
  });
@@ -12486,7 +12588,7 @@ class WorkitCore {
12486
12588
  return attested;
12487
12589
  const changed = this.store.mutateTaskAndWorkspace({
12488
12590
  taskId: task.data.id,
12489
- expectedTaskRevision: input.expectedRevision,
12591
+ expectedRevision: input.expectedRevision,
12490
12592
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12491
12593
  now: trustedNow(this.context),
12492
12594
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
@@ -12652,6 +12754,12 @@ class WorkitCore {
12652
12754
  if (!sameSession2(helper.data.data.session, this.context))
12653
12755
  return failure("permission_denied", "worker session does not match the caller");
12654
12756
  }
12757
+ const gate = this.view(task.data);
12758
+ if (gate.ok) {
12759
+ const writeGates = (task.data.policy?.requirements ?? []).filter((requirement) => requirement.before === "write" && gate.data.requirements.some((evaluation) => evaluation.requirementId === requirement.id && (evaluation.status === "unsatisfied" || evaluation.status === "unavailable")));
12760
+ if (writeGates.length)
12761
+ return failure("requirements_unsatisfied", "writer ownership is gated by unsatisfied requirements; record their evidence or an approved limitation waiver first", { requirementIds: writeGates.map((requirement) => requirement.id) });
12762
+ }
12655
12763
  const owner = {
12656
12764
  taskId: task.data.id,
12657
12765
  workerId: helperId,
@@ -12663,7 +12771,7 @@ class WorkitCore {
12663
12771
  };
12664
12772
  const changed = this.store.mutateTaskAndWorkspace({
12665
12773
  taskId: task.data.id,
12666
- expectedTaskRevision: input.expectedRevision,
12774
+ expectedRevision: input.expectedRevision,
12667
12775
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12668
12776
  now: trustedNow(this.context),
12669
12777
  workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
@@ -12694,7 +12802,7 @@ class WorkitCore {
12694
12802
  }
12695
12803
  const changed = this.store.mutateTaskAndWorkspace({
12696
12804
  taskId: task.data.id,
12697
- expectedTaskRevision: input.expectedRevision,
12805
+ expectedRevision: input.expectedRevision,
12698
12806
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
12699
12807
  now: trustedNow(this.context),
12700
12808
  workspace: (currentWorkspace, mutation) => success(mutation.revision, mutation.revision, { ...currentWorkspace, writer: null }),
@@ -12943,7 +13051,15 @@ class WorkitCore {
12943
13051
  if (seen.has(observation.workerId))
12944
13052
  return failure("invalid_input", "worker observations must be unique");
12945
13053
  seen.add(observation.workerId);
12946
- if (observation.taskId !== view.task.id || observation.expectedRevision !== view.task.revision || observation.expectedWorkspaceRevision !== view.workspace.revision)
13054
+ const freshTask = this.store.readTask(view.task.id);
13055
+ const freshWorkspace = this.store.readWorkspace();
13056
+ if (!freshTask.ok)
13057
+ return freshTask;
13058
+ if (!freshWorkspace.ok)
13059
+ return freshWorkspace;
13060
+ if (!freshWorkspace.data)
13061
+ return failure("not_found", "workspace not found");
13062
+ if (observation.taskId !== view.task.id || observation.expectedRevision !== freshTask.data.revision || observation.expectedWorkspaceRevision !== freshWorkspace.data.revision)
12947
13063
  return failure("revision_conflict", "worker observation is stale");
12948
13064
  if (!view.task.workers.some((entry) => entry.id === observation.workerId))
12949
13065
  return failure("permission_denied", "worker observation is not assigned");
@@ -13029,19 +13145,26 @@ class WorkitCore {
13029
13145
  return failure("permission_denied", "imported resume requires native destination approval");
13030
13146
  resumeCandidate = current.data.candidate;
13031
13147
  }
13032
- const blocker = this.activeWorkerBlocker(task.data, workspace.data);
13148
+ const blocker = status === "paused" ? workspace.data.writer ? failure("recovery_required", "writer ownership must be released first") : null : this.activeWorkerBlocker(task.data, workspace.data);
13033
13149
  if (blocker)
13034
13150
  return blocker;
13151
+ let pauseCandidate = null;
13152
+ if (status === "paused") {
13153
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
13154
+ if (!captured.ok)
13155
+ return captured;
13156
+ pauseCandidate = captured.data;
13157
+ }
13035
13158
  const changed = this.store.mutateTaskAndWorkspace({
13036
13159
  taskId: task.data.id,
13037
- expectedTaskRevision: input.expectedRevision,
13160
+ expectedRevision: input.expectedRevision,
13038
13161
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
13039
13162
  now: trustedNow(this.context),
13040
13163
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
13041
13164
  task: (current, context) => success(context.revision, null, {
13042
13165
  ...current,
13043
13166
  status,
13044
- candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
13167
+ candidates: (resumeCandidate ?? pauseCandidate) && !current.candidates.some((candidate) => candidate.id === (resumeCandidate ?? pauseCandidate).id) ? [...current.candidates, resumeCandidate ?? pauseCandidate] : current.candidates,
13045
13168
  progress: current.progress,
13046
13169
  pauseReason: status === "paused" ? input.reason ?? null : null
13047
13170
  })
@@ -13079,12 +13202,13 @@ class WorkitCore {
13079
13202
  return workspace;
13080
13203
  if (!workspace.data)
13081
13204
  return failure("not_found", "workspace not found");
13082
- if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
13083
- return failure("recovery_required", "scope revision requires worker ownership reconciliation");
13205
+ const revisionBlocker = this.activeWorkerBlocker(task.data, workspace.data);
13206
+ if (revisionBlocker)
13207
+ return revisionBlocker;
13084
13208
  this.fillRevisions(input, task.data, workspace.data);
13085
13209
  const changed = this.store.mutateTaskAndWorkspace({
13086
13210
  taskId: task.data.id,
13087
- expectedTaskRevision: input.expectedRevision,
13211
+ expectedRevision: input.expectedRevision,
13088
13212
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
13089
13213
  now: trustedNow(this.context),
13090
13214
  workspace: (workspace, context) => success(context.revision, context.revision, workspace),
@@ -13140,7 +13264,7 @@ class WorkitCore {
13140
13264
  return closure;
13141
13265
  const changed = this.store.mutateTaskAndWorkspace({
13142
13266
  taskId: task.data.id,
13143
- expectedTaskRevision: input.expectedRevision,
13267
+ expectedRevision: input.expectedRevision,
13144
13268
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
13145
13269
  now: trustedNow(this.context),
13146
13270
  workspace: (value, context) => success(context.revision, context.revision, value),