toolroll 0.9.91 → 0.9.93

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/approval-authority.d.ts +9 -1
  3. package/dist/approval-authority.js +15 -2
  4. package/dist/assignment-status.d.ts +1 -1
  5. package/dist/assignment-ui.js +2 -2
  6. package/dist/assignment.js +11 -3
  7. package/dist/browser/workspace.css +1 -1
  8. package/dist/browser/workspace.js +30 -30
  9. package/dist/browser-crew.js +2 -2
  10. package/dist/browser-workspace.d.ts +3 -5
  11. package/dist/build-review.js +2 -0
  12. package/dist/builder.js +21 -2
  13. package/dist/chat-actions.d.ts +11 -3
  14. package/dist/chat-actions.js +47 -2
  15. package/dist/chat-decide.js +2 -0
  16. package/dist/chat-review.d.ts +2 -1
  17. package/dist/chat-review.js +2 -2
  18. package/dist/check-facts.d.ts +124 -0
  19. package/dist/check-facts.js +272 -0
  20. package/dist/check-report.d.ts +5 -0
  21. package/dist/check-report.js +4 -3
  22. package/dist/cli.js +5 -0
  23. package/dist/contracts/chat-actions.d.ts +10 -0
  24. package/dist/contracts/chat-actions.js +4 -0
  25. package/dist/contracts/console-api.d.ts +3 -2
  26. package/dist/contracts/console-api.js +5 -2
  27. package/dist/contracts/release-check.d.ts +25 -1
  28. package/dist/contracts/release-check.js +19 -0
  29. package/dist/contracts/route-results.d.ts +121 -0
  30. package/dist/contracts/route-results.js +71 -0
  31. package/dist/contracts/route.d.ts +343 -5
  32. package/dist/contracts/route.js +36 -7
  33. package/dist/contracts/store-json.d.ts +2 -0
  34. package/dist/desktop-update.d.ts +2 -2
  35. package/dist/desktop-update.js +13 -9
  36. package/dist/dispose.js +6 -0
  37. package/dist/eval-hooks.d.ts +55 -0
  38. package/dist/eval-hooks.js +227 -4
  39. package/dist/flaky.d.ts +17 -0
  40. package/dist/flaky.js +9 -0
  41. package/dist/flow-engine.js +1 -1
  42. package/dist/flow-pull-request.js +1 -1
  43. package/dist/lead-doors.js +4 -2
  44. package/dist/lead-tools.js +10 -2
  45. package/dist/local-owner.d.ts +1 -1
  46. package/dist/local-owner.js +11 -0
  47. package/dist/memory-cli.d.ts +3 -0
  48. package/dist/memory-cli.js +3 -3
  49. package/dist/memory-pass.js +5 -1
  50. package/dist/model-choices.d.ts +7 -3
  51. package/dist/model-choices.js +26 -11
  52. package/dist/models-cli.d.ts +205 -0
  53. package/dist/models-cli.js +143 -4
  54. package/dist/models-ui.d.ts +57 -0
  55. package/dist/models-ui.js +44 -1
  56. package/dist/needs-you.d.ts +7 -7
  57. package/dist/needs-you.js +12 -10
  58. package/dist/operate-remote-arguments.js +4 -4
  59. package/dist/operate-remote.js +1 -1
  60. package/dist/operate.js +191 -23
  61. package/dist/phase-routing.d.ts +82 -2
  62. package/dist/phase-routing.js +132 -18
  63. package/dist/project-delete.d.ts +4 -0
  64. package/dist/project-delete.js +16 -4
  65. package/dist/project-memory.d.ts +8 -0
  66. package/dist/project-memory.js +20 -1
  67. package/dist/pull-request-flow.js +1 -1
  68. package/dist/push.d.ts +10 -3
  69. package/dist/push.js +22 -6
  70. package/dist/release-adapters.js +11 -1
  71. package/dist/release-approval.d.ts +191 -0
  72. package/dist/release-approval.js +382 -0
  73. package/dist/release-auto.js +22 -4
  74. package/dist/release-card.d.ts +73 -4
  75. package/dist/release-card.js +168 -23
  76. package/dist/release-cli.js +9 -1
  77. package/dist/release.d.ts +30 -0
  78. package/dist/release.js +42 -3
  79. package/dist/result-actions.d.ts +4 -0
  80. package/dist/result-actions.js +3 -0
  81. package/dist/result-acts.d.ts +3 -3
  82. package/dist/result-acts.js +1 -1
  83. package/dist/route-hooks.d.ts +54 -0
  84. package/dist/route-hooks.js +179 -0
  85. package/dist/route-results.d.ts +70 -0
  86. package/dist/route-results.js +340 -0
  87. package/dist/route-rules.d.ts +324 -0
  88. package/dist/route-rules.js +781 -0
  89. package/dist/route-savings.d.ts +136 -0
  90. package/dist/route-savings.js +193 -0
  91. package/dist/route-store.d.ts +300 -0
  92. package/dist/route-store.js +598 -0
  93. package/dist/scope.d.ts +3 -1
  94. package/dist/scope.js +4 -1
  95. package/dist/server/guards.js +1 -0
  96. package/dist/server/pages-assets.js +1 -1
  97. package/dist/server/render-chat.js +2 -2
  98. package/dist/server/render-release.d.ts +17 -0
  99. package/dist/server/render-release.js +84 -0
  100. package/dist/server/render-results.d.ts +0 -2
  101. package/dist/server/render-results.js +4 -6
  102. package/dist/server/render-tasks.d.ts +7 -0
  103. package/dist/server/render-tasks.js +16 -3
  104. package/dist/server/route-table.d.ts +1 -1
  105. package/dist/server/route-table.js +19 -1
  106. package/dist/server/settings.d.ts +2 -1
  107. package/dist/server/settings.js +87 -1
  108. package/dist/server/task-views.js +6 -0
  109. package/dist/server/tasks.d.ts +15 -0
  110. package/dist/server/tasks.js +105 -20
  111. package/dist/settings-proofs.js +2 -0
  112. package/dist/spend-ui.d.ts +11 -0
  113. package/dist/spend-ui.js +49 -2
  114. package/dist/stop-proof.d.ts +67 -0
  115. package/dist/stop-proof.js +293 -0
  116. package/dist/store.d.ts +106 -31
  117. package/dist/store.js +653 -128
  118. package/dist/surface.js +17 -5
  119. package/dist/task-outcome-cli.js +24 -2
  120. package/dist/toolroll-update-ui.js +2 -2
  121. package/dist/toolroll-update.js +4 -2
  122. package/dist/work-index.js +7 -9
  123. package/dist/work-summary.d.ts +2 -2
  124. package/package.json +1 -1
