auroq-os 2.5.10 → 2.5.12

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.
@@ -123,9 +123,8 @@ class PipelineMetrics {
123
123
  * Record that a layer encountered an error.
124
124
  *
125
125
  * @param {string} name - Layer name
126
- * @param {Error} error - The error object
127
126
  */
128
- errorLayer(name, error) {
127
+ errorLayer(name) {
129
128
  const existing = this.layers[name] || {};
130
129
  if (existing.start) {
131
130
  const endTime = process.hrtime.bigint();
@@ -135,7 +134,7 @@ class PipelineMetrics {
135
134
  this.layers[name] = {
136
135
  ...existing,
137
136
  status: 'error',
138
- error: error && error.message ? error.message : String(error),
137
+ errorCode: 'SYNAPSE_LAYER_FAILED',
139
138
  };
140
139
  }
141
140
 
@@ -170,8 +169,27 @@ class PipelineMetrics {
170
169
  // SynapseEngine
171
170
  // ---------------------------------------------------------------------------
172
171
 
173
- /** Hard pipeline timeout in milliseconds. */
172
+ /** Cooperative budget between layers; synchronous work cannot be interrupted. */
174
173
  const PIPELINE_TIMEOUT_MS = 100;
174
+ // Keep substantial headroom below the outer hook's 5000 ms safety timeout.
175
+ const MAX_PIPELINE_TIMEOUT_MS = 1000;
176
+ const SYNAPSE_PIPELINE_TIMEOUT_ENV = 'AUROQ_SYNAPSE_PIPELINE_TIMEOUT_MS';
177
+
178
+ /** Resolve a short pipeline budget. Invalid values preserve the safe default. */
179
+ function resolvePipelineTimeoutMs(config = {}) {
180
+ const fromEnv = process.env[SYNAPSE_PIPELINE_TIMEOUT_ENV];
181
+ const value = fromEnv !== undefined && fromEnv !== ''
182
+ ? fromEnv
183
+ : config.synapse?.pipelineTimeoutMs;
184
+ if (value === undefined || value === null) return PIPELINE_TIMEOUT_MS;
185
+ const timeout = typeof value === 'number' || typeof value === 'string' ? Number(value) : NaN;
186
+ if (!Number.isInteger(timeout) || timeout <= 0 || timeout > MAX_PIPELINE_TIMEOUT_MS) {
187
+ // Do not echo invalid values: environment/config may contain private text.
188
+ console.warn('[synapse:engine] SYNAPSE_INVALID_PIPELINE_TIMEOUT');
189
+ return PIPELINE_TIMEOUT_MS;
190
+ }
191
+ return timeout;
192
+ }
175
193
 
176
194
  /**
177
195
  * NOG-18: Default active layers (L0-L2 only).
@@ -250,8 +268,13 @@ class SynapseEngine {
250
268
  async process(prompt, session, processConfig) {
251
269
  const safeProcessConfig = (processConfig && typeof processConfig === 'object') ? processConfig : {};
252
270
  const mergedConfig = { ...this.config, ...safeProcessConfig };
271
+ mergedConfig.synapse = { ...(this.config.synapse || {}), ...(safeProcessConfig.synapse || {}) };
272
+ const pipelineTimeoutMs = resolvePipelineTimeoutMs(mergedConfig);
273
+ const pipelineNow = typeof safeProcessConfig.nowNs === 'function'
274
+ ? safeProcessConfig.nowNs
275
+ : process.hrtime.bigint;
253
276
  const metrics = new PipelineMetrics();
254
- metrics.totalStart = process.hrtime.bigint();
277
+ metrics.totalStart = pipelineNow();
255
278
 
256
279
  // 1. Calculate bracket (or use fixed layers in non-legacy mode)
257
280
  const promptCount = (session && session.prompt_count) || 0;
@@ -266,7 +289,7 @@ class SynapseEngine {
266
289
 
267
290
  // Guard: no layer config (invalid bracket — should not happen)
268
291
  if (!layerConfig) {
269
- metrics.totalEnd = process.hrtime.bigint();
292
+ metrics.totalEnd = pipelineNow();
270
293
  return { xml: '', metrics: metrics.getSummary() };
271
294
  }
272
295
  activeLayers = layerConfig.layers;
@@ -291,8 +314,8 @@ class SynapseEngine {
291
314
  continue;
292
315
  }
293
316
 
294
- // Check hard pipeline timeout (convert hrtime to ms for comparison)
295
- if (Number(process.hrtime.bigint() - metrics.totalStart) / 1e6 > PIPELINE_TIMEOUT_MS) {
317
+ // Cooperative check between layers; the host owns interruption of a stuck process.
318
+ if (Number(pipelineNow() - metrics.totalStart) / 1e6 >= pipelineTimeoutMs) {
296
319
  // Log remaining layers as skipped
297
320
  const remaining = this.layers.slice(this.layers.indexOf(layer));
298
321
  for (const r of remaining) {
@@ -314,14 +337,28 @@ class SynapseEngine {
314
337
  previousLayers,
315
338
  });
316
339
 
317
- const result = layer._safeProcess(context);
340
+ let result;
341
+ try {
342
+ result = layer._safeProcess(context);
343
+ } catch (_error) {
344
+ metrics.errorLayer(layer.name);
345
+ continue;
346
+ }
318
347
 
319
348
  if (result && Array.isArray(result.rules)) {
320
349
  metrics.endLayer(layer.name, result.rules.length);
321
350
  results.push(result);
322
351
  previousLayers.push(result);
323
352
  } else if (result === null || result === undefined) {
324
- metrics.skipLayer(layer.name, 'Returned null');
353
+ try {
354
+ if (typeof layer.getLastError === 'function' && layer.getLastError()) {
355
+ metrics.errorLayer(layer.name);
356
+ } else {
357
+ metrics.skipLayer(layer.name, 'Returned null');
358
+ }
359
+ } catch (_error) {
360
+ metrics.errorLayer(layer.name);
361
+ }
325
362
  } else {
326
363
  metrics.skipLayer(layer.name, 'Invalid result format');
327
364
  }
@@ -341,8 +378,9 @@ class SynapseEngine {
341
378
  }
342
379
  }
343
380
 
344
- metrics.totalEnd = process.hrtime.bigint();
381
+ metrics.totalEnd = pipelineNow();
345
382
  const summary = metrics.getSummary();
383
+ summary.pipeline_timeout_ms = pipelineTimeoutMs;
346
384
 
347
385
  // Persist hook metrics (fire-and-forget)
348
386
  this._persistHookMetrics(summary, bracket, mergedConfig);
@@ -382,6 +420,7 @@ class SynapseEngine {
382
420
  const hookBootMs = hookBootTime ? Number(process.hrtime.bigint() - hookBootTime) / 1e6 : 0;
383
421
  const data = {
384
422
  totalDuration: summary.total_ms,
423
+ pipelineTimeoutMs: summary.pipeline_timeout_ms,
385
424
  hookBootMs,
386
425
  bracket,
387
426
  layersLoaded: summary.layers_loaded,
@@ -397,6 +436,7 @@ class SynapseEngine {
397
436
  duration: info.duration || 0,
398
437
  status: info.status || 'unknown',
399
438
  rules: info.rules || 0,
439
+ ...(info.errorCode ? { errorCode: info.errorCode } : {}),
400
440
  };
401
441
  }
402
442
  fs.writeFileSync(
@@ -413,4 +453,7 @@ module.exports = {
413
453
  SynapseEngine,
414
454
  PipelineMetrics,
415
455
  PIPELINE_TIMEOUT_MS,
456
+ MAX_PIPELINE_TIMEOUT_MS,
457
+ SYNAPSE_PIPELINE_TIMEOUT_ENV,
458
+ resolvePipelineTimeoutMs,
416
459
  };
@@ -34,6 +34,7 @@ class LayerProcessor {
34
34
  this.name = name;
35
35
  this.layer = layer;
36
36
  this.timeout = timeout;
37
+ this._lastError = null;
37
38
  }
38
39
 
39
40
  /**
@@ -65,6 +66,7 @@ class LayerProcessor {
65
66
  */
66
67
  _safeProcess(context) {
67
68
  const start = Date.now();
69
+ this._lastError = null;
68
70
  try {
69
71
  const result = this.process(context);
70
72
  const elapsed = Date.now() - start;
@@ -72,11 +74,18 @@ class LayerProcessor {
72
74
  console.warn(`[synapse:${this.name}] Warning: Layer exceeded timeout (${elapsed}ms > ${this.timeout}ms)`);
73
75
  }
74
76
  return result;
75
- } catch (error) {
76
- console.warn(`[synapse:${this.name}] Error: ${error.message}`);
77
+ } catch (_error) {
78
+ // Raw error messages/stacks can contain prompt or domain content.
79
+ this._lastError = new Error('SYNAPSE_LAYER_FAILED');
80
+ console.warn(`[synapse:${this.name}] SYNAPSE_LAYER_FAILED`);
77
81
  return null;
78
82
  }
79
83
  }
84
+
85
+ /** Return a content-free failure marker for the engine's metrics. */
86
+ getLastError() {
87
+ return this._lastError;
88
+ }
80
89
  }
81
90
 
82
91
  module.exports = LayerProcessor;
@@ -6,25 +6,29 @@ const fs = require('fs');
6
6
  const DEFAULT_STALE_TTL_HOURS = 168; // 7 days
7
7
 
8
8
  /**
9
- * Read stale session TTL from core-config.yaml.
10
- * Falls back to DEFAULT_STALE_TTL_HOURS (168h = 7 days).
9
+ * Read optional project configuration without disclosing parse failures.
11
10
  *
12
11
  * @param {string} cwd - Working directory
13
- * @returns {number} TTL in hours
12
+ * @returns {object} Configuration or an empty object
14
13
  */
15
- function getStaleSessionTTL(cwd) {
14
+ function loadCoreConfig(cwd) {
16
15
  try {
17
- const yaml = require('js-yaml');
18
16
  const configPath = path.join(cwd, '.auroq-core', 'core-config.yaml');
19
- if (!fs.existsSync(configPath)) return DEFAULT_STALE_TTL_HOURS;
17
+ if (!fs.existsSync(configPath)) return {};
18
+ const yaml = require('js-yaml');
20
19
  const config = yaml.load(fs.readFileSync(configPath, 'utf8'));
21
- const ttl = config && config.synapse && config.synapse.session && config.synapse.session.staleTTLHours;
22
- return typeof ttl === 'number' && ttl > 0 ? ttl : DEFAULT_STALE_TTL_HOURS;
20
+ return config && typeof config === 'object' ? config : {};
23
21
  } catch (_err) {
24
- return DEFAULT_STALE_TTL_HOURS;
22
+ // YAML errors can contain configuration values; never log their text.
23
+ return {};
25
24
  }
26
25
  }
27
26
 
27
+ function getStaleSessionTTL(config) {
28
+ const ttl = config && config.synapse && config.synapse.session && config.synapse.session.staleTTLHours;
29
+ return typeof ttl === 'number' && ttl > 0 ? ttl : DEFAULT_STALE_TTL_HOURS;
30
+ }
31
+
28
32
  /**
29
33
  * Resolve runtime dependencies for Synapse hook execution.
30
34
  *
@@ -46,7 +50,7 @@ function resolveHookRuntime(input) {
46
50
  if (!fs.existsSync(synapsePath)) return null;
47
51
 
48
52
  try {
49
- const { loadSession, cleanStaleSessions } = require(
53
+ const { loadSession, createSession, cleanStaleSessions } = require(
50
54
  path.join(cwd, '.auroq-core', 'core', 'synapse', 'session', 'session-manager.js'),
51
55
  );
52
56
  const { SynapseEngine } = require(
@@ -54,13 +58,21 @@ function resolveHookRuntime(input) {
54
58
  );
55
59
 
56
60
  const sessionsDir = path.join(synapsePath, 'sessions');
57
- const session = loadSession(sessionId, sessionsDir) || { prompt_count: 0 };
58
- const engine = new SynapseEngine(synapsePath);
61
+ let session = sessionId ? loadSession(sessionId, sessionsDir) : null;
62
+ // loadSession validates the ID before we use it as a filename. Create only
63
+ // a missing session: an unreadable/corrupt existing file is kept intact.
64
+ if (!session && sessionId && !fs.existsSync(path.join(sessionsDir, `${sessionId}.json`))) {
65
+ session = createSession(sessionId, cwd, sessionsDir);
66
+ }
67
+ const shouldCleanStaleSessions = session?.prompt_count === 0;
68
+ session = session || { prompt_count: 0 };
69
+ const coreConfig = loadCoreConfig(cwd);
70
+ const engine = new SynapseEngine(synapsePath, { synapse: coreConfig.synapse || {} });
59
71
 
60
72
  // AC3: Run cleanup on first prompt only (fire-and-forget)
61
- if (session.prompt_count === 0) {
73
+ if (shouldCleanStaleSessions) {
62
74
  try {
63
- const ttlHours = getStaleSessionTTL(cwd);
75
+ const ttlHours = getStaleSessionTTL(coreConfig);
64
76
  const removed = cleanStaleSessions(sessionsDir, ttlHours);
65
77
  if (removed > 0 && process.env.DEBUG === '1') {
66
78
  console.error(`[hook-runtime] Cleaned ${removed} stale session(s) (TTL: ${ttlHours}h)`);
@@ -73,7 +85,7 @@ function resolveHookRuntime(input) {
73
85
  return { engine, session, sessionId, sessionsDir, cwd };
74
86
  } catch (error) {
75
87
  if (process.env.DEBUG === '1') {
76
- console.error(`[hook-runtime] Failed to resolve runtime: ${error.message}`);
88
+ console.error('[hook-runtime] SYNAPSE_RUNTIME_FAILED');
77
89
  }
78
90
  return null;
79
91
  }
@@ -1,7 +1,7 @@
1
1
  project:
2
2
  name: "Auroq OS"
3
3
  type: greenfield
4
- version: 2.5.10
4
+ version: 2.5.12
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 2.5.12 — 2026-09-21
2
+
3
+ - SYNAPSE persiste a sessao desde o primeiro prompt, limita o orcamento total entre camadas e distingue falhas sem expor mensagens/conteudo privado. Mantem manifest, eventos dos hosts e camadas opcionais desligadas.
4
+ - Pacote real passa por inventario SHA-256 apos a casca, arquivos obrigatorios/proibidos e referencias locais do runtime. O empacotamento restaura o checkout, inclusive em falhas. Integridade de conteudo nao equivale a assinatura de autoria.
5
+ - Atualiza js-yaml para 4.3.2, corrigindo avisos de consumo excessivo de CPU no parser usado pelo motor.
6
+ - Manutencao reconhece as versoes oficiais anteriores do SYNAPSE e preserva arquivos personalizados e dados do negocio. Mantem arcane-mcp@0.6.1.
7
+ - Instaladores preparados para esta versao; copias publicas so sao promovidas depois dos gates nativos macOS/Windows Node 22/24 e da verificacao no npm. Maquinas existentes atualizam pelo fluxo de manutencao.
8
+
9
+ ## 2.5.11 — 2026-09-21
10
+
11
+ ### O diagnostico passa a dizer o que achou (EMB-050)
12
+
13
+ - Sair com 1 no `doctor` nao e mais `INSTALL_FAILED`: agora e `DOCTOR_FINDINGS`. Pendencia encontrada e o diagnostico trabalhando, nao instalacao quebrada — na janela de 20/09 isso gerou 3 alarmes de gravidade maxima para zero instalacoes quebradas.
14
+ - O `doctor` grava o resultado em `~/.auroq/install.log`: `pendencias=N etiqueta,etiqueta` quando acha algo e `ok` quando fecha limpo. As etiquetas sao vocabulario fechado (`arquivos-faltando`, `motor-fora-do-perfil`, `sem-login`, ...) e nunca levam caminho, e-mail, token nem nome de maquina. O suporte pede uma linha: `cat ~/.auroq/install.log`.
15
+ - Registro MCP passa a `arcane-mcp@0.6.1` (publicado no npm em 21/09/2026), que conhece o codigo novo: os dois clientes compartilham a mesma fila local e qualquer um dos dois a envia, entao a lista de codigos tem que ser a mesma nos dois e no servidor. Feche e abra o Claude Code e o Codex depois da manutencao.
16
+ - Instaladores oficiais continuam fixando 2.5.10; promover a versao que instalacoes NOVAS recebem segue sendo ato explicito.
17
+
1
18
  ## 2.5.10 — 2026-09-19
2
19
 
3
20
  ### Conteudo ao vivo passa a usar o cliente 0.6.0
package/README.md CHANGED
@@ -228,14 +228,14 @@ agents/ → Seu exercito (companion, workers, minds, squads)
228
228
  *Auroq OS — by Euriler Jube / Arka*
229
229
 
230
230
 
231
- ## Observabilidade do motor (candidato 2.5.2)
231
+ ## Observabilidade e integridade do motor
232
232
 
233
- Apos publicacao, `npx -y auroq-os@2.5.10 doctor` mostra a fila local segura e os comandos pinados de
234
- manutencao/diagnostico. `npx -y auroq-os@2.5.10 manutencao` preserva o caminho existente
233
+ `npx -y auroq-os@2.5.12 doctor` mostra a fila local segura e os comandos pinados de
234
+ manutencao/diagnostico. `npx -y auroq-os@2.5.12 manutencao` preserva o caminho existente
235
235
  de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
236
- migracao forcada. O registro MCP aponta para `arcane-mcp@0.6.0`, publicado no npm em
237
- 19/09/2026; `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
238
- Os instaladores oficiais devem fixar 2.5.10 depois da publicacao e da verificacao do tarball.
236
+ migracao forcada. O registro MCP aponta para `arcane-mcp@0.6.1`, publicado no npm em
237
+ 21/09/2026; `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
238
+ A versao 2.5.12 so deve chegar aos instaladores publicos apos os gates nativos e a verificacao do tarball publicado. O pacote inclui `lib/runtime-integrity.json`: inventario SHA-256 gerado depois da montagem da casca, verificavel com `npm run integrity:verify` dentro do pacote extraido. Ele comprova consistencia do conteudo; nao e uma assinatura de autoria.
239
239
 
240
240
  O CLI inicia captura antes das dependencias pesadas e registra resultado ao sair.
241
241
  Eventos com identidade ja existente podem seguir pela rota autenticada na proxima
@@ -254,8 +254,7 @@ instalacao/manutencao/doctor/login/register-mcp. Bootstrap conversacional e o tr
254
254
  final do squad nao sao inferidos como sucesso por sair do CLI ou preparar arquivos.
255
255
  Antes de o script chegar ou em computador que nunca executa nosso codigo, nao ha sinal.
256
256
  A rota events continua autenticada, limitada e idempotente. A plataforma deve receber
257
- a whitelist antes de distribuir/promover os clientes; os instaladores sincronizados
258
- mantem o pin 2.5.1 publicado ate a promocao npm ser possivel. Nao precisa migration adicional.
257
+ a whitelist antes de distribuir/promover os clientes; a promocao dos instaladores sincronizados depende da publicacao e verificacao da versao fixada. Nao precisa migration adicional.
259
258
 
260
259
  O canal de adocao e o instalador oficial, o README/changelog, o doctor e o aviso de
261
260
  comando aposentado quando a versao nova e executada. Isso nao atualiza silenciosamente
package/bin/auroq-os.js CHANGED
@@ -20,7 +20,10 @@ async function observedMain() {
20
20
  const record = (outcome, error_code) => telemetry.enqueue(owner,{endpoint:'lifecycle',stage,outcome,error_code});
21
21
  telemetry.setHost('auroq-os');telemetry.importInstallerSpool();record('started');
22
22
  let importing = true;
23
- process.once('exit',code=>record(code===0?'ok':'error',code===0?null:importing?'BOOTSTRAP_IMPORT':stage==='update'?'UPDATE_FAILED':'INSTALL_FAILED'));
23
+ // Sair com 1 no `doctor` e o diagnostico fazendo o trabalho dele, nao a instalacao quebrando:
24
+ // `DOCTOR_FINDINGS` = achou pendencia pra resolver. Chamar isso de INSTALL_FAILED gerava alarme
25
+ // de gravidade maxima pra aluno com o caminho inteiro de pe (Marinei e Alessandra, 20/09).
26
+ process.once('exit',code=>record(code===0?'ok':'error',code===0?null:importing?'BOOTSTRAP_IMPORT':stage==='update'?'UPDATE_FAILED':stage==='doctor'?'DOCTOR_FINDINGS':'INSTALL_FAILED'));
24
27
  process.once('uncaughtException',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
25
28
  process.once('unhandledRejection',()=>{console.error('[auroq-os] RUNTIME_FAILURE');process.exit(1);});
26
29
  const cred = credentials.load();await telemetry.flush(cred?.user?.id,cred?.access_token);
@@ -28,7 +31,7 @@ async function observedMain() {
28
31
  telemetry.runtimeReady=()=>{importing=false;};
29
32
  runRuntime(telemetry);
30
33
  } catch {
31
- console.error('[auroq-os] '+(importing?'BOOTSTRAP_IMPORT':'RUNTIME_FAILURE')+'. Rode: npx -y auroq-os@2.5.10 doctor');
34
+ console.error('[auroq-os] '+(importing?'BOOTSTRAP_IMPORT':'RUNTIME_FAILURE')+'. Rode: npx -y auroq-os@2.5.12 doctor');
32
35
  process.exitCode=1;
33
36
  }
34
37
  }
@@ -214,13 +217,45 @@ function registrarInstallLog(evento, detalhe = '') {
214
217
  const dir = path.join(require('os').homedir(), '.auroq');
215
218
  fs.ensureDirSync(dir);
216
219
  const quando = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
217
- const limpo = String(detalhe).replace(/[^\w .:=+/\\-]/g, '');
220
+ const limpo = String(detalhe).replace(/[^\w ,.:=+/\\-]/g, '');
218
221
  fs.appendFileSync(path.join(dir, 'install.log'), `${quando}\t${evento}\t${limpo}\n`);
219
222
  } catch {
220
223
  /* registro e conveniencia pro suporte */
221
224
  }
222
225
  }
223
226
 
227
+ /**
228
+ * Etiqueta curta e FIXA pra cada pendencia que o `doctor` lista (EMB-050).
229
+ *
230
+ * O suporte precisa saber "falhou no que", nao so "falhou": a lista do doctor morria na tela do
231
+ * aluno e o plantao ficava adivinhando (da Alessandra nunca soubemos qual item era). A etiqueta e
232
+ * vocabulario fechado de proposito — nunca caminho, e-mail, token, nome de maquina nem o texto da
233
+ * pendencia. O casamento e por trecho estavel da mensagem; o teste `doctor-recibo` reprova qualquer
234
+ * pendencia nova que caia em `outro`.
235
+ */
236
+ const ETIQUETAS_DOCTOR = [
237
+ ['repor o que falta', 'arquivos-faltando'],
238
+ ['sem tirar nada seu', 'synapse-nao-registrado'],
239
+ ['Synapse antigo', 'synapse-antigo'],
240
+ ['no Windows, install.ps1', 'motor-ausente'],
241
+ ['FECHE e abra', 'motor-fora-do-perfil'],
242
+ ['2 minutos de espera', 'mcp-timeout-curto'],
243
+ ['com o tempo certo', 'codex-timeout-curto'],
244
+ ['mais tempo ao Codex', 'arranque-lento'],
245
+ ['reescreve o registro)', 'mcp-nao-registrado'],
246
+ ['register-mcp na pasta. Depois', 'mcp-pin-antigo'],
247
+ ['arcane-mcp login', 'sem-login'],
248
+ ['fora do ar', 'servidor-fora'],
249
+ ['Confira sua internet', 'servidor-fora'],
250
+ ['whoami', 'mcp-nao-abre'],
251
+ ['tela INTEIRA', 'codex-sem-conteudo'],
252
+ ];
253
+ function rotulo(pendencia) {
254
+ const texto = String(pendencia || '');
255
+ for (const [marca, etiqueta] of ETIQUETAS_DOCTOR) if (texto.includes(marca)) return etiqueta;
256
+ return 'outro';
257
+ }
258
+
224
259
  /**
225
260
  * Identificador curto e nao reversivel da maquina — so pro suporte casar dois
226
261
  * registros do mesmo computador. Nao sai da maquina e nao contem nome nem e-mail.
@@ -503,12 +538,22 @@ async function runDoctor(targetDir) {
503
538
  }
504
539
 
505
540
  log('');
541
+ // Recibo duravel pro suporte, inclusive quando fechou limpo: sem o historico completo o plantao
542
+ // nao sabe se a pendencia de ontem foi resolvida. Conveniencia — nunca pode derrubar o doctor,
543
+ // por isso montar o texto tambem fica DENTRO do try.
544
+ const recibo = pendencias => {
545
+ try {
546
+ registrarInstallLog('doctor', pendencias.length ? `pendencias=${pendencias.length} ${pendencias.map(p => rotulo(p)).join(',')}` : 'ok');
547
+ } catch { /* recibo e conveniencia pro suporte */ }
548
+ };
506
549
  if (problemas.length === 0) {
507
550
  success('Tudo certo. Pode chamar o Companion.');
551
+ recibo(problemas);
508
552
  return 0;
509
553
  }
510
554
  error(`${problemas.length} coisa(s) pra resolver:`);
511
555
  for (const p of problemas) log(` ${DIM}- ${p}${RESET}`);
556
+ recibo(problemas);
512
557
  log('');
513
558
  log(`${DIM}Se travar, chame o suporte: 11 93621-4486${RESET}`);
514
559
  return 1;
@@ -524,7 +569,7 @@ async function runDoctor(targetDir) {
524
569
  // 2.5.4/2.5.5 pinaram uma versao que ainda nao estava no npm e o MCP fechava a conexao.
525
570
  // Por isso `scripts/assert-mcp-pin.mjs` (release:verify/prepublishOnly) recusa publicar
526
571
  // com um pin que o `npm view` nao resolva exatamente. Trocar o pin aqui exige o pacote ja publicado.
527
- const ARCANE_MCP_PIN = 'arcane-mcp@0.6.0';
572
+ const ARCANE_MCP_PIN = 'arcane-mcp@0.6.1';
528
573
  const CODEX_MCP_STARTUP_SEC = 120;
529
574
  // O Claude Code tambem desistia aos 30s (MCP_TIMEOUT) e o aluno via CONNECT_TIMEOUT com o
530
575
  // servidor no ar e nenhum pedido chegando nele (11/09, EMB-041). Ele le o tempo do `env` das
@@ -577,7 +622,7 @@ function blocoCodexArcane(pin, opcoes) {
577
622
  }
578
623
 
579
624
  // Baixa o pacote do conteudo ao vivo com o MESMO nome que o registro usa (o cache do
580
- // npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.6.0` sao caches diferentes).
625
+ // npx e por nome+versao: `arcane-mcp` e `arcane-mcp@0.6.1` sao caches diferentes).
581
626
  // `npx` no Windows e um .cmd: sem shell o Node recusa (EINVAL) e com `shell: true` o Node 24
582
627
  // imprime um DeprecationWarning na tela do aluno (DEP0190). Chamar o cmd.exe direto, sem shell.
583
628
  function comandoNpx(args) {
@@ -1803,7 +1848,7 @@ if (['init', 'install', 'update'].includes(command)) {
1803
1848
  error('Comando aposentado. Nada foi instalado ou alterado.');
1804
1849
  error('Primeira instalacao: https://arcane.arka.education/auroq');
1805
1850
  error('No negocio existente, peca ao Ops: manutencao do motor. Conteudo chega pelo MCP.');
1806
- error('Diagnostico: npx -y auroq-os@2.5.10 doctor. Manutencao: npx -y auroq-os@2.5.10 manutencao.');
1851
+ error('Diagnostico: npx -y auroq-os@2.5.12 doctor. Manutencao: npx -y auroq-os@2.5.12 manutencao.');
1807
1852
  process.exitCode = 1;
1808
1853
  } else if (command === 'instalar' || command === 'manutencao') {
1809
1854
  // `--pasta <nome>`: o instalador manda o CLI criar e entrar na pasta do negocio,
@@ -1930,6 +1975,6 @@ if (['init', 'install', 'update'].includes(command)) {
1930
1975
  log('');
1931
1976
  }
1932
1977
 
1933
- if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal, envComoCodex, argumentosCodexSmoke, avaliarEventosCodex, ARCANE_MCP_PIN };
1978
+ if (process.env.AUROQ_EXPORTA_INTERNOS === '1') module.exports = { blocoCodexArcane, envCodexArcane, lerBlocoArcane, lerEnvDoBloco, limparArcaneDoGlobal, envComoCodex, argumentosCodexSmoke, avaliarEventosCodex, rotulo, ARCANE_MCP_PIN };
1934
1979
 
1935
1980
  }
@@ -589,7 +589,7 @@ function Invoke-AuroqLogin {
589
589
  # sucesso. Usado como pergunta de sim/nao, ele dizia "voce ja estava logado"
590
590
  # pra quem nunca logou, pulava o portao e deixava a conta sem acesso chegar ate
591
591
  # a criacao da pasta.
592
- & (Get-AuroqNpx) -y auroq-os@2.5.10 check-access
592
+ & (Get-AuroqNpx) -y auroq-os@2.5.12 check-access
593
593
  if ($LASTEXITCODE -ne 0) {
594
594
  Write-AuroqLog 'login' 'recusado'
595
595
  Write-Host ''
@@ -677,7 +677,7 @@ function Invoke-AuroqInit {
677
677
  if ($env:AUROQ_INSTALL_DRY_RUN) { Write-AuroqOk 'Sistema - seria montado aqui'; return }
678
678
 
679
679
  Set-Location $script:AuroqPastaAlvo
680
- & (Get-AuroqNpx) -y auroq-os@2.5.10 instalar
680
+ & (Get-AuroqNpx) -y auroq-os@2.5.12 instalar
681
681
  if ($LASTEXITCODE -ne 0) {
682
682
  Write-AuroqLog 'init' 'falhou'
683
683
  Write-AuroqErro 'A montagem do sistema parou no meio.' 'Rode o mesmo comando de novo. Se repetir, mande pro suporte o arquivo install.log da pasta .auroq.'
@@ -219,9 +219,9 @@ auroq_tem_terminal() {
219
219
  # recebem pedaco do script no lugar da resposta dele.
220
220
  auroq_npx_aluno() {
221
221
  if [ -t 0 ]; then
222
- npx -y auroq-os@2.5.10 "$@"
222
+ npx -y auroq-os@2.5.12 "$@"
223
223
  else
224
- npx -y auroq-os@2.5.10 "$@" < /dev/tty
224
+ npx -y auroq-os@2.5.12 "$@" < /dev/tty
225
225
  fi
226
226
  }
227
227
 
@@ -0,0 +1,288 @@
1
+ {
2
+ "schema": 1,
3
+ "algorithm": "sha256",
4
+ "scope": "packaged-runtime-and-cascas",
5
+ "version": "2.5.12",
6
+ "files": [
7
+ {
8
+ "path": ".auroq-core/constitution.md",
9
+ "sha256": "8b6df553e2708e3d9eff3f12627808ca953a4272cfbd2f94cb9ae3d4c70a73de"
10
+ },
11
+ {
12
+ "path": ".auroq-core/core-config.yaml",
13
+ "sha256": "bb98d40d411423cfd4e92100a003a029309d5db1a5514b1a7fed880b96fc1f56"
14
+ },
15
+ {
16
+ "path": ".auroq-core/core/synapse/context/context-builder.js",
17
+ "sha256": "121cd0a1df8a44098831cd4335536e8facf4e65b8aec48f4ce9c2d432dc6252a"
18
+ },
19
+ {
20
+ "path": ".auroq-core/core/synapse/context/context-tracker.js",
21
+ "sha256": "48e94db7b1778dedecc8eae829139579ad7778ff47668597ebe766610696553f"
22
+ },
23
+ {
24
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/consistency-collector.js",
25
+ "sha256": "65f4255f87c9900400649dc8b9aedaac4851b5939d93e127778bd93cee99db12"
26
+ },
27
+ {
28
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/hook-collector.js",
29
+ "sha256": "c2cfa1b760bcb05decf5ad05f9159140cbe0cdc6b0f91581790e44d83dc6b660"
30
+ },
31
+ {
32
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/manifest-collector.js",
33
+ "sha256": "3dc895eb94485320ecbaca3a1d29e3776cfb691dd7dcc71cf44b34af30e8ebb6"
34
+ },
35
+ {
36
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/output-analyzer.js",
37
+ "sha256": "e6846b1aba0a6cba17c297a871861d4f8199d7500220bff296a6a3291e32493e"
38
+ },
39
+ {
40
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/pipeline-collector.js",
41
+ "sha256": "8655b6240e2f54b70def1a8c2fae00d40e2615cb95fd7ca0d64c2e0a6dfe3b73"
42
+ },
43
+ {
44
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/quality-collector.js",
45
+ "sha256": "30ae299eab6d569d09afe3530a5b2f1ff35ef75366a1ab56a9e2a57d39d3611c"
46
+ },
47
+ {
48
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/relevance-matrix.js",
49
+ "sha256": "f92c4f7061dc82eed4310a27b69eade33d3015f9beb1bed688601a2dccbad22e"
50
+ },
51
+ {
52
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/safe-read-json.js",
53
+ "sha256": "dc7bcd13779207ad67b1c3929b7e1e0ccfa3563f3458c20cad28cb1922e9a74c"
54
+ },
55
+ {
56
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/session-collector.js",
57
+ "sha256": "a116d884d6947ddc8e5f3def012d93696576c584c4fde1639b8d895924fc09ea"
58
+ },
59
+ {
60
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/timing-collector.js",
61
+ "sha256": "2523ce93f863a28f798d992c4f2fab041c91a09413b3186fd290e6035b391587"
62
+ },
63
+ {
64
+ "path": ".auroq-core/core/synapse/diagnostics/collectors/uap-collector.js",
65
+ "sha256": "dd025894f8f0d3bd22a147dbc0debef8b83e96f3c59483653404b3cd5a01d5aa"
66
+ },
67
+ {
68
+ "path": ".auroq-core/core/synapse/diagnostics/report-formatter.js",
69
+ "sha256": "1aa2853928df194b669ce32c3b050a90de1311739045074f699d10210ed8d381"
70
+ },
71
+ {
72
+ "path": ".auroq-core/core/synapse/diagnostics/synapse-diagnostics.js",
73
+ "sha256": "de9dffce0e380637027cbd64b062d3eeffc37e42a84a337e5758fbef39fe3a00"
74
+ },
75
+ {
76
+ "path": ".auroq-core/core/synapse/domain/domain-loader.js",
77
+ "sha256": "af788f9da956b89eef1e5eb4ef4efdf05ca758c8969a2c375f568119495ebc05"
78
+ },
79
+ {
80
+ "path": ".auroq-core/core/synapse/engine.js",
81
+ "sha256": "775d232f67c92596f4a4a4302d5f4c69b6473680badead6ff4772f46e157381c"
82
+ },
83
+ {
84
+ "path": ".auroq-core/core/synapse/layers/l0-constitution.js",
85
+ "sha256": "2123a6a44915aaac2a6bbd26c67c285c9d1e12b50fe42a8ada668306b07d1c4a"
86
+ },
87
+ {
88
+ "path": ".auroq-core/core/synapse/layers/l1-global.js",
89
+ "sha256": "21f6969e6d64e9a85c876be6799db4ca7d090f0009057f4a06ead8da12392d45"
90
+ },
91
+ {
92
+ "path": ".auroq-core/core/synapse/layers/l2-agent.js",
93
+ "sha256": "a8677dc58ae7927c5292a4b52883bbc905c8112573b8b8631f0b8bc01ea2b6e6"
94
+ },
95
+ {
96
+ "path": ".auroq-core/core/synapse/layers/l3-workflow.js",
97
+ "sha256": "496cbd71d7dac9c1daa534ffac45c622d0c032f334fedf493e9322a565b2b181"
98
+ },
99
+ {
100
+ "path": ".auroq-core/core/synapse/layers/l4-task.js",
101
+ "sha256": "30df70c04b16e3aff95899211ef6ae3d9f0a8097ebdc7de92599fc6cb792e5f0"
102
+ },
103
+ {
104
+ "path": ".auroq-core/core/synapse/layers/l5-squad.js",
105
+ "sha256": "d6d35cbf966deb5a7d2df2b09f5abe8da8c6d4e346b385418f8dc1fae3ddf830"
106
+ },
107
+ {
108
+ "path": ".auroq-core/core/synapse/layers/l6-keyword.js",
109
+ "sha256": "8e5405999a2ce2f3ca4e62e863cf702ba27448914241f5eb8f02760bc7477523"
110
+ },
111
+ {
112
+ "path": ".auroq-core/core/synapse/layers/l7-star-command.js",
113
+ "sha256": "3b8372ac1c51830c1ef560b1012b112a3559651b0750e42f2037f7fe9e6787d6"
114
+ },
115
+ {
116
+ "path": ".auroq-core/core/synapse/layers/layer-processor.js",
117
+ "sha256": "46a7d08e088017a61fa6033ce4d9b3b8caba622e2c732fe158e0f531690fa3d9"
118
+ },
119
+ {
120
+ "path": ".auroq-core/core/synapse/memory/memory-bridge.js",
121
+ "sha256": "820875f97ceea80fc6402c0dab1706cfe58de527897b22dea68db40b0d6ec368"
122
+ },
123
+ {
124
+ "path": ".auroq-core/core/synapse/memory/synapse-memory-provider.js",
125
+ "sha256": "5d613d1fac7ee82c49a3f03b38735fd3cabfe87dd868494672ddfef300ea3145"
126
+ },
127
+ {
128
+ "path": ".auroq-core/core/synapse/output/formatter.js",
129
+ "sha256": "3e722c4736642ef96ec73a54cd2011eed7bd1074394a38eac66cb6f0af2c855b"
130
+ },
131
+ {
132
+ "path": ".auroq-core/core/synapse/runtime/hook-runtime.js",
133
+ "sha256": "77a88b9324940b4feb14de6fe4de51c0fd82e2062ef4470b93de2e57bd02c88a"
134
+ },
135
+ {
136
+ "path": ".auroq-core/core/synapse/scripts/generate-constitution.js",
137
+ "sha256": "e2f645d5de44e6b3721a5de9b19982bc7b8cf7eed3ca4d62bc53221649c59af1"
138
+ },
139
+ {
140
+ "path": ".auroq-core/core/synapse/session/session-manager.js",
141
+ "sha256": "98e8c8fa15b074b6102bcd51c6f91f101743338e8cde6de9c31d6b6eea5d6580"
142
+ },
143
+ {
144
+ "path": ".auroq-core/core/synapse/utils/atomic-write.js",
145
+ "sha256": "efeef0fbcebb184df5b79f4ba4b4b7fe274c3ba7d1c705ce1af92628e920bd8b"
146
+ },
147
+ {
148
+ "path": ".auroq-core/core/synapse/utils/paths.js",
149
+ "sha256": "bf8cf93c1a16295e7de055bee292e2778a152b6e7d6c648dbc054a4b04dffc10"
150
+ },
151
+ {
152
+ "path": ".auroq-core/core/synapse/utils/tokens.js",
153
+ "sha256": "3b927daec51d0a791f3fe4ef9aafc362773450e7cf50eb4b6d8ae9011d70df9a"
154
+ },
155
+ {
156
+ "path": ".auroq-core/dna-operacional.md",
157
+ "sha256": "36d3118831d1d406a1e5eef2394fef6628396712534c59888a5968f9ffdba796"
158
+ },
159
+ {
160
+ "path": ".claude/commands/AuroqOS/agents/ops.md",
161
+ "sha256": "6f0df7c0dfbe28f4efa4d49d010f5a2520293c972623eb5c7dc25aa62ba7fc37"
162
+ },
163
+ {
164
+ "path": ".claude/commands/auroq-companion.md",
165
+ "sha256": "789bb61f076847f5695be0ea1133cfe026024c3e7a77a48f142297cc30e02196"
166
+ },
167
+ {
168
+ "path": ".claude/hooks/ahi-capture.cjs",
169
+ "sha256": "d99564a5bebc23281da5dbba2e82ef099c390768bed2b63f70be17c75ab8fd57"
170
+ },
171
+ {
172
+ "path": ".claude/hooks/ahi/auth-config.cjs",
173
+ "sha256": "bbe1f01165f0cf4562b8685a59bd2aac94758dbd0b072d287c09db10d9c6e0eb"
174
+ },
175
+ {
176
+ "path": ".claude/hooks/ahi/runtime.cjs",
177
+ "sha256": "82c0b93351f25959e5cbe0a9a57b7ae29a3b497298ab351bddd7361a2ef6ce1a"
178
+ },
179
+ {
180
+ "path": ".claude/hooks/ahi/security.cjs",
181
+ "sha256": "dc27db1392acd0abd8102490860bfad6a94276bbc92f63e086dc997dbe6b208c"
182
+ },
183
+ {
184
+ "path": ".claude/hooks/ahi/worker.cjs",
185
+ "sha256": "3eabd293f3c9c6887f6850ca4be9ad7021d6659967f2f5eecadefb2bb78e9b85"
186
+ },
187
+ {
188
+ "path": ".claude/hooks/auroq-sync.cjs",
189
+ "sha256": "2655dbd0a6f73ae83d0fabfb9567fc7dbf79708adcf9bfa455ea6da6fc183b9f"
190
+ },
191
+ {
192
+ "path": ".claude/hooks/precompact-session-digest.cjs",
193
+ "sha256": "cca89df5591fd52581d62bd35cc097d7e3d2622bd2227e7e53b425d5a6d8bdb2"
194
+ },
195
+ {
196
+ "path": ".claude/hooks/synapse-engine.cjs",
197
+ "sha256": "4d01f376993b570b9ca995b839d292ce9b73c597a0a1ebc8cb1ffbd214093bfe"
198
+ },
199
+ {
200
+ "path": ".claude/settings.json",
201
+ "sha256": "3d0a9836997e7524d1538e96799bd5ea06575620c97a2eb03d3410b6df5d0e5a"
202
+ },
203
+ {
204
+ "path": ".claude/settings.local.json",
205
+ "sha256": "cb81d47fef6536bd472841811bb85f36a8219994d55399eac85732f920c566c6"
206
+ },
207
+ {
208
+ "path": "bin/auroq-os.js",
209
+ "sha256": "b043982de702e524799b51968b29578c04e8a6ed5f66c318d661bf776f0ef838"
210
+ },
211
+ {
212
+ "path": "lib/ahi/client.mjs",
213
+ "sha256": "ded0ba89b58193e59b4ced26321965f85b8b19abc498ed8b079cab956806d789"
214
+ },
215
+ {
216
+ "path": "lib/ahi/historico.mjs",
217
+ "sha256": "cb85e53dae59e793bdb83f59c3f8d14342912953b43dd9408d757a7b7182733a"
218
+ },
219
+ {
220
+ "path": "lib/ahi/install.cjs",
221
+ "sha256": "f3a9fec92e64b8624fb45e062b51777788831337dec6a89ce8222d7813baca9d"
222
+ },
223
+ {
224
+ "path": "lib/auth.js",
225
+ "sha256": "e775938f20b86c305e44c2b5834eeb886fec0316dccc0f3565536689b7bfb46a"
226
+ },
227
+ {
228
+ "path": "lib/credentials.js",
229
+ "sha256": "92ecfba2b7ded12de5b236d64b24781ffdf05c1e770614a8d02bf05ab78d59e0"
230
+ },
231
+ {
232
+ "path": "lib/diagnostico.js",
233
+ "sha256": "a7c15e8aa9dd3a7ceca3784ce7207f9593baaabedc87e88d0a674096b20c6835"
234
+ },
235
+ {
236
+ "path": "lib/legacy-shims.json",
237
+ "sha256": "1ea331604dfcadc579a7a27d53bfb2c1d1a2263e1d2ca83ed780c5ce5d2fe877"
238
+ },
239
+ {
240
+ "path": "lib/onepassword.js",
241
+ "sha256": "a65d425182e0698d3f80b8d81bb088b13c5e5b60ae1aa92b095ab38d60e22ea9"
242
+ },
243
+ {
244
+ "path": "lib/supabase.js",
245
+ "sha256": "a922dee14500e95b4b1f7a8d2a67baedee71c547f863e69e63f25830f9786f28"
246
+ },
247
+ {
248
+ "path": "lib/synapse-hashes.json",
249
+ "sha256": "061dfe2e2c7e2a75472ae133e3465931c1267cc18e0740e7c1515404095b47e6"
250
+ },
251
+ {
252
+ "path": "lib/telemetry.mjs",
253
+ "sha256": "13643a1f8c33d7063a07b37190ba157755f8e26aadd4059e1afdc25fada0cf38"
254
+ },
255
+ {
256
+ "path": "package.json",
257
+ "sha256": "17d5e5a26ffd8044fd8b98260ca7961b37a326acbe8b63dd3a65af73edff1317"
258
+ },
259
+ {
260
+ "path": "scripts/build-pack.mjs",
261
+ "sha256": "c4f7bbc40320dcf848307ab9d7d7c84c864ddc70a0e4db933a35406f55dcf881"
262
+ },
263
+ {
264
+ "path": "scripts/pack-state.mjs",
265
+ "sha256": "7399b388eab97dd35da49055b126aba39a0c95d82cf422c62b603e9bf5de7f36"
266
+ },
267
+ {
268
+ "path": "scripts/postpack-casca.mjs",
269
+ "sha256": "e361ed44a688d0cd6804bdbf3e240649b69171c2396433f0fdb55cb0f739d993"
270
+ },
271
+ {
272
+ "path": "scripts/prepack-casca.mjs",
273
+ "sha256": "ecd4381a411d275af339e0bdc3acc5a0ea33ad65c1eaafad6ff3d6c79b415aff"
274
+ },
275
+ {
276
+ "path": "scripts/runtime-integrity.mjs",
277
+ "sha256": "af0cb75622dcf4c049da3fc053817087648a86fc31df3be4762e974279eb87a3"
278
+ },
279
+ {
280
+ "path": "scripts/sync-codex-skills.mjs",
281
+ "sha256": "36e703e8d298a5fc4cff3a85828b93a5123ab9563eeb2808c30f23f3eefd58f8"
282
+ },
283
+ {
284
+ "path": "scripts/validate-hybrid.mjs",
285
+ "sha256": "d70636ad4fd6a7975df552de5cc4a88d535f0ec8596b7cf74c20e9ff73f02505"
286
+ }
287
+ ]
288
+ }
@@ -49,7 +49,8 @@
49
49
  ],
50
50
  "engine.js": [
51
51
  "47bab93a2144edce875ee68cc2717fee1bd824c9affdd5964454f5141310b8e0",
52
- "c061df38ec24956da9a752f13f9455c7cd69546adb330c7587b9d456e79b4aa1"
52
+ "c061df38ec24956da9a752f13f9455c7cd69546adb330c7587b9d456e79b4aa1",
53
+ "775d232f67c92596f4a4a4302d5f4c69b6473680badead6ff4772f46e157381c"
53
54
  ],
54
55
  "layers/l0-constitution.js": [
55
56
  "2123a6a44915aaac2a6bbd26c67c285c9d1e12b50fe42a8ada668306b07d1c4a"
@@ -76,7 +77,8 @@
76
77
  "3b8372ac1c51830c1ef560b1012b112a3559651b0750e42f2037f7fe9e6787d6"
77
78
  ],
78
79
  "layers/layer-processor.js": [
79
- "73cb0e5b4bada80d8e256009004679e483792077fac4358c6466cd77136f79fa"
80
+ "73cb0e5b4bada80d8e256009004679e483792077fac4358c6466cd77136f79fa",
81
+ "46a7d08e088017a61fa6033ce4d9b3b8caba622e2c732fe158e0f531690fa3d9"
80
82
  ],
81
83
  "memory/memory-bridge.js": [
82
84
  "820875f97ceea80fc6402c0dab1706cfe58de527897b22dea68db40b0d6ec368"
@@ -90,7 +92,8 @@
90
92
  ],
91
93
  "runtime/hook-runtime.js": [
92
94
  "5518692eeee7dcb570507c4bb96ef68a1ef51d6ad6c417e9d1f649cd8b21b199",
93
- "7c34b84c5693045a007356081959c335493bbd3afc69822fb6661e19cc10d56c"
95
+ "7c34b84c5693045a007356081959c335493bbd3afc69822fb6661e19cc10d56c",
96
+ "77a88b9324940b4feb14de6fe4de51c0fd82e2062ef4470b93de2e57bd02c88a"
94
97
  ],
95
98
  "scripts/generate-constitution.js": [
96
99
  "e2f645d5de44e6b3721a5de9b19982bc7b8cf7eed3ca4d62bc53221649c59af1"
package/lib/telemetry.mjs CHANGED
@@ -20,7 +20,10 @@ export function networkCode(e) {
20
20
  }
21
21
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
22
22
  const uuid = v => typeof v === 'string' && UUID.test(v) ? v : null;
23
- const ERRORS = new Set(['NETWORK_TLS','NETWORK_DNS','NETWORK_TIMEOUT','NETWORK_OTHER','AUTH_MISSING','AUTH_REFRESH','HTTP_ERROR','CLIENT_ERROR','BOOTSTRAP_IMPORT','RUNTIME_FAILURE','INSTALL_FAILED','UPDATE_FAILED']);
23
+ // DOCTOR_FINDINGS: o doctor achou pendencia e saiu com 1 — diagnostico trabalhando, nao instalacao
24
+ // quebrada. Os dois clientes gravam no MESMO outbox e qualquer um faz o flush, entao a lista tem que
25
+ // ser a mesma nos dois (e no servidor): o que falta aqui vira null e apaga o alarme (EMB-050).
26
+ const ERRORS = new Set(['NETWORK_TLS','NETWORK_DNS','NETWORK_TIMEOUT','NETWORK_OTHER','AUTH_MISSING','AUTH_REFRESH','HTTP_ERROR','CLIENT_ERROR','BOOTSTRAP_IMPORT','RUNTIME_FAILURE','INSTALL_FAILED','UPDATE_FAILED','DOCTOR_FINDINGS']);
24
27
  const STAGES = new Set(['startup','runtime','install','update','doctor','login','bootstrap','git','node','access','folder','framework']);
25
28
  const tools = new Set(['catalogo_listar','squad_ativar','squad_passo','squad_conhecimento','forja_bundle','busca']);
26
29
  const TTL = 7*86400000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auroq-os",
3
- "version": "2.5.10",
3
+ "version": "2.5.12",
4
4
  "description": "Auroq OS — Sistema Operacional de IA para Experts",
5
5
  "bin": {
6
6
  "auroq-os": "bin/auroq-os.js"
@@ -18,14 +18,15 @@
18
18
  "auroq:sync:codex:check": "npm run sync:codex:check",
19
19
  "auroq:validate": "npm run validate:hybrid",
20
20
  "validate:hybrid": "node scripts/validate-hybrid.mjs",
21
- "lint": "node --check bin/auroq-os.js && node --check lib/auth.js && node --check lib/diagnostico.js && node --check lib/credentials.js && node --check lib/onepassword.js && node --check lib/supabase.js && node --check scripts/sync-codex-skills.mjs && node --check scripts/validate-hybrid.mjs && node --check scripts/prepack-casca.mjs && node --check scripts/postpack-casca.mjs && node --check scripts/assert-mcp-pin.mjs && node --check lib/telemetry.mjs && node --check scripts/synapse-hashes.mjs && node --check lib/ahi/historico.mjs && node --check .claude/hooks/ahi-capture.cjs && node --check .claude/hooks/ahi/security.cjs && node --check .claude/hooks/ahi/runtime.cjs && node --check .claude/hooks/ahi/worker.cjs && node --check lib/ahi/install.cjs && node --check .claude/hooks/ahi/auth-config.cjs && node --check lib/ahi/client.mjs && node --check scripts/build-pack.mjs && node --check scripts/assert-native-release.mjs",
21
+ "lint": "node --check bin/auroq-os.js && node --check lib/auth.js && node --check lib/diagnostico.js && node --check lib/credentials.js && node --check lib/onepassword.js && node --check lib/supabase.js && node --check scripts/sync-codex-skills.mjs && node --check scripts/validate-hybrid.mjs && node --check scripts/prepack-casca.mjs && node --check scripts/postpack-casca.mjs && node --check scripts/assert-mcp-pin.mjs && node --check lib/telemetry.mjs && node --check scripts/synapse-hashes.mjs && node --check lib/ahi/historico.mjs && node --check .claude/hooks/ahi-capture.cjs && node --check .claude/hooks/ahi/security.cjs && node --check .claude/hooks/ahi/runtime.cjs && node --check .claude/hooks/ahi/worker.cjs && node --check lib/ahi/install.cjs && node --check .claude/hooks/ahi/auth-config.cjs && node --check lib/ahi/client.mjs && node --check scripts/build-pack.mjs && node --check scripts/assert-native-release.mjs && node --check scripts/runtime-integrity.mjs && node --check scripts/pack-state.mjs",
22
22
  "typecheck": "npm run validate:hybrid",
23
23
  "test": "node --test tests/*.test.mjs",
24
24
  "release:verify": "node scripts/assert-mcp-pin.mjs && node scripts/assert-native-release.mjs",
25
25
  "prepublishOnly": "npm run release:verify",
26
26
  "prepack": "node scripts/prepack-casca.mjs",
27
27
  "postpack": "node scripts/postpack-casca.mjs",
28
- "build": "node scripts/build-pack.mjs"
28
+ "build": "node scripts/build-pack.mjs",
29
+ "integrity:verify": "node scripts/runtime-integrity.mjs --verify"
29
30
  },
30
31
  "files": [
31
32
  "bin/",
@@ -56,7 +57,7 @@
56
57
  "dependencies": {
57
58
  "@supabase/supabase-js": "^2.103.0",
58
59
  "fs-extra": "^11.3.0",
59
- "js-yaml": "^4.1.0",
60
+ "js-yaml": "^4.3.2",
60
61
  "prompts": "^2.4.2"
61
62
  },
62
63
  "author": "Euriler Jube / Arka",
@@ -1,13 +1,44 @@
1
1
  #!/usr/bin/env node
2
- // Portable dry-run package validation. npm lifecycle still checks/restores the casca overlay.
3
- import { spawnSync } from 'node:child_process';
4
- const command = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npm';
5
- const args = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm pack --dry-run --json'] : ['pack', '--dry-run', '--json'];
6
- const result = spawnSync(command, args, { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, windowsHide: true });
7
- if (result.status !== 0 || result.error) { process.stderr.write(result.stderr || 'Package validation failed.\n'); process.exit(1); }
8
- // npm versions may print lifecycle logs before/after the JSON manifest.
9
- const manifest = result.stdout.match(/(\[\s*\{[\s\S]*\}\s*\])/);
10
- if (!manifest) throw new Error('Package manifest unavailable');
11
- const packages = JSON.parse(manifest[1]);
12
- if (!packages[0]?.files?.some(file => file.path === '.claude/hooks/ahi/runtime.cjs')) throw new Error('AHI runtime missing from package');
13
- process.stdout.write('Package validated with AHI runtime.\n');
2
+ // Validate actual tarball bytes AFTER the casca overlay; never pack this checkout.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { execFileSync } from 'node:child_process';
8
+ import { verifyRuntimeInventory } from './runtime-integrity.mjs';
9
+
10
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
+ const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'auroq-build-pack-'));
12
+ try {
13
+ const copy = path.join(temporary, 'source');
14
+ const excluded = new Set(['.git', 'node_modules', '.pack-casca-backup']);
15
+ if (fs.existsSync(path.join(root, '.pack-casca-backup'))) throw new Error('Pending casca backup: restore the checkout before building.');
16
+ fs.cpSync(root, copy, { recursive: true, filter: source => !path.relative(root, source).split(path.sep).some(part => excluded.has(part) || part.endsWith('.tgz')) });
17
+ const cli = process.env.npm_execpath || (process.platform === 'win32'
18
+ ? path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js') : null);
19
+ const args = ['pack', '--json', '--pack-destination', temporary];
20
+ const output = execFileSync(cli ? process.execPath : 'npm', cli ? [cli, ...args] : args,
21
+ { cwd: copy, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
22
+ const match = output.match(/(\[\s*\{[\s\S]*\}\s*\])/);
23
+ if (!match) throw new Error('Package manifest unavailable');
24
+ const packages = JSON.parse(match[1]);
25
+ if (packages.length !== 1 || !packages[0].filename || path.basename(packages[0].filename) !== packages[0].filename) throw new Error('Invalid package result');
26
+ const artifact = path.join(temporary, packages[0].filename);
27
+ const listing = execFileSync('tar', ['-tzf', artifact], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
28
+ const packedPaths = listing.split(/\r?\n/).filter(Boolean).map(file => {
29
+ if (!file.startsWith('package/') || file.endsWith('/') || file.includes('..') || file.includes('\\')) throw new Error('Invalid tarball path');
30
+ return file.slice('package/'.length);
31
+ });
32
+ const extracted = path.join(temporary, 'extracted');
33
+ fs.mkdirSync(extracted);
34
+ execFileSync('tar', ['-xzf', artifact, '-C', extracted], { stdio: ['ignore', 'pipe', 'pipe'] });
35
+ const result = verifyRuntimeInventory(path.join(extracted, 'package'), packedPaths);
36
+ if (fs.existsSync(path.join(copy, '.pack-casca-backup'))) throw new Error('Pack did not restore its casca overlay');
37
+ process.stdout.write(`Package validated: ${result.files} protected files, SHA256 and local runtime references.\n`);
38
+ } catch (error) {
39
+ // Do not print npm lifecycle output: it may include environment-specific data.
40
+ console.error(`Package validation failed: ${error.status !== undefined ? 'pack/extraction command failed' : error.message}`);
41
+ process.exitCode = 1;
42
+ } finally {
43
+ fs.rmSync(temporary, { recursive: true, force: true });
44
+ }
@@ -0,0 +1,64 @@
1
+ /** Journal the temporary casca overlay, including files absent before packing. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const BACKUP = '.pack-casca-backup';
6
+ const JOURNAL = 'overlay-state.json';
7
+
8
+ function assertPath(relative) {
9
+ if (!relative || path.isAbsolute(relative) || /^[A-Za-z]:/.test(relative) || relative.includes('\\') || relative.split('/').some(p => !p || p === '.' || p === '..')) {
10
+ throw new Error('Invalid overlay path');
11
+ }
12
+ }
13
+
14
+ function assertNoSymlink(root, relative) {
15
+ let current = root;
16
+ for (const segment of relative.split('/')) {
17
+ current = path.join(current, segment);
18
+ let stat;
19
+ try { stat = fs.lstatSync(current); } catch (error) { if (error.code !== 'ENOENT') throw error; }
20
+ if (stat?.isSymbolicLink()) throw new Error(`Symlink in overlay path: ${relative}`);
21
+ }
22
+ }
23
+
24
+ export function beginOverlay(root) {
25
+ const backup = path.join(root, BACKUP);
26
+ if (fs.existsSync(backup)) throw new Error('Pending casca backup: restore with node scripts/postpack-casca.mjs before packing again.');
27
+ fs.mkdirSync(backup);
28
+ fs.writeFileSync(path.join(backup, JOURNAL), '[]\n');
29
+ }
30
+
31
+ export function preserveOverlayFile(root, relative) {
32
+ assertPath(relative);
33
+ assertNoSymlink(root, relative);
34
+ const backup = path.join(root, BACKUP);
35
+ const journalFile = path.join(backup, JOURNAL);
36
+ const journal = JSON.parse(fs.readFileSync(journalFile, 'utf8'));
37
+ if (journal.some(entry => entry.path === relative)) return;
38
+ const source = path.join(root, relative);
39
+ const existed = fs.existsSync(source);
40
+ if (existed) {
41
+ if (!fs.lstatSync(source).isFile()) throw new Error(`Overlay target is not a regular file: ${relative}`);
42
+ const target = path.join(backup, 'files', relative);
43
+ fs.mkdirSync(path.dirname(target), { recursive: true });
44
+ fs.copyFileSync(source, target);
45
+ }
46
+ journal.push({ path: relative, existed });
47
+ fs.writeFileSync(journalFile, `${JSON.stringify(journal)}\n`);
48
+ }
49
+
50
+ export function restoreOverlay(root) {
51
+ const backup = path.join(root, BACKUP);
52
+ if (!fs.existsSync(backup)) return;
53
+ const journal = JSON.parse(fs.readFileSync(path.join(backup, JOURNAL), 'utf8'));
54
+ for (const entry of journal.reverse()) {
55
+ assertPath(entry.path);
56
+ assertNoSymlink(root, entry.path);
57
+ const target = path.join(root, entry.path);
58
+ if (entry.existed) {
59
+ fs.mkdirSync(path.dirname(target), { recursive: true });
60
+ fs.copyFileSync(path.join(backup, 'files', entry.path), target);
61
+ } else fs.rmSync(target, { force: true });
62
+ }
63
+ fs.rmSync(backup, { recursive: true });
64
+ }
@@ -1,32 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /** Restaura o miolo depois do npm pack/publish. */
3
- import fs from 'node:fs';
4
3
  import path from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
5
+ import { restoreOverlay } from './pack-state.mjs';
6
6
 
7
- const root = fs.realpathSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'));
8
- const cascaRoot = path.join(root, 'casca');
9
- const backupRoot = path.join(root, '.pack-casca-backup');
10
-
11
- function walk(dir) {
12
- const out = [];
13
- if (!fs.existsSync(dir)) return out;
14
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
15
- const full = path.join(dir, entry.name);
16
- if (entry.isDirectory()) out.push(...walk(full));
17
- else out.push(full);
18
- }
19
- return out;
20
- }
21
-
22
- if (!fs.existsSync(backupRoot)) process.exit(0);
23
-
24
- for (const bak of walk(backupRoot)) {
25
- const rel = path.relative(backupRoot, bak);
26
- const dest = path.join(root, rel);
27
- fs.mkdirSync(path.dirname(dest), { recursive: true });
28
- fs.copyFileSync(bak, dest);
29
- }
30
-
31
- fs.rmSync(backupRoot, { recursive: true, force: true });
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+ restoreOverlay(root);
32
9
  console.log('postpack-casca: working tree restaurado');
@@ -6,10 +6,11 @@
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
+ import { beginOverlay, preserveOverlayFile, restoreOverlay } from './pack-state.mjs';
10
+ import { INVENTORY_PATH, writeRuntimeInventory, verifyRuntimeInventory } from './runtime-integrity.mjs';
9
11
 
10
12
  const root = fs.realpathSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'));
11
13
  const cascaRoot = path.join(root, 'casca');
12
- const backupRoot = path.join(root, '.pack-casca-backup');
13
14
 
14
15
  function walk(dir) {
15
16
  const out = [];
@@ -22,45 +23,44 @@ function walk(dir) {
22
23
  return out;
23
24
  }
24
25
 
25
- if (fs.existsSync(backupRoot)) {
26
- fs.rmSync(backupRoot, { recursive: true, force: true });
27
- }
28
-
29
26
  const files = walk(cascaRoot);
30
27
  if (!files.length) {
31
28
  console.error('casca/ vazia — recusando pack');
32
29
  process.exit(1);
33
30
  }
34
31
 
35
- for (const src of files) {
36
- const rel = path.relative(cascaRoot, src);
37
- const dest = path.join(root, rel);
38
- if (fs.existsSync(dest)) {
39
- const bak = path.join(backupRoot, rel);
40
- fs.mkdirSync(path.dirname(bak), { recursive: true });
41
- fs.copyFileSync(dest, bak);
32
+ // A missing validator fails at import time, before touching the checkout.
33
+ beginOverlay(root);
34
+ try {
35
+ for (const src of files) {
36
+ const rel = path.relative(cascaRoot, src).split(path.sep).join('/');
37
+ const dest = path.join(root, rel);
38
+ preserveOverlayFile(root, rel);
39
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
40
+ fs.copyFileSync(src, dest);
42
41
  }
43
- fs.mkdirSync(path.dirname(dest), { recursive: true });
44
- fs.copyFileSync(src, dest);
45
- }
46
42
 
47
- // O hook auroq-sync compara a versao lida de .auroq-core/core-config.yaml com a
48
- // do npm para avisar de update. Se o core-config ficar para tras, TODO aluno ja
49
- // atualizado ve "saiu versao nova" para sempre. Sincroniza no pack.
50
- const pkgVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version;
51
- const cfgPath = path.join(root, '.auroq-core', 'core-config.yaml');
52
- if (fs.existsSync(cfgPath)) {
53
- const cfg = fs.readFileSync(cfgPath, 'utf8');
54
- const synced = cfg.replace(/^(\s*version:\s*)["']?\d+\.\d+\.\d+["']?/m, `$1${pkgVersion}`);
55
- if (synced !== cfg) {
56
- const bak = path.join(backupRoot, '.auroq-core', 'core-config.yaml');
57
- if (!fs.existsSync(bak)) {
58
- fs.mkdirSync(path.dirname(bak), { recursive: true });
59
- fs.copyFileSync(cfgPath, bak);
43
+ // O hook auroq-sync compara a versao lida de .auroq-core/core-config.yaml com a
44
+ // do npm para avisar de update. Se o core-config ficar para tras, TODO aluno ja
45
+ // atualizado ve "saiu versao nova" para sempre. Sincroniza no pack.
46
+ const pkgVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version;
47
+ const cfgPath = path.join(root, '.auroq-core', 'core-config.yaml');
48
+ if (fs.existsSync(cfgPath)) {
49
+ const cfg = fs.readFileSync(cfgPath, 'utf8');
50
+ const synced = cfg.replace(/^(\s*version:\s*)["']?\d+\.\d+\.\d+["']?/m, `$1${pkgVersion}`);
51
+ if (synced !== cfg) {
52
+ preserveOverlayFile(root, '.auroq-core/core-config.yaml');
53
+ fs.writeFileSync(cfgPath, synced);
54
+ console.log(`prepack-casca: core-config.yaml sincronizado para ${pkgVersion}`);
60
55
  }
61
- fs.writeFileSync(cfgPath, synced);
62
- console.log(`prepack-casca: core-config.yaml sincronizado para ${pkgVersion}`);
63
56
  }
64
- }
65
57
 
66
- console.log(`prepack-casca: ${files.length} arquivos sobrepostos`);
58
+ preserveOverlayFile(root, INVENTORY_PATH);
59
+ writeRuntimeInventory(root);
60
+ const verified = verifyRuntimeInventory(root);
61
+ console.log(`prepack-casca: ${files.length} arquivos sobrepostos`);
62
+ console.log(`prepack-casca: inventario SHA256 de ${verified.files} arquivos (nao e assinatura)`);
63
+ } catch (error) {
64
+ restoreOverlay(root);
65
+ throw error;
66
+ }
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /** Package completeness and accidental-change checks; SHA256 is not a signature.
3
+ * Scope: shipped engine/cascas only. Business files and optional legacy modules
4
+ * are not a source of dependencies. Generate AFTER the casca overlay.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import crypto from 'node:crypto';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ export const INVENTORY_PATH = 'lib/runtime-integrity.json';
12
+ const SYNAPSE_ROOT = '.auroq-core/core/synapse/';
13
+ const SYNAPSE_LAYERS = ['l0-constitution', 'l1-global', 'l2-agent', 'l3-workflow', 'l4-task', 'l5-squad', 'l6-keyword', 'l7-star-command'];
14
+ export const REQUIRED_FILES = [
15
+ 'package.json', 'bin/auroq-os.js',
16
+ 'lib/auth.js', 'lib/credentials.js', 'lib/diagnostico.js', 'lib/supabase.js',
17
+ 'lib/onepassword.js', 'lib/telemetry.mjs', 'lib/synapse-hashes.json', 'lib/legacy-shims.json',
18
+ 'lib/ahi/install.cjs', 'lib/ahi/client.mjs', 'lib/ahi/historico.mjs',
19
+ 'scripts/runtime-integrity.mjs', 'scripts/pack-state.mjs',
20
+ 'scripts/prepack-casca.mjs', 'scripts/postpack-casca.mjs', 'scripts/build-pack.mjs',
21
+ 'scripts/sync-codex-skills.mjs', 'scripts/validate-hybrid.mjs',
22
+ '.auroq-core/core-config.yaml', '.auroq-core/constitution.md', '.auroq-core/dna-operacional.md',
23
+ '.auroq-core/core/synapse/engine.js', '.auroq-core/core/synapse/runtime/hook-runtime.js',
24
+ '.auroq-core/core/synapse/session/session-manager.js', '.auroq-core/core/synapse/domain/domain-loader.js',
25
+ ...SYNAPSE_LAYERS.map(layer => `${SYNAPSE_ROOT}layers/${layer}.js`),
26
+ '.claude/settings.json', '.claude/settings.local.json',
27
+ '.claude/commands/AuroqOS/agents/ops.md', '.claude/commands/auroq-companion.md',
28
+ '.claude/hooks/synapse-engine.cjs', '.claude/hooks/auroq-sync.cjs',
29
+ '.claude/hooks/ahi-capture.cjs', '.claude/hooks/ahi/runtime.cjs',
30
+ '.claude/hooks/ahi/security.cjs', '.claude/hooks/ahi/worker.cjs', '.claude/hooks/ahi/auth-config.cjs',
31
+ ];
32
+ const PROTECTED_TREES = ['lib/', '.auroq-core/core/synapse/', '.claude/hooks/'];
33
+ const FORBIDDEN_PREFIXES = ['business/', 'docs/knowledge/', 'casca/', '.git/', '.pack-casca-backup/'];
34
+
35
+ function safeRelative(relative) {
36
+ if (typeof relative !== 'string' || !relative || relative.includes('\\') || relative.startsWith('/')
37
+ || relative.split('/').some(part => !part || part === '.' || part === '..') || /^[A-Za-z]:/.test(relative)) {
38
+ throw new Error('Invalid package path');
39
+ }
40
+ return relative;
41
+ }
42
+
43
+ function checkedFile(root, relative) {
44
+ safeRelative(relative);
45
+ let current = root;
46
+ for (const segment of relative.split('/')) {
47
+ current = path.join(current, segment);
48
+ if (fs.lstatSync(current).isSymbolicLink()) throw new Error(`Symlink in protected path: ${relative}`);
49
+ }
50
+ if (!fs.statSync(current).isFile()) throw new Error(`Not a regular file: ${relative}`);
51
+ return current;
52
+ }
53
+
54
+ function walk(root, relative) {
55
+ const out = [];
56
+ const dir = path.join(root, relative);
57
+ if (!fs.existsSync(dir)) throw new Error(`Missing protected directory: ${relative}`);
58
+ if (fs.lstatSync(dir).isSymbolicLink()) throw new Error(`Symlink in protected directory: ${relative}`);
59
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
60
+ const rel = `${relative}${item.name}`;
61
+ if (item.isSymbolicLink()) throw new Error(`Symlink in protected path: ${rel}`);
62
+ if (item.isDirectory()) out.push(...walk(root, `${rel}/`));
63
+ else if (item.isFile() && rel !== INVENTORY_PATH) out.push(rel);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ function inventoryPaths(root) {
69
+ return [...new Set([...REQUIRED_FILES, ...PROTECTED_TREES.flatMap(dir => walk(root, dir))])].sort();
70
+ }
71
+
72
+ function digest(root, relative) {
73
+ return crypto.createHash('sha256').update(fs.readFileSync(checkedFile(root, relative))).digest('hex');
74
+ }
75
+
76
+ function resolveLocal(root, source, reference) {
77
+ const relative = path.posix.normalize(path.posix.join(path.posix.dirname(source), reference));
78
+ safeRelative(relative);
79
+ const candidates = path.posix.extname(relative) ? [relative] : [relative, `${relative}.js`, `${relative}.json`, `${relative}/index.js`];
80
+ const found = candidates.find(candidate => fs.existsSync(path.join(root, candidate)) && fs.statSync(path.join(root, candidate)).isFile());
81
+ if (!found) throw new Error(`Broken required local reference: ${source} -> ${reference}`);
82
+ checkedFile(root, found);
83
+ return found;
84
+ }
85
+
86
+ /** Literal imports/requires in active CLI/lib/AHI/SYNAPSE code.
87
+ * Dynamic SYNAPSE layers/entry targets are explicitly required above. Diagnostic
88
+ * tooling is outside this runtime scope; only the precise optional Pro edge is exempt.
89
+ */
90
+ function verifyReferences(root, paths) {
91
+ const sources = paths.filter(p => p === 'bin/auroq-os.js'
92
+ || (/\.(?:js|mjs|cjs)$/.test(p) && (p.startsWith('lib/') || p.startsWith('.claude/hooks/ahi/')))
93
+ || p === `${SYNAPSE_ROOT}engine.js`
94
+ || /^\.auroq-core\/core\/synapse\/(?:context|domain|layers|output|session|runtime|memory|utils)\/.*\.js$/.test(p)
95
+ || p === '.claude/hooks/ahi-capture.cjs');
96
+ const protectedSet = new Set(paths);
97
+ for (const source of sources) {
98
+ const content = fs.readFileSync(checkedFile(root, source), 'utf8')
99
+ .replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
100
+ const references = /(?:\brequire\s*\(\s*|\bimport\s*\(\s*|\bfrom\s+|\bimport\s+)['"](\.[^'"\r\n]+)['"]/g;
101
+ for (const match of content.matchAll(references)) {
102
+ if (source === `${SYNAPSE_ROOT}memory/synapse-memory-provider.js`
103
+ && match[1] === '../../../../pro/memory/memory-loader') continue;
104
+ const target = resolveLocal(root, source, match[1]);
105
+ if (!protectedSet.has(target)) throw new Error(`Required reference outside protected inventory: ${source} -> ${match[1]}`);
106
+ }
107
+ }
108
+ for (const source of ['.claude/settings.json', '.claude/settings.local.json']) {
109
+ const settings = JSON.parse(fs.readFileSync(checkedFile(root, source), 'utf8'));
110
+ for (const groups of Object.values(settings.hooks || {})) {
111
+ for (const group of groups) for (const hook of group.hooks || []) {
112
+ const match = typeof hook.command === 'string' && hook.command.match(/^node\s+(\.claude\/hooks\/[^\s]+)(?:\s|$)/);
113
+ if (match) checkedFile(root, match[1]);
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ export function writeRuntimeInventory(root) {
120
+ const paths = inventoryPaths(root);
121
+ verifyReferences(root, paths);
122
+ const version = JSON.parse(fs.readFileSync(checkedFile(root, 'package.json'), 'utf8')).version;
123
+ const inventory = { schema: 1, algorithm: 'sha256', scope: 'packaged-runtime-and-cascas', version,
124
+ files: paths.map(file => ({ path: file, sha256: digest(root, file) })) };
125
+ fs.writeFileSync(path.join(root, INVENTORY_PATH), `${JSON.stringify(inventory, null, 2)}\n`);
126
+ return inventory;
127
+ }
128
+
129
+ export function verifyRuntimeInventory(root, packedPaths) {
130
+ const inventory = JSON.parse(fs.readFileSync(checkedFile(root, INVENTORY_PATH), 'utf8'));
131
+ const version = JSON.parse(fs.readFileSync(checkedFile(root, 'package.json'), 'utf8')).version;
132
+ if (inventory.schema !== 1 || inventory.algorithm !== 'sha256' || inventory.scope !== 'packaged-runtime-and-cascas'
133
+ || inventory.version !== version || !Array.isArray(inventory.files)) throw new Error('Invalid runtime inventory');
134
+ const expected = inventoryPaths(root);
135
+ const entries = new Map();
136
+ for (const entry of inventory.files) {
137
+ safeRelative(entry.path);
138
+ if (entries.has(entry.path) || !/^[a-f0-9]{64}$/.test(entry.sha256)) throw new Error('Invalid or duplicate inventory entry');
139
+ entries.set(entry.path, entry.sha256);
140
+ }
141
+ if (entries.size !== expected.length || expected.some(file => !entries.has(file))) throw new Error('Protected inventory is incomplete or changed');
142
+ for (const file of expected) {
143
+ if (digest(root, file) !== entries.get(file)) throw new Error(`Runtime hash mismatch: ${file}`);
144
+ }
145
+ verifyReferences(root, expected);
146
+ if (packedPaths) {
147
+ const included = new Set(packedPaths.map(safeRelative));
148
+ for (const file of [...expected, INVENTORY_PATH]) {
149
+ if (!included.has(file)) throw new Error(`Protected file missing from package: ${file}`);
150
+ }
151
+ for (const file of included) {
152
+ const parts = file.split('/');
153
+ if (FORBIDDEN_PREFIXES.some(prefix => file.startsWith(prefix))
154
+ || (file.startsWith('agents/') && !file.startsWith('agents/organizer/'))
155
+ || parts.some(part => part === '.npmrc' || part === '.env' || (part.startsWith('.env.') && part !== '.env.example'))
156
+ || file.includes('arcane-method') || file.includes('consultor/knowledge')) throw new Error(`Forbidden package file: ${file}`);
157
+ }
158
+ }
159
+ return { version, files: expected.length };
160
+ }
161
+
162
+ if (process.argv[1] && fs.existsSync(process.argv[1])
163
+ && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
164
+ try {
165
+ const args = process.argv.slice(2).filter(arg => arg !== '--verify');
166
+ if (args.length > 1 || args.some(arg => arg.startsWith('--'))) throw new Error('Usage: runtime-integrity.mjs [--verify] [package-root]');
167
+ const result = verifyRuntimeInventory(path.resolve(args[0] || '.'));
168
+ console.log(`Runtime inventory verified: ${result.files} files (SHA256; not a signature).`);
169
+ } catch (error) {
170
+ console.error(`Runtime inventory failed: ${error.message}`);
171
+ process.exitCode = 1;
172
+ }
173
+ }