@dsh-enhanced/assistant-skills 0.1.30

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/capture-expansion.d.ts +18 -0
  5. package/lib/capture-expansion.d.ts.map +1 -0
  6. package/lib/capture-expansion.js +187 -0
  7. package/lib/capture-expansion.js.map +1 -0
  8. package/lib/comparison.d.ts +69 -0
  9. package/lib/comparison.d.ts.map +1 -0
  10. package/lib/comparison.js +173 -0
  11. package/lib/comparison.js.map +1 -0
  12. package/lib/definition.d.ts +163 -0
  13. package/lib/definition.d.ts.map +1 -0
  14. package/lib/definition.js +450 -0
  15. package/lib/definition.js.map +1 -0
  16. package/lib/external-holdout.d.ts +34 -0
  17. package/lib/external-holdout.d.ts.map +1 -0
  18. package/lib/external-holdout.js +212 -0
  19. package/lib/external-holdout.js.map +1 -0
  20. package/lib/holdout-authority.d.ts +123 -0
  21. package/lib/holdout-authority.d.ts.map +1 -0
  22. package/lib/holdout-authority.js +263 -0
  23. package/lib/holdout-authority.js.map +1 -0
  24. package/lib/holdout-cli.d.ts +4 -0
  25. package/lib/holdout-cli.d.ts.map +1 -0
  26. package/lib/holdout-cli.js +221 -0
  27. package/lib/holdout-cli.js.map +1 -0
  28. package/lib/holdout-qualification.d.ts +83 -0
  29. package/lib/holdout-qualification.d.ts.map +1 -0
  30. package/lib/holdout-qualification.js +306 -0
  31. package/lib/holdout-qualification.js.map +1 -0
  32. package/lib/index.d.ts +22 -0
  33. package/lib/index.d.ts.map +1 -0
  34. package/lib/index.js +9 -0
  35. package/lib/index.js.map +1 -0
  36. package/lib/prospective-holdout.d.ts +35 -0
  37. package/lib/prospective-holdout.d.ts.map +1 -0
  38. package/lib/prospective-holdout.js +254 -0
  39. package/lib/prospective-holdout.js.map +1 -0
  40. package/lib/repair-admission.d.ts +11 -0
  41. package/lib/repair-admission.d.ts.map +1 -0
  42. package/lib/repair-admission.js +25 -0
  43. package/lib/repair-admission.js.map +1 -0
  44. package/lib/repair-agent.d.ts +44 -0
  45. package/lib/repair-agent.d.ts.map +1 -0
  46. package/lib/repair-agent.js +241 -0
  47. package/lib/repair-agent.js.map +1 -0
  48. package/lib/repair-feedback.d.ts +70 -0
  49. package/lib/repair-feedback.d.ts.map +1 -0
  50. package/lib/repair-feedback.js +79 -0
  51. package/lib/repair-feedback.js.map +1 -0
  52. package/lib/repair-profile.d.ts +27 -0
  53. package/lib/repair-profile.d.ts.map +1 -0
  54. package/lib/repair-profile.js +51 -0
  55. package/lib/repair-profile.js.map +1 -0
  56. package/lib/repair-runtime.d.ts +35 -0
  57. package/lib/repair-runtime.d.ts.map +1 -0
  58. package/lib/repair-runtime.js +181 -0
  59. package/lib/repair-runtime.js.map +1 -0
  60. package/lib/replay.d.ts +38 -0
  61. package/lib/replay.d.ts.map +1 -0
  62. package/lib/replay.js +240 -0
  63. package/lib/replay.js.map +1 -0
  64. package/lib/sealed-holdout.d.ts +20 -0
  65. package/lib/sealed-holdout.d.ts.map +1 -0
  66. package/lib/sealed-holdout.js +12 -0
  67. package/lib/sealed-holdout.js.map +1 -0
  68. package/lib/service.d.ts +968 -0
  69. package/lib/service.d.ts.map +1 -0
  70. package/lib/service.js +1929 -0
  71. package/lib/service.js.map +1 -0
  72. package/lib/store.d.ts +366 -0
  73. package/lib/store.d.ts.map +1 -0
  74. package/lib/store.js +1517 -0
  75. package/lib/store.js.map +1 -0
  76. package/lib/version.d.ts +2 -0
  77. package/lib/version.d.ts.map +1 -0
  78. package/lib/version.js +2 -0
  79. package/lib/version.js.map +1 -0
  80. package/lib/watch-proof.d.ts +30 -0
  81. package/lib/watch-proof.d.ts.map +1 -0
  82. package/lib/watch-proof.js +209 -0
  83. package/lib/watch-proof.js.map +1 -0
  84. package/package.json +141 -0
