@arnilo/prism 0.7.0 → 0.8.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.
- package/CHANGELOG.md +35 -0
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +4 -0
- package/dist/agent-approval.js +5 -1
- package/dist/agent-run-lifecycle.js +29 -5
- package/dist/agent-run-state.d.ts +13 -1
- package/dist/agent-run-state.js +12 -2
- package/dist/agent-session/helpers.js +6 -1
- package/dist/agent-session/session/assemble.js +126 -5
- package/dist/agent-session/session/persist.d.ts +16 -0
- package/dist/agent-session/session/persist.js +58 -2
- package/dist/agent-session/session/provider-round.d.ts +3 -3
- package/dist/agent-session/session/provider-round.js +12 -6
- package/dist/agent-session/session/tool-round.js +4 -1
- package/dist/agent-session/session/types.d.ts +12 -1
- package/dist/agent-session/session.d.ts +2 -0
- package/dist/agent-session/session.js +2 -0
- package/dist/checkpoints.js +7 -11
- package/dist/contracts-core/content.d.ts +5 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/run-limits.d.ts +2 -0
- package/dist/contracts-protocol.d.ts +15 -3
- package/dist/contracts-run-state.d.ts +26 -5
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/host-composition.d.ts +13 -0
- package/dist/host-composition.js +33 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +2 -2
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +149 -0
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/docs/ag-ui.md +5 -0
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +4 -3
- package/docs/coding-security.md +1 -0
- package/docs/coding-tools.md +0 -1
- package/docs/compaction-observational-memory.md +1 -1
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +13 -0
- package/docs/core.md +1 -1
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +9 -9
- package/docs/documents.md +32 -11
- package/docs/durable-runs.md +87 -0
- package/docs/enterprise-postgres-state.md +4 -0
- package/docs/execution-timeline.md +6 -0
- package/docs/guardrails.md +33 -0
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/release-handoffs.md +38 -0
- package/docs/host-compositions.md +8 -6
- package/docs/host-security.md +2 -2
- package/docs/index.md +47 -29
- package/docs/live-testing.md +5 -3
- package/docs/mcp-tools.md +1 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migration.md +30 -0
- package/docs/model-registry.md +12 -2
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +1 -3
- package/docs/options-index.md +34 -2
- package/docs/peer-dependencies.md +6 -6
- package/docs/postgres-persistence.md +1 -1
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +20 -20
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/rag.md +1 -1
- package/docs/release-and-install.md +52 -46
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +14 -0
- package/docs/server.md +2 -0
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +5 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +1 -1
- package/docs/work-connectors.md +9 -9
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +38 -16
- package/package.json +5 -3
- package/templates/business-worker/manifest.json +2 -1
- package/templates/business-worker/src/agent.ts.tmpl +1 -1
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Plan 080 Task 1 — Messaging Channel Follow-ons: Primitive, Compatibility, and Threat-Model Review
|
|
2
|
+
|
|
3
|
+
Plan: [080-Messaging-Channel-Followons-And-0-8-0-Cut.md](../../plans/080-Messaging-Channel-Followons-And-0-8-0-Cut.md) Task 1
|
|
4
|
+
Depends on: [079](../../plans/079-Prism-Messaging-Channels-Telegram-Signal.md) Tasks 1–9 (complete 2026-09-16) and [docs/history/079-messaging-primitive-review.md](079-messaging-primitive-review.md)
|
|
5
|
+
Baseline reviewed: working tree after 079 (version still `0.7.0`; 0.8.0 cut is Task 10).
|
|
6
|
+
Date: 2026-09-16
|
|
7
|
+
|
|
8
|
+
Executable evidence:
|
|
9
|
+
|
|
10
|
+
- [`packages/prism-core/src/__tests__/messaging-primitives.test.ts`](../../packages/prism-core/src/__tests__/messaging-primitives.test.ts) — 079 probes plus two 080 gap probes (foreign checkpoint load; live abort dropped by non-stream `resume`).
|
|
11
|
+
- [`packages/prism-channels/src/__tests__/runtime.test.ts`](../../packages/prism-channels/src/__tests__/runtime.test.ts) — `threadId` participates in session derivation; `ChannelReply.kind` has no `draft`.
|
|
12
|
+
|
|
13
|
+
This is a frozen review record (history). Current-contract docs stay in `docs/` from Task 2 onward. **No new public symbols in this task.**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Executive summary
|
|
18
|
+
|
|
19
|
+
079 already shipped the transport-neutral runtime, durable journal, Telegram adapter, experimental Signal adapter, approvals, and host examples. Every 079 **Further Action** is either a later 080 task or an explicit out-of-scope row. No second agent engine, no new SQL schema, no `pg` peer on `@arnilo/prism-channels`, no Signal groups/media/drafts/bulk.
|
|
20
|
+
|
|
21
|
+
What already holds (do not rebuild):
|
|
22
|
+
|
|
23
|
+
| Primitive | Span | Follow-on use |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `ChannelInboundEvent.threadId` | `packages/prism-channels/src/types.ts:67` | Topic identity is already on the event and in `deriveSessionId` / `actorKey`. Groups/topics are adapter+grant policy, not a new key type. |
|
|
26
|
+
| `ChannelAuthorization` | `types.ts:91-99` | Deny-by-default host grant. Add optional `notifications?: boolean` (missing = false). |
|
|
27
|
+
| `admit` / `deliver` | `runtime.ts:1403-1480`; `MessagingRuntimeOptions.deliver` `types.ts:344` | Unicast to the bound destination. Notify reuses `deliver`; drafts must **not**. |
|
|
28
|
+
| Journal namespaces | `state.ts:11-16` | `prism.channels.v1.{binding,operation,cursor,reply,control}`. Media bytes never become values. |
|
|
29
|
+
| `TranscriptionProvider` / `SpeechProvider` | `src/contracts-core/transcription.ts:42-47`; `speech.ts:32-36` | Host injects `transcribe`/`synthesize` wrappers. Channels does not depend on `@arnilo/prism-providers`. |
|
|
30
|
+
| `createPostgresErpMessaging` | `packages/prism-core/src/enterprise/postgres/erp-messaging.ts:44-69` | Host `BEGIN` + `outbox.append(client, …)` + `COMMIT` then `adapter.send`. Caller-owned `PoolClient`. |
|
|
31
|
+
| `resumeAgentRun` vs `resumeAgentRunStream` | `src/agent-run-lifecycle.ts:73-86,111-118` vs `88-105,121-146` | Stream threads `signal`; non-stream pre-checks then drops it. **Task 3.** |
|
|
32
|
+
| Checkpoint ownership | memory `src/checkpoints.ts:82-89,151-155`; SQLite `sessions/sqlite/checkpoints.ts:106-112`; Postgres `sessions/postgres/checkpoints.ts:97-103` | Foreign load **throws** `"Checkpoint ownership mismatch"` (existence leak). **Task 3** → miss + generic CAS conflict. |
|
|
33
|
+
| Workspace test globs | `packages/prism-core/package.json` `test`; `packages/prism-coding-tools/package.json` `test` | Unquoted `dist/**/__tests__/*.test.js` collapses under the shell. **Task 2.** |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. 079 Further Action → 080 task map
|
|
38
|
+
|
|
39
|
+
| 079 Further Action | 080 task | Notes |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| Group/topic ownership | 4 | Telegram `allowGroups` default `false`. Signal stays DM-only. |
|
|
42
|
+
| Telegram draft streaming | 5 | Bot API `sendMessageDraft`; not a journaled `ChannelReply.kind`. |
|
|
43
|
+
| Bounded image/document | 6 | Refs on the event; `getFile` + download cap; no bytes in the journal. |
|
|
44
|
+
| Voice transcription/synthesis | 6 | Host `TranscriptionProvider` / `SpeechProvider`. |
|
|
45
|
+
| Opt-in completion notifications | 7 | `runtime.notify` + `ChannelAction` `"notify"` + `notifications: true`. |
|
|
46
|
+
| ERP outbox composition | 8 | Example + docs. No new table. No `pg` on channels. |
|
|
47
|
+
| OS SIGTERM injection | 9 | Restart worker `stop()` on `SIGTERM`. |
|
|
48
|
+
| 72-hour soak runner | 9 | Same script; CI ≤30s; operator 72h. |
|
|
49
|
+
| Nested `**` test globs | 2 | Quote or expand; fix newly executed failures. |
|
|
50
|
+
| `resume()` dropped `signal` | 3 | Add `AgentRunResumeOptions.signal`; thread like stream. |
|
|
51
|
+
| Checkpoint foreign-scope leak | 3 | Load → `null`; save generic conflict. |
|
|
52
|
+
| 0.8.0 cut | 10 | Lockstep + handoff; no registry write. |
|
|
53
|
+
| OFFSET `listCheckpoints` pagination | **out of 080** | Generic store limit; not a channel follow-on. |
|
|
54
|
+
| 079 items later tasks already closed (in-process dedup, durable approvals, `maxRoutes`, SIGKILL reconcile) | **closed** | Do not re-open. |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Frozen additive vocabulary
|
|
59
|
+
|
|
60
|
+
Implementation tasks may grow **fields**. They must not rename 079 symbols (`admit`, `deliver`, `ChannelInboundEvent`, `ChannelReply`, `ChannelAuthorization`, journal namespace strings, `createTelegramAdapter`, `createSignalAdapter`).
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
type ChannelAction = "message" | "command" | "approval" | "notify";
|
|
64
|
+
|
|
65
|
+
interface ChannelAuthorization {
|
|
66
|
+
// existing: identity, agentAliases, defaultAgentAlias?, grantRevision
|
|
67
|
+
readonly notifications?: boolean; // missing === false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface ChannelAttachmentRef {
|
|
71
|
+
readonly kind: "image" | "document" | "voice";
|
|
72
|
+
readonly transportFileId: string; // Telegram file_id; never model-selected
|
|
73
|
+
readonly mimeType?: string;
|
|
74
|
+
readonly byteLength?: number;
|
|
75
|
+
readonly fileName?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ChannelInboundEvent {
|
|
79
|
+
// existing fields unchanged
|
|
80
|
+
readonly attachments?: readonly ChannelAttachmentRef[]; // ids only — never bytes
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface ChannelLimits {
|
|
84
|
+
// existing fields unchanged
|
|
85
|
+
readonly maxAttachmentBytes?: number; // default 1 MiB, hard 4 MiB
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface MessagingRuntime {
|
|
89
|
+
// existing: admit, drain, diagnostics, stop, reconcile, listUnresolved, prune
|
|
90
|
+
notify(input: {
|
|
91
|
+
readonly identity: AgentIdentity;
|
|
92
|
+
readonly connectionId: string;
|
|
93
|
+
readonly externalConversationId: string;
|
|
94
|
+
readonly threadId?: string;
|
|
95
|
+
readonly notifyId: string;
|
|
96
|
+
readonly text: string;
|
|
97
|
+
}): Promise<ChannelAdmission>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface MessagingRuntimeOptions {
|
|
101
|
+
// existing authorize/resolveAgent/deliver/…
|
|
102
|
+
readonly onAssistantDelta?: (delta: {
|
|
103
|
+
readonly connectionId: string;
|
|
104
|
+
readonly externalConversationId: string;
|
|
105
|
+
readonly threadId?: string;
|
|
106
|
+
readonly text: string;
|
|
107
|
+
}) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface TelegramAdapterOptions {
|
|
111
|
+
readonly allowGroups?: boolean; // default false
|
|
112
|
+
readonly sendDrafts?: boolean; // default false; private chats only
|
|
113
|
+
readonly maxAttachmentBytes?: number;
|
|
114
|
+
readonly transcribe?: (audio: Uint8Array, format: string | undefined, signal?: AbortSignal) => Promise<string>;
|
|
115
|
+
readonly synthesize?: (text: string, signal?: AbortSignal) => Promise<{ audio: Uint8Array; format: string }>;
|
|
116
|
+
readonly extractDocumentText?: (bytes: Uint8Array, mimeType: string | undefined, signal?: AbortSignal) => Promise<string>;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Explicitly not added:**
|
|
121
|
+
|
|
122
|
+
- `ChannelReply.kind` `"draft"` — drafts are ephemeral Bot API previews; journal stays `final` \| `notice`.
|
|
123
|
+
- Broadcast/fan-out API.
|
|
124
|
+
- Signal groups, attachments, drafts, or bulk notify.
|
|
125
|
+
- `pg` / ERP types inside `@arnilo/prism-channels`.
|
|
126
|
+
- New checkpoint SQL schema.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 4. Platform facts (re-verified 2026-09-16)
|
|
131
|
+
|
|
132
|
+
Telegram Bot API (`https://core.telegram.org/bots/api`, fetched 2026-09-16):
|
|
133
|
+
|
|
134
|
+
| Fact | Implication |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `sendMessageDraft`: `chat_id` Integer **Yes** — “Unique identifier for the target **private** chat”; `draft_id` Integer **Yes**, non-zero; `text` Optional 0–4096 (empty → “Thinking…”); `message_thread_id` Optional; ephemeral **30-second** preview; **must** follow with `sendMessage` to persist. | Task 5: private chats only. Groups/Signal: no drafts. Not journaled. Coalesce in-flight drafts. |
|
|
137
|
+
| `sendRichMessageDraft` exists | Out of 080 (YAGNI; text drafts only). |
|
|
138
|
+
| `getFile`: bots download ≤ **20 MB**; URL `https://api.telegram.org/file/bot<token>/<file_path>` valid ≥ 1 hour. Token is in the path. | Task 6 channel hard cap **4 MiB** (default 1 MiB) — below getFile. Redact token URLs. Bounded reader; fail closed on oversize. |
|
|
139
|
+
| `sendVoice`: OGG/OPUS, MP3, or M4A; voice notes. `sendDocument`: general files up to 50 MB upload. Sending by URL: photos 5 MB, other 20 MB; `sendVoice` by URL ≤ 1 MB else sent as files. | Outbound speech uses `sendVoice` to the **bound** chat/thread. Never model-selected `file_id` or URL. |
|
|
140
|
+
| `Message.message_thread_id` / forum topics; Bot API 9.3 private-chat topics | Copy into `event.threadId` when present (private and, if `allowGroups`, group/supergroup). Already in `deriveSessionId` (`runtime.ts:359-376`). |
|
|
141
|
+
| `parseMessageUpdate` today (`telegram.ts:235-254`) | Private + non-bot `from` + string `text` only. Drops groups, media, `message_thread_id`. Tasks 4–6 widen this filter. |
|
|
142
|
+
| Group privacy mode | Bots see commands only unless privacy disabled. Document; do not bypass. |
|
|
143
|
+
| Anonymous admins / missing `from.id` / `sender_chat` | No actor id → drop (fail closed). Never use chat title or username as `externalActorId`. |
|
|
144
|
+
| Forwards (`forward_date` / `forward_origin`) | `claims` only; host grant must opt in. Default: do not run a model. |
|
|
145
|
+
|
|
146
|
+
Signal (`signal.ts:225-267` `notificationEvent`): drop when `groupInfo` is set or `attachments` is a non-empty array. DM text only. Terms (079 §8): no bulk/auto-messaging. **Notify is unicast to an existing DM binding**, not a fan-out.
|
|
147
|
+
|
|
148
|
+
Speech/transcription (`docs/speech.md`; contracts above): `transcribe({ model, audio: Uint8Array, format?, signal? })`; `synthesize({ model, input, signal? })`. OpenAI adapter caps (25 MiB transcribe / 4096 chars speech) sit **above** the channel attachment cap. Host wraps providers; channels never imports them.
|
|
149
|
+
|
|
150
|
+
ERP (`erp-messaging.ts:44-69`, `docs/enterprise-postgres-state.md`): `outbox.append` requires a caller-owned `PoolClient` in the host transaction. Payload cap 64 KiB. Dispatcher never invokes business callbacks. Channel `deliver` may append **correlation ids** (`operationId`, `connectionId`), not inbound chat text.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 5. Compatibility gates still open
|
|
155
|
+
|
|
156
|
+
### Gate A — Nested workspace test globs (Task 2)
|
|
157
|
+
|
|
158
|
+
`packages/prism-core` and `packages/prism-coding-tools` `test` scripts pass unquoted `dist/**/__tests__/*.test.js` through the shell. `**` collapses to one directory, so nested suites (`runtime/server`, `sessions/*`, `governance/*`, coding-tools nested trees) are omitted. `messaging-primitives.test.js` is listed explicitly on prism-core and **must stay**. Quote the glob or reuse `scripts/run-all-tests.mjs` `expandGlob`. Newly executed suites must pass or be named-skipped — not silenced by turning off network-free/secret-scan guards.
|
|
159
|
+
|
|
160
|
+
### Gate B — `resumeAgentRun` drops live abort (Task 3)
|
|
161
|
+
|
|
162
|
+
`createAgentRunLifecycle.resume` (`agent-run-lifecycle.ts:73-86`) pre-checks `request.signal` then calls `resumeAgentRun` **without** `signal`. `AgentRunResumeOptions` (`src/contracts-run-state.ts:229-247`) has no `signal` field; only `AgentRunResumeStreamOptions` (`250-252`) does. `resumeAgentRun` (`111-118`) calls `prepareAgentRunResume(..., options)` with no fifth argument, so `executePreparedAgentRunResume` never sees the host abort.
|
|
163
|
+
|
|
164
|
+
079 probe already covers pre-abort (run stays `suspended`) and stream abort (provider sees `signal`). 080 probe: abort **during** a hanging non-stream resume — provider `request.signal` stays live and the run **succeeds**. Channels still resume via `resumeStream` until Task 3.
|
|
165
|
+
|
|
166
|
+
### Gate C — Checkpoint foreign-scope existence leak (Task 3)
|
|
167
|
+
|
|
168
|
+
`recordKey` is `namespace\0key` (`src/checkpoints.ts:123-125`). `loadCheckpoint` after a hit calls `assertOwnership` / `assertOwnershipScope` and throws `CheckpointConflictError("Checkpoint ownership mismatch")` on memory, SQLite, and Postgres. A tenant can distinguish “other tenant has this key” from “missing”.
|
|
169
|
+
|
|
170
|
+
Channel keys already embed tenant/user (`state.ts:135-157`), so a well-formed foreign lookup **misses** the key. The leak remains for any unscoped or colliding key and for direct store use. Task 3: load mismatch → `null` (same as missing); save mismatch → generic CAS conflict, same wording as a version miss. Do not log the foreign owner.
|
|
171
|
+
|
|
172
|
+
Callers that caught `"Checkpoint ownership mismatch"` as a signal (e.g. `packages/prism-coding-tools/.../workspace-lifecycle.ts:448-465` maps any throw to `ERR_PRISM_WORKSPACE_OWNERSHIP`) must keep failing closed on save conflict; load miss is already treated as absent.
|
|
173
|
+
|
|
174
|
+
### Gate D — OFFSET pagination (out of 080)
|
|
175
|
+
|
|
176
|
+
Checkpoint list cursors are numeric offsets. Concurrent writes make a page walk incomplete. 079 absorbed this (resolve explicit keys; do not trust scan completeness). Not a channel follow-on.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 6. Threat-model resolution (follow-ons)
|
|
181
|
+
|
|
182
|
+
| Threat | Control | Task |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| Group impersonation (display name, username, chat title) | `externalActorId` = numeric `from.id` only; title/username never identity | 4 |
|
|
185
|
+
| “Anyone in this group” grant | Deny-by-default; grant is exact `(connectionId, chat, thread?, actor)` | 4 |
|
|
186
|
+
| Anonymous admin / missing `from` | Drop the update | 4 |
|
|
187
|
+
| Forwarded message as the sender | `claims` only; default no model | 4 |
|
|
188
|
+
| Topic bleed (same chat, different `message_thread_id`) | `threadId` in `deriveSessionId` and `actorKey` (`runtime.ts:175-177,359-376`) | 4 (already keyed) |
|
|
189
|
+
| Model-selected destination / `file_id` / URL | Destination from the bound event only; fetch only the current event’s ref | 5–7 |
|
|
190
|
+
| Draft treated as durable / sent as the final | Drafts out of band; journal `final`\|`notice` only; must `sendMessage` after | 5 |
|
|
191
|
+
| Draft leak of secrets | Same `SecretRedactor` as finals; no controls/attachments on drafts | 5 |
|
|
192
|
+
| Journal media / disk retention of voice | Refs on the event; bytes live only for the fetch+transcribe/extract call | 6 |
|
|
193
|
+
| Oversized getFile (20 MB) | Channel cap 1 MiB / hard 4 MiB; Content-Length + bounded reader | 6 |
|
|
194
|
+
| Token in `api.telegram.org/file/bot<token>/…` | Existing URL redaction; credential provider functions, not static strings | 6 |
|
|
195
|
+
| Notify as broadcast / Signal bulk | Unicast to an existing binding; `notifications: true` re-checked; no fan-out API | 7 |
|
|
196
|
+
| Notify without a binding / revoked identity | Denied | 7 |
|
|
197
|
+
| Outbox dumps chat text into ERP | Payload = correlation ids; tenant from identity ownership | 8 |
|
|
198
|
+
| Signal terms / GPLv3 | DM-only, experimental, socket client, operator gate unchanged | — |
|
|
199
|
+
| Foreign checkpoint existence | Task 3 miss semantics | 3 |
|
|
200
|
+
|
|
201
|
+
Deny-by-default remains the recommended default for **groups** and **notify**.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 7. Task 9 budget baseline (no runtime cost this task)
|
|
206
|
+
|
|
207
|
+
Recorded 2026-09-16 from `scripts/budgets.json` (do not bump in Task 1):
|
|
208
|
+
|
|
209
|
+
| Package | Export ceiling | Notes |
|
|
210
|
+
| --- | --- | --- |
|
|
211
|
+
| `@arnilo/prism-channels` | **80** | Root + `/telegram` + `/signal` src-export count |
|
|
212
|
+
| `@arnilo/prism-core` | **1473** | Unchanged by 079 extraction |
|
|
213
|
+
| Root packedBytes / unpackedBytes / fileCount | **1247731** / **4137527** / **489** (5% tolerance) | 079 Task 1 rebaseline |
|
|
214
|
+
|
|
215
|
+
This history page and two probes are excluded from published tarballs (`dist/**/__tests__` deny; history is docs). Task 9 rebaselines only with a recorded reason if measured exports grow (notify, attachment refs, Telegram options).
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 8. Evidence and reproduction
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
npm run build --workspace @arnilo/prism-core --workspace @arnilo/prism-channels
|
|
223
|
+
node --test "packages/prism-core/dist/__tests__/messaging-primitives.test.js"
|
|
224
|
+
node --test "packages/prism-channels/dist/__tests__/runtime.test.js"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
080 probes (in addition to the seven 079 proofs):
|
|
228
|
+
|
|
229
|
+
1. `threadId` on two otherwise identical events yields two session ids (`deriveSessionId` already hashes `threadId`).
|
|
230
|
+
2. Non-stream `lifecycle.resume` with a live abort during a hanging provider turn does **not** abort the provider; the run succeeds (Task 3 gap).
|
|
231
|
+
3. Memory and SQLite `loadCheckpoint` of a foreign-owned existing key throw `/ownership mismatch/` (Task 3 gap).
|
|
232
|
+
4. `ChannelReply["kind"]` extracts no `"draft"` variant.
|
|
233
|
+
|
|
234
|
+
Core changes proposed by later tasks: glob quoting (Task 2); `signal` on `AgentRunResumeOptions` + checkpoint miss (Task 3). Everything else is `@arnilo/prism-channels` fields, Telegram filter widening, examples, and the 0.8.0 cut.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Connected apps and work-HTTP primitive review
|
|
2
|
+
|
|
3
|
+
Plan 081 Task 1 freezes the compatibility and security vocabulary for later tasks. This is evidence only: it adds no public symbol, network path, dependency, or runtime work.
|
|
4
|
+
|
|
5
|
+
## Sources reviewed
|
|
6
|
+
|
|
7
|
+
Current contracts: [MCP tools](../mcp-tools.md), [tool effects](../tool-effects.md), [work tools](../work-tools.md), [work connectors](../work-connectors.md), [host compositions](../host-compositions.md), [credential storage](../credential-storage.md), [credentials and redaction](../credentials-and-redaction.md), [host security](../host-security.md), [Obscura](../obscura.md), [computer-use-linux](../computer-use-linux.md), [messaging channels](../messaging-channels.md), and [ACP](../acp.md). The pinned MCP SDK v2 transport/OAuth API was checked against [MCP TypeScript SDK v2](https://ts.sdk.modelcontextprotocol.io/v2/).
|
|
8
|
+
|
|
9
|
+
## Existing primitives and compatibility freeze
|
|
10
|
+
|
|
11
|
+
| Existing contract | Evidence | Later use / compatibility rule |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| MCP bridge connects only host-supplied `serverId` + transport, lists before returning, and exposes `refresh`/`close`. | `packages/mcp/src/bridge.ts:49-97`; `packages/mcp/src/types.ts:265-296,354-364` | Task 2 composes `connectMcpTools`; it does not fork transport, discovery, list caching, naming, limits, or bridge lifecycle. |
|
|
14
|
+
| Effect policy receives only host-configured `serverId` and `remoteName`; descriptions/annotations are deliberately unavailable. Omission resolves to `external_mutation` / `unsupported`. | `packages/mcp/src/types.ts:256-263`; `packages/mcp/src/bridge.ts:267-288` | `ConnectedAppSelect` controls admission; optional app/session effect policy can only narrow known remote names. Never infer effects from remote metadata. |
|
|
15
|
+
| Stdio is an explicit host `command`/`args`/`env`/`cwd` transport. Streamable HTTP requires exact origins and uses pinned, redirect-free, byte-bounded fetch; OAuth discovery uses the same boundary and strips bearer credentials from discovery GETs. | `packages/mcp/src/types.ts:34-59`; `packages/mcp/src/transport.ts:37-237`; `src/pinned-fetch.ts:50-82` | Task 2 accepts already host-built `McpTransportConfig`; no command construction, path search, credential resolution, or alternate HTTP client. HTTP adapters in Tasks 4–5 reuse `pinnedFetch`, not a weaker fetch path. |
|
|
16
|
+
| ACP requires an `mcp.select` seam before client MCP config is admitted, applies server/config/header caps, rejects unstable `acp`, and its launcher separately matches explicit stdio/origin allow entries. | `packages/ag-ui/src/acp/mcp-config.ts:17-54`; `packages/acp-agent/src/index.ts:78-125` | `ConnectedAppSelect` follows this fail-closed host-admission shape but remains MCP-package-local; Tasks 2–3 do not couple ACP to connected apps. |
|
|
17
|
+
| Work adapters are structural `Microsoft365Adapter` / `GoogleWorkspaceAdapter` contracts: identity, allowed ops, readiness, `runOp`, and common draft lifecycle. The CLI adapters are additive existing implementations. | `packages/prism-core/src/integrations/work/types.ts:304-365`; `packages/prism-core/src/integrations/work/google-workspace.ts:215-360`; `packages/prism-core/src/integrations/work/microsoft365.ts:281-434` | Tasks 4–5 add HTTP implementations only. Existing CLI factories and argv maps remain byte-compatible. |
|
|
18
|
+
| Work mutations are draft-then-approve, identity/revision/digest-bound, claim before external effect, and become unknown rather than replayed after ambiguous dispatch. Reads and mutations are classified centrally. | `packages/prism-core/src/integrations/work/drafts.ts:68-113`; `packages/prism-core/src/integrations/work/tools.ts:69-286,806-814` | HTTP adapters must feed these adapters/contracts; they must not own a second draft, approval, idempotency, or result-normalization system. |
|
|
19
|
+
| `WorkTokenProvider` returns a late-bound per-identity environment map or `undefined`; OAuth token resolution rejects account and tenant mismatches. | `packages/prism-core/src/integrations/work/types.ts:304-309`; `packages/prism-core/src/credentials/node/work-token.ts:24-68` | Tasks 4–5 require this provider at request edge. Tokens stay out of model arguments, URLs, argv, reports, and logs. |
|
|
20
|
+
| Composition inspection derives identifiers and readiness synchronously; it has no connector import or I/O. | `src/host-composition.ts:181-331`; `src/__tests__/host-composition.test.ts:299-320` | Task 3 reports only bounded app/server identifiers supplied by host. It never accepts transport, headers, `env`, tokens, bridge objects, or a live MCP check. |
|
|
21
|
+
| Obscura is a thin bridge wrapper with injected `connect` test seam and an explicit read-effect override; unclassified tools retain MCP mutation default. | `packages/web-tools/src/obscura/mcp.ts:63-72` | Task 6 copies this direct composition. Do not add a generic read/write wrapper until a third production caller exists. |
|
|
22
|
+
| computer-use-linux validates device admission before bridging, uses an allow-listed child environment, filters known tools, serializes mutations, and classifies unknown/mutating work conservatively. | `packages/prism-coding-tools/src/computer-use-linux/create.ts:56-123` | This is evidence that specialized MCP wrappers remain package-local policy layers, not a reason to generalize a connector framework. |
|
|
23
|
+
| Channels are authenticated inbound execution/delivery transports; work tools are high-trust typed draft/approval tools; MCP maps selected remote tools. | `packages/prism-channels/src/runtime.ts:1-80`; [messaging channels](../messaging-channels.md); [work connectors](../work-connectors.md#out-of-scope) | Tasks 2–7 preserve three planes: MCP tools != work tools != channels. Slack/Teams inbound adapters are out of scope; M365 `teams` capability is not a channel. |
|
|
24
|
+
|
|
25
|
+
### Frozen vocabulary
|
|
26
|
+
|
|
27
|
+
Later tasks use these exact names:
|
|
28
|
+
|
|
29
|
+
- `ConnectedAppBinding`, `ConnectedAppSession`, `createConnectedAppSession`
|
|
30
|
+
- `bind`, `unbind`, `list`, `tools`, `refresh`, `close`
|
|
31
|
+
- `ConnectedAppSelect`
|
|
32
|
+
- `createGoogleWorkspaceHttpAdapter`, `createMicrosoft365HttpAdapter`
|
|
33
|
+
|
|
34
|
+
`ConnectedAppSession` is the one new generic primitive. It belongs in `@arnilo/prism-mcp`; no twelfth package, catalog abstraction, generic remote proxy, or second OAuth stack is justified. Its transport admission is mandatory and deny-by-default. Its effect policy keeps the MCP default when it returns `undefined`.
|
|
35
|
+
|
|
36
|
+
## Later-task mapping
|
|
37
|
+
|
|
38
|
+
| Task | Frozen primitive or explicit boundary |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| 2 | `connectMcpTools`, `McpTransportConfig`, `McpToolEffectPolicy`, `assertValidServerId`, bounded MCP limits. New session only orchestrates host-approved bridges. |
|
|
41
|
+
| 3 | `inspectHostComposition` / `assertHostCompositionReadiness`; identifiers-only report with no MCP dependency or network. |
|
|
42
|
+
| 4 | `GoogleWorkspaceAdapter`, `WorkTokenProvider`, work drafts, idempotency, normalizers, `pinnedFetch`. Google REST implementation is additive. |
|
|
43
|
+
| 5 | `Microsoft365Adapter`, same shared work primitives, `pinnedFetch`. Graph implementation is additive. |
|
|
44
|
+
| 6 | `createObscuraMcpTools` direct `connect` + `effect` composition. Example only; no Slack dependency or work-tool provider. |
|
|
45
|
+
| 7 | Explicitly out of core: Open Connector remains a sibling sidecar recipe/mock. No dependency, vendoring, catalog discovery, or first-class `execute_action` tool. |
|
|
46
|
+
| 8 | Documentation/verification only. Refresh graph and verify budgets after implementation; no version bump here. |
|
|
47
|
+
|
|
48
|
+
## Threat model and required posture
|
|
49
|
+
|
|
50
|
+
| Threat | Current protection | Requirement for 081 |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| MCP auto-discovery | Bridge requires explicit `serverId` and transport; docs prohibit auto-discovery. | No catalog URL, discovery scan, or implicit bind. `select` must approve every bind. |
|
|
53
|
+
| Remote effect annotations or descriptions | Policy input excludes them; missing policy is nonrecoverable mutation. | Host classifies exact server/name only. Unknown remote stays `external_mutation` / `unsupported`. |
|
|
54
|
+
| Stdio command injection / ambient secrets | Host supplies argv; specialized wrapper shows allow-listed child env. | Session receives a complete host-built transport only; model cannot choose command, args, cwd, or env. |
|
|
55
|
+
| HTTP SSRF, DNS rebinding, redirect, credential forwarding | Exact origins, DNS pinning, public-address checks, redirect rejection, byte caps, loopback-only plaintext exception. | Connected HTTP binds and work HTTP factories deny origins by default and retain `pinnedFetch`; model never supplies a request URL. |
|
|
56
|
+
| Cross-identity token reuse | Work OAuth token provider loads exact account and rejects mismatched tenant. | Bind identity must equal session identity; resolve tokens at connect/request edge per identity; do not retain secrets in inspection/listing. |
|
|
57
|
+
| `execute_action` exposed as harmless model action | Unclassified MCP tool is a nonrecoverable external mutation. | Open Connector example keeps it unclassified and allowlisted only after host `select`; no model-facing provider catalog. |
|
|
58
|
+
| In-process catalog sidecar compromise | Prism packages have explicit imports and no catalog dependency. | Sidecars run separately over an explicitly allowed loopback MCP transport; never vendor Open Connector, Klavis, or Nango. |
|
|
59
|
+
| Slack/Teams confused with inbound channels | Channel and work documentation distinguish channel adapters from M365 capability operations. | Slack example is an outbound MCP-tool wrap; no inbound Slack/Teams channel is introduced. |
|
|
60
|
+
|
|
61
|
+
## Current verification evidence
|
|
62
|
+
|
|
63
|
+
- MCP bridge default-effect coverage: `packages/mcp/src/__tests__/bridge.test.ts` exercises the bridge contract; the invariant is implemented at `packages/mcp/src/bridge.ts:267-288`.
|
|
64
|
+
- Zero-network composition probe: `src/__tests__/host-composition.test.ts:299-320` replaces `globalThis.fetch` and proves `inspectHostComposition` does not call it.
|
|
65
|
+
- Cross-account and wrong-tenant token probes: `packages/prism-core/src/credentials/node/__tests__/oauth.test.ts:369-391` expect `undefined` from `createOAuthWorkTokenProvider`.
|
|
66
|
+
- Work classification is centralized at `packages/prism-core/src/integrations/work/tools.ts:69-86,806-814`; coverage is in `packages/prism-core/src/integrations/work/__tests__/work-tools.test.ts`.
|
|
67
|
+
|
|
68
|
+
## Task 8 budget baseline
|
|
69
|
+
|
|
70
|
+
`scripts/budgets.json` has an explicit root packed-byte budget only. Task 8 must retain its 1,247,731-byte baseline with 5% tolerance (ceiling 1,310,118 bytes) and must not add a blanket package-size budget. Current `npm pack --dry-run --json` measurements are `@arnilo/prism-mcp` 46,060 bytes (32 files), `@arnilo/prism-core` 412,206 bytes (444 files), and `@arnilo/prism` 1,279,372 bytes (495 files). Public export counts exactly match their ceilings: `@arnilo/prism-mcp` 134, `@arnilo/prism-core` 1,474, and `@arnilo/prism` 1,347 (`scripts/budgets.json:3-67`; `scripts/budget-gates.mjs:151-163`). The two package-specific packed measurements are evidence, not a new ceiling.
|
|
71
|
+
|
|
72
|
+
## Review decision
|
|
73
|
+
|
|
74
|
+
Proceed with the thin MCP session and additive HTTP adapters. Reuse existing MCP transport/effect, work draft/idempotency/token, and host-inspection primitives. Keep catalogs, remote effect trust, command construction, generic Graph/Discovery calls, and Slack/Teams inbound channels out of scope.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Prism work, sandbox, and skills primitive review
|
|
2
|
+
|
|
3
|
+
Plan 083 Task 1 freezes compatibility and security vocabulary for later tasks. This is evidence only: no public symbol, dependency, runtime path, or generated skill body changes here.
|
|
4
|
+
|
|
5
|
+
## Sources reviewed
|
|
6
|
+
|
|
7
|
+
Current contracts: [work tools](../work-tools.md), [work connectors](../work-connectors.md), [documents](../documents.md), [sheets](../sheets.md), [diagrams](../diagrams.md), [document reader](../document-reader.md), [coding security](../coding-security.md), [context and skills](../context-and-skills.md), [core](../core.md), [coding tools](../coding-tools.md), [optional peer dependencies](../peer-dependencies.md), and [host compositions](../host-compositions.md). Package topology and prior HTTP-adapter decisions were checked in [plan 054](../../plans/054-Package-Consolidation-Proposal.md) and [plan 081](../../plans/081-Connected-Apps-Mcp-Host-And-Work-Http.md).
|
|
8
|
+
|
|
9
|
+
Vendor policy sources: [Agent Skills specification](https://agentskills.io/specification), the [Hermes MIT license](https://github.com/NousResearch/hermes-agent/blob/main/LICENSE), and the [Anthropic skills README](https://github.com/anthropics/skills). Hermes is MIT (copyright 2025 Nous Research). Anthropic calls its document skills source-available rather than open source; they are excluded from redistribution.
|
|
10
|
+
|
|
11
|
+
## Existing primitives and compatibility freeze
|
|
12
|
+
|
|
13
|
+
| Existing contract | Evidence | Later use / compatibility rule |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Work types define Microsoft 365 and Google Workspace operation/capability sets, bounded limits, identity-bound drafts, `IdempotencyStore`, token provider, structural adapters, and `WorkToolsOptions`. | `packages/prism-core/src/integrations/work/types.ts:3-365` | Move these interfaces unchanged in Task 2. New work package depends only on `@arnilo/prism` at runtime; the PostgreSQL adapter remains core-side and satisfies `IdempotencyStore` structurally. |
|
|
16
|
+
| `createWorkTools` joins configured adapters and centrally assigns observations versus `external_mutation` effects. | `packages/prism-core/src/integrations/work/tools.ts:806-814` | Tasks 2, 4, and 5 retain this single tool/draft/approval/idempotency path; no second work-tool framework. |
|
|
17
|
+
| HTTP adapters require a per-identity token provider, pin allowed origins, use fixed operation maps, and share the draft store. | `packages/prism-core/src/integrations/work/{microsoft365-http,google-workspace-http}.ts:27-191,33-179`; `http.ts:25-71` | Tasks 4-5 add fixed download/update paths only. Tokens stay in `Authorization`; model arguments never contain an arbitrary URL. |
|
|
18
|
+
| Drafts bind identity, revision, canonical payload digest, and approval; checkpoint-backed drafts retain the same lifecycle. | `packages/prism-core/src/integrations/work/drafts.ts:68-113,119-311,320-645`; `types.ts:111-201` | Tasks 4-5 extend existing draft payloads. Approval and ambiguous-outcome handling remain unchanged. |
|
|
19
|
+
| PostgreSQL idempotency atomically claims/finishes/fails/marks unknown records; enterprise composition exposes it as `workIdempotency`. | `packages/prism-core/src/enterprise/postgres/work-idempotency.ts:47-166`; `enterprise.ts:38-82`; `types.ts:153-165` | Task 2 retains the core export with type-only work coupling. Core must not runtime-import `prism-work` or `@office-open/*`. |
|
|
20
|
+
| Documents generate OOXML plus a SHA-256 hash, import only PK containers under caps and optional redaction, and validate every patched model. | `packages/office/src/documents/generate.ts:36-96`; `parse.ts:181-242`; `patch.ts:430-476`; `caps.ts:59-68` | Tasks 2-3 move and wrap these functions. No second document AST, ZIP parser, macro executor, filesystem path, or process runner. |
|
|
21
|
+
| `createDocumentReader` creates bounded PDF/DOCX literal extraction and returns `null` for unsupported bytes; coding `read` accepts its structural `DocumentReader` slot. | `packages/prism-coding-tools/src/document-reader/index.ts:168-202`; `packages/prism-coding-tools/src/agent/read.ts:199-252,472-677` | Task 2 moves implementation and optional peers to work while `DocumentReader` remains in coding tools. Task 3 adds XLSX/PPTX through the moved document parser without changing the slot. |
|
|
22
|
+
| Docker validates absolute executable/image/user/source, exact environment, network mode, limits, and returns one `DisposableSandbox`; sandbox compositions return shared tools plus containment metadata. | `packages/prism-coding-tools/src/security/docker-sandbox.ts:1038-1143`; `sandbox.ts:120-150`; `sandbox-coding-operations.ts:370-376` | Task 6 injects this interface into `createWorkComposition`; it does not fork Docker or claim isolation missing from sandbox capabilities. Default work network is `none`. |
|
|
23
|
+
| `parseSkillFile` validates frontmatter/name and preserves instructions; registry duplicate policy and active-tool checks remain host-owned. | `src/contribution-parsing.ts:87-111`; `src/skills.ts:12-45` | Task 7 parses four vendored files and overlays `toolNames` in TypeScript. No Prism-authored `SKILL.md` body, auto-activation, or permission grant. |
|
|
24
|
+
| Computer-use Linux loads a packaged skill synchronously, caps bytes, parses it with `parseSkillFile`, and verifies the expected name. | `packages/prism-coding-tools/src/computer-use-linux/skill.ts:10-21` | Task 7 reuses this small loader pattern: read, cap, parse, assert known name. Work scripts remain sandbox-only, unlike this host desktop bridge. |
|
|
25
|
+
|
|
26
|
+
### Frozen vocabulary
|
|
27
|
+
|
|
28
|
+
- Package: `@arnilo/prism-work`
|
|
29
|
+
- Subpaths: `/connectors`, `/connectors/microsoft365`, `/connectors/google-workspace`, `/connectors/drafts`, `/documents`, `/sheets`, `/diagrams`, `/document-reader`, `/tools`, `/sandbox`, `/skills`
|
|
30
|
+
- New names: `createOfficeTools`, `createWorkComposition`, `loadWorkSkills`, `WORK_SANDBOX_IMAGE`
|
|
31
|
+
- Injection seam: host supplies a `DisposableSandbox`; `prism-work` does not runtime-depend on `@arnilo/prism-coding-tools`.
|
|
32
|
+
|
|
33
|
+
The cut replaces `@arnilo/prism-office`; it does not add a twelfth publishable package or a compatibility shim. Existing hosts change imports on the next lockstep pre-1.0 cut.
|
|
34
|
+
|
|
35
|
+
## Later-task mapping
|
|
36
|
+
|
|
37
|
+
| Task | Frozen primitive or explicit boundary |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| 2 | Physical moves only: work types/tools/adapters/drafts, document libraries, and reader implementation. Preserve coding-tools `DocumentReader` interface and core PostgreSQL structural store; prohibit `prism-work` ↔ core/coding-tools runtime edges. |
|
|
40
|
+
| 3 | `generateDocument`, `importDocument`, `patchDocument`, `diffDocument`, preview functions, document caps, and structural reader slot. No duplicate AST or raw OOXML transcript output. |
|
|
41
|
+
| 4 | `createWorkHttpClient`, fixed adapter operation maps, work limits, draft store, approval, and idempotency. Explicitly out: model-supplied download URLs and generic Graph/Drive clients. |
|
|
42
|
+
| 5 | Existing `Microsoft365Op` / `GoogleWorkspaceOp`, capability gates, and draft lifecycle. Explicitly out: free-form `batchUpdate`, Planner/Teams tools, and a Graph Word/Excel session. |
|
|
43
|
+
| 6 | `DisposableSandbox`, `createDockerSandbox`, and `createSandboxCodingComposition`. Explicitly out: tokens/connectors in the sandbox, a second sandbox runtime, unrestricted egress, or an isolation claim without attested capabilities. |
|
|
44
|
+
| 7 | `parseSkillFile`, `createSkillRegistry`, and capped computer-use loader pattern. Explicitly out: Prism-written skill bodies, Anthropic document-skill redistribution, npm dependency on Hermes, and scripts executed on host. |
|
|
45
|
+
| 8 | Existing document model plus injected sandbox `execFile`. Explicitly out: in-process formula evaluation and default-test LibreOffice execution. |
|
|
46
|
+
| 9 | Existing XLSX/DOCX generate translation and document caps. Explicitly out: new model kinds, charts, pivots, comments, headers, and a general numbering engine. |
|
|
47
|
+
| 10 | Existing `isZipContainer` boundary and work sandbox. Explicitly out: an in-process OLE `.doc`/`.xls`/`.ppt` parser or macro execution. |
|
|
48
|
+
| 11 | Existing package/export truth and pack probes. Preserve 11 publishable names, import isolation, optional-peer failure, and history-only references to retired imports. |
|
|
49
|
+
|
|
50
|
+
## Threat model and required posture
|
|
51
|
+
|
|
52
|
+
| Threat | Current protection | Required follow-through |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| Sandbox receives tenant tokens | Work HTTP resolves per-identity tokens only at request edge; Docker env is an exact allow-list and defaults to network none. | Task 6 rejects M365/Google token names, keeps connectors host-side, and reports `networkIsolated` only from injected capabilities. |
|
|
55
|
+
| LibreOffice macro/OLE execution | In-process document APIs never spawn a process; import reports unsupported macros/OLE as fidelity issues. | Task 6 uses headless LibreOffice with isolated profile, no macro-enable flags or socket, network none; Tasks 8/10 run it only inside that sandbox. |
|
|
56
|
+
| OOXML ZIP symlink/bomb escape | In-process parsing requires a PK container and document caps; Docker workspace import rejects unsafe archive entries. | Tasks 3 and 6 keep byte/element caps before parse and never extract model-supplied archives onto host. Add explicit regression coverage wherever sandbox filesystem import/export meets OOXML bytes. |
|
|
57
|
+
| Proprietary Anthropic skill vendoring | Anthropic labels document skills source-available, not open source. | Task 7 allows only the pinned Hermes MIT source, records `vendor-lock.json`/notice, byte-compares against Anthropic material, and fails without copying on provenance or license doubt. |
|
|
58
|
+
| Model-supplied Graph/Drive URL | Work HTTP client allowlists origins, pins fetch, bounds request/response, and resolves tokens late. | Tasks 4-5 expose item IDs and fixed builders only; schemas omit `url`, reject extra fields, and preserve origin tests. |
|
|
59
|
+
| Core runtime depends on office-open through prism-work | `PostgresEnterpriseState.workIdempotency` is structurally typed today; document imports are isolated in office. | Task 2 keeps core `workIdempotency` local, replaces value imports with local core errors/limits, and proves core ↛ prism-work at runtime. |
|
|
60
|
+
| Vendored skill scripts run outside sandbox | Existing skill loader reads text only; it does not execute scripts. | Tasks 6-7 bind scripts only to sandbox `execFile` with argv, require a work sandbox for registration, and never expose host shell/token env as a skill tool. |
|
|
61
|
+
|
|
62
|
+
## Task 11 baseline
|
|
63
|
+
|
|
64
|
+
Measured 2026-09-17 with `npm pack --dry-run --json` in each package. Export counts are `package.json` `exports` keys; pack values are evidence for Task 11, not new enforced budgets.
|
|
65
|
+
|
|
66
|
+
| Package | Packed bytes | Unpacked bytes | Files | Export keys |
|
|
67
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
68
|
+
| `@arnilo/prism-office` | 55,373 | 247,113 | 76 | 3 |
|
|
69
|
+
| `@arnilo/prism-core` | 418,550 | 2,062,441 | 450 | 23 |
|
|
70
|
+
| `@arnilo/prism-coding-tools` | 294,501 | 1,270,634 | 275 | 10 |
|
|
71
|
+
|
|
72
|
+
Current packaging remains 11 publishable packages and includes `@arnilo/prism-office`; Task 2 replaces that name with `@arnilo/prism-work` without changing the count. Task 11 compares the moved family against this pack/export snapshot before recording a justified ceiling change.
|
|
73
|
+
|
|
74
|
+
## Existing verification evidence
|
|
75
|
+
|
|
76
|
+
- Work tool construction and HTTP adapter coverage: `packages/prism-core/src/integrations/work/__tests__/{work-tools,microsoft365-http,google-workspace-http,drafts}.test.ts`; direct callers are enumerated by `graft callers createWorkTools`.
|
|
77
|
+
- PostgreSQL idempotency and enterprise composition: `packages/prism-core/src/enterprise/postgres/__tests__/{stores,work-idempotency,enterprise-conformance}.test.ts`; `createPostgresEnterpriseState` wires `workIdempotency` at `enterprise.ts:66`.
|
|
78
|
+
- Document reader cap and reader-slot coverage: `packages/prism-coding-tools/src/document-reader/__tests__/index.test.ts` and `packages/prism-coding-tools/src/__tests__/coding-tools-conformance.test.ts`.
|
|
79
|
+
- Sandbox, capability, Docker, and composition coverage: `packages/prism-coding-tools/src/security/__tests__/docker-sandbox.test.ts`, `sandbox-coding-operations.test.ts`, and related security suite.
|
|
80
|
+
- Packaging count/export truth: `src/__tests__/packaging.test.ts` and `scripts/package-truth.json`.
|
|
81
|
+
|
|
82
|
+
## Review decision
|
|
83
|
+
|
|
84
|
+
Proceed with one physical-move family, host-injected sandbox, and four pinned Hermes skills. Reuse work drafts/idempotency, document model/caps, coding-reader seam, Docker sandbox, and skill parser/registry. Keep secrets, generic provider URLs, macro/OLE execution, unpinned or proprietary skill content, host script execution, compatibility wrappers, and new package count out of scope.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Host long-run durability, steering, and honesty primitive review
|
|
2
|
+
|
|
3
|
+
Plan 084 Task 0 freezes the vocabulary, symbol inventory, threat posture, and packed baseline for Tasks 1–8. Evidence only: no public symbol, dependency, runtime path, or doc contract changes here.
|
|
4
|
+
|
|
5
|
+
Reviewed tree: `a7915d6c` with 304 dirty files (0.8.0 line in progress), root package version `0.7.0`. Every span below was verified against this revision, not from memory. Where the plan text drifted, [Plan corrections found during review](#plan-corrections-found-during-review) records the correction the implementation tasks must follow.
|
|
6
|
+
|
|
7
|
+
## Sources reviewed
|
|
8
|
+
|
|
9
|
+
Current contracts: [agent session runtime](../agent-session-runtime.md) (§Mid-run steer `90`, §Durable interruption `182`), [agent loops](../agent-loops.md), [AG-UI](../ag-ui.md), [host compositions](../host-compositions.md), [guardrails](../guardrails.md), [runs and usage](../runs-and-usage.md), [model registry](../model-registry.md), [provider layer](../provider-layer.md), [provider conformance](../provider-conformance.md), [execution timeline](../execution-timeline.md), [thinking and reasoning](../thinking-and-reasoning.md), [options index](../options-index.md).
|
|
10
|
+
|
|
11
|
+
Host evidence for the seven gaps: `~/Projects/synapta-core/docs/architecture/prism-070-and-agent-desk-review.md` §8, `plans/117-Prism070Adoption.md`, `docs/architecture/synapta-agent-mechanism.md` §9.2 (`Do` bounds and restart behavior), §9.8 (L0/L1/L2 autonomy law), Plan 111 T21 (`ERR_PRISM_AG_UI_INPUT` relay shim, `429 GoUsageLimitError` opacity), Plan 110 F9 (live `Do` run died at `maxTurns: 4`).
|
|
12
|
+
|
|
13
|
+
## Existing primitives and compatibility freeze
|
|
14
|
+
|
|
15
|
+
| Existing contract | Evidence | Later use / compatibility rule |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Durable resume validates revision, fingerprint, `expectedVersion`, ownership/fencing, and requires `status === "suspended"`. | `src/agent-run-lifecycle.ts:112` (`resumeAgentRun`), `:176-195` (`prepareAgentRunResume`; the hard gate is `state.status !== "suspended"` at `:191`); `src/agent-approval.ts:54` (`assertValidAgentRunResume`) | Task 1 adds a running-state resume path gated on a new explicit resume action. The suspension gate and CAS semantics stay; only a validated checkpoint-only path is added. |
|
|
18
|
+
| Resume vocabulary is approval-shaped: legacy `decision?: "approve" | "deny"` plus batched `RunDecision[]` whose outcome is `ApprovalOutcome = "allow_once" | "allow_for_run" | "reject_once" | "reject_for_run"`. | `src/contracts-run-state.ts:221-227` (`AgentRunResume`), `:26` (`ApprovalOutcome`), `:68-79` (`RunDecision`) | Task 1 must **not** add `"continue"` to `ApprovalOutcome` or to `AgUiInterruptResolution` (`packages/ag-ui/src/handler.ts:96`, `"approve" | "deny"`). It is a third resume action beside the legacy pair, resolved before decision work and never applied as a sticky/approval outcome. |
|
|
19
|
+
| Checkpoint persistence lives in one save path with redaction, `expectedVersion`, fencing, and byte caps; session-state payload carries loaded skills, activated tools, attention sticky frontier, and optional skill bodies. | `src/agent-run-state.ts:189` (`saveAgentRunState`); `src/agent-session/session/persist.ts:14-52` (`persistDurable`); `src/contracts-run-state.ts:177-196` (`AgentRunStateOptions`, `persistSessionState:192`, `includeSkillBodies:195`) | Task 1 reuses `saveAgentRunState` unchanged; `checkpointPolicy` only decides *when* it is called. No second checkpoint format, no new store interface. |
|
|
20
|
+
| Loop strategies call an optional steer seam at turn boundaries; the public session queue is bounded by count and bytes, and rejected steers emit a typed event. | `src/agent-loops.ts:34` (`singleShotLoop`), `:45,161` (drain points), `:395` (`resolveLoop`); `src/contracts-core/loop.ts:37-40` (`hasPendingSteers`/`applyPendingSteers`); `src/agent-session/session.ts:209` (`steer`); `src/contracts-run-state.ts:322,326` (caps); `src/contracts-protocol.ts:276` (`steer_rejected`) | Task 2 hooks the same boundary; steer already works and needs no new public surface. Steer caps and the `steer_rejected` event are frozen. |
|
|
21
|
+
| The agent fingerprint hashes id, revision, model, instructions, system prompt contributions, skills, tools, guardrails, and loop revision with SHA-256. | `src/agent-run-state.ts:118-156` (`agentFingerprint`) | Task 4's `RunBundleSnapshot` is the inspectable projection of these same inputs with the same digest algorithm — one truth, not two. |
|
|
22
|
+
| Run results and ledger records already carry a shared `ErrorInfo` (`name`, `message`, `code`, `retryAfterMs`, `cause`). | `src/contracts-core/content.ts:12-20` (`ErrorInfo`); `src/contracts-run-state.ts:285-307` (`AgentRunResult`, `error:302`, `limit:300`); `src/contracts-protocol.ts:519` (`RunRecord.error`); `src/contracts-protocol.ts:420` (`ToolResult.error`) | Task 6 adds `failureClass?` to `ErrorInfo` — it then flows to result, ledger, and tool results with no extra plumbing. No bespoke result-only field. |
|
|
23
|
+
| Provider HTTP errors are built once in the shared retry seam; 429 is already a retryable status with `Retry-After` and redacted bodies. | `src/providers/transport.ts:31` (`ProviderTransportError`); `packages/prism-providers/src/shared/retry-http.ts:12` (`RETRYABLE_STATUSES`), `:31` (`readRetryAfterMs`), `:39` (`parseErrorBody`), `:51` (`providerHttpError`) | Task 6 classifies at `providerHttpError`, from status plus redacted body only. No per-adapter copies, no header/body leakage, `unknown` as default. |
|
|
24
|
+
| Guardrail stages are a closed union with per-stage value typing; the runner and its concurrency cap are shared. | `src/contracts-core/run-limits.ts:63` (`GuardrailStage = "input" | "output" | "tool_input" | "tool_output"`), `:92` (`Guardrail`); `src/guardrails.ts:33,48,89` (`RunGuardrailsOptions`, `runGuardrails`, `assertGuardrailsAllowed`) | Task 5 needs **no** new stage — `"output"` already exists and the gap is the missing reusable factory, not a stage. `runGuardrails` stays unchanged. |
|
|
25
|
+
| Model capabilities are an advisory metadata bag that hosts read for pinning; provider catalogs stamp `thinkingFamily` derived from conformance runs, walked by a cross-catalog test and rendered into an evidence matrix. | `src/contracts-core/content.ts:123-143` (`ModelCapabilities`); stamps in `packages/prism-providers/src/*/models.ts` (e.g. `packages/prism-providers/src/anthropic/models.ts:131`); `packages/prism-providers/src/__tests__/thinking-conformance.test.ts`; `packages/prism-providers/scripts/generate-thinking-coverage.mjs`; `docs/_evidence/thinking-coverage-2026-09-05.md`; `docs/provider-conformance.md` | Task 7 clones this pattern for a second axis (`toolCallStrictness`). No runtime probes, no vendor marketing claims, absent field = unknown. |
|
|
26
|
+
| AG-UI input is fully schema-validated and byte-bounded, but the legacy no-projector path *rejects* client state/tools with `ERR_PRISM_AG_UI_INPUT`; interrupt resolution is deliberately `approve`/`deny` only. | `packages/ag-ui/src/input.ts:27` (`parseAgUiInput`), `:65` (`defaultAgUiInput` — throws when `tools.length !== 0 || !emptyState(state)`); `packages/ag-ui/src/handler.ts:64` (`AgUiInputOptions.project`), `:74` (`AgUiPreparedInput`), `:96` (`AgUiInterruptResolution`), `:160` (`createAgUiHandler`), `:237` (`resolveAgUiCapabilities`) | Task 3 makes server-authoritative mode a sanitize step on `ParsedAgUiInput` (state → `undefined`, tools → `[]`) applied before both the projector and the default path. Bounds and fail-closed parse stay untouched. |
|
|
27
|
+
| Composition inspection is zero-network and reports configuration posture without reading stores or emitting secrets. | `src/host-composition.ts:50` (`HostCompositionReport`), `:219` (`inspectHostComposition`), `:380` (`assertHostCompositionReadiness`) | Task 4's snapshot inherits the zero-network and redaction rules; it is a new pure function, not a change to composition inspection. |
|
|
28
|
+
| Execution timeline projects steps with kind, status, and a content policy; no stop-reason field exists yet, and no terminal event carries one. | `packages/prism-core/src/governance/observability/timeline-types.ts:19-35`; `timeline.ts`; terminal-ish events today: `turn_finished` (`src/contracts-protocol.ts:208`), `run_limit_exceeded` (`:272`), `queue_updated` (`:273`), `steer_rejected` (`:276`) | Task 2 adds `stopReason` additively to the result, the ledger `RunRecord`, and the timeline projection. No new event type; `run_limit_exceeded` is the vocabulary precedent. |
|
|
29
|
+
|
|
30
|
+
## Frozen vocabulary (new, additive only)
|
|
31
|
+
|
|
32
|
+
| Gap | New public surface | Explicitly out |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| G1 | `AgentRunStateOptions.checkpointPolicy?: "decision" \| "every-turn"` (default `"decision"`); a third resume action `"continue"` accepted only by `resumeAgentRun`/`resumeAgentRunStream`; `docs/durable-runs.md`; `examples/durable-investigation.ts` | `"continue"` in `ApprovalOutcome`/`AgUiInterruptResolution`/server routes; a second checkpoint format; any auto-replay of ambiguous tool effects |
|
|
35
|
+
| G2 | `RunOptions.turnPolicy?: { maxTurns?: number; stop?: (ctx: TurnBoundaryContext) => TurnStopDecision }`; `stopReason: "host_policy"` on result/ledger/timeline | New event type; tool-argument or prompt text in `TurnBoundaryContext`; any change to `resolveRunLimits` narrowing law |
|
|
36
|
+
| G3 | `CreateAgUiHandlerOptions.inputPolicy?: { clientState: "honor" \| "ignore" }` (default `"honor"`) | Client-named tool execution; relaxing `parseAgUiInput` bounds; `continue` exposure via AG-UI resume |
|
|
37
|
+
| G4 | `snapshotRunBundle({ agent, config?, run? })` → `RunBundleSnapshot` with `schemaVersion` + SHA-256 digest; `docs/run-bundle.md` | Network/store reads; secret or connection-string content; a second hashing vocabulary |
|
|
38
|
+
| G5 | `createClaimGroundingGuardrail(options)` in `src/evidence-grounding.ts` (stage `"output"`, already in the union); `docs/guardrails.md` section | New guardrail stage; LLM-judge grounding; `@arnilo/prism-memory` dependency; whole-transcript error payloads |
|
|
39
|
+
| G6 | `ProviderFailureClass`; `ErrorInfo.failureClass?` → result/ledger/tool errors; classifier in `providerHttpError` | Per-adapter error types; message-string parsing in hosts; body/header leakage; changed retry counts |
|
|
40
|
+
| G7 | `ModelCapabilities.toolCallStrictness?: "strict" \| "lenient" \| "legacy"`; catalog stamps; conformance walk; `docs/_evidence/toolcall-coverage-<date>.md` | Runtime probes; inferred upgrades; new dependency; a twelfth publishable package |
|
|
41
|
+
|
|
42
|
+
## Later-task mapping
|
|
43
|
+
|
|
44
|
+
| Task | Frozen primitive or explicit boundary |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| 1 (G1) | `saveAgentRunState` (`src/agent-run-state.ts:189`) called at the loop turn boundary when `checkpointPolicy === "every-turn"`; resume accepts a running-state checkpoint only via the new `"continue"` action; fingerprint/`expectedVersion`/ownership gates unchanged; ambiguity law (no auto-replay after unpersisted side effects) documented as the bounded window. |
|
|
47
|
+
| 2 (G2) | `turnPolicy.stop` evaluated at the same boundary; `stopReason` additive on `AgentRunResult` (`src/contracts-run-state.ts:285`), `RunRecord` (`src/contracts-protocol.ts:519`), timeline projection; `steer()` and its caps untouched. |
|
|
48
|
+
| 3 (G3) | Sanitize `ParsedAgUiInput` before projector/default path when `clientState === "ignore"`; parser bounds and `ERR_PRISM_AG_UI_INPUT`/`ERR_PRISM_AG_UI_LIMIT` semantics unchanged for malformed input. |
|
|
49
|
+
| 4 (G4) | New `src/run-bundle.ts` reuse of `agentFingerprint`'s field vocabulary (`src/agent-run-state.ts:118`) plus `ResolvedRunLimits` and provider/loop identities; `SecretRedactor` at the boundary; zero network. |
|
|
50
|
+
| 5 (G5) | Factory returning an ordinary `GuardrailDefinition` at the existing `"output"` stage; deterministic numeric attribution from run tool results; no memory-package dependency (citation shapes mirrored, not imported). |
|
|
51
|
+
| 6 (G6) | Single classifier beside `providerHttpError`; `failureClass` on `ErrorInfo`; adapters may stamp a provider `code` the classifier weighs; retry policy and budgets unchanged. |
|
|
52
|
+
| 7 (G7) | `toolCallStrictness` stamped only from conformance-observed behavior; cross-catalog walk cloned from `thinking-conformance.test.ts`; generator cloned from `generate-thinking-coverage.mjs`. |
|
|
53
|
+
| 8 | Additions-only compat diff, `docs/options-index.md` routes for the six new option surfaces, the two new `docs/index.md` entries, packed-size delta vs the baseline below. |
|
|
54
|
+
|
|
55
|
+
## Threat model and required posture
|
|
56
|
+
|
|
57
|
+
| Threat | Current protection | Required follow-through |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Forged resume `"continue"` from an untrusted caller | Resume is host-API only today; it already validates fingerprint, revision, ownership/fencing, and `expectedVersion` (`src/agent-run-lifecycle.ts:188-191`). | Task 1 keeps every gate, accepts running-state checkpoints only through the explicit action, and proves server/AG-UI surfaces still expose `approve`/`deny` only (`packages/ag-ui/src/handler.ts:96`). |
|
|
60
|
+
| Steer-queue flooding | Bounded queue (`DEFAULT_MAX_PENDING_STEERS = 8`, `DEFAULT_MAX_PENDING_STEER_BYTES = 64 KiB`, `src/contracts-run-state.ts:322,326`) with `steer_rejected` events. | Task 2 does not widen the queue or add a bypass; stop-then-steer tests assert the caps and event behavior survive. |
|
|
61
|
+
| AG-UI client-state trust | Client fields are parsed but carry no runtime authority; projection is host-owned (`packages/ag-ui/src/input.ts:27`, `packages/ag-ui/src/handler.ts:64`). | Task 3's `"ignore"` mode drops client state/tools before projection and keeps malformed/oversized envelopes failing closed. Client tool names still cannot reach the registry. |
|
|
62
|
+
| Bundle snapshot secret leakage | `SecretRedactor` already guards checkpoint payloads; composition inspection is zero-network (`src/host-composition.ts:219`). | Task 4 applies redaction to every string field, never reads stores/env, and adds a secret-shaped fixture test. |
|
|
63
|
+
| Guardrail bypass via missing evidence metadata | Guardrail runner fails closed (`src/guardrails.ts:48-89`). | Task 5 fails any numeric claim when no evidence set exists, never silently passes in flag mode (violation recorded), and bounds the reported span. |
|
|
64
|
+
| Error-classification leakage of provider headers/bodies | `providerHttpError` already redacts bodies; `ErrorInfo.message` is bounded (`src/contracts-core/content.ts:12`). | Task 6 classifies from status + redacted body, never copies headers, defaults to `unknown`, and secret-scans fixtures. |
|
|
65
|
+
| Crash-window double dispatch | The 0.6.0 contract persists tool results before treating a round as failed; resume never auto-replays an ambiguous side effect. | Task 1's `"every-turn"` policy narrows the ambiguity window to at most one provider turn of thinking and documents that residual window in `docs/durable-runs.md`; no auto-replay is added. |
|
|
66
|
+
|
|
67
|
+
## Plan corrections found during review
|
|
68
|
+
|
|
69
|
+
- `packages/prism-ag-ui` does not exist; the package directory is `packages/ag-ui` (name `@arnilo/prism-ag-ui`). Task 3 and Task 8 references use `packages/ag-ui`.
|
|
70
|
+
- `saveRunCheckpoint` does not exist; the single save path is `saveAgentRunState` (`src/agent-run-state.ts:189`), driven by `persistDurable` (`src/agent-session/session/persist.ts:14`).
|
|
71
|
+
- Resume of a crashed (never-suspended) run is today impossible by construction: `:191` requires `status === "suspended"`. Task 1 must add the running-state acceptance for the new action, and `assertValidAgentRunResume` (`src/agent-approval.ts:54`) must learn the action without touching `ApprovalOutcome`.
|
|
72
|
+
- G6 placement is `ErrorInfo.failureClass?` (`src/contracts-core/content.ts:12`), not a bespoke `AgentRunResult` field: `AgentRunResult.error`, `RunRecord.error`, and `ToolResult.error` already share `ErrorInfo`.
|
|
73
|
+
- G3 seam is sanitizing `ParsedAgUiInput` before both `project` and `defaultAgUiInput`; changing only `defaultAgUiInput` would leave projector-based handlers accepting unauthoritative client state.
|
|
74
|
+
- G5 needs no new stage: `"output"` exists in `GuardrailStage` (`src/contracts-core/run-limits.ts:63`).
|
|
75
|
+
- G7's conformance machinery is `packages/prism-providers/src/__tests__/thinking-conformance.test.ts` + `packages/prism-providers/scripts/generate-thinking-coverage.mjs` + `docs/_evidence/thinking-coverage-2026-09-05.md`; there is no `packages/prism/testing/provider-conformance`.
|
|
76
|
+
|
|
77
|
+
## Task 8 baseline
|
|
78
|
+
|
|
79
|
+
Measured 2026-09-17 with `npm pack --dry-run --json` at `a7915d6c` (304 dirty files, 0.8.0 line in progress), **after** this review doc landed, so Task 8 compares like-for-like. These are the plan-line reference points Task 8 reports deltas against; they are not new enforced budgets. Current `scripts/budgets.json` root baselines (plan 079 Task 1, 2026-09-15) are `1,247,731 / 4,137,527 / 489`; the root measurement below sits **+4.0% packed** against that baseline (tolerance 5%).
|
|
80
|
+
|
|
81
|
+
| Package | Packed bytes | Unpacked bytes | Files |
|
|
82
|
+
| --- | ---: | ---: | ---: |
|
|
83
|
+
| `@arnilo/prism` (root) | 1,297,619 | 4,285,038 | 499 |
|
|
84
|
+
| `@arnilo/prism-ag-ui` | 101,715 | 428,130 | 86 |
|
|
85
|
+
| `@arnilo/prism-core` | 385,689 | 1,876,362 | 422 |
|
|
86
|
+
|
|
87
|
+
Pack paths are `packages/ag-ui` and `packages/prism-core`; `@arnilo/prism` is the repo root. This review doc alone accounts for +5,948 packed / +18,060 unpacked / +1 file (`docs/history` is packed into the root tarball). Tasks 1, 4, and 7 add three more docs (`docs/durable-runs.md`, `docs/run-bundle.md`, the tool-call evidence matrix) plus this plan's source growth, so the root baseline will likely need a dated `$comment` rebaseline in Task 8 — the plan 079 Task 1 precedent — rather than being reported as over-tolerance after the fact.
|
|
88
|
+
|
|
89
|
+
## Existing verification evidence
|
|
90
|
+
|
|
91
|
+
- Durable run state, resume, and checkpoint primitives: `src/__tests__/agent-run-state.test.ts`, `durable-loops.test.ts`, `checkpoint-event-primitives.test.ts`.
|
|
92
|
+
- Steer, limits, guardrails, composition: `src/__tests__/run-limits.test.ts`, `guardrails.test.ts`, `host-composition.test.ts`.
|
|
93
|
+
- AG-UI input handling and interrupt resolution: `packages/ag-ui/src/__tests__/` (handler/input suites, A2UI and resume cases).
|
|
94
|
+
- Provider retry/error seam: `packages/prism-providers/src/__tests__/` transport and retry suites (`provider-transport`), plus per-provider HTTP adapter tests.
|
|
95
|
+
- Conformance walk precedent: `packages/prism-providers/src/__tests__/thinking-conformance.test.ts`.
|
|
96
|
+
- Budget/baseline gates: `scripts/budget-gates.mjs` (`measureRootPack`), `scripts/budget-gate.test.mjs`, `scripts/release.mjs gate --update-baseline`.
|