@ornexus/neocortex 4.60.18 → 4.60.20

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 (88) hide show
  1. package/dist/sbom.cdx.json +6504 -6410
  2. package/docs/install/installer-diagnostics.md +52 -0
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +27 -4
  6. package/packages/client/dist/agent/refresh-stubs.d.ts +7 -0
  7. package/packages/client/dist/agent/refresh-stubs.js +2 -2
  8. package/packages/client/dist/cache/encrypted-cache.d.ts +3 -0
  9. package/packages/client/dist/cache/encrypted-cache.js +1 -1
  10. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -2
  11. package/packages/client/dist/cli.js +3 -3
  12. package/packages/client/dist/commands/activate.js +7 -7
  13. package/packages/client/dist/commands/config.js +1 -1
  14. package/packages/client/dist/commands/invoke.d.ts +64 -0
  15. package/packages/client/dist/commands/invoke.js +54 -47
  16. package/packages/client/dist/config/secure-config.d.ts +9 -3
  17. package/packages/client/dist/config/secure-config.js +8 -8
  18. package/packages/client/dist/config.d.ts +1 -1
  19. package/packages/client/dist/config.js +1 -1
  20. package/packages/client/dist/continuity/continuity-client-state-store.d.ts +1 -0
  21. package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1
  22. package/packages/client/dist/continuity/invoke-hooks.d.ts +2 -1
  23. package/packages/client/dist/continuity/invoke-hooks.js +1 -1
  24. package/packages/client/dist/continuity/sqlite-store.d.ts +21 -0
  25. package/packages/client/dist/continuity/sqlite-store.js +49 -49
  26. package/packages/client/dist/effects/invoke-effect-gateways.d.ts +22 -0
  27. package/packages/client/dist/effects/invoke-effect-gateways.js +1 -0
  28. package/packages/client/dist/errors/frontmatter-diagnostics.d.ts +20 -0
  29. package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -0
  30. package/packages/client/dist/generated/command-registry.generated.d.ts +2836 -0
  31. package/packages/client/dist/generated/command-registry.generated.js +1 -0
  32. package/packages/client/dist/graph-retrieval/pre-command-hook.d.ts +2 -0
  33. package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -1
  34. package/packages/client/dist/index.d.ts +1 -0
  35. package/packages/client/dist/index.js +1 -1
  36. package/packages/client/dist/license/license-client.d.ts +3 -0
  37. package/packages/client/dist/license/license-client.js +1 -1
  38. package/packages/client/dist/machine/fingerprint.d.ts +3 -1
  39. package/packages/client/dist/machine/fingerprint.js +2 -2
  40. package/packages/client/dist/memory/project-memory-writer.js +19 -15
  41. package/packages/client/dist/{subagent-approval-policy.d.ts → policy/subagent-approval-policy.d.ts} +8 -3
  42. package/packages/client/dist/policy/subagent-approval-policy.js +1 -0
  43. package/packages/client/dist/resolvers/local-resolver.d.ts +3 -0
  44. package/packages/client/dist/resolvers/local-resolver.js +4 -4
  45. package/packages/client/dist/runner/cli.d.ts +1 -0
  46. package/packages/client/dist/runner/cli.js +1 -1
  47. package/packages/client/dist/runner/headless/prepared-story-bundle.d.ts +20 -0
  48. package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -0
  49. package/packages/client/dist/runner/headless/run-yoloop.d.ts +2 -0
  50. package/packages/client/dist/runner/headless/run-yoloop.js +1 -1
  51. package/packages/client/dist/runner/portal-sync-worker.js +1 -1
  52. package/packages/client/dist/runner/scheduler.d.ts +3 -0
  53. package/packages/client/dist/runner/scheduler.js +7 -7
  54. package/packages/client/dist/state/project-state-snapshot.js +1 -1
  55. package/packages/client/dist/tier/tier-aware-client.js +1 -1
  56. package/packages/client/dist/version.d.ts +3 -0
  57. package/packages/client/dist/version.js +1 -0
  58. package/packages/client/dist/yoloop/discovery-hook.js +2 -2
  59. package/packages/client/dist/yoloop/invoke-hooks.js +4 -4
  60. package/packages/client/dist/yoloop/shared-discover-epics.d.ts +9 -3
  61. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1
  62. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +24 -0
  63. package/packages/client/dist/yoloop/target-normalization.d.ts +24 -0
  64. package/packages/client/dist/yoloop/target-normalization.js +1 -0
  65. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +4 -0
  66. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  67. package/packages/shared/dist/command-registry-contract.js +382 -0
  68. package/packages/shared/dist/generated/command-registry.generated.js +4216 -0
  69. package/packages/shared/dist/generated/invocation-schema.generated.js +1850 -0
  70. package/packages/shared/dist/invocation/index.d.ts +175 -0
  71. package/packages/shared/dist/invocation/index.js +587 -0
  72. package/packages/shared/dist/nx-invoke-signature.js +233 -0
  73. package/packages/shared/dist/strict-readonly.js +381 -0
  74. package/packages/shared/dist/yoloop/wave-decision-receipt.js +77 -0
  75. package/targets-stubs/antigravity/gemini.md +1 -1
  76. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  77. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  78. package/targets-stubs/claude-code/neocortex-root.md +5 -2
  79. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  80. package/targets-stubs/claude-code/neocortex.md +7 -2
  81. package/targets-stubs/codex/AGENTS.md +4 -1
  82. package/targets-stubs/codex/neocortex.toml +4 -0
  83. package/targets-stubs/cursor/agent.md +2 -2
  84. package/targets-stubs/gemini-cli/agent.md +2 -2
  85. package/targets-stubs/opencode/neocortex-root.md +4 -1
  86. package/targets-stubs/opencode/neocortex.md +3 -0
  87. package/targets-stubs/vscode/neocortex.agent.md +2 -2
  88. package/packages/client/dist/subagent-approval-policy.js +0 -1