@@ -0,0 +1,212 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createPublicKey } from 'node:crypto';
3
+ import { isAbsolute } from 'node:path';
4
+ import { StringDecoder } from 'node:string_decoder';
5
+ import { holdoutPathsOverlap, validateCanaryAdmission, validateHoldoutExecution } from './holdout-qualification.js';
6
+ import { validateCanaryAdmissionTemplate } from './repair-admission.js';
7
+ function reject() { throw new Error('assistant-skills: invalid external holdout configuration'); }
8
+ function plain(value) {
9
+ return value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype
10
+ && Object.getOwnPropertySymbols(value).length === 0 && Object.values(Object.getOwnPropertyDescriptors(value)).every(item => item.enumerable && 'value' in item);
11
+ }
12
+ function exact(value, required, optional = []) {
13
+ return plain(value) && required.every(key => Object.hasOwn(value, key)) && Object.keys(value).every(key => required.includes(key) || optional.includes(key));
14
+ }
15
+ function freeze(value) { if (value && typeof value === 'object') {
16
+ for (const child of Object.values(value))
17
+ freeze(child);
18
+ Object.freeze(value);
19
+ } return value; }
20
+ function validateAuthority(authority) {
21
+ if (!exact(authority, ['executable', 'args', 'publicKey'], ['datasetDigest', 'generatorDigest']) || typeof authority.executable !== 'string' || !isAbsolute(authority.executable) || authority.executable.includes('\0')
22
+ || !Array.isArray(authority.args) || authority.args.length > 64 || authority.args.some(arg => typeof arg !== 'string' || arg.includes('\0')) || Buffer.byteLength(JSON.stringify(authority.args)) > 16384
23
+ || typeof authority.publicKey !== 'string' || authority.publicKey.length > 16384
24
+ || Object.hasOwn(authority, 'datasetDigest') === Object.hasOwn(authority, 'generatorDigest')
25
+ || typeof (authority.datasetDigest ?? authority.generatorDigest) !== 'string' || !/^[a-f0-9]{64}$/u.test((authority.datasetDigest ?? authority.generatorDigest)))
26
+ reject();
27
+ try {
28
+ if (createPublicKey(authority.publicKey).asymmetricKeyType !== 'ed25519')
29
+ reject();
30
+ }
31
+ catch {
32
+ reject();
33
+ }
34
+ }
35
+ export function validateExternalHoldoutProfiles(values) {
36
+ if (!Array.isArray(values) || values.length > 16)
37
+ reject();
38
+ const ids = new Set(), commands = new Set(), roots = [];
39
+ for (const value of values) {
40
+ if (!exact(value, ['id', 'version', 'scope', 'execution', 'authority', 'maxComparisons'], ['inputs', 'files', 'canaryAdmission', 'canaryAdmissionTemplate']) || typeof value.id !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/u.test(value.id) || ids.has(value.id)
41
+ || !Number.isSafeInteger(value.version) || value.version < 1 || value.maxComparisons !== 1
42
+ || !exact(value.scope, ['principalId', 'principalRecordId', 'principalVersion', 'workspace', 'preset']) || ['principalId', 'principalRecordId', 'workspace', 'preset'].some(key => typeof value.scope[key] !== 'string' || !value.scope[key])
43
+ || !Number.isSafeInteger(value.scope.principalVersion) || value.scope.principalVersion < 1 || !isAbsolute(value.scope.workspace))
44
+ reject();
45
+ validateAuthority(value.authority);
46
+ validateHoldoutExecution(value.execution);
47
+ if (holdoutPathsOverlap(value.scope.workspace, value.execution.stateRoot) || roots.some(root => holdoutPathsOverlap(root, value.execution.stateRoot)))
48
+ reject();
49
+ const command = JSON.stringify([value.authority.executable, value.authority.args]);
50
+ if (commands.has(command) || value.inputs !== undefined && !plain(value.inputs) || value.files !== undefined && (!Array.isArray(value.files) || value.files.length > 32 || value.files.some((file) => !exact(file, ['path', 'content']) || typeof file.path !== 'string' || typeof file.content !== 'string'))
51
+ || value.canaryAdmission !== undefined && value.canaryAdmissionTemplate !== undefined
52
+ || (value.canaryAdmission !== undefined || value.canaryAdmissionTemplate !== undefined) && value.authority.generatorDigest === undefined)
53
+ reject();
54
+ if (value.canaryAdmission !== undefined) {
55
+ try {
56
+ validateCanaryAdmission(value.canaryAdmission);
57
+ }
58
+ catch {
59
+ reject();
60
+ }
61
+ }
62
+ if (value.canaryAdmissionTemplate !== undefined) {
63
+ try {
64
+ validateCanaryAdmissionTemplate(value.canaryAdmissionTemplate);
65
+ }
66
+ catch {
67
+ reject();
68
+ }
69
+ }
70
+ if (Buffer.byteLength(JSON.stringify({ inputs: value.inputs, files: value.files })) > value.execution.maxBytes)
71
+ reject();
72
+ ids.add(value.id);
73
+ commands.add(command);
74
+ roots.push(value.execution.stateRoot);
75
+ }
76
+ return freeze(JSON.parse(JSON.stringify(values)));
77
+ }
78
+ /** One bounded NDJSON pipe per qualification; malformed/uncertain responses poison the channel. */
79
+ export async function openHoldoutProcess(authority, signal) {
80
+ validateAuthority(authority);
81
+ signal.throwIfAborted();
82
+ const child = spawn(authority.executable, [...authority.args], { stdio: ['pipe', 'pipe', 'pipe'], detached: process.platform !== 'win32', shell: false });
83
+ const decoder = new StringDecoder('utf8');
84
+ let buffer = '', ready = false, ended = false, closing = false, serial = 0, failed;
85
+ let pending;
86
+ let acceptReady, rejectReady;
87
+ const handshake = new Promise((resolve, rejectPromise) => { acceptReady = resolve; rejectReady = rejectPromise; });
88
+ const terminal = new Promise(resolve => { child.once('close', () => { ended = true; fail('authority closed'); resolve(); }); });
89
+ const kill = (kind) => { if (ended || !child.pid)
90
+ return; try {
91
+ if (process.platform !== 'win32')
92
+ process.kill(-child.pid, kind);
93
+ else
94
+ child.kill(kind);
95
+ }
96
+ catch { /* Already exited. */ } };
97
+ function fail(reason) {
98
+ if (failed)
99
+ return;
100
+ failed = new Error(`assistant-skills: holdout ${reason}`);
101
+ rejectReady(failed);
102
+ pending?.reject(failed);
103
+ pending = undefined;
104
+ if (!closing) {
105
+ child.stdin.destroy();
106
+ kill('SIGTERM');
107
+ }
108
+ }
109
+ const abort = () => fail('authority cancelled');
110
+ signal.addEventListener('abort', abort, { once: true });
111
+ child.once('error', () => fail('authority unavailable'));
112
+ child.stdin.on('error', () => fail('authority pipe failed'));
113
+ child.stdout.on('error', () => fail('authority output failed'));
114
+ child.stderr.on('data', () => { });
115
+ child.stdout.on('data', (chunk) => {
116
+ if (failed)
117
+ return;
118
+ buffer += decoder.write(chunk);
119
+ if (Buffer.byteLength(buffer) > 1048576) {
120
+ fail('authority response too large');
121
+ return;
122
+ }
123
+ let at;
124
+ while ((at = buffer.indexOf('\n')) >= 0 && !failed) {
125
+ const line = buffer.slice(0, at);
126
+ buffer = buffer.slice(at + 1);
127
+ let message;
128
+ try {
129
+ const parsed = JSON.parse(line);
130
+ if (!plain(parsed))
131
+ throw new Error();
132
+ message = parsed;
133
+ }
134
+ catch {
135
+ fail('authority protocol rejected');
136
+ return;
137
+ }
138
+ if (!ready) {
139
+ if (message.event !== 'ready' || message.protocol !== 'assistant-skills/holdout-ipc/v1') {
140
+ fail('authority protocol rejected');
141
+ return;
142
+ }
143
+ ready = true;
144
+ acceptReady();
145
+ continue;
146
+ }
147
+ if (!pending || message.id !== pending.id || typeof message.ok !== 'boolean') {
148
+ fail('authority response identity rejected');
149
+ return;
150
+ }
151
+ if (!message.ok) {
152
+ fail('authority request rejected');
153
+ return;
154
+ }
155
+ const current = pending;
156
+ pending = undefined;
157
+ current.resolve(message.value);
158
+ }
159
+ });
160
+ let cleanup;
161
+ const close = () => cleanup ??= (async () => {
162
+ closing = true;
163
+ signal.removeEventListener('abort', abort);
164
+ if (!ended)
165
+ child.stdin.end();
166
+ const soft = setTimeout(() => kill('SIGTERM'), 1000), hard = setTimeout(() => kill('SIGKILL'), 2000);
167
+ try {
168
+ await Promise.race([terminal, new Promise((_resolve, rejectPromise) => {
169
+ const timer = setTimeout(() => rejectPromise(new Error('assistant-skills: holdout process termination unconfirmed')), 4000);
170
+ void terminal.then(() => clearTimeout(timer));
171
+ })]);
172
+ }
173
+ finally {
174
+ clearTimeout(soft);
175
+ clearTimeout(hard);
176
+ }
177
+ })();
178
+ const handshakeTimer = setTimeout(() => fail('authority ready deadline exceeded'), 10000);
179
+ try {
180
+ if (signal.aborted)
181
+ abort();
182
+ await handshake;
183
+ }
184
+ catch (error) {
185
+ await close();
186
+ throw error;
187
+ }
188
+ finally {
189
+ clearTimeout(handshakeTimer);
190
+ }
191
+ return { close, transport: { async request(operation, value, requestSignal) {
192
+ if (failed || closing || ended || pending || !['begin', 'next', 'record', 'finish'].includes(operation))
193
+ throw new Error('assistant-skills: holdout pipe unavailable');
194
+ signal.throwIfAborted();
195
+ requestSignal?.throwIfAborted();
196
+ const id = `request-${++serial}`, body = JSON.stringify({ id, operation, ...(value === undefined ? {} : { value }) }) + '\n';
197
+ if (Buffer.byteLength(body) > 1048576)
198
+ throw new Error('assistant-skills: holdout request too large');
199
+ const cancelled = () => fail('authority request cancelled');
200
+ const timer = setTimeout(() => fail('authority response deadline exceeded'), 300000);
201
+ requestSignal?.addEventListener('abort', cancelled, { once: true });
202
+ try {
203
+ return await new Promise((resolve, rejectPromise) => { pending = { id, resolve, reject: rejectPromise }; child.stdin.write(body, error => { if (error)
204
+ fail('authority write failed'); }); });
205
+ }
206
+ finally {
207
+ clearTimeout(timer);
208
+ requestSignal?.removeEventListener('abort', cancelled);
209
+ }
210
+ } } };
211
+ }
212
+ //# sourceMappingURL=external-holdout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-holdout.js","sourceRoot":"","sources":["../src/external-holdout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ import { KeyObject } from 'node:crypto';
2
+ import { type ProspectiveHoldoutCertificate } from './prospective-holdout.js';
3
+ export type HoldoutCaseKind = 'replay' | 'evaluation' | 'regression';
4
+ export type CellVerdict = 'achieved' | 'not-achieved' | 'unknown';
5
+ export type ObservationStatus = 'completed' | 'failed' | 'unknown';
6
+ export type StopReason = 'expired' | 'recovered-outstanding' | 'non-quiescent' | 'execution-unknown';
7
+ export interface HoldoutCase {
8
+ readonly id: string;
9
+ readonly kind: HoldoutCaseKind;
10
+ readonly stdin: string;
11
+ readonly expectedStdout: string;
12
+ readonly expectedExitCode: number;
13
+ }
14
+ export interface HoldoutDataset {
15
+ readonly id: string;
16
+ readonly version: string;
17
+ readonly cases: readonly HoldoutCase[];
18
+ }
19
+ export interface QualificationBinding {
20
+ readonly scopeDigest: string;
21
+ readonly baselineDigest: string;
22
+ readonly candidateDigest: string;
23
+ readonly budgetDigest: string;
24
+ /** Optional exact canary admission policy/configuration pin. */
25
+ readonly admissionDigest?: string;
26
+ readonly expiresAt: number;
27
+ readonly repeats: number;
28
+ }
29
+ export interface HoldoutLimits {
30
+ readonly maxToolCalls: number;
31
+ readonly maxOutputBytes: number;
32
+ }
33
+ export interface ToolCall {
34
+ readonly name: string;
35
+ readonly inputDigest: string;
36
+ readonly outputDigest?: string;
37
+ }
38
+ export interface CellObservation {
39
+ readonly cellId: string;
40
+ readonly armDigest: string;
41
+ readonly stdout: string;
42
+ readonly exitCode: number | null;
43
+ readonly quiescent: boolean;
44
+ readonly status: ObservationStatus;
45
+ readonly artifactDigest: string;
46
+ readonly toolCalls: readonly ToolCall[];
47
+ }
48
+ export interface BeginResult {
49
+ readonly sessionId: string;
50
+ readonly planDigest: string;
51
+ readonly datasetDigest: string;
52
+ readonly publicKey: string;
53
+ readonly scopeDigest: string;
54
+ readonly baselineDigest: string;
55
+ readonly candidateDigest: string;
56
+ readonly budgetDigest: string;
57
+ readonly admissionDigest?: string;
58
+ readonly expiresAt: number;
59
+ readonly repeats: number;
60
+ readonly limits: HoldoutLimits;
61
+ readonly cellCount: number;
62
+ /** Present only for a prospective dataset generated after the binding was frozen. */
63
+ readonly prospective?: ProspectiveHoldoutCertificate;
64
+ }
65
+ export interface SignedCell {
66
+ readonly sessionId: string;
67
+ readonly planDigest: string;
68
+ readonly cellId: string;
69
+ readonly armDigest: string;
70
+ readonly stdin: string;
71
+ readonly signature: string;
72
+ }
73
+ export interface PublicCellVerdict {
74
+ readonly cellId: string;
75
+ readonly armDigest: string;
76
+ readonly caseId: string;
77
+ readonly kind: HoldoutCaseKind;
78
+ readonly repeat: number;
79
+ readonly verdict: CellVerdict;
80
+ readonly observationDigest?: string;
81
+ }
82
+ export interface HoldoutReceipt extends BeginResult {
83
+ /** False means one or more cells were made unknown by expiry, recovery, or non-quiescence. */
84
+ readonly complete: boolean;
85
+ readonly stoppedReason?: StopReason;
86
+ readonly cellVerdicts: readonly PublicCellVerdict[];
87
+ readonly observationDigest: string;
88
+ readonly signature: string;
89
+ }
90
+ export interface AuthorityOptions {
91
+ readonly dataset: HoldoutDataset;
92
+ readonly privateKey: KeyObject | string | Buffer;
93
+ readonly limits: HoldoutLimits;
94
+ readonly prospective?: ProspectiveHoldoutCertificate;
95
+ /** Deterministic clock hook for tests; production callers should omit it. */
96
+ readonly now?: () => number;
97
+ }
98
+ /** A synchronous, stateful authority. It has no Cordis dependency and never claims an OS seal or promotion authority. */
99
+ export declare class HoldoutAuthority {
100
+ #private;
101
+ private constructor();
102
+ static create(options: AuthorityOptions): HoldoutAuthority;
103
+ /** Public operator pins; inspecting them does not issue a cell or create a qualification. */
104
+ metadata(): Readonly<{
105
+ publicKey: string;
106
+ datasetDigest: string;
107
+ limits: HoldoutLimits;
108
+ }>;
109
+ static restore(serialized: string, options: AuthorityOptions): HoldoutAuthority;
110
+ begin(binding: QualificationBinding): BeginResult;
111
+ next(): SignedCell | undefined;
112
+ record(observation: CellObservation): CellVerdict;
113
+ finish(): HoldoutReceipt;
114
+ serialize(): string;
115
+ private validateRestoredState;
116
+ private validateObservation;
117
+ private caseFor;
118
+ private assertBegun;
119
+ private expireOutstanding;
120
+ }
121
+ /** Verifies a signed next-cell or receipt payload with the public key emitted by begin. */
122
+ export declare function verifyHoldoutSignature(payload: Record<string, unknown>, publicKey: string): boolean;
123
+ //# sourceMappingURL=holdout-authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holdout-authority.d.ts","sourceRoot":"","sources":["../src/holdout-authority.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,263 @@
1
+ import { createHash, createPrivateKey, createPublicKey, KeyObject, randomUUID, sign, verify } from 'node:crypto';
2
+ import { isProspectiveGeneratorName, prospectiveGeneratorDigest, verifyProspectiveCertificate } from './prospective-holdout.js';
3
+ const digestPattern = /^[a-f0-9]{64}$/u;
4
+ const kinds = ['replay', 'evaluation', 'regression'];
5
+ const maxStringBytes = 256 * 1024;
6
+ function canonical(value) {
7
+ if (value === null || typeof value !== 'object')
8
+ return JSON.stringify(value);
9
+ if (Array.isArray(value))
10
+ return `[${value.map(canonical).join(',')}]`;
11
+ const record = value;
12
+ return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
13
+ }
14
+ function sha256(value) {
15
+ return createHash('sha256').update(canonical(value)).digest('hex');
16
+ }
17
+ function byteLength(value) { return Buffer.byteLength(value, 'utf8'); }
18
+ function signature(payload, privateKey) {
19
+ return sign(null, Buffer.from(canonical(payload)), privateKey).toString('base64url');
20
+ }
21
+ function validDigest(value) { return typeof value === 'string' && digestPattern.test(value); }
22
+ function assert(condition, message) { if (!condition)
23
+ throw new Error(`holdout-authority: ${message}`); }
24
+ function exact(value, required, optional = []) {
25
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype || Object.getOwnPropertySymbols(value).length)
26
+ return false;
27
+ const descriptors = Object.getOwnPropertyDescriptors(value);
28
+ return required.every(key => Object.hasOwn(descriptors, key)) && Object.entries(descriptors).every(([key, descriptor]) => (required.includes(key) || optional.includes(key)) && descriptor.enumerable && 'value' in descriptor);
29
+ }
30
+ function validateLimits(limits) {
31
+ assert(exact(limits, ['maxToolCalls', 'maxOutputBytes']), 'limits shape is invalid');
32
+ assert(Number.isInteger(limits.maxToolCalls) && limits.maxToolCalls >= 1 && limits.maxToolCalls <= 32, 'maxToolCalls must be an integer from 1 to 32');
33
+ assert(Number.isInteger(limits.maxOutputBytes) && limits.maxOutputBytes >= 0 && limits.maxOutputBytes <= 262144, 'maxOutputBytes must be an integer no greater than 262144');
34
+ }
35
+ function validateDataset(dataset, limits) {
36
+ assert(exact(dataset, ['id', 'version', 'cases']), 'dataset shape is invalid');
37
+ assert(dataset && typeof dataset.id === 'string' && dataset.id.length > 0 && dataset.id.length <= 128, 'dataset id is invalid');
38
+ assert(typeof dataset.version === 'string' && dataset.version.length > 0 && dataset.version.length <= 128, 'dataset version is invalid');
39
+ assert(Array.isArray(dataset.cases) && dataset.cases.length >= 3 && dataset.cases.length <= 12, 'dataset requires 3 to 12 cases');
40
+ const ids = new Set();
41
+ const seenKinds = new Set();
42
+ let total = byteLength(dataset.id) + byteLength(dataset.version);
43
+ for (const item of dataset.cases) {
44
+ assert(exact(item, ['id', 'kind', 'stdin', 'expectedStdout', 'expectedExitCode']), 'case shape is invalid');
45
+ assert(item && typeof item.id === 'string' && item.id.length > 0 && item.id.length <= 128 && !ids.has(item.id), 'case ids must be unique bounded strings');
46
+ ids.add(item.id);
47
+ assert(kinds.includes(item.kind), 'case kind is invalid');
48
+ seenKinds.add(item.kind);
49
+ assert(typeof item.stdin === 'string' && typeof item.expectedStdout === 'string', 'case input and expected output must be strings');
50
+ assert(Number.isInteger(item.expectedExitCode) && item.expectedExitCode >= 0 && item.expectedExitCode <= 255, 'expected exit code must be an integer from 0 to 255');
51
+ assert(byteLength(item.expectedStdout) <= limits.maxOutputBytes, 'expected output exceeds maxOutputBytes');
52
+ total += byteLength(item.id) + byteLength(item.stdin) + byteLength(item.expectedStdout);
53
+ }
54
+ assert(seenKinds.size === kinds.length, 'dataset must contain replay, evaluation, and regression cases');
55
+ assert(total <= maxStringBytes, 'dataset strings exceed 256KiB');
56
+ }
57
+ function validateBinding(binding, now) {
58
+ assert(exact(binding, ['scopeDigest', 'baselineDigest', 'candidateDigest', 'budgetDigest', 'expiresAt', 'repeats'], ['admissionDigest']), 'binding shape is invalid');
59
+ for (const value of [binding.scopeDigest, binding.baselineDigest, binding.candidateDigest, binding.budgetDigest])
60
+ assert(validDigest(value), 'binding digests must be lowercase SHA-256 hex');
61
+ assert(binding.admissionDigest === undefined || validDigest(binding.admissionDigest), 'admissionDigest must be lowercase SHA-256 hex');
62
+ assert(Number.isInteger(binding.repeats) && binding.repeats >= 2 && binding.repeats <= 4, 'repeats must be an integer from 2 to 4');
63
+ assert(Number.isSafeInteger(binding.expiresAt) && binding.expiresAt > now && binding.expiresAt <= now + 7 * 24 * 60 * 60 * 1000, 'expiresAt must be a future timestamp within seven days');
64
+ }
65
+ function bindingFromBegin(begin) {
66
+ return { scopeDigest: begin.scopeDigest, baselineDigest: begin.baselineDigest, candidateDigest: begin.candidateDigest, budgetDigest: begin.budgetDigest,
67
+ ...(begin.admissionDigest === undefined ? {} : { admissionDigest: begin.admissionDigest }), expiresAt: begin.expiresAt, repeats: begin.repeats };
68
+ }
69
+ /** A synchronous, stateful authority. It has no Cordis dependency and never claims an OS seal or promotion authority. */
70
+ export class HoldoutAuthority {
71
+ #dataset;
72
+ #datasetDigest;
73
+ #privateKey;
74
+ #publicKey;
75
+ #limits;
76
+ #prospective;
77
+ #now;
78
+ #createdAt;
79
+ #begin;
80
+ #cells = [];
81
+ #stopped = false;
82
+ #stoppedReason;
83
+ constructor(options) {
84
+ validateLimits(options.limits);
85
+ validateDataset(options.dataset, options.limits);
86
+ this.#dataset = structuredClone(options.dataset);
87
+ this.#datasetDigest = sha256(this.#dataset);
88
+ this.#privateKey = options.privateKey instanceof KeyObject ? options.privateKey : createPrivateKey(options.privateKey);
89
+ assert(this.#privateKey.type === 'private' && this.#privateKey.asymmetricKeyType === 'ed25519', 'private key must be Ed25519');
90
+ this.#publicKey = createPublicKey(this.#privateKey).export({ format: 'pem', type: 'spki' }).toString();
91
+ this.#limits = Object.freeze({ ...options.limits });
92
+ if (options.prospective !== undefined) {
93
+ assert(isProspectiveGeneratorName(options.dataset.version), 'prospective dataset generator is invalid');
94
+ assert(options.prospective.generatorDigest === prospectiveGeneratorDigest(options.dataset.version), 'prospective certificate generator does not match dataset');
95
+ if (options.prospective.profileVersion !== undefined || options.prospective.profileDigest !== undefined) {
96
+ assert(options.prospective.profileVersion === options.dataset.version && options.prospective.profileDigest === options.prospective.generatorDigest, 'prospective certificate profile does not match dataset');
97
+ }
98
+ assert(verifyProspectiveCertificate(options.prospective, options.prospective.binding, this.#publicKey, options.prospective.generatorDigest), 'prospective certificate is invalid');
99
+ assert(options.prospective.datasetDigest === this.#datasetDigest, 'prospective certificate dataset does not match');
100
+ this.#prospective = Object.freeze(structuredClone(options.prospective));
101
+ }
102
+ this.#now = options.now ?? Date.now;
103
+ this.#createdAt = this.#now();
104
+ }
105
+ static create(options) { return new HoldoutAuthority(options); }
106
+ /** Public operator pins; inspecting them does not issue a cell or create a qualification. */
107
+ metadata() {
108
+ return Object.freeze({ publicKey: this.#publicKey, datasetDigest: this.#datasetDigest, limits: this.#limits });
109
+ }
110
+ static restore(serialized, options) {
111
+ assert(typeof serialized === 'string' && byteLength(serialized) <= 1024 * 1024, 'serialized state is invalid');
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(serialized);
115
+ }
116
+ catch {
117
+ throw new Error('holdout-authority: serialized state is invalid JSON');
118
+ }
119
+ assert(parsed && parsed.state && typeof parsed.signature === 'string', 'serialized state is malformed');
120
+ const authority = new HoldoutAuthority(options);
121
+ assert(parsed.state.version === 1 && parsed.state.datasetDigest === authority.#datasetDigest && parsed.state.publicKey === authority.#publicKey && canonical(parsed.state.limits) === canonical(authority.#limits), 'serialized state does not match operator dataset, key, or limits');
122
+ assert(verify(null, Buffer.from(canonical(parsed.state)), createPublicKey(options.privateKey), Buffer.from(parsed.signature, 'base64url')), 'serialized state signature is invalid');
123
+ if (parsed.state.begin)
124
+ authority.#begin = parsed.state.begin;
125
+ authority.#cells = parsed.state.cells.map(cell => ({ ...cell }));
126
+ authority.#stopped = parsed.state.stopped;
127
+ if (parsed.state.stoppedReason)
128
+ authority.#stoppedReason = parsed.state.stoppedReason;
129
+ assert(Number.isSafeInteger(parsed.state.createdAt), 'serialized creation timestamp is invalid');
130
+ authority.#createdAt = parsed.state.createdAt;
131
+ authority.validateRestoredState();
132
+ // An issued cell could have run while the authority was down. It is never reissued or accepted after recovery.
133
+ const hadOutstanding = authority.#cells.some(cell => cell.issued && !cell.verdict);
134
+ for (const cell of authority.#cells)
135
+ if (cell.issued && !cell.verdict)
136
+ cell.verdict = 'unknown';
137
+ if (hadOutstanding)
138
+ authority.expireOutstanding('recovered-outstanding');
139
+ return authority;
140
+ }
141
+ begin(binding) {
142
+ assert(!this.#begin, 'qualification has already begun');
143
+ assert(binding?.expiresAt > this.#now(), 'expiresAt must be a finite future timestamp');
144
+ validateBinding(binding, this.#createdAt);
145
+ const sessionId = randomUUID();
146
+ const cells = [];
147
+ let index = 0;
148
+ for (let repeat = 1; repeat <= binding.repeats; repeat++)
149
+ for (const item of this.#dataset.cases) {
150
+ const arms = (index++ % 2 === 0) ? [binding.baselineDigest, binding.candidateDigest] : [binding.candidateDigest, binding.baselineDigest];
151
+ for (const armDigest of arms)
152
+ cells.push({ cellId: `cell-${cells.length + 1}`, armDigest, caseId: item.id, kind: item.kind, repeat, issued: false });
153
+ }
154
+ assert(binding.baselineDigest !== binding.candidateDigest, 'baseline and candidate digests must differ');
155
+ const planDigest = sha256({ sessionId, datasetDigest: this.#datasetDigest, binding, limits: this.#limits, cells: cells.map(({ cellId, armDigest, caseId, kind, repeat }) => ({ cellId, armDigest, caseId, kind, repeat })) });
156
+ if (this.#prospective)
157
+ assert(verifyProspectiveCertificate(this.#prospective, binding, this.#publicKey, this.#prospective.generatorDigest), 'prospective certificate binding does not match');
158
+ this.#begin = Object.freeze({ sessionId, planDigest, datasetDigest: this.#datasetDigest, publicKey: this.#publicKey, ...binding, limits: this.#limits, cellCount: cells.length, ...(this.#prospective ? { prospective: this.#prospective } : {}) });
159
+ this.#cells = cells;
160
+ return { ...this.#begin };
161
+ }
162
+ next() {
163
+ this.assertBegun();
164
+ if (this.#stopped || this.#now() >= this.#begin.expiresAt) {
165
+ this.expireOutstanding('expired');
166
+ return undefined;
167
+ }
168
+ assert(!this.#cells.some(item => item.issued && !item.verdict), 'an issued cell must be settled before requesting another');
169
+ const cell = this.#cells.find(item => !item.issued && !item.verdict);
170
+ if (!cell)
171
+ return undefined;
172
+ cell.issued = true;
173
+ const stdin = this.caseFor(cell).stdin;
174
+ const unsigned = { sessionId: this.#begin.sessionId, planDigest: this.#begin.planDigest, cellId: cell.cellId, armDigest: cell.armDigest, stdin };
175
+ return { ...unsigned, signature: signature(unsigned, this.#privateKey) };
176
+ }
177
+ record(observation) {
178
+ this.assertBegun();
179
+ assert(!this.#stopped, 'qualification is stopped');
180
+ if (this.#now() >= this.#begin.expiresAt) {
181
+ this.expireOutstanding('expired');
182
+ throw new Error('holdout-authority: qualification has expired');
183
+ }
184
+ this.validateObservation(observation);
185
+ const cell = this.#cells.find(item => item.cellId === observation.cellId);
186
+ assert(cell && cell.issued, 'cell was not issued');
187
+ assert(!cell.verdict, 'cell has already been settled');
188
+ assert(cell.armDigest === observation.armDigest, 'cell arm digest does not match');
189
+ const expected = this.caseFor(cell);
190
+ const verdict = !observation.quiescent || observation.status === 'unknown' || observation.exitCode === null
191
+ ? 'unknown'
192
+ : observation.stdout === expected.expectedStdout && observation.exitCode === expected.expectedExitCode ? 'achieved' : 'not-achieved';
193
+ cell.verdict = verdict;
194
+ cell.observationDigest = sha256({ cellId: observation.cellId, armDigest: observation.armDigest, stdout: observation.stdout, exitCode: observation.exitCode, quiescent: observation.quiescent, status: observation.status, artifactDigest: observation.artifactDigest, toolCalls: observation.toolCalls });
195
+ if (verdict === 'unknown')
196
+ this.expireOutstanding(!observation.quiescent ? 'non-quiescent' : 'execution-unknown');
197
+ return verdict;
198
+ }
199
+ finish() {
200
+ this.assertBegun();
201
+ if (this.#now() >= this.#begin.expiresAt)
202
+ this.expireOutstanding('expired');
203
+ assert(this.#cells.every(cell => !!cell.verdict), 'all cells must be terminal before finish');
204
+ const cellVerdicts = this.#cells.map(({ cellId, armDigest, caseId, kind, repeat, verdict, observationDigest }) => ({ cellId, armDigest, caseId, kind, repeat, verdict: verdict, ...(observationDigest ? { observationDigest } : {}) }));
205
+ const observationDigest = sha256(cellVerdicts.map(cell => ({ cellId: cell.cellId, observationDigest: cell.observationDigest ?? null, verdict: cell.verdict })));
206
+ const unsigned = { ...this.#begin, complete: !cellVerdicts.some(cell => cell.verdict === 'unknown'), ...(this.#stoppedReason ? { stoppedReason: this.#stoppedReason } : {}), cellVerdicts, observationDigest };
207
+ return { ...unsigned, signature: signature(unsigned, this.#privateKey) };
208
+ }
209
+ serialize() {
210
+ const state = { version: 1, datasetDigest: this.#datasetDigest, publicKey: this.#publicKey, createdAt: this.#createdAt, limits: this.#limits, ...(this.#begin ? { begin: this.#begin } : {}), cells: this.#cells, stopped: this.#stopped, ...(this.#stoppedReason ? { stoppedReason: this.#stoppedReason } : {}) };
211
+ return canonical({ state, signature: signature(state, this.#privateKey) });
212
+ }
213
+ validateRestoredState() {
214
+ if (!this.#begin) {
215
+ assert(this.#cells.length === 0 && !this.#stopped && !this.#stoppedReason, 'unbegun serialized state is inconsistent');
216
+ return;
217
+ }
218
+ assert(exact(this.#begin, ['sessionId', 'planDigest', 'datasetDigest', 'publicKey', 'scopeDigest', 'baselineDigest', 'candidateDigest', 'budgetDigest', 'expiresAt', 'repeats', 'limits', 'cellCount'], ['admissionDigest', 'prospective']), 'restored begin shape is invalid');
219
+ const restoredBinding = bindingFromBegin(this.#begin);
220
+ validateBinding(restoredBinding, this.#createdAt);
221
+ assert(typeof this.#begin.sessionId === 'string' && validDigest(this.#begin.planDigest) && Number.isSafeInteger(this.#begin.cellCount), 'restored begin identity is invalid');
222
+ assert(this.#begin.baselineDigest !== this.#begin.candidateDigest && this.#begin.datasetDigest === this.#datasetDigest && this.#begin.publicKey === this.#publicKey && canonical(this.#begin.limits) === canonical(this.#limits), 'restored qualification binding is invalid');
223
+ if (this.#prospective)
224
+ assert(this.#begin.prospective && this.#begin.prospective.generatorDigest === this.#prospective.generatorDigest && verifyProspectiveCertificate(this.#begin.prospective, restoredBinding, this.#publicKey, this.#prospective.generatorDigest), 'restored prospective certificate is invalid');
225
+ else
226
+ assert(this.#begin.prospective === undefined, 'restored prospective certificate is inconsistent');
227
+ const expectedCount = this.#dataset.cases.length * this.#begin.repeats * 2;
228
+ assert(this.#cells.length === expectedCount && this.#begin.cellCount === expectedCount, 'restored cell count is invalid');
229
+ const seen = new Set();
230
+ for (const cell of this.#cells) {
231
+ assert(!seen.has(cell.cellId) && /^cell-[1-9][0-9]*$/u.test(cell.cellId) && validDigest(cell.armDigest) && this.#dataset.cases.some(item => item.id === cell.caseId) && kinds.includes(cell.kind) && Number.isInteger(cell.repeat) && cell.repeat >= 1 && cell.repeat <= this.#begin.repeats && typeof cell.issued === 'boolean' && (cell.verdict === undefined || ['achieved', 'not-achieved', 'unknown'].includes(cell.verdict)), 'restored cell is invalid');
232
+ seen.add(cell.cellId);
233
+ }
234
+ const planDigest = sha256({ sessionId: this.#begin.sessionId, datasetDigest: this.#datasetDigest, binding: restoredBinding, limits: this.#limits, cells: this.#cells.map(({ cellId, armDigest, caseId, kind, repeat }) => ({ cellId, armDigest, caseId, kind, repeat })) });
235
+ assert(planDigest === this.#begin.planDigest, 'restored plan digest is invalid');
236
+ }
237
+ validateObservation(observation) {
238
+ assert(exact(observation, ['cellId', 'armDigest', 'stdout', 'exitCode', 'quiescent', 'status', 'artifactDigest', 'toolCalls']), 'observation shape is invalid');
239
+ assert(observation && typeof observation.cellId === 'string' && /^cell-[1-9][0-9]*$/u.test(observation.cellId) && validDigest(observation.armDigest), 'observation cell identity is invalid');
240
+ assert(typeof observation.stdout === 'string' && byteLength(observation.stdout) <= this.#limits.maxOutputBytes, 'observation stdout exceeds maxOutputBytes');
241
+ assert((observation.exitCode === null || (Number.isInteger(observation.exitCode) && observation.exitCode >= 0 && observation.exitCode <= 255)) && typeof observation.quiescent === 'boolean' && ['completed', 'failed', 'unknown'].includes(observation.status), 'observation execution status is invalid');
242
+ assert(validDigest(observation.artifactDigest), 'observation artifact digest is invalid');
243
+ assert(Array.isArray(observation.toolCalls) && observation.toolCalls.length <= this.#limits.maxToolCalls, 'observation tool calls exceed maxToolCalls');
244
+ let total = byteLength(observation.stdout);
245
+ for (const call of observation.toolCalls) {
246
+ assert(exact(call, ['name', 'inputDigest'], ['outputDigest']), 'tool observation shape is invalid');
247
+ assert(call && typeof call.name === 'string' && call.name.length > 0 && call.name.length <= 128 && validDigest(call.inputDigest) && (call.outputDigest === undefined || validDigest(call.outputDigest)), 'observation tool call is invalid');
248
+ total += byteLength(call.name) + byteLength(call.inputDigest) + (call.outputDigest ? byteLength(call.outputDigest) : 0);
249
+ }
250
+ assert(total <= maxStringBytes, 'observation strings exceed 256KiB');
251
+ }
252
+ caseFor(cell) { return this.#dataset.cases.find(item => item.id === cell.caseId); }
253
+ assertBegun() { assert(this.#begin, 'qualification has not begun'); }
254
+ expireOutstanding(reason) { for (const cell of this.#cells)
255
+ if (!cell.verdict)
256
+ cell.verdict = 'unknown'; this.#stopped = true; this.#stoppedReason ??= reason; }
257
+ }
258
+ /** Verifies a signed next-cell or receipt payload with the public key emitted by begin. */
259
+ export function verifyHoldoutSignature(payload, publicKey) {
260
+ const { signature: value, ...unsigned } = payload;
261
+ return typeof value === 'string' && typeof publicKey === 'string' && verify(null, Buffer.from(canonical(unsigned)), publicKey, Buffer.from(value, 'base64url'));
262
+ }
263
+ //# sourceMappingURL=holdout-authority.js.map