@skill-harness/adapters 0.12.0 → 0.14.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/dist/archive-access.d.ts +58 -0
- package/dist/archive-access.js +61 -0
- package/dist/archive-checkpoint.d.ts +47 -0
- package/dist/archive-checkpoint.js +116 -0
- package/dist/archive-facts.d.ts +19 -0
- package/dist/archive-facts.js +20 -0
- package/dist/archive-observer.d.ts +21 -0
- package/dist/archive-observer.js +79 -0
- package/dist/archive-policy.d.ts +107 -0
- package/dist/archive-policy.js +135 -0
- package/dist/archive-read-capability.d.ts +49 -0
- package/dist/archive-read-capability.js +45 -0
- package/dist/archive-retention-policy.d.ts +52 -0
- package/dist/archive-retention-policy.js +49 -0
- package/dist/archived-work.d.ts +44 -0
- package/dist/archived-work.js +64 -0
- package/dist/blind-intervention.d.ts +29 -0
- package/dist/blind-intervention.js +211 -0
- package/dist/codex-diagnostic-model.d.ts +14 -0
- package/dist/codex-diagnostic-model.js +39 -0
- package/dist/codex-host-observer.d.ts +111 -0
- package/dist/codex-host-observer.js +309 -0
- package/dist/codex-https.d.ts +7 -0
- package/dist/codex-https.js +221 -0
- package/dist/codex-model-profile.d.ts +38 -0
- package/dist/codex-model-profile.js +54 -0
- package/dist/codex-producer-ipc.d.ts +55 -0
- package/dist/codex-producer-ipc.js +67 -0
- package/dist/codex-sdk-transport.d.ts +50 -0
- package/dist/codex-sdk-transport.js +294 -0
- package/dist/codex-subscription.d.ts +114 -0
- package/dist/codex-subscription.js +268 -0
- package/dist/evidence-archive.d.ts +57 -0
- package/dist/evidence-archive.js +221 -0
- package/dist/execution-projection-schema.d.ts +10 -0
- package/dist/execution-projection-schema.js +30 -0
- package/dist/execution-retention-archive.d.ts +69 -0
- package/dist/execution-retention-archive.js +154 -0
- package/dist/generated/retention-v2-contract.d.ts +72 -0
- package/dist/generated/retention-v2-contract.js +105 -0
- package/dist/generated/retention-v2-json.d.ts +24 -0
- package/dist/generated/retention-v2-json.js +268 -0
- package/dist/generated/retention-v2-native-json.d.ts +2 -0
- package/dist/generated/retention-v2-native-json.js +33 -0
- package/dist/generated/retention-v2-native.d.ts +58 -0
- package/dist/generated/retention-v2-native.js +155 -0
- package/dist/generated/retention-v2-pin.d.ts +2 -0
- package/dist/generated/retention-v2-pin.js +4 -0
- package/dist/generated/retention-v2-types.d.ts +49 -0
- package/dist/generated/retention-v2-types.js +2 -0
- package/dist/generated/retention-v2-work-types.d.ts +260 -0
- package/dist/generated/retention-v2-work-types.js +13 -0
- package/dist/generated/work-v4/execution-id.d.ts +5 -0
- package/dist/generated/work-v4/execution-id.js +14 -0
- package/dist/generated/work-v4/json.d.ts +24 -0
- package/dist/generated/work-v4/json.js +268 -0
- package/dist/generated/work-v4/occurrences.d.ts +226 -0
- package/dist/generated/work-v4/occurrences.js +116 -0
- package/dist/generated/work-v4/pin.d.ts +1 -0
- package/dist/generated/work-v4/pin.js +3 -0
- package/dist/generated/work-v4/projection.d.ts +294 -0
- package/dist/generated/work-v4/projection.js +238 -0
- package/dist/generated/work-v4/reader.d.ts +24 -0
- package/dist/generated/work-v4/reader.js +109 -0
- package/dist/generated/work-v4/snapshot.d.ts +28 -0
- package/dist/generated/work-v4/snapshot.js +268 -0
- package/dist/generated/work-v4/types.d.ts +260 -0
- package/dist/generated/work-v4/types.js +13 -0
- package/dist/generated/work-v4/validation.d.ts +13 -0
- package/dist/generated/work-v4/validation.js +227 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/installed-review-session.d.ts +28 -0
- package/dist/installed-review-session.js +166 -0
- package/dist/intervention-run.d.ts +403 -0
- package/dist/intervention-run.js +169 -0
- package/dist/learning-case.d.ts +12 -0
- package/dist/learning-case.js +15 -0
- package/dist/learning-journal.d.ts +18 -0
- package/dist/learning-journal.js +256 -0
- package/dist/principal-payload-port.d.ts +25 -0
- package/dist/principal-payload-port.js +40 -0
- package/dist/producer-product.d.ts +70 -0
- package/dist/producer-product.js +186 -0
- package/dist/producer-review.d.ts +53 -0
- package/dist/producer-review.js +207 -0
- package/dist/reviewed-archive-export.d.ts +42 -0
- package/dist/reviewed-archive-export.js +78 -0
- package/dist/trust-lifecycle.d.ts +128 -0
- package/dist/trust-lifecycle.js +200 -0
- package/dist/weekly-investigation.d.ts +237 -0
- package/dist/weekly-investigation.js +330 -0
- package/dist/work-candidates.d.ts +29 -0
- package/dist/work-candidates.js +78 -0
- package/dist/work-case-archive.d.ts +25 -0
- package/dist/work-case-archive.js +63 -0
- package/dist/work-case-review.d.ts +45 -0
- package/dist/work-case-review.js +220 -0
- package/dist/work-signal-cases.d.ts +15 -0
- package/dist/work-signal-cases.js +54 -0
- package/dist/work-signal-observation.d.ts +23 -0
- package/dist/work-signal-observation.js +89 -0
- package/package.json +3 -2
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { learningCopy, learningJournal, learningHash } from './learning-journal.js';
|
|
3
|
+
import { collapseVotePanel } from '@skill-harness/core';
|
|
4
|
+
import { inertCodexSdkStreams, boundCodexSdkStreams } from './codex-sdk-transport.js';
|
|
5
|
+
/** LOCAL host protocol with an inert installed-SDK seam; no auth loader or live network switch.
|
|
6
|
+
* The trusted host owns this module and transport streams; the subject gets framed IPC only.
|
|
7
|
+
* Stream finish proves a host-side write, NOT provider receipt, backend identity or token limits.
|
|
8
|
+
* Journal integrity assumes a trusted cooperative host/filesystem, not hostile same-UID rollback. */
|
|
9
|
+
export const CODEX_SUBSCRIPTION_DESTINATION = 'https://chatgpt.com/backend-api/codex/responses';
|
|
10
|
+
const MODELS = ['gpt-5.3-codex-spark', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.5', 'gpt-5.6-luna', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-6-astra'];
|
|
11
|
+
const sha = (b) => createHash('sha256').update(b).digest('hex');
|
|
12
|
+
function closed(v, keys) { if (!v || Object.keys(v).sort().join() !== keys.sort().join())
|
|
13
|
+
throw Error('closed local Codex contract required'); }
|
|
14
|
+
function admit(raw) {
|
|
15
|
+
const s = learningCopy(raw);
|
|
16
|
+
closed(s, ['version', 'maxCalls', 'wallMs', 'invocations']);
|
|
17
|
+
if (s.version !== 'codex-host-local-v1' || !Number.isSafeInteger(s.maxCalls) || s.maxCalls < 1 || s.maxCalls > 16 || !Number.isSafeInteger(s.wallMs) || s.wallMs < 1 || s.wallMs > 150000 || !Array.isArray(s.invocations) || !s.invocations.length || s.invocations.length > s.maxCalls)
|
|
18
|
+
throw Error('bounded local host spec required');
|
|
19
|
+
for (const i of s.invocations) {
|
|
20
|
+
closed(i, ['id', 'role', 'model', 'effort', 'instructions', 'input', 'expectedSha256', 'subjectId']);
|
|
21
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(i.id) || !['proposer', 'subject', 'judge'].includes(i.role) || !MODELS.includes(i.model) || !['low', 'medium', 'high'].includes(i.effort) || typeof i.instructions !== 'string' || typeof i.input !== 'string' || Buffer.byteLength(i.instructions) + Buffer.byteLength(i.input) > 4096 || !/^[a-f0-9]{64}$/.test(i.expectedSha256))
|
|
22
|
+
throw Error('unresolved or unbounded subscription invocation');
|
|
23
|
+
}
|
|
24
|
+
if (new Set(s.invocations.map(i => i.id)).size !== s.invocations.length)
|
|
25
|
+
throw Error('duplicate invocation');
|
|
26
|
+
if (s.invocations.some((i, k) => s.invocations.some((j, l) => k !== l && i.model === j.model && (i.role !== j.role || i.role === 'judge' && i.subjectId === j.subjectId))))
|
|
27
|
+
throw Error('known model role conflict before effects');
|
|
28
|
+
for (const i of s.invocations) {
|
|
29
|
+
if (i.role === 'judge' ? !s.invocations.some(t => t.role === 'subject' && t.id === i.subjectId) : i.subjectId !== null)
|
|
30
|
+
throw Error('frozen subject/panel binding required');
|
|
31
|
+
if (s.invocations.filter(t => t.role === 'judge' && t.subjectId === i.id).length > 3)
|
|
32
|
+
throw Error('at most three panel roles');
|
|
33
|
+
}
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
/** This checks supplied policy consistency, never authenticates its source or invents lineage. */
|
|
37
|
+
export function inspectCodexRoleSeparation(raw) {
|
|
38
|
+
const rows = learningCopy(raw);
|
|
39
|
+
const blocked = (reason) => ({ state: 'BLOCKED', reason, liveQualified: false });
|
|
40
|
+
if (!Array.isArray(rows) || rows.length < 3 || rows.length > 16 || !['proposer', 'subject', 'judge'].every(r => rows.some(x => x.role === r)))
|
|
41
|
+
return blocked('required roles absent');
|
|
42
|
+
for (const r of rows) {
|
|
43
|
+
closed(r, ['role', 'model', 'canonical', 'lineage']);
|
|
44
|
+
if (!['proposer', 'subject', 'judge'].includes(r.role) || !MODELS.includes(r.model))
|
|
45
|
+
return blocked('unsupported exact subscription identity');
|
|
46
|
+
}
|
|
47
|
+
if (rows.some(r => typeof r.canonical !== 'string' || !r.canonical || r.canonical.length > 512 || /[\u0000-\u001f\u007f]/.test(r.canonical)))
|
|
48
|
+
return blocked('unresolved canonical identity');
|
|
49
|
+
if (rows.some(r => r.lineage !== null && (typeof r.lineage !== 'string' || !r.lineage || r.lineage.length > 512 || /[\u0000-\u001f\u007f]/.test(r.lineage))))
|
|
50
|
+
return blocked('malformed lineage disclosure');
|
|
51
|
+
if (new Set(rows.map(r => r.model)).size !== rows.length || new Set(rows.map(r => r.canonical)).size !== rows.length)
|
|
52
|
+
return blocked('canonical identity conflict');
|
|
53
|
+
const disclosures = [...new Set(rows.flatMap(r => r.lineage === null ? ['lineage unresolved; correlation unknown'] : rows.filter(x => x.lineage === r.lineage).length > 1 ? [`shared lineage: ${r.lineage}`] : []))];
|
|
54
|
+
return { state: 'CONSISTENT_DECLARATION_ONLY', reason: 'canonical declarations are not authenticated resolution; lineage is disclosed, not proof of training independence', disclosures, liveQualified: false };
|
|
55
|
+
}
|
|
56
|
+
function sourceSettled(rows, id) {
|
|
57
|
+
const bound = rows.find(r => r.type === 'producer-ipc-bound' && r.id === id);
|
|
58
|
+
return !bound || rows.some(r => r.type === 'producer-ipc-settled' && r.id === id && r.bindingHash === bound.bindingHash);
|
|
59
|
+
}
|
|
60
|
+
function readBounded(stream, limit, signal) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
let size = 0, ended = false;
|
|
64
|
+
const cleanup = () => { stream.off('data', data); stream.off('end', end); stream.off('error', error); stream.off('close', close); signal.removeEventListener('abort', abort); };
|
|
65
|
+
const error = (e) => { cleanup(); stream.destroy(); reject(e); };
|
|
66
|
+
const data = (b) => { if (!Buffer.isBuffer(b) && typeof b !== 'string') {
|
|
67
|
+
error(Error('host stream requires byte chunks'));
|
|
68
|
+
return;
|
|
69
|
+
} size += typeof b === 'string' ? Buffer.byteLength(b) : b.length; if (size > limit) {
|
|
70
|
+
error(Error('host stream byte bound'));
|
|
71
|
+
return;
|
|
72
|
+
} chunks.push(Buffer.from(b)); };
|
|
73
|
+
const end = () => { ended = true; cleanup(); resolve(Buffer.concat(chunks)); };
|
|
74
|
+
const close = () => { if (!ended)
|
|
75
|
+
error(Error('incomplete host stream')); };
|
|
76
|
+
const abort = () => error(Error(signal.reason === 'producer-source-cancelled' ? 'source cancelled' : 'host deadline exceeded'));
|
|
77
|
+
stream.on('data', data);
|
|
78
|
+
stream.once('end', end);
|
|
79
|
+
stream.once('error', error);
|
|
80
|
+
stream.once('close', close);
|
|
81
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
82
|
+
if (signal.aborted)
|
|
83
|
+
abort();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function writeBody(stream, body, signal) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const cleanup = () => { stream.off('error', error); stream.off('finish', finish); stream.off('close', close); signal.removeEventListener('abort', abort); };
|
|
89
|
+
const error = (e) => { cleanup(); stream.destroy(); reject(e); };
|
|
90
|
+
const finish = () => { cleanup(); resolve(); };
|
|
91
|
+
const close = () => error(Error('host write incomplete'));
|
|
92
|
+
const abort = () => error(Error(signal.reason === 'producer-source-cancelled' ? 'source cancelled' : 'host deadline exceeded'));
|
|
93
|
+
stream.once('error', error);
|
|
94
|
+
stream.once('finish', finish);
|
|
95
|
+
stream.once('close', close);
|
|
96
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
97
|
+
if (signal.aborted) {
|
|
98
|
+
abort();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
stream.end(body);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function parseVote(text, ordinal) { const v = JSON.parse(text); closed(v, ['verdict', 'suspect']); if (typeof v.suspect !== 'boolean' || !['PASS', 'FAIL', 'ERROR', 'NOT-MEASURED', 'JUDGE-AMBIGUOUS'].includes(v.verdict))
|
|
105
|
+
throw Error('malformed host-observed vote'); return { ...v, ordinal }; }
|
|
106
|
+
function body(i, blindInput) {
|
|
107
|
+
// Exact supported request subset inspected in installed Codex buildRequestBody. No tools,
|
|
108
|
+
// service tier, cache/session key, extension onPayload, endpoint or sampling overrides.
|
|
109
|
+
return JSON.stringify({ model: i.model, store: false, stream: true, instructions: i.instructions, input: [{ role: 'user', content: [{ type: 'input_text', text: blindInput ?? i.input }] }], text: { verbosity: 'low' }, include: ['reasoning.encrypted_content'], tool_choice: 'none', parallel_tool_calls: false, reasoning: { effort: i.effort, summary: 'auto' } });
|
|
110
|
+
}
|
|
111
|
+
export function createLocalCodexHost(path, input, rolePolicy, subscription) {
|
|
112
|
+
const spec = admit(input), policy = rolePolicy === undefined ? null : learningCopy(rolePolicy);
|
|
113
|
+
if (policy) {
|
|
114
|
+
const separation = inspectCodexRoleSeparation(policy);
|
|
115
|
+
if (separation.state === 'BLOCKED')
|
|
116
|
+
throw Error(separation.reason);
|
|
117
|
+
for (const i of spec.invocations)
|
|
118
|
+
if (!policy.some(r => r.role === i.role && r.model === i.model))
|
|
119
|
+
throw Error('unbound canonical role policy');
|
|
120
|
+
}
|
|
121
|
+
if (subscription) {
|
|
122
|
+
closed(subscription, ['charterSha256', 'mode', 'requestBytes', 'responseBytes', 'totalRequestBytes', 'totalResponseBytes', 'callMs']);
|
|
123
|
+
if (!/^[a-f0-9]{64}$/.test(subscription.charterSha256) || !['fixture', 'subscription-live'].includes(subscription.mode) || ![subscription.requestBytes, subscription.responseBytes, subscription.totalRequestBytes, subscription.totalResponseBytes, subscription.callMs].every(n => Number.isSafeInteger(n) && n > 0) || subscription.requestBytes > 4096 || subscription.responseBytes > 16384 || subscription.callMs > 30000)
|
|
124
|
+
throw Error('invalid subscription reservations');
|
|
125
|
+
}
|
|
126
|
+
learningJournal(path, { type: 'codex-host-local-v1', spec, rolePolicy: policy, subscription: subscription ?? null, seed: randomBytes(32).toString('hex'), createdAt: Date.now() });
|
|
127
|
+
return openLocalCodexHost(path);
|
|
128
|
+
}
|
|
129
|
+
export function openLocalCodexHost(path) {
|
|
130
|
+
const store = learningJournal(path), first = store.read()[0].value;
|
|
131
|
+
if (first.type !== 'codex-host-local-v1' || typeof first.createdAt !== 'number' || typeof first.seed !== 'string')
|
|
132
|
+
throw Error('local host owner required');
|
|
133
|
+
const spec = admit(first.spec);
|
|
134
|
+
const append = (value) => { const prior = store.read().at(-1).id; return store.append(prior, value); };
|
|
135
|
+
const inspect = () => { const rows = store.read().map(e => e.value), claims = rows.filter(r => r.type === 'claim'), results = rows.filter(r => r.type === 'observation'); return { calls: claims.length, complete: results.length === spec.invocations.length && claims.length === results.length && results.every(r => sourceSettled(rows, r.id)) && !rows.some(r => r.type === 'abort'), aborted: rows.some(r => r.type === 'abort') || claims.length !== results.length, liveQualified: false }; };
|
|
136
|
+
const observeSdk = (value) => { const rows = store.read(); if (value.type === 'sdk-response-validated' && rows.some(e => e.value.type === 'sdk-response-validated' && e.value.responseId === value.responseId))
|
|
137
|
+
throw Error('replayed SDK response'); const claim = rows.filter(e => e.value.type === 'claim').at(-1)?.value; if (!claim || value.type === 'sdk-wire-observed' && value.requestSha256 !== claim.requestSha256)
|
|
138
|
+
throw Error('SDK observation without bound claim'); append({ ...value, id: claim.id, sequence: claim.sequence }); };
|
|
139
|
+
const host = { inspect,
|
|
140
|
+
async exchangeSubscriptionSdk(subjectFrames, binding, transport, charterSha256, sourceSignal) {
|
|
141
|
+
const reservation = first.subscription;
|
|
142
|
+
if (!reservation || reservation.charterSha256 !== charterSha256 || !first.rolePolicy || (reservation.mode === 'fixture' ? 'fixture-http' : 'subscription-http') !== transport.kind) {
|
|
143
|
+
subjectFrames.destroy();
|
|
144
|
+
throw Error('subscription charter/transport binding refused');
|
|
145
|
+
}
|
|
146
|
+
const separation = inspectCodexRoleSeparation(first.rolePolicy);
|
|
147
|
+
if (separation.state === 'BLOCKED') {
|
|
148
|
+
subjectFrames.destroy();
|
|
149
|
+
throw Error(separation.reason);
|
|
150
|
+
}
|
|
151
|
+
const streams = boundCodexSdkStreams(binding, transport, observeSdk, reservation);
|
|
152
|
+
try {
|
|
153
|
+
return await host.exchange(subjectFrames, streams.transport, streams.response, true, sourceSignal);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
subjectFrames.destroy();
|
|
157
|
+
streams.transport.destroy();
|
|
158
|
+
streams.response.destroy();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
async exchangeSdk(subjectFrames, binding, fixture) {
|
|
162
|
+
if (!first.rolePolicy) {
|
|
163
|
+
subjectFrames.destroy();
|
|
164
|
+
throw Error('frozen canonical policy required before SDK effects');
|
|
165
|
+
}
|
|
166
|
+
const separation = inspectCodexRoleSeparation(first.rolePolicy);
|
|
167
|
+
if (separation.state === 'BLOCKED') {
|
|
168
|
+
subjectFrames.destroy();
|
|
169
|
+
throw Error(separation.reason);
|
|
170
|
+
}
|
|
171
|
+
const streams = inertCodexSdkStreams(binding, fixture, observeSdk);
|
|
172
|
+
return host.exchange(subjectFrames, streams.transport, streams.response);
|
|
173
|
+
},
|
|
174
|
+
async exchange(subjectFrames, hostTransport, hostResponse, subscriptionMode = false, sourceSignal) {
|
|
175
|
+
const reservation = first.subscription;
|
|
176
|
+
if (!!reservation !== subscriptionMode) {
|
|
177
|
+
subjectFrames.destroy();
|
|
178
|
+
hostTransport.destroy();
|
|
179
|
+
hostResponse.destroy();
|
|
180
|
+
throw Error('subscription owner requires bound transport');
|
|
181
|
+
}
|
|
182
|
+
if (reservation && store.read().some(e => e.value.type === 'subscription-finished')) {
|
|
183
|
+
subjectFrames.destroy();
|
|
184
|
+
hostTransport.destroy();
|
|
185
|
+
hostResponse.destroy();
|
|
186
|
+
throw Error('subscription execution finished');
|
|
187
|
+
}
|
|
188
|
+
const state = inspect();
|
|
189
|
+
if (state.aborted)
|
|
190
|
+
throw Error('host aborted or stranded claim; no retry');
|
|
191
|
+
if (state.calls >= spec.maxCalls)
|
|
192
|
+
throw Error('call budget exhausted');
|
|
193
|
+
const clockHistory = store.read(), now = Date.now(), lastClock = Math.max(Number(first.createdAt), ...clockHistory.filter(e => e.value.type === 'clock').map(e => Number(e.value.at)));
|
|
194
|
+
if (!Number.isFinite(now) || now < lastClock) {
|
|
195
|
+
store.append(clockHistory.at(-1).id, { type: 'abort', reason: 'host clock rollback refused' });
|
|
196
|
+
throw Error('host clock rollback refused');
|
|
197
|
+
}
|
|
198
|
+
store.append(clockHistory.at(-1).id, { type: 'clock', at: now });
|
|
199
|
+
const controller = new AbortController(), remaining = Math.min(spec.wallMs - (now - Number(first.createdAt)), reservation?.callMs ?? Infinity);
|
|
200
|
+
let deadlineExceeded = false;
|
|
201
|
+
const timer = setTimeout(() => { deadlineExceeded = true; controller.abort(); }, Math.max(0, remaining));
|
|
202
|
+
const cancelSource = () => controller.abort('producer-source-cancelled');
|
|
203
|
+
sourceSignal?.addEventListener('abort', cancelSource, { once: true });
|
|
204
|
+
if (sourceSignal?.aborted)
|
|
205
|
+
cancelSource();
|
|
206
|
+
try {
|
|
207
|
+
if (remaining <= 0)
|
|
208
|
+
throw Error('host deadline exceeded');
|
|
209
|
+
const frameBytes = await readBounded(subjectFrames, 1024, controller.signal), text = new TextDecoder('utf8', { fatal: true }).decode(frameBytes), frame = JSON.parse(text);
|
|
210
|
+
closed(frame, ['id', 'sequence']);
|
|
211
|
+
if (frame.sequence !== 1 || text !== JSON.stringify({ id: frame.id, sequence: 1 }) + '\n')
|
|
212
|
+
throw Error('incomplete or noncanonical invocation sequence');
|
|
213
|
+
const i = spec.invocations.find(i => i.id === frame.id);
|
|
214
|
+
if (!i)
|
|
215
|
+
throw Error('unbound invocation');
|
|
216
|
+
const history = store.read();
|
|
217
|
+
if (history.some(e => e.value.type === 'claim' && e.value.id === i.id))
|
|
218
|
+
throw Error('invocation already claimed; no retry');
|
|
219
|
+
if (history.some(e => e.value.type === 'abort') || history.filter(e => e.value.type === 'claim').length !== history.filter(e => e.value.type === 'observation').length)
|
|
220
|
+
throw Error('host aborted or concurrent claim');
|
|
221
|
+
let blindInput;
|
|
222
|
+
if (i.role === 'judge') {
|
|
223
|
+
const subject = history.find(e => e.value.type === 'observation' && e.value.id === i.subjectId)?.value;
|
|
224
|
+
if (!subject || subject.objective !== 'PASS' || !sourceSettled(history.map(e => e.value), i.subjectId))
|
|
225
|
+
throw Error('objective gate blocks judge emission or source unsettled');
|
|
226
|
+
const siblings = spec.invocations.filter(t => t.role === 'judge' && t.subjectId === i.subjectId), ordinal = siblings.findIndex(t => t.id === i.id);
|
|
227
|
+
const previous = siblings.slice(0, ordinal).map((t, k) => { const r = history.find(e => e.value.type === 'observation' && e.value.id === t.id)?.value; if (!r || !sourceSettled(history.map(e => e.value), t.id))
|
|
228
|
+
throw Error('preceding panel observation missing or source unsettled'); return parseVote(Buffer.from(String(r.outputBase64), 'base64').toString('utf8'), k + 1); });
|
|
229
|
+
if (ordinal === 2 && !collapseVotePanel(previous).split)
|
|
230
|
+
throw Error('tie-break requires clean split');
|
|
231
|
+
blindInput = JSON.stringify({ label: sha(String(first.seed) + String(i.subjectId)).slice(0, 24), outputBase64: subject.outputBase64, criterion: i.input });
|
|
232
|
+
}
|
|
233
|
+
const requestBody = body(i, blindInput), sequence = history.filter(e => e.value.type === 'claim').length + 1;
|
|
234
|
+
if (Buffer.byteLength(requestBody) > (reservation?.requestBytes ?? 4096))
|
|
235
|
+
throw Error('host request byte bound');
|
|
236
|
+
if (sequence > spec.maxCalls)
|
|
237
|
+
throw Error('call budget exhausted');
|
|
238
|
+
if (reservation) {
|
|
239
|
+
const claims = history.filter(e => e.value.type === 'claim');
|
|
240
|
+
if ((claims.length + 1) * reservation.requestBytes > reservation.totalRequestBytes || (claims.length + 1) * reservation.responseBytes > reservation.totalResponseBytes) {
|
|
241
|
+
append({ type: 'subscription-reservation-refused', id: i.id, charterSha256: reservation.charterSha256, claimedCalls: claims.length, limits: reservation });
|
|
242
|
+
throw Error('aggregate subscription byte reservation exhausted');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
store.append(history.at(-1).id, { type: 'claim', id: i.id, sequence, role: i.role, destination: CODEX_SUBSCRIPTION_DESTINATION, requestSha256: sha(requestBody), ...(reservation ? { requestReservation: reservation.requestBytes, responseReservation: reservation.responseBytes, charterSha256: reservation.charterSha256, executionMode: reservation.mode } : {}) });
|
|
246
|
+
await writeBody(hostTransport, Buffer.from(requestBody), controller.signal);
|
|
247
|
+
append({ type: 'host-write-completed', id: i.id, sequence, requestBody, requestSha256: sha(requestBody) });
|
|
248
|
+
const output = await readBounded(hostResponse, reservation?.responseBytes ?? 16384, controller.signal);
|
|
249
|
+
const result = { id: i.id, sequence, requestBody, requestSha256: sha(requestBody), outputBase64: output.toString('base64'), outputSha256: sha(output), objective: i.role === 'judge' ? (parseVote(new TextDecoder('utf8', { fatal: true }).decode(output), 1), 'PASS') : sha(output) === i.expectedSha256 ? 'PASS' : 'FAIL', providerInternalFacts: null, liveQualified: false };
|
|
250
|
+
if (sourceSignal?.aborted)
|
|
251
|
+
throw Error('source cancelled before observation');
|
|
252
|
+
if (reservation && store.read().some(e => e.value.type === 'abort'))
|
|
253
|
+
throw Error('concurrent subscription abort');
|
|
254
|
+
append({ type: 'observation', ...result });
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
append({ type: 'abort', reason: 'local exchange failed; no automatic retry', deadlineExceeded, sourceAborted: sourceSignal?.aborted === true });
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
clearTimeout(timer);
|
|
263
|
+
sourceSignal?.removeEventListener('abort', cancelSource);
|
|
264
|
+
subjectFrames.destroy();
|
|
265
|
+
hostTransport.destroy();
|
|
266
|
+
hostResponse.destroy();
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
panel(subjectId, judgeIds, rawPolicy) {
|
|
270
|
+
const policy = learningCopy(rawPolicy);
|
|
271
|
+
if (first.rolePolicy && learningHash(policy) !== learningHash(first.rolePolicy))
|
|
272
|
+
throw Error('frozen canonical policy changed');
|
|
273
|
+
const separation = inspectCodexRoleSeparation(policy);
|
|
274
|
+
if (separation.state === 'BLOCKED')
|
|
275
|
+
throw Error(separation.reason);
|
|
276
|
+
if (!Array.isArray(judgeIds) || ![2, 3].includes(judgeIds.length) || new Set(judgeIds).size !== judgeIds.length)
|
|
277
|
+
throw Error('panel cardinality');
|
|
278
|
+
const subject = spec.invocations.find(i => i.id === subjectId && i.role === 'subject');
|
|
279
|
+
if (!subject)
|
|
280
|
+
throw Error('subject binding');
|
|
281
|
+
const judges = spec.invocations.filter(i => i.role === 'judge' && i.subjectId === subjectId);
|
|
282
|
+
if (judgeIds.some((id, k) => judges[k]?.id !== id))
|
|
283
|
+
throw Error('frozen panel order');
|
|
284
|
+
for (const i of [subject, ...judges.slice(0, judgeIds.length)])
|
|
285
|
+
if (!policy.some(p => p.role === i.role && p.model === i.model))
|
|
286
|
+
throw Error('exact panel role binding');
|
|
287
|
+
const rows = store.read().map(e => e.value);
|
|
288
|
+
if (rows.some(r => r.type === 'abort'))
|
|
289
|
+
throw Error('aborted owner blocks panel');
|
|
290
|
+
const output = rows.find(r => r.type === 'observation' && r.id === subjectId);
|
|
291
|
+
if (!output || output.objective !== 'PASS' || !sourceSettled(rows, subjectId))
|
|
292
|
+
throw Error('objective gate blocks panel or source unsettled');
|
|
293
|
+
const votes = judgeIds.map((id, k) => { const r = rows.find(r => r.type === 'observation' && r.id === id); if (!r || !sourceSettled(rows, id))
|
|
294
|
+
throw Error('panel observation missing or source unsettled'); return parseVote(Buffer.from(String(r.outputBase64), 'base64').toString('utf8'), k + 1); });
|
|
295
|
+
if (collapseVotePanel(votes.slice(0, 2)).split !== (votes.length === 3))
|
|
296
|
+
throw Error('tie-break required exactly for clean split');
|
|
297
|
+
const binding = learningHash({ subjectId, judgeIds, policy }), old = rows.find(r => r.type === 'panel' && r.subjectId === subjectId);
|
|
298
|
+
if (old && old.binding !== binding)
|
|
299
|
+
throw Error('panel binding changed');
|
|
300
|
+
const result = { label: sha(String(first.seed) + subjectId).slice(0, 24), collapse: collapseVotePanel(votes), identityStatus: separation.state, disclosures: separation.disclosures, liveQualified: false, routingDefault: null };
|
|
301
|
+
if (!old)
|
|
302
|
+
append({ type: 'panel', subjectId, binding, result });
|
|
303
|
+
return result;
|
|
304
|
+
},
|
|
305
|
+
eligibleOutputs() { const rows = store.read(); return rows.filter(e => sourceSettled(rows.map(r => r.value), e.value.id) && e.value.type === 'observation' && e.value.objective === 'PASS' && spec.invocations.some(i => i.id === e.value.id && i.role === 'subject')).map(e => ({ label: sha(String(first.seed) + String(e.value.id)).slice(0, 24), outputBase64: String(e.value.outputBase64), outputSha256: String(e.value.outputSha256), liveQualified: false })); },
|
|
306
|
+
};
|
|
307
|
+
return host;
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=codex-host-observer.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CodexHttpPort } from './codex-subscription.js';
|
|
2
|
+
/** Low-level trusted-host capability. No auth discovery, redirects, fetch/global agents,
|
|
3
|
+
* proxy support, retries or WebSocket path. The frozen entrypoint owns approval/reservations.
|
|
4
|
+
* Constructing this port does not connect; only a separately approved entry may call exchange. */
|
|
5
|
+
export declare function createCodexHttpsPort(): CodexHttpPort;
|
|
6
|
+
/** Explicit larger-request profile for producer-review-v1, not a legacy cap override. */
|
|
7
|
+
export declare function createCodexReviewHttpsPort(): CodexHttpPort;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Agent, request } from 'node:https';
|
|
2
|
+
import { rootCertificates, checkServerIdentity } from 'node:tls';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { SUBSCRIPTION_ENDPOINT, validateCodexOAuth } from './codex-subscription.js';
|
|
5
|
+
const hash = (b) => createHash('sha256').update(b).digest('hex');
|
|
6
|
+
const ERROR_CODES = new Set(['ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH', 'EACCES', 'EPERM', 'ERR_TLS_CERT_ALTNAME_INVALID', 'CERT_HAS_EXPIRED', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'SELF_SIGNED_CERT_IN_CHAIN', 'DEPTH_ZERO_SELF_SIGNED_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', 'ERR_SSL_WRONG_VERSION_NUMBER']);
|
|
7
|
+
function errorCode(error) {
|
|
8
|
+
// No messages, getters, arbitrary codes or implicit string conversion in diagnostics.
|
|
9
|
+
try {
|
|
10
|
+
const code = error && Object.getOwnPropertyDescriptor(error, 'code')?.value;
|
|
11
|
+
return typeof code === 'string' && ERROR_CODES.has(code) ? code : 'unknown';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return 'unknown';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const REFUSAL_TYPES = new Set(['invalid_request_error', 'authentication_error', 'permission_error', 'rate_limit_error', 'server_error']);
|
|
18
|
+
const REFUSAL_CODES = new Set(['invalid_json', 'invalid_request', 'invalid_parameter', 'unknown_parameter', 'unsupported_parameter', 'unsupported_value', 'missing_required_parameter', 'model_not_found', 'invalid_api_key', 'insufficient_quota', 'rate_limit_exceeded']);
|
|
19
|
+
const REFUSAL_PARAMETERS = new Set(['model', 'input', 'instructions', 'reasoning', 'reasoning.effort', 'reasoning.summary', 'text', 'text.verbosity', 'tools', 'tool_choice', 'parallel_tool_calls', 'store', 'stream', 'max_output_tokens', 'temperature', 'top_p', 'include', 'service_tier', 'prompt_cache_key', 'previous_response_id', 'background']);
|
|
20
|
+
// Diagnostic prose is untrusted data, never a dispatch/retry instruction. A vocabulary
|
|
21
|
+
// allowlist redacts unfamiliar tokens rather than retaining opaque identities.
|
|
22
|
+
const DIAGNOSTIC_WORDS = new Set(('a an the this that is are was be not no only for with without and or to of in on at as by from must cannot can could does do has have failed failure error request response body json field parameter value model unsupported supported support invalid unknown missing required expected found available unavailable permitted allowed valid format type string object array parse parsing deserialize into target provide provided please input instructions reasoning effort low medium high minimal none verbosity authentication authorization permission denied unauthorized forbidden access expired quota exceeded rate limit too many requests service temporarily internal server detail message redacted').split(' '));
|
|
23
|
+
function explanation(value) {
|
|
24
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 1024 || /[^\x20-\x7e]/.test(value))
|
|
25
|
+
return null;
|
|
26
|
+
const text = value.replace(/\bBearer\s+[^\s,;]+/gi, '[redacted]')
|
|
27
|
+
.replace(/\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, '[redacted]')
|
|
28
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '[redacted]')
|
|
29
|
+
.replace(/\b[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\b/gi, '[redacted]')
|
|
30
|
+
.replace(/\b(?:account(?:[_ -]?id)?|user[_ -]?id|email|authorization|api[_ -]?key|token)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi, '[redacted]')
|
|
31
|
+
.replace(/\b(?:sk-[A-Za-z0-9_-]+|gpt-[A-Za-z0-9.-]+)\b/g, '[redacted]').trim();
|
|
32
|
+
if (/[^A-Za-z0-9 _.,:;!?()\[\]'"`\/-]/.test(text))
|
|
33
|
+
return null;
|
|
34
|
+
const words = text.match(/[A-Za-z0-9_]+(?:[.-][A-Za-z0-9_]+)*/g) ?? [];
|
|
35
|
+
const known = (w) => DIAGNOSTIC_WORDS.has(w.toLowerCase()) || REFUSAL_PARAMETERS.has(w) || REFUSAL_TYPES.has(w) || REFUSAL_CODES.has(w);
|
|
36
|
+
if (!words.some(w => w.toLowerCase() !== 'redacted' && known(w)))
|
|
37
|
+
return null;
|
|
38
|
+
return text.replace(/[A-Za-z0-9_]+(?:[.-][A-Za-z0-9_]+)*/g, w => known(w) ? w : '[redacted]').slice(0, 256);
|
|
39
|
+
}
|
|
40
|
+
function refusalMetadata(value) {
|
|
41
|
+
const object = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
|
|
42
|
+
const root = object(value), envelope = object(root?.error) ? 'error-object' : typeof root?.error === 'string' ? 'error-string' : typeof root?.detail === 'string' ? 'detail-string' : object(root?.detail) ? 'detail-object' : Array.isArray(root?.detail) ? 'detail-list' : typeof root?.message === 'string' ? 'message-string' : 'unknown';
|
|
43
|
+
let node = root;
|
|
44
|
+
// Only documented error/detail envelopes, a single validation error, and bounded depth.
|
|
45
|
+
for (let depth = 0; node && depth < 3; depth++) {
|
|
46
|
+
const child = node.error ?? node.detail;
|
|
47
|
+
if (Array.isArray(child)) {
|
|
48
|
+
node = child.length === 1 ? object(child[0]) : undefined;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
const next = object(child);
|
|
52
|
+
if (!next)
|
|
53
|
+
break;
|
|
54
|
+
node = next;
|
|
55
|
+
}
|
|
56
|
+
const allowed = (v, set) => typeof v === 'string' && set.has(v) ? v : 'unknown';
|
|
57
|
+
let parameter = allowed(node?.param, REFUSAL_PARAMETERS);
|
|
58
|
+
if (parameter === 'unknown' && Array.isArray(node?.loc) && node.loc.length >= 2 && node.loc.length <= 4 && node.loc[0] === 'body' && node.loc.slice(1).every(p => typeof p === 'string'))
|
|
59
|
+
parameter = allowed(node.loc.slice(1).join('.'), REFUSAL_PARAMETERS);
|
|
60
|
+
return { type: allowed(node?.type, REFUSAL_TYPES), code: allowed(node?.code, REFUSAL_CODES), parameter, envelope, explanation: explanation(node?.message ?? node?.msg ?? node?.detail ?? node?.error) };
|
|
61
|
+
}
|
|
62
|
+
/** Low-level trusted-host capability. No auth discovery, redirects, fetch/global agents,
|
|
63
|
+
* proxy support, retries or WebSocket path. The frozen entrypoint owns approval/reservations.
|
|
64
|
+
* Constructing this port does not connect; only a separately approved entry may call exchange. */
|
|
65
|
+
export function createCodexHttpsPort() { return httpsPort(4096, 16384); }
|
|
66
|
+
/** Explicit larger-request profile for producer-review-v1, not a legacy cap override. */
|
|
67
|
+
export function createCodexReviewHttpsPort() { return httpsPort(65536, 262144); }
|
|
68
|
+
function httpsPort(requestLimit, responseLimit) {
|
|
69
|
+
let inFlight = false;
|
|
70
|
+
const preflight = () => { if (['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy', 'NODE_EXTRA_CA_CERTS'].some(k => !!process.env[k]) || process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0')
|
|
71
|
+
throw Error('unsupported production transport environment'); };
|
|
72
|
+
return { kind: 'subscription-http', preflight, async exchange(wire, rawCredential, signal, record, limits) {
|
|
73
|
+
preflight();
|
|
74
|
+
if (![limits.requestBytes, limits.responseBytes, limits.callMs].every(n => Number.isSafeInteger(n) && n > 0) || !Buffer.isBuffer(wire.body) || inFlight || wire.destination !== SUBSCRIPTION_ENDPOINT || wire.method !== 'POST' || !['zstd', null].includes(wire.encoding) || wire.body.length > limits.requestBytes || limits.requestBytes > requestLimit || limits.responseBytes > responseLimit || limits.callMs > 30000)
|
|
75
|
+
throw Error('subscription-http-refused');
|
|
76
|
+
const credential = validateCodexOAuth(rawCredential, rawCredential.accountId, 'subscription-live');
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
const body = Buffer.from(wire.body);
|
|
79
|
+
inFlight = true;
|
|
80
|
+
const agent = new Agent({ keepAlive: false, maxSockets: 1, maxCachedSessions: 0 });
|
|
81
|
+
try {
|
|
82
|
+
return await new Promise((resolve, reject) => {
|
|
83
|
+
let settled = false, written = false, req, timer;
|
|
84
|
+
const finish = (error, response) => { if (settled)
|
|
85
|
+
return; settled = true; clearTimeout(timer); signal.removeEventListener('abort', abort); agent.destroy(); inFlight = false; if (error)
|
|
86
|
+
reject(Error('subscription-http-failed'));
|
|
87
|
+
else
|
|
88
|
+
resolve(response); };
|
|
89
|
+
let phase = 'request';
|
|
90
|
+
const fail = (error) => { if (settled)
|
|
91
|
+
return; try {
|
|
92
|
+
record({ type: 'http-request-failed', classification: signal.aborted ? 'aborted' : 'transport-failure', phase, errorCode: errorCode(error) });
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
finish(Error('record failed'));
|
|
96
|
+
return;
|
|
97
|
+
} finish(Error('request failed')); };
|
|
98
|
+
const refuse = (reason) => { if (settled)
|
|
99
|
+
return; try {
|
|
100
|
+
record({ type: 'http-request-failed', classification: 'response-refused', phase: 'response', reason });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
finish(Error('record failed'));
|
|
104
|
+
return;
|
|
105
|
+
} finish(Error('response refused')); };
|
|
106
|
+
const abort = () => { req?.destroy(); finish(Error('aborted')); };
|
|
107
|
+
// All routing and TLS fields are fixed here, not taken from SDK headers or caller options.
|
|
108
|
+
try {
|
|
109
|
+
req = request({ protocol: 'https:', hostname: 'chatgpt.com', port: 443, path: '/backend-api/codex/responses', method: 'POST', agent, servername: 'chatgpt.com', rejectUnauthorized: true, checkServerIdentity, ca: [...rootCertificates], minVersion: 'TLSv1.2', headers: { authorization: `Bearer ${credential.access}`, 'chatgpt-account-id': credential.accountId, originator: 'pi', 'user-agent': 'skill-harness-codex-host', 'openai-beta': 'responses=experimental', accept: 'text/event-stream', 'accept-encoding': 'identity', 'content-type': 'application/json', 'content-length': body.length, ...(wire.encoding ? { 'content-encoding': wire.encoding } : {}) } }, res => {
|
|
110
|
+
// Node's internal end listener can detach res.socket before our end listener.
|
|
111
|
+
// Retain the original socket, not a later nullable/replaced IncomingMessage reference.
|
|
112
|
+
const socket = res.socket, tlsAuthorized = socket?.authorized === true;
|
|
113
|
+
phase = 'response';
|
|
114
|
+
const chunks = [];
|
|
115
|
+
let bytes = 0;
|
|
116
|
+
try {
|
|
117
|
+
record({ type: 'http-response-observed', phase: 'headers', status: res.statusCode ?? 0, providerInternalFacts: null });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
req?.destroy();
|
|
121
|
+
finish(Error('record failed'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
res.on('error', fail);
|
|
125
|
+
res.on('aborted', () => refuse('response-aborted'));
|
|
126
|
+
res.on('close', () => { if (!settled)
|
|
127
|
+
refuse('response-closed'); });
|
|
128
|
+
res.on('data', (chunk) => { if (settled)
|
|
129
|
+
return; bytes += chunk.length; if (bytes > limits.responseBytes) {
|
|
130
|
+
refuse('response-byte-limit');
|
|
131
|
+
return;
|
|
132
|
+
} chunks.push(Buffer.from(chunk)); });
|
|
133
|
+
res.on('end', () => {
|
|
134
|
+
if (settled)
|
|
135
|
+
return;
|
|
136
|
+
try {
|
|
137
|
+
if (!written) {
|
|
138
|
+
refuse('write-incomplete');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!tlsAuthorized || socket?.authorized !== true) {
|
|
142
|
+
refuse('tls-unverified');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (res.complete !== true || res.aborted) {
|
|
146
|
+
refuse('message-incomplete');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (res.headers['content-encoding'] && res.headers['content-encoding'] !== 'identity') {
|
|
150
|
+
refuse('response-encoding');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const raw = Buffer.concat(chunks), status = res.statusCode ?? 0;
|
|
154
|
+
// Inspect reflections before ANY body digest or structured refusal is recorded, at all statuses.
|
|
155
|
+
let parsed;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(raw.toString('utf8'));
|
|
158
|
+
}
|
|
159
|
+
catch { /* Non-JSON refusal fields remain unknown; no parser error text is retained. */ }
|
|
160
|
+
const decoded = parsed === undefined ? '' : JSON.stringify(parsed);
|
|
161
|
+
if ([credential.access, credential.accountId].some(value => raw.includes(Buffer.from(value)) || decoded.includes(value))) {
|
|
162
|
+
refuse('credential-reflection');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (status >= 300 && status < 400) {
|
|
166
|
+
refuse('redirect');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const response = status === 200 ? new Response(raw, { status, headers: { 'content-type': String(res.headers['content-type'] ?? '') } }) : new Response(JSON.stringify({ error: { message: 'subscription request refused' } }), { status: status >= 400 && status <= 599 ? status : 502, headers: { 'content-type': 'application/json' } });
|
|
170
|
+
record({ type: 'http-response-observed', status, bodyBytes: raw.length, bodySha256: hash(raw), tlsAuthorized: true, complete: true, serverName: 'chatgpt.com', providerInternalFacts: null, ...(status !== 200 ? { refusal: refusalMetadata(parsed) } : {}) });
|
|
171
|
+
finish(undefined, response);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
refuse('internal-completion');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
fail(error);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (settled) {
|
|
184
|
+
req.destroy();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Last entered phase, not a claim that DNS/connect/TLS caused the error.
|
|
188
|
+
req.once('socket', socket => { if (settled)
|
|
189
|
+
return; phase = 'lookup'; socket.once('lookup', error => { if (!settled && !error)
|
|
190
|
+
phase = 'connect'; }); socket.once('connect', () => { if (!settled)
|
|
191
|
+
phase = 'tls'; }); socket.once('secureConnect', () => { if (!settled)
|
|
192
|
+
phase = 'write'; }); });
|
|
193
|
+
timer = setTimeout(() => { req?.destroy(); finish(Error('deadline')); }, limits.callMs);
|
|
194
|
+
req.once('error', fail);
|
|
195
|
+
req.once('finish', () => { if (settled)
|
|
196
|
+
return; try {
|
|
197
|
+
record({ type: 'http-request-started', requestBytes: body.length, wireSha256: hash(body), meaning: 'host write finish, not provider receipt' });
|
|
198
|
+
written = true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
req?.destroy();
|
|
202
|
+
finish(Error('record failed'));
|
|
203
|
+
} });
|
|
204
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
205
|
+
if (signal.aborted) {
|
|
206
|
+
abort();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
req.end(body);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
throw Error('subscription-http-failed');
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
inFlight = false;
|
|
217
|
+
agent.destroy();
|
|
218
|
+
}
|
|
219
|
+
} };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=codex-https.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Separate from linux-bwrap-digest-v1: this proves only a fixed IPC probe workload.
|
|
2
|
+
* It MUST NOT be used to run arbitrary model/extension code. A model-bearing profile
|
|
3
|
+
* additionally needs aggregate host/descendant memory/CPU/egress limits and token
|
|
4
|
+
* semantics; these are deliberately reported unqualified rather than bypassed. */
|
|
5
|
+
export declare const CODEX_LOCAL_PROFILE: Readonly<{
|
|
6
|
+
id: "linux-bwrap-codex-ipc-probe-v1";
|
|
7
|
+
network: "unshared-no-egress";
|
|
8
|
+
credentials: "none";
|
|
9
|
+
cpuSeconds: 2;
|
|
10
|
+
nofile: 64;
|
|
11
|
+
heapMiB: 32;
|
|
12
|
+
wallMs: 3000;
|
|
13
|
+
outputBytes: 4096;
|
|
14
|
+
arbitraryCode: false;
|
|
15
|
+
modelExecutionQualified: false;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function probeLocalCodexProfile(id: string): Promise<{
|
|
18
|
+
profile: Readonly<{
|
|
19
|
+
id: "linux-bwrap-codex-ipc-probe-v1";
|
|
20
|
+
network: "unshared-no-egress";
|
|
21
|
+
credentials: "none";
|
|
22
|
+
cpuSeconds: 2;
|
|
23
|
+
nofile: 64;
|
|
24
|
+
heapMiB: 32;
|
|
25
|
+
wallMs: 3000;
|
|
26
|
+
outputBytes: 4096;
|
|
27
|
+
arbitraryCode: false;
|
|
28
|
+
modelExecutionQualified: false;
|
|
29
|
+
}>;
|
|
30
|
+
fingerprints: Record<string, string>;
|
|
31
|
+
modelExecutionQualified: false;
|
|
32
|
+
remaining: string[];
|
|
33
|
+
frame: string;
|
|
34
|
+
netNamespace: string;
|
|
35
|
+
envKeys: string[];
|
|
36
|
+
pwd: string;
|
|
37
|
+
rootReadOnly: boolean;
|
|
38
|
+
}>;
|