@xingular/harness 1.1.1 → 1.2.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.
Files changed (223) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +28 -25
  3. package/dist/cli.js +6675 -2501
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.js +15 -1
  11. package/dist/sdk/agents/planResearch.d.ts +5 -0
  12. package/dist/sdk/agents/planResearch.js +19 -0
  13. package/dist/sdk/agents/profiles.d.ts +2 -1
  14. package/dist/sdk/agents/profiles.js +9 -4
  15. package/dist/sdk/annotations/pageScript.js +349 -48
  16. package/dist/sdk/annotations/part.js +34 -23
  17. package/dist/sdk/annotations/store.d.ts +7 -0
  18. package/dist/sdk/annotations/store.js +13 -5
  19. package/dist/sdk/browser/backend.d.ts +16 -0
  20. package/dist/sdk/browser/backend.js +17 -0
  21. package/dist/sdk/browser/contracts.d.ts +19 -0
  22. package/dist/sdk/browser/contracts.js +116 -0
  23. package/dist/sdk/browser/tools.d.ts +1 -4
  24. package/dist/sdk/browser/tools.js +14 -91
  25. package/dist/sdk/cli.js +5 -4
  26. package/dist/sdk/commands/list.d.ts +3 -0
  27. package/dist/sdk/commands/list.js +75 -0
  28. package/dist/sdk/commands/registry.d.ts +250 -0
  29. package/dist/sdk/commands/registry.js +47 -0
  30. package/dist/sdk/commands/routes.d.ts +16 -0
  31. package/dist/sdk/commands/routes.js +33 -0
  32. package/dist/sdk/core/commandBudget.d.ts +23 -0
  33. package/dist/sdk/core/commandBudget.js +57 -0
  34. package/dist/sdk/core/index.js +17 -1
  35. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  36. package/dist/sdk/core/interactiveProfile.js +143 -0
  37. package/dist/sdk/core/workspace.d.ts +13 -0
  38. package/dist/sdk/core/workspace.js +88 -0
  39. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  40. package/dist/sdk/core/workspaceBashTool.js +36 -0
  41. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  42. package/dist/sdk/core/workspaceSandbox.js +320 -0
  43. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  44. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  45. package/dist/sdk/doctor/types.d.ts +2 -2
  46. package/dist/sdk/harness.d.ts +7 -0
  47. package/dist/sdk/harness.js +42 -2
  48. package/dist/sdk/index.d.ts +6 -1
  49. package/dist/sdk/index.js +3 -0
  50. package/dist/sdk/pi/runtime.d.ts +2 -1
  51. package/dist/sdk/pi/runtime.js +2 -2
  52. package/dist/sdk/plugins/builtin.js +29 -12
  53. package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
  54. package/dist/sdk/plugins/builtinRegistry.js +30 -8
  55. package/dist/sdk/plugins/catalogue.js +2 -1
  56. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  57. package/dist/sdk/plugins/commandPrefix.js +21 -0
  58. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  59. package/dist/sdk/plugins/firstParty.js +8 -2
  60. package/dist/sdk/plugins/inventory.js +4 -3
  61. package/dist/sdk/plugins/manifest.d.ts +2 -0
  62. package/dist/sdk/plugins/manifest.js +2 -1
  63. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  64. package/dist/sdk/plugins/surfaces.js +11 -0
  65. package/dist/sdk/plugins/types.d.ts +15 -0
  66. package/dist/sdk/prompt/compile.d.ts +14 -0
  67. package/dist/sdk/prompt/compile.js +65 -29
  68. package/dist/sdk/prompt/standard.d.ts +17 -0
  69. package/dist/sdk/prompt/standard.js +21 -0
  70. package/dist/sdk/server/methods.js +18 -0
  71. package/dist/sdk/server/remote.js +36 -11
  72. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  73. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  74. package/dist/sdk/sessions/continuation.d.ts +3 -0
  75. package/dist/sdk/sessions/continuation.js +5 -1
  76. package/dist/sdk/sessions/contracts.d.ts +168 -0
  77. package/dist/sdk/sessions/contracts.js +59 -1
  78. package/dist/sdk/sessions/control.d.ts +72 -0
  79. package/dist/sdk/sessions/control.js +158 -0
  80. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  81. package/dist/sdk/sessions/initialBash.js +7 -0
  82. package/dist/sdk/sessions/planMode.d.ts +14 -3
  83. package/dist/sdk/sessions/planMode.js +73 -16
  84. package/dist/sdk/sessions/registry.d.ts +1 -1
  85. package/dist/sdk/sessions/registry.js +2 -2
  86. package/dist/sdk/sessions/runStore.d.ts +14 -0
  87. package/dist/sdk/sessions/runStore.js +35 -0
  88. package/dist/sdk/sessions/runner.d.ts +40 -2
  89. package/dist/sdk/sessions/runner.js +161 -13
  90. package/dist/sdk/sessions/sessions.d.ts +23 -3
  91. package/dist/sdk/sessions/sessions.js +202 -17
  92. package/dist/sdk/sessions/subagentService.d.ts +9 -0
  93. package/dist/sdk/sessions/subagentService.js +63 -4
  94. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  95. package/dist/sdk/sessions/workSlice.js +297 -0
  96. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  97. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  98. package/dist/sdk/terminal/browserControl.js +2 -1
  99. package/dist/sdk/terminal/controller.d.ts +11 -3
  100. package/dist/sdk/terminal/controller.js +40 -13
  101. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  102. package/dist/sdk/terminal/opentui/theme.js +87 -0
  103. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  104. package/dist/sdk/terminal/piExtension.js +154 -54
  105. package/dist/sdk/terminal/standalone.d.ts +7 -0
  106. package/dist/sdk/terminal/standalone.js +72 -13
  107. package/dist/sdk/terminal/subagentMonitor.d.ts +11 -2
  108. package/dist/sdk/terminal/subagentMonitor.js +32 -1
  109. package/dist/sdk/terminal/terminals.d.ts +19 -2
  110. package/dist/sdk/terminal/terminals.js +245 -35
  111. package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
  112. package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
  113. package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
  114. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  115. package/dist/sdk/terminal/tui/commands.d.ts +2 -0
  116. package/dist/sdk/terminal/tui/commands.js +205 -124
  117. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  118. package/dist/sdk/terminal/tui/list.js +96 -14
  119. package/dist/sdk/terminal/tui/pageOverlay.js +13 -4
  120. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
  121. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
  122. package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  124. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  125. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
  126. package/dist/sdk/terminal/tui/pages/subagentsPage.js +98 -16
  127. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  128. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  129. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  130. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  131. package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
  132. package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
  133. package/dist/sdk/terminal/tui/runtime.js +23 -3
  134. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  135. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  136. package/dist/sdk/tools/checklist.js +1 -0
  137. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  138. package/dist/sdk/tools/checkpoint.js +154 -0
  139. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  140. package/dist/sdk/tools/getCapabilities.js +34 -0
  141. package/dist/sdk/tools/spawnSubagent.js +4 -3
  142. package/dist/sdk/usage/analytics.d.ts +20 -0
  143. package/dist/sdk/usage/analytics.js +151 -0
  144. package/dist/sdk/usage/ledger.d.ts +38 -0
  145. package/dist/sdk/usage/ledger.js +142 -0
  146. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  147. package/dist/sdk/usage/limits/adapters.js +349 -0
  148. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  149. package/dist/sdk/usage/limits/capture.js +47 -0
  150. package/dist/sdk/usage/limits/http.d.ts +12 -0
  151. package/dist/sdk/usage/limits/http.js +51 -0
  152. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  153. package/dist/sdk/usage/limits/normalize.js +211 -0
  154. package/dist/sdk/usage/limits/service.d.ts +30 -0
  155. package/dist/sdk/usage/limits/service.js +176 -0
  156. package/dist/sdk/usage/limits/types.d.ts +65 -0
  157. package/dist/sdk/usage/limits/types.js +1 -0
  158. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  159. package/dist/sdk/usage/serverMethods.js +22 -0
  160. package/dist/sdk/usage/service.d.ts +32 -0
  161. package/dist/sdk/usage/service.js +66 -0
  162. package/dist/sdk/usage/settings.d.ts +7 -0
  163. package/dist/sdk/usage/settings.js +28 -0
  164. package/dist/sdk/usage/types.d.ts +60 -0
  165. package/dist/sdk/usage/types.js +4 -0
  166. package/dist/terminal-opentui.js +5207 -1883
  167. package/dist/terminal-pi.js +1658 -271
  168. package/docs/architecture/02-sessions-and-runs.md +128 -26
  169. package/docs/architecture/04-prompt.md +50 -12
  170. package/docs/architecture/06-plugins-and-capabilities.md +24 -7
  171. package/docs/architecture/07-tools-and-gate.md +104 -24
  172. package/docs/architecture/08-memory-and-history.md +4 -2
  173. package/docs/architecture/09-accounts-and-connectors.md +31 -1
  174. package/docs/architecture/10-server-protocol.md +58 -3
  175. package/docs/architecture/11-terminal.md +105 -42
  176. package/docs/architecture/12-storage.md +30 -2
  177. package/docs/architecture/13-world-tools.md +22 -1
  178. package/docs/architecture/14-surfaces-and-parity.md +54 -7
  179. package/docs/architecture/README.md +3 -1
  180. package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
  181. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
  182. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  183. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  184. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  185. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  186. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  187. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  188. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  189. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  190. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  191. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  192. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  193. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  194. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  195. package/docs/architecture/proposals/README.md +14 -0
  196. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  197. package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
  198. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  199. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  200. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  201. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  202. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  203. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  204. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  205. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  206. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  207. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  208. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  209. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  210. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  211. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  212. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  213. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  214. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  215. package/docs/parity.md +80 -24
  216. package/docs/releases/1.1.1.md +28 -0
  217. package/docs/releases/1.2.0.md +49 -0
  218. package/docs/releases/4.0.0.md +10 -9
  219. package/docs/releases/README.md +35 -30
  220. package/docs/study/00-index.md +3 -0
  221. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  222. package/docs/terminal-tui.md +100 -59
  223. package/package.json +4 -3
