@arcfold/dsh 0.0.0-stage → 0.4.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -2
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/cordis.patch.yml +138 -0
  5. package/package.json +70 -4
  6. package/patches/deepseek-0.2.2-rc.1-transport.json +65 -0
  7. package/patches/deepseek-0.2.2-rc.1-transport.patch +61 -0
  8. package/presets/arcfold.patch.yml +110 -0
  9. package/runtime/README.md +19 -0
  10. package/runtime/bootstrap.json +15 -0
  11. package/runtime/desktop-python.tar.gz +0 -0
  12. package/runtime/host/package.json +11 -0
  13. package/runtime/host/pnpm-workspace.yaml +9 -0
  14. package/runtime/initial-policy.json +94 -0
  15. package/runtime/profile/pnpm-workspace.yaml +7 -0
  16. package/scripts/exact-package-patch.mjs +106 -0
  17. package/scripts/patch-deepseek-transport.mjs +14 -0
  18. package/src/agent.mjs +56 -0
  19. package/src/browse-scope.mjs +14 -0
  20. package/src/budget.mjs +369 -0
  21. package/src/business.mjs +334 -0
  22. package/src/context-material.mjs +119 -0
  23. package/src/deepseek-budget-plugin.mjs +58 -0
  24. package/src/deepseek-provider.mjs +57 -0
  25. package/src/deepseek-receipt.mjs +97 -0
  26. package/src/deepseek-transport.mjs +97 -0
  27. package/src/desktop/adapter.mjs +16 -0
  28. package/src/desktop/bootstrap.mjs +84 -0
  29. package/src/desktop/configuration.mjs +117 -0
  30. package/src/desktop/provider.mjs +46 -0
  31. package/src/desktop/python-runtime.mjs +105 -0
  32. package/src/desktop/setup-service.mjs +81 -0
  33. package/src/desktop/vendor/LICENSE.deepseek +21 -0
  34. package/src/desktop/vendor/NOTICE.md +5 -0
  35. package/src/desktop/vendor/deepseek-0.2.2-rc.1.mjs +2919 -0
  36. package/src/evidence-ownership.mjs +51 -0
  37. package/src/evidence.mjs +35 -0
  38. package/src/goal-context.mjs +52 -0
  39. package/src/goal-execution-tool.mjs +74 -0
  40. package/src/goal-execution.mjs +333 -0
  41. package/src/goal-tool.mjs +56 -0
  42. package/src/goal-work-control.mjs +63 -0
  43. package/src/goal-work.mjs +257 -0
  44. package/src/goals.mjs +578 -0
  45. package/src/importer.mjs +298 -0
  46. package/src/index.mjs +53 -0
  47. package/src/learning-contracts.mjs +220 -0
  48. package/src/learning-defaults.mjs +98 -0
  49. package/src/learning-goals.mjs +165 -0
  50. package/src/learning-plugin.mjs +28 -0
  51. package/src/learning-runner.mjs +275 -0
  52. package/src/learning.mjs +534 -0
  53. package/src/legacy-export.mjs +332 -0
  54. package/src/legacy-import-source.mjs +356 -0
  55. package/src/metered-provider-host.mjs +101 -0
  56. package/src/metered-transport.mjs +435 -0
  57. package/src/method-context.mjs +57 -0
  58. package/src/method-lifecycle.mjs +34 -0
  59. package/src/method-tool.mjs +61 -0
  60. package/src/methods.mjs +425 -0
  61. package/src/model-selection.mjs +41 -0
  62. package/src/native-context-call.mjs +88 -0
  63. package/src/native-continuation.mjs +217 -0
  64. package/src/native-request-authority.mjs +90 -0
  65. package/src/openai-models.mjs +4 -0
  66. package/src/openai-receipt.mjs +98 -0
  67. package/src/openai-transport.mjs +167 -0
  68. package/src/openai-usage.mjs +194 -0
  69. package/src/opportunities.mjs +295 -0
  70. package/src/preparation-artifacts.mjs +213 -0
  71. package/src/preparation-plugin.mjs +290 -0
  72. package/src/runtime-command.mjs +264 -0
  73. package/src/runtime-config.mjs +417 -0
  74. package/src/runtime-guard.mjs +284 -0
  75. package/src/runtime-host.mjs +212 -0
  76. package/src/runtime-init.mjs +278 -0
  77. package/src/runtime-installation.mjs +518 -0
  78. package/src/runtime-lifecycle.mjs +227 -0
  79. package/src/runtime-model-settings.mjs +111 -0
  80. package/src/runtime-readiness.mjs +285 -0
  81. package/src/runtime-workspace.mjs +18 -0
  82. package/src/session-bridge.mjs +365 -0
  83. package/src/store.mjs +394 -0
  84. package/src/tree/LICENSE +21 -0
  85. package/src/tree/attribution.mjs +120 -0
  86. package/src/tree/clusters.mjs +61 -0
  87. package/src/tree/consolidation.mjs +103 -0
  88. package/src/tree/context.mjs +211 -0
  89. package/src/tree/contracts.mjs +142 -0
  90. package/src/tree/experience.mjs +140 -0
  91. package/src/tree/experiment-budget.mjs +84 -0
  92. package/src/tree/hooks.mjs +254 -0
  93. package/src/tree/host-context.mjs +107 -0
  94. package/src/tree/host-process.mjs +288 -0
  95. package/src/tree/host.mjs +37 -0
  96. package/src/tree/improvement.mjs +106 -0
  97. package/src/tree/items.mjs +294 -0
  98. package/src/tree/jobs.mjs +184 -0
  99. package/src/tree/learning.mjs +202 -0
  100. package/src/tree/metrics.mjs +203 -0
  101. package/src/tree/model-owner.mjs +254 -0
  102. package/src/tree/observations.mjs +73 -0
  103. package/src/tree/prompts.mjs +500 -0
  104. package/src/tree/reports.mjs +67 -0
  105. package/src/tree/repository.mjs +236 -0
  106. package/src/tree/runtime.mjs +745 -0
  107. package/src/tree/script-writer.mjs +149 -0
  108. package/src/tree/scripts.mjs +208 -0
  109. package/src/tree/signals.mjs +118 -0
  110. package/src/tree/snapshots.mjs +379 -0
  111. package/src/tree/token-counter.mjs +75 -0
  112. package/src/tree/token-counter.py +37 -0
  113. package/src/tree/tokenizer-resources/LICENSE +21 -0
  114. package/src/tree/tokenizer-resources/README.md +31 -0
  115. package/src/tree/tokenizer-resources/deepseek_recipe-0.2.0-cp310-abi3-macosx_11_0_arm64.whl +0 -0
  116. package/src/tree/tokenizer-resources/manifest.json +61 -0
  117. package/src/tree/tokenizer-resources/native.pyi +1042 -0
  118. package/src/tree/tokenizer-resources/tokenizer-NOTICE.md +26 -0
  119. package/src/tree/tokenizer-resources/tokenizer-v4-NOTICE.md +27 -0
  120. package/src/tree/tokenizer-resources/tokenizer-v4.json +267359 -0
  121. package/src/tree/tokenizer-resources/tokenizer.json +267359 -0
  122. package/src/tree/tools.mjs +99 -0
  123. package/src/tree/tree.mjs +202 -0
  124. package/src/tree/ui.mjs +285 -0
  125. package/src/tree/verification.mjs +143 -0
  126. package/src/understandings.mjs +309 -0
  127. package/src/web-budget-recovery.mjs +181 -0