@@ -0,0 +1,233 @@
1
+ /**
2
+ * P205.03 -- dependency-free nx-invoke-v1 HTTP Message Signatures profile.
3
+ *
4
+ * The module accepts exact caller-supplied bytes. It never serializes parsed
5
+ * JSON and never reads configuration, environment secrets, or storage keys.
6
+ */
7
+ import { createHash, createHmac, hkdfSync, randomBytes, timingSafeEqual, } from 'node:crypto';
8
+ export const NX_INVOKE_LABEL = 'nx-invoke';
9
+ export const NX_INVOKE_TAG = 'neocortex-invoke-v1';
10
+ export const NX_INVOKE_ALGORITHM = 'hmac-sha256';
11
+ export const NX_INVOKE_CONTENT_TYPE = 'application/json';
12
+ export const NX_INVOKE_SIGNATURE_TTL_SECONDS = 120;
13
+ export const NX_INVOKE_MAX_LIFETIME_SECONDS = 300;
14
+ export const NX_INVOKE_MAX_AGE_SECONDS = 300;
15
+ export const NX_INVOKE_CLOCK_SKEW_SECONDS = 30;
16
+ export const NX_INVOKE_COVERED_COMPONENTS = Object.freeze([
17
+ '@method',
18
+ '@path',
19
+ '@query',
20
+ 'content-type',
21
+ 'content-digest',
22
+ 'x-client-version',
23
+ ]);
24
+ const SIGNATURE_INPUT_PREFIX = 'nx-invoke=("@method" "@path" "@query" "content-type" "content-digest" "x-client-version")';
25
+ const HKDF_SALT = createHash('sha256')
26
+ .update('neocortex/http-message-signature/salt/v1', 'utf8')
27
+ .digest();
28
+ const HKDF_INFO = Buffer.concat([
29
+ Buffer.from('neocortex/invoke-request/v1', 'utf8'),
30
+ Buffer.from([0]),
31
+ Buffer.from(NX_INVOKE_ALGORITHM, 'ascii'),
32
+ ]);
33
+ const KEY_ID_DOMAIN = Buffer.from('neocortex/invoke-key-id/v1', 'utf8');
34
+ const BASE64URL_32_RE = /^[A-Za-z0-9_-]{43}$/;
35
+ const BASE64_32_RE = /^[A-Za-z0-9+/]{43}=$/;
36
+ const CLIENT_VERSION_RE = /^[A-Za-z0-9._+-]{1,64}$/;
37
+ const SAFE_INTEGER_RE = /^(0|[1-9][0-9]{0,15})$/;
38
+ function exactBase64Url32(value) {
39
+ if (!BASE64URL_32_RE.test(value))
40
+ return null;
41
+ const decoded = Buffer.from(value, 'base64url');
42
+ if (decoded.length !== 32 || decoded.toString('base64url') !== value)
43
+ return null;
44
+ return decoded;
45
+ }
46
+ function exactBase6432(value) {
47
+ if (!BASE64_32_RE.test(value))
48
+ return null;
49
+ const decoded = Buffer.from(value, 'base64');
50
+ if (decoded.length !== 32 || decoded.toString('base64') !== value)
51
+ return null;
52
+ return decoded;
53
+ }
54
+ function safeSeconds(value) {
55
+ return Number.isSafeInteger(value) && value >= 0;
56
+ }
57
+ function splitRawTarget(target) {
58
+ if (!target.startsWith('/') || /[\u0000-\u0020\u007f#]/.test(target))
59
+ return null;
60
+ const queryIndex = target.indexOf('?');
61
+ const path = queryIndex === -1 ? target : target.slice(0, queryIndex);
62
+ const query = queryIndex === -1 ? '?' : target.slice(queryIndex);
63
+ return { path: path || '/', query };
64
+ }
65
+ function contentDigest(body) {
66
+ const digest = createHash('sha256').update(body).digest();
67
+ return { header: `sha-256=:${digest.toString('base64')}:`, digest };
68
+ }
69
+ export function deriveNxInvokeKey(bearerToken) {
70
+ if (!bearerToken || /[^\x21-\x7e]/.test(bearerToken)) {
71
+ throw new Error('invalid-bearer-token');
72
+ }
73
+ return Buffer.from(hkdfSync('sha256', Buffer.from(bearerToken, 'ascii'), HKDF_SALT, HKDF_INFO, 32));
74
+ }
75
+ export function deriveNxInvokeKeyId(key) {
76
+ if (key.byteLength !== 32)
77
+ throw new Error('invalid-derived-key-length');
78
+ return createHmac('sha256', key).update(KEY_ID_DOMAIN).digest('base64url');
79
+ }
80
+ export function buildNxInvokeSignatureInput(metadata) {
81
+ return `${SIGNATURE_INPUT_PREFIX};created=${metadata.created};expires=${metadata.expires};nonce="${metadata.nonce}";keyid="${metadata.keyId}";alg="${NX_INVOKE_ALGORITHM}";tag="${NX_INVOKE_TAG}"`;
82
+ }
83
+ export function parseNxInvokeSignatureInput(value) {
84
+ const match = new RegExp(`^${SIGNATURE_INPUT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}` +
85
+ ';created=([0-9]+);expires=([0-9]+);nonce="([A-Za-z0-9_-]+)";keyid="([A-Za-z0-9_-]+)";alg="hmac-sha256";tag="neocortex-invoke-v1"$').exec(value);
86
+ if (!match || !SAFE_INTEGER_RE.test(match[1]) || !SAFE_INTEGER_RE.test(match[2]))
87
+ return null;
88
+ const created = Number(match[1]);
89
+ const expires = Number(match[2]);
90
+ if (!safeSeconds(created) || !safeSeconds(expires))
91
+ return null;
92
+ if (!exactBase64Url32(match[3]) || !exactBase64Url32(match[4]))
93
+ return null;
94
+ return { created, expires, nonce: match[3], keyId: match[4] };
95
+ }
96
+ export function buildNxInvokeSignatureBase(input) {
97
+ const method = input.method.toUpperCase();
98
+ if (method !== input.method || !/^[A-Z]+$/.test(method))
99
+ throw new Error('invalid-method');
100
+ const target = splitRawTarget(input.target);
101
+ if (!target)
102
+ throw new Error('invalid-target');
103
+ if (input.contentType !== NX_INVOKE_CONTENT_TYPE)
104
+ throw new Error('invalid-content-type');
105
+ if (!CLIENT_VERSION_RE.test(input.clientVersion))
106
+ throw new Error('invalid-client-version');
107
+ return Buffer.from([
108
+ `"@method": ${method}`,
109
+ `"@path": ${target.path}`,
110
+ `"@query": ${target.query}`,
111
+ `"content-type": ${input.contentType}`,
112
+ `"content-digest": ${input.contentDigest}`,
113
+ `"x-client-version": ${input.clientVersion}`,
114
+ `"@signature-params": ${input.signatureInput.slice(`${NX_INVOKE_LABEL}=`.length)}`,
115
+ ].join('\n'), 'utf8');
116
+ }
117
+ export function signNxInvokeRequest(input) {
118
+ const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);
119
+ const expires = input.expiresSeconds ?? now + NX_INVOKE_SIGNATURE_TTL_SECONDS;
120
+ if (!safeSeconds(now) || !safeSeconds(expires) || expires <= now || expires - now > NX_INVOKE_MAX_LIFETIME_SECONDS) {
121
+ throw new Error('invalid-signature-time');
122
+ }
123
+ if (!CLIENT_VERSION_RE.test(input.clientVersion))
124
+ throw new Error('invalid-client-version');
125
+ const target = splitRawTarget(input.target);
126
+ if (!target)
127
+ throw new Error('invalid-target');
128
+ const nonceBytes = input.nonceBytes ? Buffer.from(input.nonceBytes) : randomBytes(32);
129
+ if (nonceBytes.length !== 32)
130
+ throw new Error('invalid-nonce-length');
131
+ const nonce = nonceBytes.toString('base64url');
132
+ const key = deriveNxInvokeKey(input.bearerToken);
133
+ const keyId = deriveNxInvokeKeyId(key);
134
+ const metadata = { created: now, expires, nonce, keyId };
135
+ const signatureInput = buildNxInvokeSignatureInput(metadata);
136
+ const body = Buffer.from(input.body);
137
+ const digest = contentDigest(body);
138
+ const signatureBase = buildNxInvokeSignatureBase({
139
+ method: input.method,
140
+ target: input.target,
141
+ contentType: NX_INVOKE_CONTENT_TYPE,
142
+ contentDigest: digest.header,
143
+ clientVersion: input.clientVersion,
144
+ signatureInput,
145
+ });
146
+ const signature = createHmac('sha256', key).update(signatureBase).digest('base64');
147
+ key.fill(0);
148
+ return {
149
+ body,
150
+ metadata,
151
+ headers: Object.freeze({
152
+ 'Content-Type': NX_INVOKE_CONTENT_TYPE,
153
+ 'Content-Digest': digest.header,
154
+ 'Signature-Input': signatureInput,
155
+ Signature: `${NX_INVOKE_LABEL}=:${signature}:`,
156
+ 'X-Client-Version': input.clientVersion,
157
+ }),
158
+ signatureBase,
159
+ };
160
+ }
161
+ function fail(reason) {
162
+ return { ok: false, reason };
163
+ }
164
+ export function verifyNxInvokeRequest(input) {
165
+ if (input.contentType !== NX_INVOKE_CONTENT_TYPE)
166
+ return fail('invalid-content-type');
167
+ if (!CLIENT_VERSION_RE.test(input.clientVersion))
168
+ return fail('invalid-client-version');
169
+ if (!splitRawTarget(input.target))
170
+ return fail('invalid-target');
171
+ const metadata = parseNxInvokeSignatureInput(input.signatureInput);
172
+ if (!metadata)
173
+ return fail('invalid-profile');
174
+ const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);
175
+ if (!safeSeconds(now))
176
+ return fail('invalid-created');
177
+ if (metadata.expires <= metadata.created || metadata.expires - metadata.created > NX_INVOKE_MAX_LIFETIME_SECONDS) {
178
+ return fail('invalid-lifetime');
179
+ }
180
+ if (metadata.created > now + NX_INVOKE_CLOCK_SKEW_SECONDS || metadata.created < now - NX_INVOKE_MAX_AGE_SECONDS) {
181
+ return fail('signature-expired');
182
+ }
183
+ if (metadata.expires < now - NX_INVOKE_CLOCK_SKEW_SECONDS)
184
+ return fail('signature-expired');
185
+ if (!exactBase64Url32(metadata.nonce))
186
+ return fail('invalid-nonce');
187
+ const receivedKeyId = exactBase64Url32(metadata.keyId);
188
+ if (!receivedKeyId)
189
+ return fail('invalid-key-id');
190
+ const digestMatch = /^sha-256=:([A-Za-z0-9+/]{43}=):$/.exec(input.contentDigest);
191
+ const receivedDigest = digestMatch ? exactBase6432(digestMatch[1]) : null;
192
+ if (!receivedDigest)
193
+ return fail('invalid-content-digest');
194
+ const actualDigest = contentDigest(input.body).digest;
195
+ if (!timingSafeEqual(receivedDigest, actualDigest))
196
+ return fail('invalid-content-digest');
197
+ const signatureMatch = /^nx-invoke=:([A-Za-z0-9+/]{43}=):$/.exec(input.signature);
198
+ const receivedSignature = signatureMatch ? exactBase6432(signatureMatch[1]) : null;
199
+ if (!receivedSignature)
200
+ return fail('invalid-signature');
201
+ let key;
202
+ try {
203
+ key = deriveNxInvokeKey(input.bearerToken);
204
+ }
205
+ catch {
206
+ return fail('invalid-key-id');
207
+ }
208
+ const expectedKeyId = Buffer.from(deriveNxInvokeKeyId(key), 'base64url');
209
+ if (!timingSafeEqual(receivedKeyId, expectedKeyId)) {
210
+ key.fill(0);
211
+ return fail('invalid-key-id');
212
+ }
213
+ let signatureBase;
214
+ try {
215
+ signatureBase = buildNxInvokeSignatureBase({
216
+ method: input.method,
217
+ target: input.target,
218
+ contentType: input.contentType,
219
+ contentDigest: input.contentDigest,
220
+ clientVersion: input.clientVersion,
221
+ signatureInput: input.signatureInput,
222
+ });
223
+ }
224
+ catch {
225
+ key.fill(0);
226
+ return fail('invalid-profile');
227
+ }
228
+ const expectedSignature = createHmac('sha256', key).update(signatureBase).digest();
229
+ key.fill(0);
230
+ if (!timingSafeEqual(receivedSignature, expectedSignature))
231
+ return fail('invalid-signature');
232
+ return { ok: true, metadata, signatureBase };
233
+ }
@@ -0,0 +1,381 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const STRICT_READ_ONLY_POLICY = 'strict-read-only-v1';
3
+ export const STRICT_READ_ONLY_RECEIPT_SCHEMA = 'nx-strict-readonly-receipt-v1';
4
+ export const STRICT_READ_ONLY_SECURITY_SCHEMA = 'nx-security-admission-receipt-v1';
5
+ export const STRICT_READ_ONLY_MAX_DESCRIPTORS = 64;
6
+ export const STRICT_READ_ONLY_MAX_COUNT = 0x7fff_ffff;
7
+ export const STRICT_READ_ONLY_PHASES = ['client-preflight', 'security-admission', 'server-before', 'engine', 'server-after', 'client-writeback'];
8
+ export const STRICT_READ_ONLY_DOMAINS = ['client-persistence', 'server-business', 'external-effect', 'security-admission'];
9
+ export const STRICT_READ_ONLY_OPERATIONS = ['create', 'update', 'delete', 'consume', 'enqueue', 'spawn', 'send'];
10
+ export const STRICT_READ_ONLY_CATEGORIES = {
11
+ 'client-persistence': ['first-run-marker', 'auth-cache', 'tier-cache', 'state-repair', 'checkpoint-consume', 'pending-claim', 'continuity-store', 'state-writeback', 'scheduler-session'],
12
+ 'server-business': ['checkpoint-persist', 'quota', 'history', 'analytics', 'audit', 'evidence', 'funnel', 'scheduler-session'],
13
+ 'external-effect': ['telemetry', 'network-mutation', 'process', 'git'],
14
+ 'security-admission': ['replay-claim', 'rate-safety', 'abuse-protection'],
15
+ };
16
+ export class StrictReadOnlyError extends Error {
17
+ code;
18
+ retryable = false;
19
+ remediationClass;
20
+ constructor(code, remediationClass = 'integrity') {
21
+ super(code);
22
+ this.code = code;
23
+ this.name = 'StrictReadOnlyError';
24
+ this.remediationClass = remediationClass;
25
+ }
26
+ }
27
+ /**
28
+ * Closed projection manifests consumed by the client and server gateways.
29
+ * Keeping these descriptors beside the taxonomy prevents either gateway from
30
+ * growing an independent list of effects while preserving their separate
31
+ * command-registry admission decisions.
32
+ */
33
+ export const STRICT_READ_ONLY_CLIENT_PROJECTION = Object.freeze([
34
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'first-run-marker', operation: 'create' }),
35
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'auth-cache', operation: 'update' }),
36
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'tier-cache', operation: 'update' }),
37
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'state-repair', operation: 'update' }),
38
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'checkpoint-consume', operation: 'consume' }),
39
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'pending-claim', operation: 'update' }),
40
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'continuity-store', operation: 'create' }),
41
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'continuity-store', operation: 'update' }),
42
+ Object.freeze({ phase: 'client-writeback', domain: 'client-persistence', category: 'state-writeback', operation: 'update' }),
43
+ Object.freeze({ phase: 'client-preflight', domain: 'client-persistence', category: 'scheduler-session', operation: 'create' }),
44
+ Object.freeze({ phase: 'client-preflight', domain: 'external-effect', category: 'process', operation: 'spawn' }),
45
+ Object.freeze({ phase: 'client-preflight', domain: 'external-effect', category: 'git', operation: 'spawn' }),
46
+ Object.freeze({ phase: 'client-preflight', domain: 'external-effect', category: 'network-mutation', operation: 'send' }),
47
+ Object.freeze({ phase: 'client-writeback', domain: 'client-persistence', category: 'auth-cache', operation: 'update' }),
48
+ Object.freeze({ phase: 'client-writeback', domain: 'client-persistence', category: 'tier-cache', operation: 'update' }),
49
+ Object.freeze({ phase: 'client-writeback', domain: 'client-persistence', category: 'pending-claim', operation: 'update' }),
50
+ Object.freeze({ phase: 'client-writeback', domain: 'client-persistence', category: 'continuity-store', operation: 'update' }),
51
+ ]);
52
+ export const STRICT_READ_ONLY_SERVER_PROJECTION = Object.freeze([
53
+ Object.freeze({ phase: 'server-before', domain: 'server-business', category: 'checkpoint-persist', operation: 'create' }),
54
+ Object.freeze({ phase: 'server-before', domain: 'server-business', category: 'audit', operation: 'create' }),
55
+ Object.freeze({ phase: 'engine', domain: 'server-business', category: 'checkpoint-persist', operation: 'delete' }),
56
+ Object.freeze({ phase: 'engine', domain: 'server-business', category: 'audit', operation: 'create' }),
57
+ Object.freeze({ phase: 'server-before', domain: 'server-business', category: 'quota', operation: 'update' }),
58
+ Object.freeze({ phase: 'server-after', domain: 'server-business', category: 'history', operation: 'create' }),
59
+ Object.freeze({ phase: 'server-after', domain: 'server-business', category: 'analytics', operation: 'create' }),
60
+ Object.freeze({ phase: 'server-after', domain: 'server-business', category: 'audit', operation: 'create' }),
61
+ Object.freeze({ phase: 'server-after', domain: 'server-business', category: 'evidence', operation: 'create' }),
62
+ Object.freeze({ phase: 'server-after', domain: 'server-business', category: 'funnel', operation: 'create' }),
63
+ Object.freeze({ phase: 'server-after', domain: 'external-effect', category: 'telemetry', operation: 'send' }),
64
+ Object.freeze({ phase: 'server-after', domain: 'external-effect', category: 'network-mutation', operation: 'send' }),
65
+ Object.freeze({ phase: 'engine', domain: 'external-effect', category: 'process', operation: 'spawn' }),
66
+ Object.freeze({ phase: 'engine', domain: 'external-effect', category: 'git', operation: 'spawn' }),
67
+ Object.freeze({ phase: 'engine', domain: 'server-business', category: 'scheduler-session', operation: 'update' }),
68
+ ]);
69
+ const phaseSet = new Set(STRICT_READ_ONLY_PHASES);
70
+ const domainSet = new Set(STRICT_READ_ONLY_DOMAINS);
71
+ const operationSet = new Set(STRICT_READ_ONLY_OPERATIONS);
72
+ function exactKeys(value, keys) {
73
+ const actual = Object.keys(value).sort();
74
+ const expected = [...keys].sort();
75
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
76
+ }
77
+ export function parseStrictReadOnlyIntent(value) {
78
+ if (value === undefined || value === null)
79
+ return null;
80
+ if (!value || typeof value !== 'object' || Array.isArray(value))
81
+ throw new StrictReadOnlyError('readonly-policy-unsupported', 'policy');
82
+ const record = value;
83
+ if (!exactKeys(record, ['schemaVersion', 'policy', 'registryVersion', 'registryChecksum'])
84
+ || record.schemaVersion !== 1 || record.policy !== STRICT_READ_ONLY_POLICY
85
+ || typeof record.registryVersion !== 'string' || record.registryVersion.length < 1 || record.registryVersion.length > 32
86
+ || typeof record.registryChecksum !== 'string' || !/^[a-f0-9]{64}$/.test(record.registryChecksum)) {
87
+ throw new StrictReadOnlyError('readonly-policy-unsupported', 'policy');
88
+ }
89
+ return Object.freeze(record);
90
+ }
91
+ function validateDescriptor(input) {
92
+ if (!input || typeof input !== 'object' || Array.isArray(input)
93
+ || (!exactKeys(input, ['phase', 'domain', 'category', 'operation'])
94
+ && !exactKeys(input, ['phase', 'domain', 'category', 'operation', 'count']))) {
95
+ throw new StrictReadOnlyError('readonly-effect-unknown');
96
+ }
97
+ if (!phaseSet.has(input.phase) || !domainSet.has(input.domain) || !operationSet.has(input.operation)) {
98
+ throw new StrictReadOnlyError('readonly-effect-unknown');
99
+ }
100
+ const categories = STRICT_READ_ONLY_CATEGORIES[input.domain];
101
+ if (!categories.includes(input.category))
102
+ throw new StrictReadOnlyError('readonly-effect-unknown');
103
+ const count = input.count ?? 1;
104
+ if (!Number.isSafeInteger(count) || count < 1)
105
+ throw new StrictReadOnlyError('readonly-effect-unknown');
106
+ return Object.freeze({
107
+ phase: input.phase,
108
+ domain: input.domain,
109
+ category: input.category,
110
+ operation: input.operation,
111
+ count: Math.min(count, STRICT_READ_ONLY_MAX_COUNT),
112
+ });
113
+ }
114
+ function descriptorKey(descriptor) {
115
+ return `${descriptor.phase}\0${descriptor.domain}\0${descriptor.category}\0${descriptor.operation}`;
116
+ }
117
+ function sha256(value) {
118
+ return createHash('sha256').update(value, 'utf8').digest('hex');
119
+ }
120
+ export function canonicalizeInvokeEffects(descriptors) {
121
+ const aggregated = new Map();
122
+ for (const raw of descriptors) {
123
+ const descriptor = validateDescriptor(raw);
124
+ const key = descriptorKey(descriptor);
125
+ const prior = aggregated.get(key);
126
+ if (!prior && aggregated.size >= STRICT_READ_ONLY_MAX_DESCRIPTORS) {
127
+ throw new StrictReadOnlyError('readonly-projection-capacity-exceeded', 'boundary');
128
+ }
129
+ aggregated.set(key, Object.freeze({ ...descriptor, count: Math.min(STRICT_READ_ONLY_MAX_COUNT, (prior?.count ?? 0) + descriptor.count) }));
130
+ }
131
+ return Object.freeze([...aggregated.values()].sort((a, b) => Buffer.from(descriptorKey(a)).compare(Buffer.from(descriptorKey(b)))));
132
+ }
133
+ const capabilityOwner = new WeakMap();
134
+ const capabilityDescriptor = new WeakMap();
135
+ const consumedCapabilities = new WeakSet();
136
+ const CAPABILITY = Symbol('InvokeEffectCapability');
137
+ export class InvokeEffectContext {
138
+ invocationId;
139
+ mode;
140
+ intent;
141
+ registryGeneration;
142
+ requestFingerprintPrefix;
143
+ stateValue = 'created';
144
+ projected = [];
145
+ sealedProjection = null;
146
+ applied = new Set();
147
+ deniedCode;
148
+ constructor(invocationId, mode, intent, registryGeneration, requestFingerprintPrefix) {
149
+ this.invocationId = invocationId;
150
+ this.mode = mode;
151
+ this.intent = intent;
152
+ this.registryGeneration = registryGeneration;
153
+ this.requestFingerprintPrefix = requestFingerprintPrefix;
154
+ if (!/^[a-zA-Z0-9_-]{8,128}$/.test(invocationId) || !/^[a-f0-9]{8,32}$/.test(requestFingerprintPrefix)) {
155
+ throw new StrictReadOnlyError('readonly-authority-missing');
156
+ }
157
+ if ((mode === 'strict') !== (intent !== null))
158
+ throw new StrictReadOnlyError('readonly-authority-conflict');
159
+ }
160
+ get state() { return this.stateValue; }
161
+ get failureCode() { return this.deniedCode; }
162
+ beginProjection() {
163
+ if (this.stateValue !== 'created')
164
+ return this.deny('readonly-authority-conflict');
165
+ this.stateValue = 'projecting';
166
+ }
167
+ project(input) {
168
+ if (this.stateValue === 'sealed' || this.stateValue === 'strict_projected' || this.stateValue === 'committed')
169
+ return this.deny('readonly-late-effect-denied');
170
+ if (this.stateValue !== 'projecting')
171
+ return this.deny('readonly-authority-missing');
172
+ try {
173
+ const next = validateDescriptor(input);
174
+ canonicalizeInvokeEffects([...this.projected, next]);
175
+ this.projected.push(next);
176
+ }
177
+ catch (error) {
178
+ this.deny(error instanceof StrictReadOnlyError ? error.code : 'readonly-effect-unknown');
179
+ }
180
+ }
181
+ seal() {
182
+ if (this.stateValue !== 'projecting')
183
+ return this.deny('readonly-authority-conflict');
184
+ this.sealedProjection = canonicalizeInvokeEffects(this.projected);
185
+ this.stateValue = 'sealed';
186
+ return this.sealedProjection;
187
+ }
188
+ capability(input) {
189
+ if (this.stateValue !== 'sealed' || this.mode === 'strict' || !this.sealedProjection)
190
+ return this.deny('readonly-effect-unregistered');
191
+ const descriptor = validateDescriptor(input);
192
+ const key = descriptorKey(descriptor);
193
+ if (!this.sealedProjection.some((item) => descriptorKey(item) === key))
194
+ return this.deny('readonly-effect-unregistered');
195
+ const capability = Object.freeze({ [CAPABILITY]: true });
196
+ capabilityOwner.set(capability, this);
197
+ capabilityDescriptor.set(capability, key);
198
+ return capability;
199
+ }
200
+ apply(capability, effect) {
201
+ if (this.stateValue === 'denied')
202
+ throw new StrictReadOnlyError(this.deniedCode ?? 'readonly-authority-missing');
203
+ if (this.mode === 'strict')
204
+ return this.deny('readonly-effect-unregistered');
205
+ if (capabilityOwner.get(capability) !== this)
206
+ return this.deny('readonly-authority-conflict');
207
+ const key = capabilityDescriptor.get(capability);
208
+ if (!key || consumedCapabilities.has(capability))
209
+ return this.deny('readonly-effect-unregistered');
210
+ this.stateValue = 'applying';
211
+ let result;
212
+ try {
213
+ result = effect();
214
+ }
215
+ catch (error) {
216
+ this.stateValue = 'sealed';
217
+ throw error;
218
+ }
219
+ consumedCapabilities.add(capability);
220
+ this.applied.add(key);
221
+ this.stateValue = 'sealed';
222
+ return result;
223
+ }
224
+ async applyAsync(capability, effect) {
225
+ if (this.stateValue === 'denied')
226
+ throw new StrictReadOnlyError(this.deniedCode ?? 'readonly-authority-missing');
227
+ if (this.mode === 'strict')
228
+ return this.deny('readonly-effect-unregistered');
229
+ if (capabilityOwner.get(capability) !== this)
230
+ return this.deny('readonly-authority-conflict');
231
+ const key = capabilityDescriptor.get(capability);
232
+ if (!key || consumedCapabilities.has(capability))
233
+ return this.deny('readonly-effect-unregistered');
234
+ this.stateValue = 'applying';
235
+ try {
236
+ const result = await effect();
237
+ consumedCapabilities.add(capability);
238
+ this.applied.add(key);
239
+ this.stateValue = 'sealed';
240
+ return result;
241
+ }
242
+ catch (error) {
243
+ this.stateValue = 'sealed';
244
+ throw error;
245
+ }
246
+ }
247
+ execute(descriptor, effect, projected) {
248
+ if (this.stateValue !== 'sealed')
249
+ return this.deny('readonly-authority-missing');
250
+ this.assertRegisteredDescriptor(descriptor);
251
+ if (this.mode === 'strict')
252
+ return projected();
253
+ return this.apply(this.capability(descriptor), effect);
254
+ }
255
+ executeAsync(descriptor, effect, projected) {
256
+ if (this.stateValue !== 'sealed')
257
+ return Promise.reject(new StrictReadOnlyError('readonly-authority-missing'));
258
+ try {
259
+ this.assertRegisteredDescriptor(descriptor);
260
+ }
261
+ catch (error) {
262
+ return Promise.reject(error);
263
+ }
264
+ if (this.mode === 'strict')
265
+ return projected();
266
+ return this.applyAsync(this.capability(descriptor), effect);
267
+ }
268
+ complete(security = emptySecurityAdmissionReceipt()) {
269
+ if (this.stateValue !== 'sealed' || !this.sealedProjection)
270
+ return this.deny('readonly-receipt-mismatch');
271
+ this.stateValue = this.mode === 'strict' ? 'strict_projected' : 'committed';
272
+ const canonical = JSON.stringify(this.sealedProjection);
273
+ return Object.freeze({
274
+ schema: STRICT_READ_ONLY_RECEIPT_SCHEMA,
275
+ policy: this.intent?.policy ?? null,
276
+ effectiveMode: this.mode,
277
+ registryGeneration: this.registryGeneration.slice(0, 32),
278
+ requestFingerprintPrefix: this.requestFingerprintPrefix,
279
+ projectionFingerprint: sha256(`nx-effect-projection-v1\0${canonical}`),
280
+ descriptorCount: this.sealedProjection.length,
281
+ wouldHaveMutated: this.sealedProjection,
282
+ security,
283
+ completed: true,
284
+ projectBusinessMutation: this.mode === 'normal' && this.applied.size > 0,
285
+ });
286
+ }
287
+ assertReceiptBinding(receipt) {
288
+ if (!this.sealedProjection
289
+ || receipt.registryGeneration !== this.registryGeneration.slice(0, 32)
290
+ || JSON.stringify(receipt.wouldHaveMutated) !== JSON.stringify(this.sealedProjection)) {
291
+ throw new StrictReadOnlyError('readonly-receipt-mismatch');
292
+ }
293
+ }
294
+ deny(code) {
295
+ this.stateValue = 'denied';
296
+ this.deniedCode = code;
297
+ throw new StrictReadOnlyError(code);
298
+ }
299
+ assertRegisteredDescriptor(input) {
300
+ if (!this.sealedProjection)
301
+ return this.deny('readonly-authority-missing');
302
+ let descriptor;
303
+ try {
304
+ descriptor = validateDescriptor(input);
305
+ }
306
+ catch (error) {
307
+ return this.deny(error instanceof StrictReadOnlyError ? error.code : 'readonly-effect-unknown');
308
+ }
309
+ const key = descriptorKey(descriptor);
310
+ if (!this.sealedProjection.some((item) => descriptorKey(item) === key)) {
311
+ return this.deny('readonly-effect-unregistered');
312
+ }
313
+ }
314
+ }
315
+ export function emptySecurityAdmissionReceipt() {
316
+ return Object.freeze({ schema: STRICT_READ_ONLY_SECURITY_SCHEMA, replay: 'not-applicable', rate: 'not-applicable', abuse: 'not-applicable' });
317
+ }
318
+ export function verifyStrictReadOnlyReceipt(expected, value) {
319
+ if (!value || typeof value !== 'object' || Array.isArray(value))
320
+ throw new StrictReadOnlyError('readonly-receipt-mismatch');
321
+ const receipt = value;
322
+ if (!exactKeys(value, [
323
+ 'schema', 'policy', 'effectiveMode', 'registryGeneration', 'requestFingerprintPrefix',
324
+ 'projectionFingerprint', 'descriptorCount', 'wouldHaveMutated', 'security', 'completed',
325
+ 'projectBusinessMutation',
326
+ ])
327
+ || !receipt.security || typeof receipt.security !== 'object' || Array.isArray(receipt.security)
328
+ || !exactKeys(receipt.security, ['schema', 'replay', 'rate', 'abuse'])
329
+ || receipt.security.schema !== STRICT_READ_ONLY_SECURITY_SCHEMA
330
+ || !['accepted', 'not-applicable'].includes(receipt.security.replay)
331
+ || !['accepted', 'not-applicable'].includes(receipt.security.rate)
332
+ || !['accepted', 'not-applicable'].includes(receipt.security.abuse)
333
+ || receipt.schema !== STRICT_READ_ONLY_RECEIPT_SCHEMA || receipt.effectiveMode !== expected.mode
334
+ || receipt.policy !== expected.intent?.policy || receipt.requestFingerprintPrefix !== expected.requestFingerprintPrefix
335
+ || receipt.completed !== true || (expected.mode === 'strict' && receipt.projectBusinessMutation !== false)
336
+ || !Array.isArray(receipt.wouldHaveMutated) || receipt.descriptorCount !== receipt.wouldHaveMutated.length
337
+ || receipt.descriptorCount > STRICT_READ_ONLY_MAX_DESCRIPTORS
338
+ || receipt.projectionFingerprint !== sha256(`nx-effect-projection-v1\0${JSON.stringify(canonicalizeInvokeEffects(receipt.wouldHaveMutated))}`)) {
339
+ throw new StrictReadOnlyError('readonly-receipt-mismatch');
340
+ }
341
+ expected.assertReceiptBinding(receipt);
342
+ }
343
+ export function createInvokeEffectContext(input) {
344
+ const intent = parseStrictReadOnlyIntent(input.intent);
345
+ if (intent && (!input.registryAllowsStrict))
346
+ throw new StrictReadOnlyError('readonly-registry-denied', 'policy');
347
+ if (intent && (intent.registryVersion !== input.registryVersion || intent.registryChecksum !== input.registryChecksum)) {
348
+ throw new StrictReadOnlyError('readonly-authority-conflict');
349
+ }
350
+ return new InvokeEffectContext(input.invocationId, intent ? 'strict' : 'normal', intent, `${input.registryVersion}:${input.registryChecksum.slice(0, 12)}`, sha256(`nx-request-public-prefix-v1\0${input.requestCanonicalBytes}`).slice(0, 16));
351
+ }
352
+ export const STRICT_PROCESS_PROFILES = Object.freeze({
353
+ 'node-version': Object.freeze({ executable: 'node', argv: ['--version'] }),
354
+ 'git-status': Object.freeze({ executable: 'git', argv: ['status', '--short', '--branch'] }),
355
+ 'git-rev-parse': Object.freeze({ executable: 'git', argv: ['rev-parse', '--show-toplevel'] }),
356
+ });
357
+ export function resolveClosedProcessProfile(profile, cwd) {
358
+ const selected = STRICT_PROCESS_PROFILES[profile];
359
+ const absoluteCwd = cwd.startsWith('/') || /^[A-Za-z]:[\\/]/u.test(cwd);
360
+ if (!selected || !absoluteCwd || /[\r\n\0]/u.test(cwd) || cwd.split(/[\\/]/u).includes('..')) {
361
+ throw new StrictReadOnlyError('readonly-physical-boundary-unsafe', 'boundary');
362
+ }
363
+ const env = profile.startsWith('git-')
364
+ ? Object.freeze({
365
+ PATH: '/usr/bin:/bin',
366
+ GIT_OPTIONAL_LOCKS: '0',
367
+ GIT_CONFIG_NOSYSTEM: '1',
368
+ GIT_CONFIG_GLOBAL: '/dev/null',
369
+ GIT_CONFIG_COUNT: '4',
370
+ GIT_CONFIG_KEY_0: 'core.hooksPath', GIT_CONFIG_VALUE_0: '/dev/null',
371
+ GIT_CONFIG_KEY_1: 'core.fsmonitor', GIT_CONFIG_VALUE_1: 'false',
372
+ GIT_CONFIG_KEY_2: 'maintenance.auto', GIT_CONFIG_VALUE_2: 'false',
373
+ GIT_CONFIG_KEY_3: 'gc.auto', GIT_CONFIG_VALUE_3: '0',
374
+ })
375
+ : Object.freeze({ PATH: '/usr/bin:/bin' });
376
+ return Object.freeze({ ...selected, cwd, shell: false, timeoutMs: 10_000, env });
377
+ }
378
+ export function assertDeclaredNetworkTransport(kind) {
379
+ if (kind !== 'invoke-query')
380
+ throw new StrictReadOnlyError('readonly-physical-boundary-unsafe', 'boundary');
381
+ }