@@ -0,0 +1,73 @@
1
+ ---
2
+ id: PROP-0038
3
+ title: Annotate inside open shadow roots
4
+ status: Implemented
5
+ created: 2026-10-25
6
+ updated: 2026-10-25
7
+ affects: [13-world-tools.md, 12-storage.md (note target), PROP-0017, PROP-0018, parity]
8
+ ---
9
+
10
+ # PROP-0038: Annotate inside open shadow roots
11
+
12
+ Requested by the owner on 2026-10-25 after the inspector did nothing on reddit.com; they approved
13
+ this design in the same session.
14
+
15
+ ## Problem
16
+
17
+ Sites built from web components keep their content in shadow roots. Reddit's posts, comments and
18
+ feed are `shreddit-*` elements with open shadow roots. The page helper (`annotations/pageScript.ts`)
19
+ refused anything whose root node is not `document`, anything under an element with a shadow root, and
20
+ any custom-element tag (PROP-0017, PROP-0018: "frames, shadow roots and custom elements are never
21
+ picked"). Its text stream (`TreeWalker` over `document.body`) and element lookup
22
+ (`document.getElementsByTagName`) never entered a shadow root. On such sites the inspector drew no
23
+ outline, ignored clicks and said nothing. In WebKit, `getRangeAt` reports a selection inside a shadow
24
+ root as a collapsed point on its host, so text capture could not work there either.
25
+
26
+ ## Proposal
27
+
28
+ 1. **Walk the composed tree.** New helper primitives walk open shadow roots and slotted content in
29
+ rendered order (`composedParent`, `flatChildren`, a bounded composed text stream). Visibility and
30
+ the excluded-content rules are checked on composed ancestors, and `aria-labelledby` ids are looked up
31
+ in the element's own root. Closed shadow roots stay unreadable, and light text a closed component
32
+ does not render is left out. Frames are never entered.
33
+ 2. **Version the target.** Text and element targets captured this way carry `tree: 'composed'`. A
34
+ target without it (every note saved before) is captured, stored and resolved by the document-only
35
+ rules exactly as before, so a saved note never changes what it points at, and a composed note can
36
+ never match a light-DOM node through the old path. The store accepts only the literal `'composed'`
37
+ and never on a page target.
38
+ 3. **Selections across trees.** Selections are read with `Selection.getComposedRanges` (dictionary
39
+ form, then variadic). Boundary points map to stream positions through the composed walk, so a
40
+ selection may start in the page and end inside a component. A selection that spans a visible form
41
+ field or private content is refused. A selection that grows over such content no longer leaves the
42
+ last acceptable selection behind; this also fixes the document-only path. Without
43
+ `getComposedRanges` the helper keeps the document-only capture.
44
+ 4. **Matches and marks.** A composed text match is a list of per-text-node ranges, since a `Range`
45
+ cannot span trees. Element candidates come from the document and every open shadow root, with the
46
+ same 2 000-candidate cap and equality rules. Context at the top of a shadow root is the component
47
+ that holds it. Marks clip to scrollports inside components and follow their scrolls and mutations.
48
+ 5. **Inspector.** The inspector picks the nearest annotatable element on the event's composed path, so
49
+ an icon gives way to its button, and content inside open components is reachable. A custom element
50
+ is pickable when it carries readable text or a label. It says why when it refuses, in a muted chip
51
+ and a toast on click: form fields and private content, embedded frames, or a component whose
52
+ contents are private.
53
+ 6. **Agents.** `page_notes_read` adds that a composed target lies inside a page component, where a
54
+ document-wide selector will not reach it.
55
+
56
+ ## Out of scope
57
+
58
+ Iframes (cross-origin frames are unreachable by page script) and closed shadow roots.
59
+
60
+ ## Migration
61
+
62
+ None. Existing notes have no `tree` and keep the old behaviour. Hosts that validate targets strictly
63
+ accept the optional field (the App's `shared/browserAnnotations.ts`).
64
+
65
+ ## Checks
66
+
67
+ `src/annotations/pageScript.test.ts` (headless Chromium) adds a web-component fixture: picks inside
68
+ open roots, slotted and nested content, duplicate controls told apart by context, text inside and
69
+ across roots, refusal over a field, hints for fields and a closed component, and identical
70
+ legacy/composed captures on a page without components. `store.test.ts` covers the marker. In the App's
71
+ isolated probe instance (real WKWebView), text inside a component and text crossing into one were
72
+ captured, saved, resolved and revealed. Inspector clicks in WKWebView need real pointer input and were
73
+ not exercised there.
@@ -0,0 +1,139 @@
1
+ ---
2
+ id: PROP-0039
3
+ title: Harden workspace writes against inode escapes and unverified retries
4
+ status: Accepted
5
+ created: 2026-10-26
6
+ updated: 2026-10-26
7
+ affects: [INV-GATE-3, INV-GATE-8, INV-TUI-8, 02-sessions-and-runs.md, 07-tools-and-gate.md, 11-terminal.md, 14-surfaces-and-parity.md, parity]
8
+ ---
9
+
10
+ # PROP-0039: Harden workspace writes against inode escapes and unverified retries
11
+
12
+ ## Problem
13
+
14
+ PROP-0037 promises that model-controlled writes stay inside the pinned workspace unless a person
15
+ approves an escape. The current implementation checks canonical pathnames, but Pi performs native
16
+ `write` and `edit` operations afterward using ordinary filesystem calls. A hard link keeps an
17
+ in-workspace pathname while sharing an outside inode, and a parent symlink can change after the
18
+ check. Source review found both cases can bypass the intended approval boundary.
19
+
20
+ The Bash sandbox also exposes the live workspace as a writable path. A macOS integration
21
+ reproduction created a hard link from a file outside the workspace to a name inside it, then ran a
22
+ sandboxed Bash write through the in-workspace name. The command succeeded and the outside alias
23
+ contained the new content. Linux behavior has not yet been reproduced locally.
24
+
25
+ Finally, `workspaceSandbox.ts` infers a sandbox denial from matching text anywhere in captured
26
+ stdout/stderr. A command controls that output; the text can prompt for an unconfined retry even when
27
+ no sandbox denial occurred. Approval is still required, but the prompt's reason is not verified and
28
+ approval replays the entire command.
29
+
30
+ A separate launcher-selection issue was fixed in the 1.2.0 working tree: the sandbox launcher now
31
+ comes only from fixed system paths, with a fake-PATH regression test. This does not resolve the
32
+ remaining findings. No candidate, tag, or publication has been created.
33
+
34
+ ## Affected rules
35
+
36
+ - **INV-GATE-3**, as it stands: “Bash first passes the gate's scope and authorization, then executes
37
+ only inside the OS sandbox; a detected write denial may enter a separate one-shot approval before
38
+ retry. The policy may tighten or deny a call, but cannot waive the boundary. Outside file writes
39
+ and sandbox-denied Bash retries cannot use remembered per-tool approval; only the eligible Full
40
+ Permission switch can answer automatically.” The output-triggered retry is replaced by an
41
+ explicit pre-execution scope request.
42
+ - **INV-GATE-8**, as it stands: “Native reads are bounded by process and operating-system
43
+ permissions, not by the workspace root; absolute and `~` paths are valid. Every run pins its
44
+ canonical `workspaceRoot`. Native `edit` and `write` targets must resolve inside it by default,
45
+ with symlink escapes refused; outside targets require one-call approval and cannot use remembered
46
+ tool grants. Bash, including the initial `!` command, is OS-sandboxed to write only inside that
47
+ root while retaining broad reads. Sandbox denial permits at most one retry after explicit
48
+ approval. Full Permission is the only standing boundary bypass and remains subject to host
49
+ policy, authorization, and read-only, plan, unattended, probe, improver and reflection
50
+ restrictions. Missing enforcement fails closed.” The rule must define containment by the actual
51
+ filesystem effect, not just the path string.
52
+ - **INV-TUI-8**: the terminal must present the same safe write and pre-execution approval behavior
53
+ as SDK sessions.
54
+
55
+ ## Proposal
56
+
57
+ 1. **Keep workspace-only as the default.** Each run continues to pin its canonical workspace root.
58
+ Reads remain broad as today.
59
+ 2. **Enforce native writes at the operation.** Harness supplies the `write` and `edit` tools with
60
+ safe filesystem operations instead of delegating their mutations to Pi's default in-place
61
+ `fsWriteFile`. The operation revalidates the pinned target and performs an atomic replacement
62
+ under the OS-enforced workspace boundary. Replacing the in-workspace directory entry rather
63
+ than truncating a shared inode prevents a hard-link alias from changing an outside file. A
64
+ symlink or parent-directory swap cannot widen the operation's OS write scope. Outside targets
65
+ retain their existing human-only, one-call approval; the helper receives only that approved
66
+ canonical target. Tool names and model-facing schemas stay the same.
67
+ 3. **Fail closed on external hard links for sandboxed Bash.** Before a default Bash call, inspect the
68
+ workspace for regular-file inodes whose link count exceeds the names found inside the workspace.
69
+ If the scan is incomplete or finds a possible outside alias, do not run the command; explain that
70
+ the alias must be removed or copied. The OS sandbox must also prevent the model's sandboxed
71
+ processes from creating a cross-boundary hard link. If a backend cannot establish both
72
+ conditions, it fails closed. A local process outside Harness with the same user's filesystem
73
+ permissions is not controlled by this rule; if protection from such peer processes is required,
74
+ that needs a separate inode-isolated workspace/snapshot design.
75
+ 4. **Ask before broad Bash writes, never infer from output.** Add an explicit
76
+ `workspaceWriteScope` choice to the Harness-owned Bash call, defaulting to `workspace`. A call
77
+ requesting `outside` is approved before it runs, as a human-only, nonrememberable operation; the
78
+ approval presentation must expose the complete command (scrollable in the terminal). The
79
+ eligible Full Permission switch may answer only under its existing restrictions. After approval,
80
+ run that exact command once with broader filesystem access. stdout/stderr text never triggers
81
+ approval or an automatic retry, and a denied command is never replayed after partial effects.
82
+ 5. **Keep other gates intact.** Plan, read-only, unattended, probe, improver and reflection
83
+ restrictions, host policy, authorization, and the tool capability set remain in force. Outside
84
+ writes and broad Bash calls cannot use remembered per-tool grants.
85
+
86
+ ## Alternatives
87
+
88
+ - **Keep pathname checks and the output regex.** Rejected: hard links are invisible to `realpath`,
89
+ native operations race after validation, and command output is not evidence of a sandbox event.
90
+ - **Only reject files with `nlink > 1` for native writes.** Rejected: a link can be added after the
91
+ check, and the native operation remains subject to path swaps.
92
+ - **Run every shell call in a private copy-on-write workspace and commit changes back.** This gives
93
+ stronger inode isolation, including against peer processes, but changes shell visibility,
94
+ writeback/conflict behavior, metadata, storage use, and performance. It remains the alternative
95
+ if protection from same-user processes outside Harness is required.
96
+ - **Remove all outside-write capability.** Rejected for now: the existing contract intentionally
97
+ supports explicitly approved escapes. The proposed pre-execution request preserves that choice
98
+ without guessing from output.
99
+ - **Do nothing.** Leaves the default workspace boundary bypassable.
100
+
101
+ ## Consequences
102
+
103
+ - The public server protocol and stored `RunRecord` layout need no new fields. The Bash tool schema
104
+ gains an optional scope choice; existing calls default to the current workspace-only behavior.
105
+ - SDK sessions and the standalone terminal must register the same safe native tools and apply the
106
+ same pre-execution Bash gate. Terminal approval remains available through its existing inbox.
107
+ - Atomic replacement changes the inode for a written path. The implementation must preserve normal
108
+ file mode behavior where possible and document any platform metadata that cannot be retained.
109
+ - Reject the filesystem root as an active workspace; otherwise every path is inside the pinned root
110
+ and the boundary provides no containment.
111
+ - A workspace containing a possible external hard link cannot use default sandboxed Bash until it
112
+ is repaired. Explicit outside scope remains a separate, visible approval, not a way to silently
113
+ reuse remembered grants.
114
+ - The boundary protects model-controlled operations inside Harness. Other local processes running
115
+ with the same user's filesystem permissions remain outside this proposal's protection. On macOS,
116
+ the OS sandbox denies all hard-link creation by sandboxed Bash, including links entirely inside the
117
+ workspace.
118
+
119
+ ## Migration
120
+
121
+ No stored runs or workspace data are rewritten. Old sessions continue to pin the same workspace
122
+ root. Existing Bash calls omit `workspaceWriteScope` and therefore remain workspace-only; a command
123
+ that needs broader writes must be issued again with an explicit outside scope and approved before
124
+ execution. Existing outside-write approvals remain per-call and are not migrated into standing
125
+ grants.
126
+
127
+ ## Checks
128
+
129
+ - Add adversarial tests for native hard-link writes, symlink/parent swaps between validation and
130
+ mutation, shell writes through pre-existing external hard links, and cross-boundary hard-link
131
+ creation on macOS and Linux.
132
+ - Verify command output containing denial-like text cannot request approval or rerun a command;
133
+ verify explicit outside scope asks before execution and runs at most once after approval.
134
+ - Verify the complete requested command is visible in outside-scope approval details, and Full
135
+ Permission and all read-only, plan, unattended, refinement, authorization, and host policy
136
+ restrictions remain intact in SDK and terminal paths.
137
+ - Run Harness `npm run typecheck`, `npm test`, and `npm run prove`; run the App compatibility checks
138
+ and inspect the relevant terminal behavior. Exercise the sandbox integration tests on macOS and
139
+ Linux before release.
@@ -0,0 +1,87 @@
1
+ ---
2
+ id: PROP-0040
3
+ title: One launch path and command contract across front ends
4
+ status: Accepted
5
+ created: 2026-11-19
6
+ updated: 2026-11-19
7
+ affects: [INV-EXT-1, INV-RUN-1, INV-VAR-4, INV-PLG-11, INV-PAR-1, INV-PAR-2, INV-STO-3, 01-extension-points.md, 02-sessions-and-runs.md, 03-variants.md, 04-prompt.md, 06-plugins-and-capabilities.md, 07-tools-and-gate.md, 10-server-protocol.md, 11-terminal.md, 12-storage.md, 13-world-tools.md, 14-surfaces-and-parity.md]
8
+ ---
9
+
10
+ # PROP-0040: One launch path and command contract across front ends
11
+
12
+ ## Problem
13
+
14
+ The approved App parity and Solo tabs handoff implemented a shared command registry and standard
15
+ prompt sections, but the actual terminal still loads Pi command built-ins, terminal run records
16
+ omit variant pins, session operations differ across front ends, and native browser schemas differ.
17
+ Matching data roots alone does not share credentials: the terminal defaults to a file SecretStore
18
+ while a host may supply its Keychain. Existing compiler-only tests do not establish runtime parity.
19
+
20
+ ## Affected rules
21
+
22
+ - INV-PAR-1: every capability a person uses in a host is a Harness capability with a terminal surface.
23
+ - INV-PAR-2: both surfaces must pass acceptance before a capability is complete.
24
+ - INV-RUN-1 and INV-VAR-4: runs retain account, variant and capability pins.
25
+ - INV-EXT-1: extension-point changes require a proposal; native backends remain tool providers.
26
+ - INV-STO-3: storage layout changes are explicit and owned by one Harness module.
27
+ - INV-PLG-11: plugin categories remain internal; surface availability is optional metadata, not a category.
28
+
29
+ ## Proposal
30
+
31
+ The owner approved “Complete App parity and Solo tabs”, including one shared interactive default,
32
+ on 2026-11-19. This proposal records that design; implementation status is tracked separately.
33
+
34
+ 1. Core commands are browser-safe data in `commands/registry.ts`. `commands.list` resolves the
35
+ effective scope's trusted plugin commands, prompts and skills with identical namespace and
36
+ collision rules. A menu entry alone does not prove command acceptance.
37
+ 2. Saved-history controls cover tree, navigate, fork, clone, rename, last reply and JSONL export.
38
+ Commands requiring a live session use `SessionRunInput.command`: compact, summarized navigation
39
+ and HTML export. They retain normal run lifecycle, busy fencing, cancellation and pins.
40
+ 3. `Launch.standard` supplies shared role, working-folder and connection wording. Tool and prompt
41
+ differences must be attributable to declared add-ons or explicit caller policy, not a front end.
42
+ 4. Interactive defaults are Harness-owned: data root, state directory, neutral default variant
43
+ scope and credential backend. Explicit roots/scopes and existing session/continuation pins win.
44
+ No App-specific discovery, automatic root merge, account substitution, credential downgrade or
45
+ OAuth-store copying is permitted. Adoption of conflicting populated profiles is explicit.
46
+ 5. Both surfaces load Harness-trusted resources and first-party hooks. Terminal-only Pi built-ins
47
+ and `ask_user_question` are removed; attended plan mode uses `ask_user`.
48
+ 6. Browser contracts are backend-neutral and owned by Harness; the isolated and native host
49
+ backends share names, schemas, safety metadata and ref freshness. A backend must report an
50
+ unsupported operation honestly, never reinterpret a snapshot ref as a selector.
51
+ 7. `get_capabilities` is a read-only Harness tool bound to the current run's frozen callable set
52
+ and actual registered schemas. Discovery never grants permission or accepts caller identity.
53
+ 8. Optional `PluginDescriptor.surfaces` / `Plugin.surfaces` metadata is
54
+ `{ preferred: 'host', terminal: 'fallback' | 'unavailable', note }`. Generic host-backed
55
+ capabilities retain Harness ownership; inspection shows fallback/unavailable reasons.
56
+ 9. Shared session references/catalog registration belong to Harness. Existing host transcript
57
+ presentation remains host-owned; native entry identities, not positional message counts,
58
+ connect navigation and fork to that presentation.
59
+
60
+ ## Alternatives
61
+
62
+ - Do nothing: leaves command names that only approximate the advertised capabilities.
63
+ - Match roots manually: rejected by the owner's explicit choice of one shared default.
64
+ - Put browser contracts or session orchestration in the host: violates INV-PAR-1.
65
+ - Merge existing stores automatically: unsafe for account pins, credentials and conflicting variants.
66
+
67
+ ## Consequences
68
+
69
+ Server methods and SDK fields are additive. Existing session histories and account IDs remain valid.
70
+ Terminal run records gain variant identity. Host browser adapters implement the common contract;
71
+ legacy native routes may remain non-model-visible compatibility aliases for one release. Metadata
72
+ labels do not certify acceptance or waive the gate.
73
+
74
+ ## Migration
75
+
76
+ Keep explicit custom roots and historical pins. Seed only an absent default scope through validated
77
+ variant APIs. Shared profile adoption must preserve originals, report conflicts/failure, and never
78
+ silently select an empty replacement. Credentials remain in their owning backend. Shared processes
79
+ must coordinate credential refresh and respect live run owners. Development/disposable profiles
80
+ remain isolated from production. No migration is exercised on owner data during implementation.
81
+
82
+ ## Checks
83
+
84
+ Harness typecheck, full tests and prove; deterministic terminal verification; App renderer/sidecar,
85
+ layer/UI and Rust checks; full browser fixtures; shared UI and both consuming applications' checks.
86
+ Runtime parity compares actual prepared launches, tool schemas and recorded pins. Native WebKit
87
+ acceptance uses an isolated profile. Real-provider/cache checks require separate owner permission.
@@ -0,0 +1,247 @@
1
+ ---
2
+ id: PROP-0041
3
+ title: Bounded work slices, a durable checkpoint, and a stall guard for long replies
4
+ status: Implemented; terminal card, App presentation and /slices open
5
+ created: 2026-10-26
6
+ updated: 2026-10-26
7
+ affects: [INV-RUN-2, INV-RUN-3, INV-RUN-4, INV-RUN-12, INV-PRM-1, INV-PRM-2, INV-PRM-4, INV-PRM-5, INV-PRM-10, INV-GATE-2, INV-STO-3, INV-PAR-1, INV-PAR-2, 02-sessions-and-runs.md, 04-prompt.md, 07-tools-and-gate.md, 10-server-protocol.md, 11-terminal.md, 12-storage.md, 14-surfaces-and-parity.md, parity.md]
8
+ ---
9
+
10
+ # PROP-0041: Bounded work slices, a durable checkpoint, and a stall guard
11
+
12
+ ## Problem
13
+
14
+ A single reply can run for hours. Two measured sessions (STUDY-0003) ran 114 and 75 minutes in
15
+ **one** reply: 448 and 274 tool calls, 5 and 2 context compactions at ~256k tokens, 42 repeated
16
+ identical calls, and 41 minutes in three Playwright runs — 25 of them on two timeouts. The first had
17
+ addressed the person with **no text at all** at 114 minutes when it was measured, and was still
18
+ going; the second produced a single 2 028-character report at minute 74.9, and it said the release
19
+ was still blocked.
20
+
21
+ Nothing in the foundation bounds a reply. The only limiter is the opt-in `budget-guard` plugin
22
+ (`src/plugins/firstParty.ts:31,105-140`), which is off by default and, when on, refuses tools and
23
+ terminates the reply with no handoff. The post-compaction checkpoint (PROP-0033) restores only the
24
+ goal and the checklist, returns `null` when no checklist was written (`src/prompt/compile.ts:196`),
25
+ and carries no files, failing commands or decisions. Bash has no default timeout
26
+ (`src/core/workspaceBashTool.ts:23`). There is no stall detection, no heartbeat, and dead-owner runs
27
+ are only reconciled at startup (`src/sessions/runStore.ts:228`).
28
+
29
+ The result is not a loop on one action; it is a reply with no boundary. Nothing the model learns
30
+ becomes a checkpoint, a commit or a message, so the person cannot steer, review or stop it, and the
31
+ model re-derives state by re-reading files after every compaction.
32
+
33
+ Task and goal lifecycle stay in the application (`docs/study/feature-gap-review.md:7,35`). Every
34
+ mechanism below is **run execution**, which the harness owns.
35
+
36
+ ## Affected rules
37
+
38
+ - **INV-RUN-4** currently says continuing is "always a new run the application asks for". A slice
39
+ policy asks on the run's behalf. This proposal amends the asker, not the rule: continuation remains
40
+ a new run with the earlier run's pins, and the earlier record is never reopened.
41
+ - **INV-RUN-12 / INV-PRM-10** define the post-compaction checkpoint as goal + checklist + plan. The
42
+ checkpoint becomes a bounded ledger assembled by the compiler and used at both compaction and slice
43
+ boundaries. It stays ephemeral, bounded, and free of hidden reasoning.
44
+ - **INV-RUN-3** every event is validated before it is persisted or relayed; the new `checkpoint`,
45
+ `progress` and `heartbeat` events follow the same path.
46
+ - **INV-RUN-2 / INV-PRM-5** a new foundation tool (`checkpoint_write`) joins the frozen capability
47
+ set and the compiled tool manifest. Runs pin it; nothing widens after launch.
48
+ - **INV-GATE-2** the gate stays the last extension. The loop guard is a foundation extension loaded
49
+ before it, like the compaction checkpoint.
50
+ - **INV-PRM-1/2/4** all checkpoint and guard text is assembled in `src/prompt/compile.ts`; the
51
+ stable system prompt does not change.
52
+ - **INV-STO-3** no new file or folder: checkpoints are run events in `events.jsonl`.
53
+ - **INV-PAR-1/2** the budget, checkpoint card, stall report and `/slices` control exist in the
54
+ terminal and are presented by the App; `parity.md` gains a row.
55
+
56
+ ## Proposal
57
+
58
+ ### 1. A reply budget, enforced at the tool boundary
59
+
60
+ A foundation policy, on by default for every run (interactive, automatic, plan, probe, subagent):
61
+
62
+ - `workSlice.enabled` true; `workSlice.maxToolCalls` 160, `workSlice.maxMinutes` 40,
63
+ `workSlice.maxNewTokens` 320 000. `enabled: false`, or `XINGULAR_WORK_SLICE=off` at launch,
64
+ removes the guard from a run: no budget, no stall notes, no continuation. Any
65
+ limit reached, or `0` to disable that one, ends the slice. A token cap counts new input, cache
66
+ writes and output; counting the cached prefix made one large read end a reply before any work
67
+ (measured on the "Confirm plan model support" thread: 76 807 counted tokens after four requests).
68
+ - Counted by the runner from observed events: tool calls started, wall clock since the run started,
69
+ and the sum of `inputTokens + cacheReadTokens + outputTokens` per request.
70
+ - Enforcement is a Pi `tool_call` hook, like `budget-guard`, but the exit is a checkpoint, not a
71
+ silent stop: the next tool call is refused once with a reason that names the spent budget and
72
+ instructs the model to call `checkpoint_write`; that tool (and `session_name`) remain callable.
73
+ After a valid checkpoint, the model gets one request to write the person-facing progress line, and
74
+ the run settles with terminal reason `slice_complete`.
75
+ - A reply that finishes inside its budget is unaffected at runtime: no extra request and no extra
76
+ context. The only global cost is the new tool entry in the compiled manifest (Consequences).
77
+
78
+ ### 2. `checkpoint_write`: a bounded, evidence-linked ledger
79
+
80
+ A foundation tool, always present beside `checklist_write`:
81
+
82
+ ```
83
+ checkpoint_write {
84
+ summary: string // ≤ 1 200 chars, what the person should know now
85
+ done: [{ item, evidence[] }] // ≤ 20; evidence is a path, a check command, or a decision
86
+ next: string // the single next step
87
+ blocked?: string // what stops progress, if anything
88
+ files: [{ path, why }] // ≤ 20
89
+ checks: [{ command, ok, detail }] // ≤ 8; detail holds the failing line, ≤ 300 chars
90
+ decisions: [{ what, why }] // ≤ 8
91
+ }
92
+ ```
93
+
94
+ - The harness validates shape and bounds, then records one `checkpoint` run event (bounded 8 000
95
+ chars). The latest checkpoint is the run's resumable state; `runs.continuation` returns it next to
96
+ the checklist.
97
+ - Evidence is checked against the run's own events since the previous checkpoint. A `done` item whose
98
+ evidence was not observed is kept but marked `unverified` in the record and told to the model; the
99
+ tool never refuses a checkpoint, because a checkpoint is better than a stall.
100
+ - The prompt section that asks for a checkpoint is compiled, bounded, and states that the ledger is
101
+ read by the next slice, not shown as a plan.
102
+
103
+ ### 3. Continuation from the checkpoint
104
+
105
+ - `continueFrom` already starts a new run with the earlier run's pins. A slice policy may perform
106
+ that continuation without an application request when the account's setting is on: same session,
107
+ same account, variant and capability set, `resumedFromRunId` set, earlier record untouched.
108
+ - The new run's context begins with the compiled checkpoint (goal, ledger, plan instructions), so the
109
+ model resumes from state rather than from a summary. The normal PROP-0033 checkpoint continues to
110
+ cover mid-slice compactions.
111
+ - Both surfaces offer the same control: `/slices on|off` in the terminal, the matching setting in the
112
+ App, and a per-run override on `sessions.run` (`workSlice: { maxToolCalls?, maxMinutes?, maxNewTokens?, continue? }`).
113
+ - Off means the run still stops at the budget, writes the checkpoint, and returns to the person with
114
+ a "continue" affordance. Slices are a convenience; the checkpoint is the contract.
115
+
116
+ ### 4. Stall guard
117
+
118
+ A foundation extension (`src/sessions/workSlice.ts`), registered where the compaction checkpoint is:
119
+
120
+ - Signatures are `toolName + canonicalised args`, hashed, so every argument participates and a
121
+ changed argument (a checklist whose steps moved on, a different path) is a different call. Detect,
122
+ per reply:
123
+ - the same signature 3 times (`checkpoint_write`, `session_name`, `wait_subagents` and
124
+ `subagents.*` polling exempt);
125
+ - the same Bash command failing twice;
126
+ - 25 tool calls with no `file.changed` and no passing check;
127
+ - one file edited 4 times with no intervening passing check.
128
+ - The counts and the nudge start fresh in each reply, because each reply is one slice: progress a
129
+ later reply reports cannot make an earlier reply's call look repeated. Reading a fixed handful of
130
+ field names instead of all arguments once made every `checklist_write` look identical, which is
131
+ how a reply that reported its progress was told it was repeating work.
132
+ - First hit: block that call once and inject a compiler-rendered, bounded evidence note (the earlier
133
+ timestamps, exit codes and paths). This is the Manus "keep the failure visible" rule.
134
+ - Second hit for the same signature, or any hit after one nudge in the same slice: force a
135
+ checkpoint, end the slice with `blocked`, and report the evidence instead of continuing. Never a
136
+ silent kill, never an unbounded retry.
137
+
138
+ ### 5. Command budget
139
+
140
+ - `workspaceBashTool` gains a default `timeout` of 120 s and a hard ceiling of 300 s. A longer call is
141
+ allowed only with an explicit `reason`, is shown in the approval action, and is capped at 900 s.
142
+ - A timed-out command returns its partial output plus "timed out after Ns; narrow the command (one
143
+ test file, `--grep`, a line range)". After two timeouts in a run, the guard's evidence note asks for
144
+ the narrow run first.
145
+ - Browser suites and full CI are run as background work where the surface supports it; the slice
146
+ budget must not be spent blocking on them.
147
+
148
+ ### 6. Compaction discipline
149
+
150
+ - Before Pi compacts, drop the oldest tool results beyond a character budget through the existing
151
+ `context` projection (the mechanism PROP-0033 already uses), keeping every path and command line.
152
+ This is the cheapest lever and the one these runs lacked with `output-trim` off.
153
+ - Count compactions per run. At the third, do not compact again: force a checkpoint and end the slice,
154
+ naming the largest tool results as the cause (Claude Code's documented thrash guard).
155
+ - Where Pi exposes its summariser prompt, the foundation sets the same requirements the checkpoint
156
+ has: paths with line ranges, failing command lines, and decisions survive.
157
+
158
+ ### 7. Liveness and progress
159
+
160
+ - One new run event while a reply is active: `heartbeat` every 30 s `{ steps, elapsedMs, lastTool,
161
+ compactions, slice, maxToolCalls }`, relayed to every surface. `message.completed` already carries
162
+ every visible assistant text block, so no separate `progress` event is added (implemented this
163
+ way; the second copy bought nothing).
164
+ - A stall watchdog settles what the process table cannot. The store reconciles on activity as well as
165
+ on the owner pid, and periodically rather than only at startup: no heartbeat for five minutes while
166
+ the host is alive marks the run `stalled` (a long command still heartbeats, so a 16-minute build is
167
+ not mistaken for a hang), and a `running` or `waiting_*` run whose owner process is gone becomes
168
+ `interrupted` with `terminalReason: "owner_gone"`. Model work is never restarted (INV-RUN-4).
169
+ - `owner.json` records a process start marker next to the pid, so a recycled pid cannot make a dead
170
+ owner look alive.
171
+
172
+ ### 8. Defaults and the existing plugin
173
+
174
+ - The slice policy, checkpoint tool, stall guard and heartbeat are foundation and on by default. The
175
+ `budget-guard` plugin stays for people who want a different cap; the Slice policy supersedes it, and
176
+ the plugin's page says so.
177
+ - Until this lands, the quickest real relief is configuration: `output-trim` on (20 000 chars) and
178
+ `budget-guard` at 120 requests.
179
+
180
+ ## Alternatives
181
+
182
+ - **Turn `budget-guard` on by default.** Rejected: it stops the reply with no state and no handoff,
183
+ which converts a long run into a lost run.
184
+ - **Raise the context window or compact more often.** Rejected: latency and context rot both grow with
185
+ input; the two runs compacted five and two times and still re-read `host.rs` 11 times.
186
+ - **Extend `checklist_write` instead of a new tool.** Rejected: the checklist is the person-visible
187
+ plan and is reworded as scope changes; the checkpoint is execution state with evidence. Mixing them
188
+ is what produced five reworded plans in one run.
189
+ - **Let the App drive continuation.** Rejected: that is orchestration in the wrong layer, and the
190
+ terminal would lose parity (`INV-PAR-1`).
191
+ - **Add a task/goal lifecycle.** Rejected here: it belongs to the application
192
+ (`feature-gap-review.md:7,35`) and is not needed to bound a reply.
193
+
194
+ ## Consequences
195
+
196
+ - More runs per long task, each with its own record and pins. Continuation already supports this, so
197
+ session and thread views group them; a person sees a checkpoint every ≤ 40 minutes instead of
198
+ nothing.
199
+ - Two new tools/events and two new settings, which the App must present for parity. The compiled tool
200
+ manifest gains one entry, which changes the provider-cache prefix once (INV-PRM-8: measure with real
201
+ requests, with the owner's permission).
202
+ - A model can be stopped mid-slice. It is stopped with its own written state and a person-facing line,
203
+ never silently.
204
+ - The context stops growing to 256k in a single reply, so per-step latency and compaction cost fall;
205
+ the exact gain is to be measured, not assumed.
206
+
207
+ ## Migration
208
+
209
+ No format migration. Existing runs and native histories are readable; runs without a checkpoint
210
+ continue to resume from the checklist. `terminalReason` gains `slice_complete`, `owner_gone` and
211
+ `stalled`; readers that switch on unknown reasons keep their default. Record validators accept the
212
+ new event types; older records stay valid. The App's event schemas accept the new types in the same
213
+ change (its sidecar test parses every reply of this package).
214
+
215
+ ## Checks
216
+
217
+ - Unit: budget counting and refusal, checkpoint validation and bounds, evidence marking, continuation
218
+ pins and `resumedFromRunId`, stall signatures and exemptions, Bash ceiling and partial output,
219
+ heartbeat cadence, owner-gone reconciliation, event validators.
220
+ - Integration: a fake Pi session that makes 60 tool calls ends in one `checkpoint` event and a
221
+ `slice_complete` run, and the continuation starts with the rendered checkpoint; a duplicated call
222
+ is blocked once and then ends the slice; a dead owner is reconciled while the process is alive.
223
+ - `npm run typecheck`, `npm test`, `npm run prove` here; the App's `npm run typecheck`, `npm test`,
224
+ `npm run check:layers`, `npm run check:ui` and `cargo test`; then compare the STUDY-0003 metrics on
225
+ the same two tasks with real runs.
226
+
227
+ ## Implementation status (2026-10-26)
228
+
229
+ Implemented in this tree:
230
+
231
+ - `checkpoint_write` (`src/tools/checkpoint.ts`) with its plugin descriptor, frozen capability entry,
232
+ prompt group and terminal socket bridge; the run records `checkpoint`.
233
+ - The slice guard (`src/sessions/workSlice.ts`): budget refusal, checkpoint allowance (including the
234
+ first call past the limit), termination,
235
+ repeated-call, repeated-failure, edit-churn and no-progress notes, compaction-thrash guard, and
236
+ per-reply reset. Wired for structured and terminal replies.
237
+ - The runner's `heartbeat`, derived fallback checkpoint, `slice_complete`/`stalled` terminal reasons
238
+ and the third `finish` argument.
239
+ - Auto-continue with `maxSlices` 8, a fruitless-slice stop and a blocked stop, off for automatic,
240
+ plan, subagent, command and worktree runs; the continuation renders the checkpoint.
241
+ - The command budget (120/300/900 s) on the Bash tool and both initial `!` paths.
242
+ - The liveness watchdog (`reconcileStaleRuns`, 60 s timer) with heartbeat-age and legacy silence
243
+ rules.
244
+
245
+ Open, tracked as `PAR-004` in [`docs/parity.md`](../../parity.md): the terminal's checkpoint card,
246
+ `/slices`, and the App's event schemas, checkpoint card and slice line. Rerun of the STUDY-0003
247
+ tasks with real requests is still to be done with the account owner's permission.