@@ -0,0 +1,217 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './store.mjs';
3
+ import { sourceAvailable, hash as sourceHash } from './evidence.mjs';
4
+
5
+ const digest = value => createHash('sha256').update(canonicalJson(value)).digest('hex');
6
+ const reject = message => { throw Object.assign(new Error(message), { code: 'ARCFOLD_NATIVE_CONTINUATION_INVALID' }); };
7
+ const same = (a, b) => canonicalJson(a) === canonicalJson(b);
8
+ const positive = value => Number.isSafeInteger(value) && value > 0;
9
+ const text = message => (message?.content || []).filter(block => block.type === 'text').map(block => block.text).join('');
10
+ const goalMessages = messages => messages.filter(message => message.source?.kind === 'goal' && message.source.round > 0);
11
+
12
+ function ref(store, sessionId, event) {
13
+ const rows = store.listAll('dsh_events', { where: { sessionId, seq: event.seq } });
14
+ if (rows.length !== 1 || rows[0].sha256 !== digest(event) || rows[0].type !== event.type)
15
+ reject('Delegation needs its exact durable native event index');
16
+ return { id: rows[0].id, sessionId, seq: event.seq, type: event.type, sha256: digest(event) };
17
+ }
18
+ function commandOrigin(events, change) {
19
+ const matches = [];
20
+ for (const run of events.filter(event => event.type === 'command/run' && event.seq < change.seq &&
21
+ event.data.name === 'goal' && event.data.source?.kind === 'user')) {
22
+ const dones = events.filter(event => event.type === 'command/done' && event.data.commandId === run.data.commandId);
23
+ if (dones.length !== 1 || dones[0].seq < change.seq || dones[0].data.kind !== 'success') continue;
24
+ const changes = events.filter(event => event.type === 'goal/change' && event.seq > run.seq && event.seq < dones[0].seq);
25
+ if (changes.length === 1 && changes[0] === change) matches.push({ kind: 'native-command', actor: 'user',
26
+ command: run.data, events: [run, change, dones[0]], inputEvents: [] });
27
+ }
28
+ return matches;
29
+ }
30
+ function toolOrigin(events, change, actions = ['create', 'edit']) {
31
+ const matches = [];
32
+ for (const call of events.filter(event => event.type === 'tool/call' && event.seq < change.seq &&
33
+ ['create_goal', 'update_goal', 'arcfold_start_work', 'arcfold_revise_work', 'arcfold_decide_work'].includes(event.data.name))) {
34
+ let args;
35
+ try { args = JSON.parse(call.data.arguments); } catch { continue; }
36
+ const action = ['create_goal', 'arcfold_start_work'].includes(call.data.name) ? 'create'
37
+ : call.data.name === 'arcfold_revise_work' ? 'edit'
38
+ : call.data.name === 'arcfold_decide_work' && args.action === 'wait' ? 'pause'
39
+ : args.action === 'blocked' ? 'block' : args.action;
40
+ if (!actions.includes(action) || action !== change.data.operation) continue;
41
+ if (action === 'create' && args.objective?.trim() !== change.data.goal.objective) continue;
42
+ if (action !== 'create' && (args.goal_id !== change.data.goal.id || args.revision + 1 !== change.data.goal.revision)) continue;
43
+ if (action === 'edit' && args.objective !== undefined && args.objective.trim() !== change.data.goal.objective) continue;
44
+ const results = events.filter(event => event.type === 'tool/result' && event.data.message?.toolCallId === call.data.callId);
45
+ if (results.length !== 1 || results[0].seq <= change.seq || results[0].data.message.isError ||
46
+ results[0].data.error || !same(results[0].sourceEventSeqs, [call.seq])) continue;
47
+ let result;
48
+ try { result = JSON.parse(text(results[0].data.message)); } catch { continue; }
49
+ if (!result?.goal || !Object.entries(change.data.goal).every(([key, value]) => same(result.goal[key], value))) continue;
50
+ const assistants = events.filter(event => event.type === 'assistant/message' && event.seq < call.seq &&
51
+ event.data.turn === call.data.turn && event.data.step === call.data.step &&
52
+ event.data.message?.role === 'assistant' && event.data.message.source?.kind === 'model' &&
53
+ event.data.message.content.some(block => block.type === 'tool-call' && block.id === call.data.callId &&
54
+ block.name === call.data.name && block.arguments === call.data.arguments));
55
+ if (assistants.length !== 1) continue;
56
+ const competing = events.filter(event => event.type === 'goal/change' && event.seq > call.seq && event.seq < results[0].seq);
57
+ if (competing.length !== 1 || competing[0] !== change) continue;
58
+ const start = events.findLast(event => event.seq < call.seq && event.type === 'turn/start');
59
+ if (!start || start.data.turn !== call.data.turn) continue;
60
+ const inputEvents = events.filter(event => event.seq > start.seq && event.seq < call.seq && event.type === 'user/message' &&
61
+ event.data.role === 'user' && event.data.source?.kind === 'user' && event.data.source.principal === undefined);
62
+ if (['create', 'edit'].includes(action) && !inputEvents.length) continue;
63
+ matches.push({ kind: 'native-goal-tool', actor: 'model', callId: call.data.callId, arguments: args,
64
+ events: [start, ...inputEvents, assistants[0], call, change, results[0]], inputEvents });
65
+ }
66
+ return matches;
67
+ }
68
+ function originalSources(store, sessionId, inputEvents) {
69
+ return inputEvents.map(event => {
70
+ const sources = store.listAll('events', { where: { sessionId } })
71
+ .filter(source => source.message?.id === event.data.id && same(source.message, event.data));
72
+ if (sources.length !== 1 || !sourceAvailable(store, sources[0])) reject('Original delegation input is missing or unavailable');
73
+ const source = sources[0];
74
+ return { id: source.id, revision: source.revision, messageHash: digest(source.message),
75
+ occurredAt: source.occurredAt, role: source.role };
76
+ });
77
+ }
78
+
79
+ /** Exact original creation, reusable for business-only work indexing after native flush. */
80
+ export function nativeGoalOrigin({ store, sessionId, events, goalId }) {
81
+ const creates = events.filter(e => e.type === 'goal/change' && e.data.operation === 'create' && e.data.goal?.id === goalId);
82
+ if (creates.length !== 1) return null;
83
+ const origins = [...commandOrigin(events, creates[0]), ...toolOrigin(events, creates[0], ['create'])];
84
+ if (origins.length !== 1) return null;
85
+ const origin = origins[0];
86
+ return { ...origin, inputs: originalSources(store, sessionId, origin.inputEvents) };
87
+ }
88
+
89
+ /** Freeze provenance, never activation or a second execution state. */
90
+ export function freezeNativeContinuation({ store, sessionId, turn, messages, events, goal, stage = null }) {
91
+ const submitted = goalMessages(messages);
92
+ if (!submitted.length) return null;
93
+ if (submitted.length !== 1) reject('A native round has more than one goal message');
94
+ const message = submitted[0], source = message.source;
95
+ if (!positive(source.revision) || !positive(source.round) || message.role !== 'user' ||
96
+ !goal || goal.id !== source.goalId || goal.revision !== source.revision ||
97
+ goal.phase !== 'active' || goal.activation !== 'armed' || source.round !== goal.roundsStarted + 1)
98
+ reject('The native round does not own the current active and armed goal');
99
+ const start = events.findLast(event => event.type === 'turn/start');
100
+ if (!start || start.data.turn !== turn || events.some(event => event.seq > start.seq && event.type === 'turn/end'))
101
+ reject('A native continuation needs the current open turn');
102
+ const insertions = events.flatMap(event => event.type === 'agent/inbox/spliced'
103
+ ? (event.data.inserted || []).flatMap((input, index) => input.id === message.id ? [{ event, input, index }] : []) : []);
104
+ if (insertions.length !== 1 || !same(insertions[0].input, message)) reject('Native goal message has no unique durable original');
105
+ const chain = events.filter(event => event.type === 'goal/change' && event.data.goal?.id === source.goalId);
106
+ if (!chain.length || chain[0].data.operation !== 'create' || chain[0].data.goal.revision !== 1 ||
107
+ chain.at(-1).data.goal.revision !== source.revision ||
108
+ chain.some((event, index) => event.data.goal.revision !== index + 1))
109
+ reject('The delegated goal definition chain is missing, stale or discontinuous');
110
+ const origins = [...commandOrigin(events, chain[0]), ...toolOrigin(events, chain[0], ['create'])];
111
+ if (origins.length !== 1) reject('The goal has no unique original command or successful create_goal delegation');
112
+ const origin = origins[0];
113
+ let activeOrigin = origin;
114
+ if (stage) {
115
+ const candidates = chain.flatMap(change => toolOrigin(events, change)).filter(candidate => candidate.callId === stage.origin.callId);
116
+ if (candidates.length !== 1 || stage.sessionId !== sessionId || stage.nativeGoalId !== goal.id || stage.nativeRevision !== goal.revision)
117
+ reject('The current Arcfold stage has no exact successful native definition receipt');
118
+ activeOrigin = candidates[0];
119
+ }
120
+ const activeInputs = stage ? activeOrigin.inputEvents.filter(event => stage.sourceRefs.some(reference =>
121
+ store.get('events', reference.id)?.message?.id === event.data.id)) : activeOrigin.inputEvents;
122
+ if (stage && !activeInputs.length) reject('The current stage has no owner source in its successful definition call');
123
+ const inputs = originalSources(store, sessionId, activeInputs);
124
+ if (stage) for (const reference of stage.sourceRefs) {
125
+ const row = store.get('events', reference.id);
126
+ if (!sourceAvailable(store, row) || row.revision !== reference.revision || sourceHash(row.message) !== reference.messageHash)
127
+ reject('The current Arcfold stage source changed');
128
+ if (!inputs.some(input => input.id === reference.id)) inputs.push({ id: row.id, revision: row.revision,
129
+ messageHash: digest(row.message), occurredAt: row.occurredAt, role: row.role });
130
+ }
131
+ const initialIds = store.listAll('events', { where: { sessionId } }).filter(row =>
132
+ origin.inputEvents.some(event => row.message?.id === event.data.id && same(row.message, event.data))).map(row => row.id);
133
+ const nativeEvents = [...new Map([...origin.events, ...activeOrigin.events, ...chain, insertions[0].event, start].map(event => [event.seq, event])).values()]
134
+ .sort((a, b) => a.seq - b.seq).map(event => ref(store, sessionId, event));
135
+ const proof = { kind: 'native-goal-continuation', sessionId, turn,
136
+ goalId: source.goalId, revision: source.revision, round: source.round,
137
+ messageId: message.id, messageHash: digest(message), inboxRef: { seq: insertions[0].event.seq, index: insertions[0].index },
138
+ cutoff: { throughSeq: events.at(-1).seq },
139
+ initialDelegation: { kind: origin.kind, actor: origin.actor,
140
+ ...(origin.command ? { command: origin.command } : { callId: origin.callId }),
141
+ sourceIds: initialIds },
142
+ ...(stage ? { activeDelegation: { kind: 'arcfold-stage', stageId: stage.id, definitionVersion: stage.definitionVersion,
143
+ sourceIds: inputs.map(row => row.id), eventIds: activeOrigin.events.map(event => ref(store, sessionId, event).id) } } : {}),
144
+ // UI controls do not emit a command/user message. Keep native facts and say
145
+ // the actor was not recorded; never manufacture a fresh user authorization.
146
+ definitionChain: chain.map(event => ({ seq: event.seq, goalId: event.data.goal.id,
147
+ revision: event.data.goal.revision, operation: event.data.operation,
148
+ attribution: event === chain[0] ? origin.kind : 'native_control', actor: event === chain[0] ? origin.actor : 'unrecorded' })),
149
+ currentDefinition: { ...chain.at(-1).data.goal },
150
+ originalInputs: inputs, nativeEvents,
151
+ authority: 'historical-delegation-and-native-control-facts; not-current-user-input-or-long-term-goal-control' };
152
+ return { ...proof, proofHash: digest(proof) };
153
+ }
154
+
155
+ /** Historical facts stay intact even after future use of their sources is withdrawn. */
156
+ export function assertNativeContinuationIntegrity(store, proof) {
157
+ const { proofHash, ...body } = proof;
158
+ if (proof.kind !== 'native-goal-continuation' || digest(body) !== proofHash) reject('Native continuation proof changed');
159
+ for (const reference of proof.nativeEvents) {
160
+ const row = store.get('dsh_events', reference.id);
161
+ if (!row || row.sessionId !== proof.sessionId || row.seq !== reference.seq || row.type !== reference.type ||
162
+ row.sha256 !== reference.sha256) reject('Native delegation original changed');
163
+ }
164
+ for (const input of proof.originalInputs) {
165
+ const source = store.get('events', input.id);
166
+ if (!source || source.sessionId !== proof.sessionId || source.revision !== input.revision ||
167
+ digest(source.message) !== input.messageHash)
168
+ reject('Original delegation input changed');
169
+ }
170
+ return proof;
171
+ }
172
+
173
+ /** Current reuse (including Learn) still requires all original sources to be available. */
174
+ export function assertNativeContinuationStored(store, proof) {
175
+ assertNativeContinuationIntegrity(store, proof);
176
+ for (const reference of proof.nativeEvents) {
177
+ if (proof.activeDelegation && !proof.activeDelegation.eventIds.includes(reference.id)) continue;
178
+ const control = store.get('source_controls', reference.id);
179
+ if (control?.withdrawn || control?.useProhibited) reject('Native delegation original was withdrawn');
180
+ }
181
+ for (const input of proof.originalInputs)
182
+ if (!sourceAvailable(store, store.get('events', input.id))) reject('Original delegation input was withdrawn');
183
+ return proof;
184
+ }
185
+
186
+ /** A synchronous final-send check; the official driver still owns scheduling. */
187
+ export function assertNativeContinuationCurrent({ store, proof, events, agent, goal, messages, historical = false }) {
188
+ if (historical) assertNativeContinuationIntegrity(store, proof);
189
+ else assertNativeContinuationStored(store, proof);
190
+ if (!agent || agent.id !== proof.sessionId || agent.status !== 'running') reject('Native continuation Agent is not current');
191
+ for (const reference of proof.nativeEvents) {
192
+ const event = events.find(row => row.seq === reference.seq);
193
+ if (!event || digest(event) !== reference.sha256) reject('Native delegation prefix changed');
194
+ }
195
+ const start = events.findLast(event => event.type === 'turn/start');
196
+ if (!start || start.data.turn !== proof.turn || events.some(event => event.seq > start.seq && event.type === 'turn/end'))
197
+ reject('Native continuation turn is no longer open');
198
+ const entered = events.filter(event => event.seq > start.seq && event.type === 'user/message' && event.data.id === proof.messageId);
199
+ const sent = messages.filter(message => message.id === proof.messageId);
200
+ if (entered.length !== 1 || sent.length !== 1 || digest(entered[0].data) !== proof.messageHash ||
201
+ digest(sent[0]) !== proof.messageHash) reject('Native goal message was not admitted unchanged');
202
+ // A new owner input or its successful Arcfold transition supersedes the old
203
+ // automatic admission. GoalExecution checks the new stage before every send/tool.
204
+ if (historical) return;
205
+ if (goal?.id === proof.goalId && goal.revision === proof.revision && goal.phase === 'active' &&
206
+ goal.activation === 'armed' && goal.roundsStarted === proof.round) return;
207
+ // Completing/blocking inside this same round may need a final answer step.
208
+ // It does not authorize any next automatic round or a definition edit.
209
+ const latest = events.findLast(event => event.type === 'goal/change');
210
+ const terminals = latest?.seq > start.seq && latest.data.goal?.id === proof.goalId &&
211
+ latest.data.goal.revision === proof.revision + 1 && ['complete', 'block', 'pause'].includes(latest.data.operation)
212
+ ? toolOrigin(events, latest, ['complete', 'block', 'pause']).filter(origin => latest.data.operation !== 'pause' ||
213
+ events.some(event => event.type === 'tool/call' && event.data.callId === origin.callId && event.data.name === 'arcfold_decide_work')) : [];
214
+ if (terminals.length === 1 && goal?.id === proof.goalId && same(goal.phase, latest.data.goal.phase) &&
215
+ goal.revision === latest.data.goal.revision && goal.roundsStarted === proof.round) return;
216
+ reject('Native goal authority changed before dispatch');
217
+ }
@@ -0,0 +1,90 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { join } from 'node:path';
3
+ import { canonicalJson } from './store.mjs';
4
+ import { LearningService } from './learning.mjs';
5
+
6
+ const digest = value => createHash('sha256').update(canonicalJson(value)).digest('hex');
7
+ const treeAuthorities = new WeakMap();
8
+
9
+ // Only trusted composition registers this per-business capability. Model input
10
+ // cannot opt into it, and unrelated auxiliary sessions keep the v1 rejection.
11
+ export function registerTreeRequestAuthority(business, authority) {
12
+ if (!business || typeof authority?.owns !== 'function' || typeof authority?.identity !== 'function' || typeof authority?.assertRequest !== 'function' || treeAuthorities.has(business)) throw new Error('One explicit tree request authority per business instance is required');
13
+ treeAuthorities.set(business, authority);
14
+ return () => { if (treeAuthorities.get(business) === authority) treeAuthorities.delete(business); };
15
+ }
16
+
17
+ export { ownEvidenceDirectory } from './evidence-ownership.mjs';
18
+
19
+ /** The ledger persists this identity before send; it is not another job state. */
20
+ export function nativeRequestIdentity(business, sessions, options) {
21
+ if (options.purpose !== undefined || typeof options.sessionId !== 'string')
22
+ throw new Error('Only an owned native Arcfold main or learning step may use this provider');
23
+ const session = sessions.get(options.sessionId);
24
+ if (!session) throw new Error('The request has no live native session owner');
25
+ const main = business.get('host_bindings', session.id)?.role === 'main';
26
+ const helpers = business.listAll('learning_runs', { where: { sessionId: session.id } });
27
+ const preparations = business.listAll('preparation_runs', { where: { sessionId: session.id } });
28
+ const tree = treeAuthorities.get(business);
29
+ if (tree?.owns(session.id)) {
30
+ if (main || helpers.length || preparations.length) throw new Error('Native request has both legacy and tree owners');
31
+ return tree.identity(options, session);
32
+ }
33
+ if (Number(main) + helpers.length + preparations.length !== 1) throw new Error('The request has no unique Arcfold business owner');
34
+ const helper = helpers[0], preparation = preparations[0];
35
+ if (preparation && preparation.state !== 'submitted') throw new Error('Preparation is not submitted');
36
+ if (helper && (helper.state !== 'submitted' || helper.dispatchCount !== 1))
37
+ throw new Error('The learning request has not passed its single-call guard');
38
+ const events = session.snapshotEvents();
39
+ const start = events.findLast(event => event.type === 'step/start');
40
+ if (!start || !Number.isSafeInteger(start.data.turn) || !Number.isSafeInteger(start.data.step))
41
+ throw new Error('The request has no native step identity');
42
+ const turn = events.findLast(event => event.type === 'turn/start');
43
+ if (!turn || turn.data.turn !== start.data.turn || turn.seq >= start.seq)
44
+ throw new Error('The native step does not belong to the current turn');
45
+ if (events.slice(start.seq + 1).some(event => ['turn/end', 'step/end', 'assistant/message', 'assistant/attempt'].includes(event.type)))
46
+ throw new Error('The native step already has a result or attempted result; automatic resending is forbidden');
47
+ if (preparation && !preparation.requests?.some(value => value.stepStartSeq === start.seq))
48
+ throw new Error('Preparation request has not passed its bounded native step guard');
49
+ const header = session.requestHeader()?.config;
50
+ if (!header || header.provider !== options.provider || header.model !== options.model ||
51
+ header.maxTokens !== options.maxTokens || header.reasoningEffort !== options.reasoningEffort)
52
+ throw new Error('The native request configuration and actual provider call disagree');
53
+ const position = { sessionId: session.id, turn: start.data.turn, step: start.data.step, stepStartSeq: start.seq };
54
+ return { requestId: `dsh-messages-${digest(position)}`, metadata: {
55
+ owner: 'arcfold-dsh-messages-v1', ...position, role: main ? 'main' : helper ? 'learning' : 'preparation',
56
+ nativeThroughSeq: events.at(-1).seq, nativePrefixHash: digest(events),
57
+ ...(helper ? { learningPendingId: helper.id, learningInputHash: helper.inputHash } : {}),
58
+ ...(preparation ? { preparationRunId: preparation.id, preparationInputHash: preparation.inputHash } : {}),
59
+ } };
60
+ }
61
+
62
+ export function assertNativeRequestCurrent(ctx, options) {
63
+ const tree = treeAuthorities.get(ctx.arcfoldBusiness);
64
+ if (tree?.owns(options.sessionId)) return tree.assertRequest(options);
65
+ if (ctx.arcfoldBridge.isBound(options.sessionId)) {
66
+ const checked = ctx.arcfoldBridge.assertMethodRequestCurrent(options);
67
+ // Return a mistaken async check to the transport so it fails closed;
68
+ // never discard it and accidentally turn a Promise into permission.
69
+ if (checked && typeof checked.then === 'function') return checked;
70
+ const goalChecked = ctx.arcfoldBridge.assertGoalRequestCurrent(options);
71
+ if (goalChecked && typeof goalChecked.then === 'function') return goalChecked;
72
+ return ctx.arcfoldBridge.assertRequestCurrent(options);
73
+ }
74
+ const preparations = ctx.arcfoldBusiness.listAll('preparation_runs', { where: { sessionId: options.sessionId } });
75
+ if (preparations.length) {
76
+ const runner = ctx.get('arcfoldPreparation');
77
+ if (preparations.length !== 1 || !runner) throw new Error('Preparation owner is unavailable');
78
+ return runner.assertRequest(options);
79
+ }
80
+ // A helper has already passed its single-call guard. Recheck only its
81
+ // existing frozen business input; never prepare, count or schedule again.
82
+ const runs = ctx.arcfoldBusiness.listAll('learning_runs', { where: { sessionId: options.sessionId } });
83
+ const run = runs[0], input = run && ctx.arcfoldBusiness.get('learning_inputs', run.id);
84
+ if (runs.length !== 1 || !input || run.inputHash !== input.inputHash)
85
+ throw new Error('The learning request has no matching frozen input before dispatch');
86
+ const learning = new LearningService({ business: ctx.arcfoldBusiness });
87
+ const checked = learning.assertInput(input);
88
+ if (checked && typeof checked.then === 'function') return checked;
89
+ return learning.assertVersions(input);
90
+ }
@@ -0,0 +1,4 @@
1
+ // Exact model IDs implemented by the pinned official Responses translator.
2
+ // Deployment modelPolicies may expose a subset; unknown models never fall back.
3
+ export const OPENAI_MODELS = Object.freeze(['gpt-6-astra', 'gpt-6.1-sol']);
4
+ export const OPENAI_REASONING_EFFORTS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
@@ -0,0 +1,98 @@
1
+ import { createParser } from 'eventsource-parser';
2
+ import { DomainError, jsonClone } from './store.mjs';
3
+ const invalid = () => new DomainError('invalid_provider_receipt', 'The original Responses receipt is incomplete or inconsistent.', 409);
4
+ const count = value => Number.isSafeInteger(value) && value >= 0;
5
+
6
+ /** Accounting uses original input_tokens, which already includes cache hits. */
7
+ export function responsesUsage(raw) {
8
+ if (!raw || !count(raw.input_tokens) || !count(raw.output_tokens) ||
9
+ !count(raw.input_tokens_details?.cached_tokens) ||
10
+ raw.input_tokens_details.cached_tokens > raw.input_tokens ||
11
+ raw.total_tokens !== undefined && (!count(raw.total_tokens) || raw.total_tokens !== raw.input_tokens + raw.output_tokens) ||
12
+ raw.output_tokens_details?.reasoning_tokens !== undefined &&
13
+ (!count(raw.output_tokens_details.reasoning_tokens) || raw.output_tokens_details.reasoning_tokens > raw.output_tokens))
14
+ throw invalid();
15
+ return { promptTokens: raw.input_tokens, completionTokens: raw.output_tokens,
16
+ cachedTokens: raw.input_tokens_details.cached_tokens };
17
+ }
18
+
19
+ /** Billing only: the pinned official SDK continues to translate content/history. */
20
+ export class ResponsesReceipt {
21
+ constructor() {
22
+ this.decoder = new TextDecoder('utf-8', { fatal: true });
23
+ this.started = false; this.stopped = false; this.finalUsage = null;
24
+ this.messageId = null; this.model = null; this.stopReason = null;
25
+ this.lineParts = []; this.skipLf = false;
26
+ this.parser = createParser({ onError: () => { throw invalid(); }, onEvent: event => this.event(event) });
27
+ }
28
+ event(event) {
29
+ let value;
30
+ try { value = JSON.parse(event.data); } catch { throw invalid(); }
31
+ if (!value || typeof value.type !== 'string' || event.event && event.event !== value.type || this.stopped)
32
+ throw invalid();
33
+ if (value.type === 'response.created') {
34
+ if (this.started || typeof value.response?.id !== 'string' || !value.response.id ||
35
+ typeof value.response?.model !== 'string' || !value.response.model) throw invalid();
36
+ this.started = true; this.messageId = value.response.id; this.model = value.response.model;
37
+ return;
38
+ }
39
+ if (!this.started) throw invalid();
40
+ if (['response.completed', 'response.incomplete'].includes(value.type)) {
41
+ const response = value.response;
42
+ if (response?.id !== this.messageId || response.model !== this.model ||
43
+ response.status !== (value.type === 'response.completed' ? 'completed' : 'incomplete') ||
44
+ value.type === 'response.incomplete' && response.incomplete_details?.reason !== 'max_output_tokens' ||
45
+ response.error != null) throw invalid();
46
+ responsesUsage(response.usage);
47
+ this.finalUsage = jsonClone(response.usage);
48
+ this.stopReason = value.type === 'response.completed' ? 'completed' : 'max_output_tokens';
49
+ this.stopped = true; return;
50
+ }
51
+ if (!['response.in_progress', 'response.output_item.added', 'response.output_item.done',
52
+ 'response.content_part.added', 'response.content_part.done', 'response.output_text.delta',
53
+ 'response.output_text.done', 'response.output_text.annotation.added',
54
+ 'response.reasoning_summary_part.added', 'response.reasoning_summary_part.done',
55
+ 'response.reasoning_summary_text.delta', 'response.reasoning_summary_text.done',
56
+ 'response.reasoning_text.delta', 'response.reasoning_text.done',
57
+ 'response.function_call_arguments.delta', 'response.function_call_arguments.done',
58
+ 'response.refusal.delta', 'response.refusal.done'].includes(value.type)) throw invalid();
59
+ if (value.response && (value.response.id !== this.messageId || value.response.model !== this.model)) throw invalid();
60
+ }
61
+ // Feed complete lines to the official SSE framer so its terminal callback
62
+ // identifies the exact boundary, even when a network chunk has extra bytes.
63
+ // After that boundary our supported protocol permits whitespace only.
64
+ feedText(fragment) {
65
+ if (this.stopped) {
66
+ if (!/^[ \t\r\n]*$/.test(fragment)) throw invalid();
67
+ return;
68
+ }
69
+ let start = 0;
70
+ for (let index = 0; index < fragment.length; index++) {
71
+ const character = fragment[index];
72
+ if (this.skipLf) {
73
+ this.skipLf = false;
74
+ if (character === '\n') { start = index + 1; continue; }
75
+ }
76
+ if (character !== '\r' && character !== '\n') continue;
77
+ this.lineParts.push(fragment.slice(start, index));
78
+ const line = this.lineParts.join(''); this.lineParts = [];
79
+ this.parser.feed(line + '\n');
80
+ start = index + 1; this.skipLf = character === '\r';
81
+ if (this.stopped) {
82
+ if (!/^[ \t\r\n]*$/.test(fragment.slice(start))) throw invalid();
83
+ return;
84
+ }
85
+ }
86
+ if (start < fragment.length) this.lineParts.push(fragment.slice(start));
87
+ }
88
+
89
+ push(bytes) { this.feedText(this.decoder.decode(bytes, { stream: true })); }
90
+
91
+
92
+ finish() {
93
+ this.feedText(this.decoder.decode());
94
+ if (!this.started || !this.stopped || !this.finalUsage) throw invalid();
95
+ return { messageId: this.messageId, model: this.model, stopReason: this.stopReason,
96
+ rawUsage: this.finalUsage, usage: responsesUsage(this.finalUsage) };
97
+ }
98
+ }
@@ -0,0 +1,167 @@
1
+ import { OPENAI_MODELS, OPENAI_REASONING_EFFORTS as efforts } from './openai-models.mjs';
2
+ import { MeteredTransport, fail, object, positive, text, keys, hash } from './metered-transport.mjs';
3
+ import { ResponsesReceipt } from './openai-receipt.mjs';
4
+ export const OPENAI_EVIDENCE_VERSION = 'arcfold-responses-transport-v1';
5
+ const OFFICIAL = 'https://api.openai.com/v1/responses';
6
+ const imageTypes = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
7
+ function nativeImage(block) {
8
+ // Validate the native durable reference, not a browser-supplied path or URL.
9
+ // DSH's attachment store remains responsible for verifying the stored bytes.
10
+ keys(block, ['type', 'attachment', 'offloaded']);
11
+ keys(block.attachment, ['attachmentId', 'mediaType', 'bytes', 'width', 'height', 'name', 'originalDimensions']);
12
+ const ref = block.attachment;
13
+ if (!text(ref.attachmentId) || !/^sha256:[a-f0-9]{64}$/.test(ref.attachmentId) ||
14
+ !imageTypes.includes(ref.mediaType) || !positive(ref.bytes) || !positive(ref.width) || !positive(ref.height) ||
15
+ ref.name !== undefined && !text(ref.name) || block.offloaded !== undefined && block.offloaded !== true)
16
+ throw fail('unsupported_model_content', 'A valid native image attachment reference is required.');
17
+ if (ref.originalDimensions !== undefined) {
18
+ keys(ref.originalDimensions, ['width', 'height']);
19
+ if (!positive(ref.originalDimensions.width) || !positive(ref.originalDimensions.height))
20
+ throw fail('unsupported_model_content', 'Original image dimensions must be positive integers.');
21
+ }
22
+ }
23
+ function wireImage(block) {
24
+ keys(block, ['type', 'image_url', 'detail']);
25
+ const prefix = text(block.image_url) && /^data:(image\/(?:png|jpeg|webp|gif));base64,/.exec(block.image_url);
26
+ const data = prefix && block.image_url.slice(prefix[0].length);
27
+ if (block.detail !== 'auto' || !data || Buffer.from(data, 'base64').toString('base64') !== data)
28
+ throw fail('unsupported_model_content', 'Only embedded native image bytes with automatic detail are supported.');
29
+ }
30
+ function functions(tools) {
31
+ if (!Array.isArray(tools)) throw fail('unsupported_model_content', 'Client functions must be an array.');
32
+ for (const tool of tools) {
33
+ keys(tool, ['type', 'name', 'description', 'parameters', 'strict', 'defer_loading']);
34
+ if (tool.type !== 'function' || !text(tool.name) || !object(tool.parameters) ||
35
+ tool.description !== undefined && !text(tool.description) ||
36
+ tool.strict !== undefined && typeof tool.strict !== 'boolean' ||
37
+ tool.defer_loading !== undefined && typeof tool.defer_loading !== 'boolean')
38
+ throw fail('unsupported_model_content', 'Only client JSON functions are supported.');
39
+ }
40
+ }
41
+ function content(blocks, types) {
42
+ if (!Array.isArray(blocks)) throw fail('unsupported_model_content', 'A content array is required.');
43
+ for (const block of blocks) {
44
+ if (block?.type === 'input_image' && types.includes('input_image')) { wireImage(block); continue; }
45
+ keys(block, ['type', 'text', 'annotations']);
46
+ if (!types.includes(block.type) || !text(block.text) ||
47
+ block.annotations !== undefined && (!Array.isArray(block.annotations) || block.annotations.length))
48
+ throw fail('unsupported_model_content', 'Only text content is supported.');
49
+ }
50
+ }
51
+ function inputItems(items) {
52
+ if (!Array.isArray(items) || !items.length) throw fail('unsupported_model_content', 'Responses input is required.');
53
+ for (const item of items) {
54
+ if (item.type === 'additional_tools') {
55
+ // The pinned native translator records local tool changes in transcript
56
+ // order. Accept its exact declaration shape; never re-encode the history.
57
+ keys(item, ['type', 'role', 'tools']);
58
+ if (item.role !== 'developer') throw fail('unsupported_model_content', 'Local tool declarations require the developer role.');
59
+ functions(item.tools);
60
+ } else if (item.type === 'configuration_update') {
61
+ keys(item, ['type', 'reasoning']); keys(item.reasoning, ['effort']);
62
+ if (!efforts.includes(item.reasoning.effort)) throw fail('unsupported_model_content', 'Invalid historical reasoning configuration.');
63
+ } else if (item.type === 'reasoning') {
64
+ keys(item, ['type', 'id', 'summary', 'encrypted_content', 'content', 'status']);
65
+ if (!text(item.id) || item.encrypted_content !== undefined && !text(item.encrypted_content) ||
66
+ !Array.isArray(item.summary)) throw fail('unsupported_model_content', 'Invalid reasoning history.');
67
+ content(item.summary, ['summary_text']);
68
+ if (item.content !== undefined) content(item.content, ['reasoning_text']);
69
+ if (item.status !== undefined && !['completed','incomplete','in_progress'].includes(item.status))
70
+ throw fail('unsupported_model_content', 'Invalid reasoning status.');
71
+ } else if (item.type === 'function_call') {
72
+ keys(item, ['type', 'id', 'call_id', 'name', 'arguments', 'status', 'namespace']);
73
+ if (!text(item.call_id) || !text(item.name) || !text(item.arguments) ||
74
+ item.id !== undefined && !text(item.id) || item.namespace !== undefined && !text(item.namespace))
75
+ throw fail('unsupported_model_content', 'Invalid client function history.');
76
+ } else if (item.type === 'function_call_output') {
77
+ keys(item, ['type', 'call_id', 'output']);
78
+ if (!text(item.call_id)) throw fail('unsupported_model_content', 'Invalid client result.');
79
+ if (!text(item.output)) content(item.output, ['input_text', 'input_image']);
80
+ } else if (item.type === 'tool_search_call' || item.type === 'tool_search_output') {
81
+ // Pinned pi-ai represents local incremental tool discovery with these
82
+ // completed client items. Remote/server tool execution is not accepted.
83
+ const output = item.type === 'tool_search_output';
84
+ keys(item, ['type', 'call_id', 'execution', 'status', output ? 'tools' : 'arguments']);
85
+ if (item.execution !== 'client' || item.status !== 'completed' || !text(item.call_id))
86
+ throw fail('unsupported_model_content', 'Only completed local tool discovery history is supported.');
87
+ if (output) functions(item.tools);
88
+ else { keys(item.arguments, ['query', 'limit']);
89
+ if (!text(item.arguments.query) || !positive(item.arguments.limit)) throw fail('unsupported_model_content', 'Invalid local tool history.'); }
90
+ } else {
91
+ keys(item, ['type', 'role', 'content', 'id', 'status', 'phase']);
92
+ if (item.type !== undefined && item.type !== 'message' ||
93
+ !['system','developer','user','assistant'].includes(item.role))
94
+ throw fail('unsupported_model_content', 'Only native messages and local function history are supported.');
95
+ if (typeof item.content !== 'string') content(item.content,
96
+ item.role === 'assistant' ? ['output_text'] : item.role === 'user' ? ['input_text', 'input_image'] : ['input_text']);
97
+ }
98
+ }
99
+ }
100
+ const protocol = Object.freeze({
101
+ version: OPENAI_EVIDENCE_VERSION, endpoint: OFFICIAL,
102
+ validateNativeImage: nativeImage,
103
+ validateEndpoint(endpoint, allowLocalTestEndpoint) {
104
+ const target = new URL(endpoint);
105
+ const local = allowLocalTestEndpoint === true && target.protocol === 'http:' &&
106
+ target.hostname === '127.0.0.1' && target.port && target.pathname === '/v1/responses';
107
+ if (endpoint !== OFFICIAL && !local || target.username || target.password || target.search || target.hash)
108
+ throw fail('invalid_transport_configuration', 'Only the fixed official Responses endpoint is supported.');
109
+ },
110
+ validatePolicies(policies) {
111
+ if (!Object.keys(policies).length || Object.keys(policies).some(model => !OPENAI_MODELS.includes(model)))
112
+ throw fail('invalid_transport_configuration', 'The supported OpenAI model must be explicit.');
113
+ for (const p of Object.values(policies)) {
114
+ if (!object(p) || !positive(p.contextWindow) || !positive(p.maxOutputTokens) ||
115
+ p.maxOutputTokens < 16 || !Array.isArray(p.reasoningEfforts) || !p.reasoningEfforts.length ||
116
+ p.reasoningEfforts.some(e => !efforts.includes(e)) ||
117
+ p.requestMaxTokens !== undefined && (!positive(p.requestMaxTokens) || p.requestMaxTokens < 16 || p.requestMaxTokens > p.maxOutputTokens))
118
+ throw fail('invalid_transport_configuration', 'Explicit OpenAI token and reasoning bounds are required.');
119
+ }
120
+ },
121
+ createReceipt: () => new ResponsesReceipt(),
122
+ reservationSpec: fixed => ({ maxOutputTokens: fixed.maxTokens, inputTokenCeiling: fixed.policy.contextWindow }),
123
+ });
124
+ export class OpenAITransport extends MeteredTransport {
125
+ constructor(options = {}) { super({ ...options, protocol }); }
126
+ validateInput(options) {
127
+ super.validateInput(options);
128
+ if (options.maxTokens < 16) throw fail('unfrozen_model_configuration', 'Responses requires at least 16 output tokens.');
129
+ return true;
130
+ }
131
+ createTransport(options) {
132
+ const local = super.createTransport(options);
133
+ return Object.freeze({ fetch: (url, init) => local.send(url, init) });
134
+ }
135
+ validateWire(fixed, url, init) {
136
+ if (url !== this.endpoint || init?.method !== 'POST' ||
137
+ init.redirect !== undefined && init.redirect !== 'error' || typeof init.body !== 'string')
138
+ throw fail('invalid_wire_request', 'Only the frozen Responses POST is permitted.');
139
+ if (Buffer.byteLength(init.body) > this.maxRequestBytes) throw fail('request_too_large', 'Request evidence capacity exceeded.');
140
+ let body;
141
+ try { body = JSON.parse(init.body); } catch { throw fail('invalid_wire_request', 'Responses body must be JSON.'); }
142
+ keys(body, ['model', 'input', 'stream', 'store', 'max_output_tokens', 'reasoning', 'include',
143
+ 'tools', 'prompt_cache_key']);
144
+ if (body.model !== fixed.model || body.stream !== true || body.store !== false ||
145
+ body.max_output_tokens !== fixed.maxTokens)
146
+ throw fail('wire_configuration_changed', 'Serialized route and token bounds changed.');
147
+ keys(body.reasoning, ['effort','summary']);
148
+ const last = body.input?.at(-1);
149
+ if (body.reasoning.effort !== 'medium' || body.reasoning.summary !== 'auto' ||
150
+ last?.type !== 'configuration_update' || last.reasoning?.effort !== fixed.reasoningEffort ||
151
+ JSON.stringify(body.include) !== JSON.stringify(['reasoning.encrypted_content']))
152
+ throw fail('wire_configuration_changed', 'Pinned GPT-6 reasoning configuration changed.');
153
+ inputItems(body.input);
154
+ if (body.tools !== undefined) functions(body.tools);
155
+ // Fixed short retention preserves the pinned official default. Long/explicit
156
+ // cache modes cannot be introduced by a request or ambient environment.
157
+ if (body.prompt_cache_key !== undefined && (!text(body.prompt_cache_key) || body.prompt_cache_key.length > 64))
158
+ throw fail('wire_configuration_changed', 'Unapproved cache configuration.');
159
+ const headers = new Headers(init.headers);
160
+ if (headers.has('openai-organization') || headers.has('openai-project'))
161
+ throw fail('invalid_wire_request', 'An ambient account/project override is not authorized.');
162
+ if (!headers.get('authorization')) throw fail('missing_provider_auth', 'Provider authentication is required.');
163
+ if (headers.has('host') && headers.get('host') !== new URL(url).host)
164
+ throw fail('invalid_wire_request', 'The HTTP host cannot override the fixed endpoint.');
165
+ return { body, headers: Object.fromEntries(headers), serialized: init.body, bodyHash: hash(init.body) };
166
+ }
167
+ }