@@ -15,8 +15,8 @@ export function browserWorkActionHref(item) {
15
15
  return '/settings/agents#providers';
16
16
  if (action.code === 'answer-agent' && runId !== null)
17
17
  return `/agents?run=${runId}`;
18
- if (action.code === 'confirm-stopped')
19
- return `/t/${encodeURIComponent(item.rootId)}#confirm-stopped`;
18
+ if (action.code === 'recheck-stopped')
19
+ return `/t/${encodeURIComponent(item.rootId)}#recheck-stopped`;
20
20
  if (action.code === 'open-result' && runId !== null)
21
21
  return chatResultHref(taskId, runId);
22
22
  if (action.code === 'open-pr' && /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/\d+$/.test(item.publicationUrl ?? ''))
@@ -381,11 +381,10 @@ export type BrowserTaskView = {
381
381
  statusHtml: string;
382
382
  /** The approval ceremony or its updated terms, exactly as signed. */
383
383
  approval: string;
384
- /** Confirm it stopped, behind the password, when the status asks for it. */
385
- confirmStopped?: {
384
+ /** Check again (a build Toolroll could not prove stopped from this computer), when the status asks for it. */
385
+ recheckStopped?: {
386
386
  action: string;
387
387
  run: number;
388
- checked?: boolean;
389
388
  } | null;
390
389
  /** Build again, in place, when the status asks for it (a result built to an earlier plan). */
391
390
  rebuild?: {
@@ -703,7 +702,7 @@ export type BrowserResultDecision = {
703
702
  why: string | null;
704
703
  };
705
704
  };
706
- /** A Needs you action: a link, (Confirm it stopped) a form behind the password, (Build again) one
705
+ /** A Needs you action: a link, (Check again, Build again) one
707
706
  * button, or (on the result itself) Accept, which records the person's acceptance; `note` asks why
708
707
  * when the evidence disagrees. */
709
708
  export type BrowserNeedAction = {
@@ -713,7 +712,6 @@ export type BrowserNeedAction = {
713
712
  action: string;
714
713
  run: number;
715
714
  returnTo: string;
716
- checked?: boolean;
717
715
  } | null;
718
716
  accept?: {
719
717
  action: string;
@@ -126,6 +126,8 @@ function sendBack(store, root, runId, taskId, repo, high, now) {
126
126
  run: runId, source: revisionSourceOf(store.getScope(taskId)?.digest ?? null), actor, repos: [repo], batch: "",
127
127
  note: revisionNoteOf(high), path: "", line: "",
128
128
  request: createHash("sha256").update(`build-review:${runId}`).digest("hex").slice(0, 32), allowMode: true,
129
+ // The automatic review's send-back, even when the mode's signer is its actor: never a person's act.
130
+ origin: "automatic-review",
129
131
  }, now);
130
132
  return revised.ok ? { ok: true, id: revised.id } : { ok: false, problem: revised.message };
131
133
  }
package/dist/builder.js CHANGED
@@ -5,6 +5,7 @@ import { checkoutGit } from "./teardown-git.js";
5
5
  import { isAcpProvider } from "./acp-agents.js";
6
6
  import { OBSERVATION_MAILBOX, observationBrief, parseObservationCases, collectObservations } from "./observations.js";
7
7
  import { checkCommandFor, effectiveCheckLevel, recordRunCheckLevel } from "./check-levels.js";
8
+ import { captureCheckConditions, learnRequestFor, recordCheckFacts } from "./check-facts.js";
8
9
  import { queueBatchCheck } from "./batch-checks.js";
9
10
  import { batchesFullCheck } from "./batch-policy.js";
10
11
  import { skillsContext } from "./project-skills.js";
@@ -2324,9 +2325,12 @@ async function settleProof(captured, statArtifactId, sealedHead) {
2324
2325
  let checkAttempt = null;
2325
2326
  let releaseFacts = null;
2326
2327
  const verification = { last: null };
2328
+ // What the check ran under, captured as its command starts (check-facts.ts): advisory, recorded at disposition.
2329
+ const checkConditions = { last: null };
2327
2330
  const checkLog = [];
2328
2331
  const checkOutcomes = [];
2329
2332
  const checkSuites = [];
2333
+ let settledReport = null;
2330
2334
  const noteCheckSuite = (name, result) => {
2331
2335
  checkSuites.push({
2332
2336
  name,
@@ -2382,7 +2386,9 @@ async function settleProof(captured, statArtifactId, sealedHead) {
2382
2386
  const release = decision.authority !== null || releaseCheckScope(store.getScope(request.taskId));
2383
2387
  try {
2384
2388
  checkAttempt = prepareCheckAttempt(store, root, { runId, repo, head: sealedHead, command: configured, access, authority: decision.authority,
2385
- ...(decision.mode === "targeted" ? { plan: decision.plan, snapshot: decision.snapshot } : {}), watch: decision.watch, release }, now());
2389
+ ...(decision.mode === "targeted" ? { plan: decision.plan, snapshot: decision.snapshot } : {}), watch: decision.watch, release,
2390
+ // Advisory only (docs/plans/learning-loops.md): the project's tracked identities, a separate handshake field.
2391
+ learn: learnRequestFor(store, repo, now()).learn }, now());
2386
2392
  }
2387
2393
  catch (error) {
2388
2394
  // A recorded last release that can't be verified (eval-harness review 2, finding 4): nothing runs.
@@ -2404,6 +2410,9 @@ async function settleProof(captured, statArtifactId, sealedHead) {
2404
2410
  const checkProgress = new CheckProgressTracker(snapshot => progressWrites.push(snapshot));
2405
2411
  const runVerification = async (label, missing = null) => {
2406
2412
  const streamed = { stdout: false, stderr: false };
2413
+ // The conditions it starts under (verified tree, command, access, inherited environment): advisory, never authority.
2414
+ if (missing === null)
2415
+ checkConditions.last = captureCheckConditions({ repo, head: sealedHead, command: configured, access: checkLaunchOf(store, runId, captured.effective.profile).access, envNames: SETUP_ENV_ALLOWLIST });
2407
2416
  // The check runs under the stop watch (v52), owned by this run.
2408
2417
  const result = missing !== null
2409
2418
  ? { code: 127, stdout: "", stderr: `Toolroll did not start the check: ${missing} wasn't found.`, timedOut: false, notFound: false }
@@ -2624,7 +2633,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
2624
2633
  const last = verification.last;
2625
2634
  let sealedReport = null;
2626
2635
  if (checkAttempt !== null && last !== null && !last.notFound) {
2627
- sealedReport = sealCheckOutcome(store, root, checkAttempt, { stdout: last.stdout, exitCode: last.timedOut ? null : last.code }, now());
2636
+ sealedReport = settledReport = sealCheckOutcome(store, root, checkAttempt, { stdout: last.stdout, exitCode: last.timedOut ? null : last.code }, now());
2628
2637
  // Where the check's time went (latency-timeline.ts): each part's start and end, display only.
2629
2638
  keepCheckTimeline(store, { runId, taskId: request.taskId, report: sealedReport, stdout: last.stdout, now: now() });
2630
2639
  }
@@ -2683,6 +2692,16 @@ async function settleProof(captured, statArtifactId, sealedHead) {
2683
2692
  exitCode: checkExitCode,
2684
2693
  suites,
2685
2694
  }, now());
2695
+ // What the check ran under and reported, every failure in it, each tagged by source (check-facts.ts): advisory, never
2696
+ // read for this outcome, a verdict or an approval.
2697
+ // A failure to record them is said on the run and never reaches the check's outcome.
2698
+ try {
2699
+ recordCheckFacts(store, root, { runId, taskRef: captured.taskRef, repo, conditions: checkConditions.last, receipt: { status: checkStatus }, report: settledReport,
2700
+ attempt: checkAttempt === null ? null : { nonce: checkAttempt.nonce, learn: checkAttempt.learn }, stdout: verification.last?.stdout ?? null, now: now() });
2701
+ }
2702
+ catch (error) {
2703
+ store.addRunNote(runId, "Toolroll", `This check's facts for the flaky-test list weren't recorded (${redactSecretText(error instanceof Error ? error.message : String(error))}); its result is unchanged.`, now());
2704
+ }
2686
2705
  // 4. The sealed diff-stat was restated above, before the correction; it
2687
2706
  // is re-read now, after the gate, and the cached facts are adjudicated
2688
2707
  // only when the artifact still states exactly them. The gate receipt
@@ -1,5 +1,3 @@
1
- /** Shared, exact-state actions. Models may prepare; only the existing human
2
- * confirmation door executes. Protected actions use a one-use review receipt. */
3
1
  import { type Authority } from "./local-owner.js";
4
2
  import { type Store } from "./store.js";
5
3
  import { type VerifiedApprover } from "./principal.js";
@@ -231,6 +229,16 @@ export declare const CHAT_ACTIONS: {
231
229
  readonly protected: false;
232
230
  readonly password: false;
233
231
  };
232
+ readonly suggestion_not_now: {
233
+ readonly label: "Not now";
234
+ readonly protected: false;
235
+ readonly password: false;
236
+ };
237
+ readonly trial_stop: {
238
+ readonly label: "Stop trial";
239
+ readonly protected: false;
240
+ readonly password: false;
241
+ };
234
242
  readonly lead_about_you: {
235
243
  readonly label: "Remember";
236
244
  readonly protected: false;
@@ -279,7 +287,7 @@ export declare function sharedActionReviewPath(id: number): string;
279
287
  export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
280
288
  payload: {
281
289
  version: 1;
282
- operation: "decision_record" | "decision_retire" | "flow_browser" | "flow_browser_off" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
290
+ operation: "decision_record" | "decision_retire" | "flow_browser" | "flow_browser_off" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "suggestion_not_now" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove" | "trial_stop";
283
291
  request: {
284
292
  [x: string]: unknown;
285
293
  };
@@ -1,5 +1,6 @@
1
1
  /** Shared, exact-state actions. Models may prepare; only the existing human
2
2
  * confirmation door executes. Protected actions use a one-use review receipt. */
3
+ import { dismissSuggestion, ruleById, stopTrial, suggestionsFor, suggestionWords, taskEvidenceWords, trialWords } from "./route-rules.js";
3
4
  import { CHAT_ACTION_LOCAL_ACTS, currentLocalOwner } from "./local-owner.js";
4
5
  import { fileTaskProposal } from "./proposal.js";
5
6
  import { heuristicSizing } from "./task-sizing.js";
@@ -128,6 +129,10 @@ export const CHAT_ACTIONS = {
128
129
  kit_setup: { label: "Set up kit", protected: false, password: false },
129
130
  decision_record: { label: "Record decision", protected: false, password: false },
130
131
  decision_retire: { label: "Retire decision", protected: false, password: false },
132
+ // Not now only takes a suggestion off the list for 30 days: nothing about which models run changes.
133
+ suggestion_not_now: { label: "Not now", protected: false, password: false },
134
+ // Stopping a trial only lowers: new tasks build on the configured model; approved ones keep theirs.
135
+ trial_stop: { label: "Stop trial", protected: false, password: false },
131
136
  // What the owner's own lead knows about them: theirs alone, in no project (lead-about.ts).
132
137
  lead_about_you: { label: "Remember", protected: false, password: false },
133
138
  scope_approve: { label: "Approve work", protected: true, password: true },
@@ -279,7 +284,7 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
279
284
  const stamp = hash({ operation, request, repo: "", state, terms, actor: who.name, generation: who.generation, ceiling: who.ceilingDigest });
280
285
  return { version: SHARED_ACTION_VERSION, operation, request, repo: "", title, terms, stamp, state };
281
286
  }
282
- const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_") || operation.startsWith("sweep_")
287
+ const task = operation.startsWith("skill_") || operation.startsWith("knowledge_") || operation.startsWith("decision_") || operation.startsWith("tool_") || operation.startsWith("flow_") || operation.startsWith("subagent_") || operation.startsWith("kit_") || operation.startsWith("sweep_") || operation.startsWith("suggestion_") || operation.startsWith("trial_")
283
288
  ? null
284
289
  : text(input, "task", 64);
285
290
  const flowTarget = operation.startsWith("flow_") && operation !== "flow_create" && operation !== "flow_script_save" && operation !== "flow_starter" ? flowTargetOf(store, input) : null;
@@ -866,6 +871,26 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
866
871
  }
867
872
  }
868
873
  }
874
+ else if (operation === "suggestion_not_now") {
875
+ // Only a suggestion on offer now, by its exact id (which binds its evidence, prices and Models generations).
876
+ const asked = text(input, "suggestion", 200);
877
+ const offer = suggestionsFor(store, repo, now).find(one => one.id === asked);
878
+ if (offer === undefined)
879
+ throw Error("That suggestion isn't on offer any more. See Settings → Models.");
880
+ state = { suggestion: offer.id };
881
+ title = `Not now: ${offer.name} for ${offer.cohortSize === "small" ? "small" : "routine"} tasks`;
882
+ terms.push(suggestionWords(offer), "Keeps this suggestion quiet for 30 days. Nothing about which models run changes. To accept it instead, review it in Settings → Models.");
883
+ }
884
+ else if (operation === "trial_stop") {
885
+ // Only a live trial of this project, by its number; the card is stale once the trial moves.
886
+ const asked = integer(input, "trial");
887
+ const trial = Number.isSafeInteger(asked) ? ruleById(store, asked) : null;
888
+ if (trial === null || trial.kind !== "trial" || trial.repo !== repo || (trial.state !== "trialling" && trial.state !== "paused"))
889
+ throw Error("That trial isn't running any more. See Settings → Models.");
890
+ state = { trial: trial.id, generation: trial.generation };
891
+ title = "Stop trying a cheaper model";
892
+ terms.push(trialWords(store, trial), "New tasks build on the configured model again. Tasks a person already approved on the trial keep that model.");
893
+ }
869
894
  else if (operation.startsWith("decision_")) {
870
895
  // The staleness fence: the newest decision id and the active count, so a
871
896
  // card drafted before a subagent recorded or retired one is refused.
@@ -947,10 +972,11 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
947
972
  if (!authority.ok)
948
973
  throw Error(authority.problem);
949
974
  state["authMode"] = authority.authMode;
950
- terms.push(...describeScope(scope)
975
+ terms.push(...describeScope(scope, undefined, taskEvidenceWords(store, scope.taskId, now))
951
976
  .filter((line) => !/^\s*(reference|approved)\s/.test(line))
952
977
  .map((line) => line
953
978
  .trimStart()
979
+ .replace(/^why model\s+/, "Why this model: ")
954
980
  .replace(/^goal\s+/, "Work: ")
955
981
  .replace(/^not this\s+/, "Excluded: ")
956
982
  .replace(/^touches\s+/, "Allowed files: ")), `Agent permissions and limits\n${runtimeTerms(authority.profile)}\n${authority.authMode === "api-key" ? "Uses your API key; spend is charged to that account." : authority.authMode === "subscription" ? "Uses your subscription login." : ""}`);
@@ -1246,6 +1272,25 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1246
1272
  if (!removeToolFrom(store, repo, String(req["name"]), actor, now))
1247
1273
  throw Error("That tool was already removed.");
1248
1274
  }
1275
+ else if (payload.operation === "trial_stop") {
1276
+ const door = currentLocalOwner() !== null ? { kind: "local-owner", actor } : { kind: "person", person: { actor: who.name, generation: who.generation, method: "session", live: () => true } };
1277
+ const trial = ruleById(store, Number(payload.state["trial"]));
1278
+ if (trial === null || trial.generation !== Number(payload.state["generation"]))
1279
+ throw Error("That trial changed since this card was made. See Settings → Models.");
1280
+ const done = stopTrial(store, trial.id, door, now);
1281
+ if (!done.ok)
1282
+ throw Error(done.problem);
1283
+ return { ok: true, taskId: null, said: "Trial stopped. New tasks build on the configured model.", href: `/settings/models?scope=${encodeURIComponent(repo)}#models-suggested` };
1284
+ }
1285
+ else if (payload.operation === "suggestion_not_now") {
1286
+ // The rule door (route-store.ts proveRuleDoor) re-proves who says it: this computer's local owner only inside
1287
+ // its `dismiss-suggestion` act, or the confirming person.
1288
+ const door = currentLocalOwner() !== null ? { kind: "local-owner", actor } : { kind: "person", person: { actor: who.name, generation: who.generation, method: "session", live: () => true } };
1289
+ const done = dismissSuggestion(store, { repo, id: String(payload.state["suggestion"]) }, door, now);
1290
+ if (!done.ok)
1291
+ throw Error(done.problem);
1292
+ return { ok: true, taskId: null, said: "Not now: this suggestion stays quiet for 30 days.", href: `/settings/models?scope=${encodeURIComponent(repo)}#models-suggested` };
1293
+ }
1249
1294
  else if (payload.operation === "decision_record")
1250
1295
  recordDecision(store, { repo, actor, draft: { claim: String(req["claim"]), why: String(req["why"]), sourceKind: "conversation",
1251
1296
  ...(req["source"] === undefined ? {} : { sourceRef: String(req["source"]) }), ...(payload.state["supersedes"] === undefined ? {} : { supersedes: Number(payload.state["supersedes"]) }) } }, now);
@@ -972,6 +972,8 @@ export function applyDecideFeedback(store, seat, prompt, text, repos, root, now)
972
972
  const revised = requestResultChanges(store, root, {
973
973
  run: prompt.run, batch: "", source: prompt.digest, actor: verified.who.name, repos: verified.who.repos,
974
974
  note: text, path: "", line: "", request: sha(`chat-decide:${seat.channel}:${prompt.id}`).slice(0, 32), allowMode: true,
975
+ // The chat's own verified person, answering "What should change?" in their own words.
976
+ origin: "person",
975
977
  }, now);
976
978
  if (!revised.ok)
977
979
  return { said: `Your feedback wasn't sent: ${revised.message}`, link: null };
@@ -1,3 +1,4 @@
1
+ import type { RevisionOrigin } from "./contracts/route-results.js";
1
2
  import type { Store } from "./store.js";
2
3
  import type { VerifiedApprover } from "./principal.js";
3
4
  import { type FeedbackImage } from "./result-review.js";
@@ -41,7 +42,7 @@ export declare function reviewInputProblem(note: string | null, path: string | n
41
42
  /** Chat and the result form write the same comments and call the same revision service.
42
43
  * A failed combined note+revision rolls back the note too; no half-completed action.
43
44
  */
44
- export declare function applyChatReview(store: Store, who: VerifiedApprover, evidenceRoot: string, request: ReviewRequest, now: Date, allowMode: boolean): {
45
+ export declare function applyChatReview(store: Store, who: VerifiedApprover, evidenceRoot: string, request: ReviewRequest, now: Date, allowMode: boolean, origin: RevisionOrigin): {
45
46
  ok: true;
46
47
  taskId: string;
47
48
  run: number;
@@ -48,7 +48,7 @@ export function reviewInputProblem(note, path, line) {
48
48
  /** Chat and the result form write the same comments and call the same revision service.
49
49
  * A failed combined note+revision rolls back the note too; no half-completed action.
50
50
  */
51
- export function applyChatReview(store, who, evidenceRoot, request, now, allowMode) {
51
+ export function applyChatReview(store, who, evidenceRoot, request, now, allowMode, origin) {
52
52
  try {
53
53
  return store.transact(() => store.savepoint(() => {
54
54
  const fail = (message) => { throw new ReviewRefusal(message); };
@@ -86,7 +86,7 @@ export function applyChatReview(store, who, evidenceRoot, request, now, allowMod
86
86
  if (batch.length === 0)
87
87
  return fail("Add feedback or select saved notes before requesting changes.");
88
88
  const result = createResultRevision(store, evidenceRoot, {
89
- run: live.run, batch: batch.join(","), source: live.source, actor: who.name, repos: who.repos, allowMode,
89
+ run: live.run, batch: batch.join(","), source: live.source, actor: who.name, repos: who.repos, allowMode, origin,
90
90
  }, now);
91
91
  if (!result.ok)
92
92
  return fail(result.message);
@@ -0,0 +1,124 @@
1
+ import { type CheckReport } from "./contracts/release-check.js";
2
+ import { type Store, type VerifyCommand } from "./store.js";
3
+ /** The sources a result can come from, and the states it can have. */
4
+ export declare const CHECK_RESULT_SOURCES: readonly ["receipt", "report", "log", "learn"];
5
+ export type CheckResultSource = typeof CHECK_RESULT_SOURCES[number];
6
+ export type CheckResultState = "failed" | "passed" | "skipped" | "passed-on-retry";
7
+ /** The receipt's whole-check identity, and the one a failed check with nothing else named records. */
8
+ export declare const WHOLE_CHECK = "check";
9
+ export declare const UNEXPLAINED = "unexplained";
10
+ /** How many result rows one check records at most (a report's own bounds are far above any real check). */
11
+ export declare const MAX_RESULTS_PER_CHECK = 20000;
12
+ /** How old a tracked observation may be and still be asked about (docs/plans/learning-loops.md, 90-day window). */
13
+ export declare const OBSERVATION_WATCH_MS: number;
14
+ /** What a check ran under, captured when its command started. `tree` null: its commit couldn't be read from verified
15
+ * objects, and nothing about the check is comparable. */
16
+ export type CheckConditions = {
17
+ head: string;
18
+ tree: string | null;
19
+ commandDigest: string;
20
+ access: string;
21
+ harnessDigest: string | null;
22
+ lockfile: string | null;
23
+ conditionsDigest: string | null;
24
+ conditionsClass: string | null;
25
+ problem: string | null;
26
+ };
27
+ /**
28
+ * The conditions a check's command starts under: its commit's verified tree facts, the approved command's digest, its
29
+ * access, and the values of `envNames` (the variables the check inherits, builder.ts SETUP_ENV_ALLOWLIST) as this
30
+ * process holds them now — what the command is given. Hashed with checkContextOf's fields; the class reads
31
+ * `<platform>-<arch>-node<major>`. Values are hashed, never stored.
32
+ */
33
+ export declare function captureCheckConditions(input: {
34
+ repo: string | null;
35
+ head: string;
36
+ command: VerifyCommand;
37
+ access: string;
38
+ envNames: readonly string[];
39
+ env?: NodeJS.ProcessEnv;
40
+ }): CheckConditions;
41
+ /** Why the task's check ran: a release's or the nightly's purpose, read from the plane's own records. */
42
+ export declare function checkPurposeOf(store: Store, taskRef: number): "release" | "nightly" | "ordinary";
43
+ /** The advisory learning line of a check's output, read strictly: exactly one line carrying this run's nonce, at most
44
+ * 64 KiB, before the report, matching its contract, naming only requested exact cases, each once. Anything else is
45
+ * not recorded, with why; that loses learning evidence only. */
46
+ export declare function learningLinesOf(stdout: string, nonce: string, asked: readonly string[]): {
47
+ ok: true;
48
+ results: {
49
+ identity: string;
50
+ state: "passed" | "failed" | "skipped";
51
+ }[];
52
+ } | {
53
+ ok: false;
54
+ why: string;
55
+ };
56
+ type Row = {
57
+ identity: string;
58
+ state: CheckResultState;
59
+ source: CheckResultSource;
60
+ };
61
+ /** The report's lines as results: every case it names; a file, group, version or part only when it didn't simply pass. */
62
+ export declare function reportResults(report: CheckReport): Row[];
63
+ export type CheckFactsInput = {
64
+ runId: number;
65
+ taskRef: number;
66
+ repo: string | null;
67
+ /** Captured when the command started; null when no command started (nothing comparable was run). */
68
+ conditions: CheckConditions | null;
69
+ /** What the plane recorded as the check's own outcome (run_check), unchanged. */
70
+ receipt: {
71
+ status: "passed" | "failed" | "not-run";
72
+ };
73
+ /** The sealed report, when the check gave a valid one. */
74
+ report: CheckReport | null;
75
+ /** The check's handshake facts, when it had one: its nonce and what it was asked about for learning. */
76
+ attempt: {
77
+ nonce: string;
78
+ learn: readonly string[];
79
+ } | null;
80
+ /** The check's final command output, for the learning line (the same bytes the report was read from). */
81
+ stdout: string | null;
82
+ now: Date;
83
+ };
84
+ /**
85
+ * Record what a settled check ran under and reported, once (first write wins), in one transaction: its fact row and
86
+ * every result row. Called at disposition by the builder right after it records the check's own outcome. Advisory:
87
+ * nothing reads these for a verdict, a rerun or an approval.
88
+ */
89
+ export declare function recordCheckFacts(store: Store, root: string, input: CheckFactsInput): void;
90
+ /** A check's recorded facts, or null. */
91
+ export type CheckFact = {
92
+ run: number;
93
+ repo: string | null;
94
+ taskRef: number;
95
+ head: string | null;
96
+ tree: string | null;
97
+ commandDigest: string | null;
98
+ access: string | null;
99
+ harnessDigest: string | null;
100
+ lockfile: string | null;
101
+ conditionsDigest: string | null;
102
+ conditionsClass: string | null;
103
+ purpose: "release" | "nightly" | "ordinary";
104
+ learnState: "recorded" | "not-recorded" | "not-asked";
105
+ learnWhy: string | null;
106
+ recordedAt: string;
107
+ };
108
+ export declare function checkFactOf(store: Store, runId: number): CheckFact | null;
109
+ /** A check's recorded results, by source then identity. */
110
+ export declare function checkResultsOf(store: Store, runId: number): {
111
+ identity: string;
112
+ state: CheckResultState;
113
+ source: CheckResultSource;
114
+ }[];
115
+ /**
116
+ * What a project's Full checks are asked about for learning (`learn`): its tracked identities (learning_watch) — an
117
+ * accepted note's until it ends, an observation's for 90 days — exact cases only, the most recently tracked first, at
118
+ * most 200. Those left out are counted (`leftOut`): a coverage gap, said, never hidden.
119
+ */
120
+ export declare function learnRequestFor(store: Store, repo: string, now: Date): {
121
+ learn: string[];
122
+ leftOut: number;
123
+ };
124
+ export {};