@nextera.one/axis-server-sdk 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -18,6 +18,7 @@ var __decorateClass = (decorators, target, key, kind) => {
18
18
  if (kind && result) __defProp(target, key, result);
19
19
  return result;
20
20
  };
21
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
21
22
 
22
23
  // src/decorators/handler.decorator.ts
23
24
  import { Injectable, SetMetadata } from "@nestjs/common";
@@ -56,6 +57,24 @@ function Intent(action, options) {
56
57
  };
57
58
  }
58
59
 
60
+ // src/decorators/intent-body.decorator.ts
61
+ import "reflect-metadata";
62
+ var INTENT_BODY_KEY = "axis:intent:body";
63
+ function IntentBody(decoder) {
64
+ return (target, propertyKey) => {
65
+ Reflect.defineMetadata(INTENT_BODY_KEY, decoder, target, propertyKey);
66
+ };
67
+ }
68
+
69
+ // src/decorators/intent-sensors.decorator.ts
70
+ import "reflect-metadata";
71
+ var INTENT_SENSORS_KEY = "axis:intent:sensors";
72
+ function IntentSensors(sensors) {
73
+ return (target, propertyKey) => {
74
+ Reflect.defineMetadata(INTENT_SENSORS_KEY, sensors, target, propertyKey);
75
+ };
76
+ }
77
+
59
78
  // src/decorators/tlv-field.decorator.ts
60
79
  import "reflect-metadata";
61
80
  var TLV_FIELDS_KEY = "axis:tlv:fields";
@@ -117,142 +136,19 @@ function TlvRange(min, max, message) {
117
136
  // src/decorators/dto-schema.util.ts
118
137
  import "reflect-metadata";
119
138
 
120
- // src/core/varint.ts
121
- function encodeVarint(value) {
122
- if (value < 0) throw new Error("Varint must be unsigned");
123
- const bytes2 = [];
124
- while (true) {
125
- const byte = value & 127;
126
- value >>>= 7;
127
- if (value === 0) {
128
- bytes2.push(byte);
129
- break;
130
- }
131
- bytes2.push(byte | 128);
132
- }
133
- return new Uint8Array(bytes2);
134
- }
135
- function decodeVarint(buf, offset = 0) {
136
- let value = 0;
137
- let shift = 0;
138
- let length = 0;
139
- while (true) {
140
- if (offset + length >= buf.length) {
141
- throw new Error("Varint decode out of bounds");
142
- }
143
- const byte = buf[offset + length];
144
- value += (byte & 127) * Math.pow(2, shift);
145
- length++;
146
- shift += 7;
147
- if ((byte & 128) === 0) {
148
- break;
149
- }
150
- if (length > 8) throw new Error("Varint too large");
151
- }
152
- return { value, length };
153
- }
154
- function varintLength(value) {
155
- if (value < 0) throw new Error("Varint must be unsigned");
156
- let len = 0;
157
- do {
158
- value >>>= 7;
159
- len++;
160
- } while (value !== 0);
161
- return len;
162
- }
163
-
164
139
  // src/core/tlv.ts
165
- function encodeTLVs(tlvs) {
166
- const sorted = [...tlvs].sort((a, b) => a.type - b.type);
167
- for (let i = 0; i < sorted.length - 1; i++) {
168
- if (sorted[i].type === sorted[i + 1].type) {
169
- throw new Error(`Duplicate TLV type: ${sorted[i].type}`);
170
- }
171
- }
172
- let totalSize = 0;
173
- for (const t of sorted) {
174
- totalSize += varintLength(t.type);
175
- totalSize += varintLength(t.value.length);
176
- totalSize += t.value.length;
177
- }
178
- const buf = new Uint8Array(totalSize);
179
- let offset = 0;
180
- for (const t of sorted) {
181
- const typeBytes = encodeVarint(t.type);
182
- buf.set(typeBytes, offset);
183
- offset += typeBytes.length;
184
- const lenBytes = encodeVarint(t.value.length);
185
- buf.set(lenBytes, offset);
186
- offset += lenBytes.length;
187
- buf.set(t.value, offset);
188
- offset += t.value.length;
189
- }
190
- return buf;
191
- }
192
- function decodeTLVsList(buf, maxItems = 1024) {
193
- const list = [];
194
- let offset = 0;
195
- while (offset < buf.length) {
196
- if (list.length >= maxItems) throw new Error("TLV_LIMIT");
197
- const { value: type, length: typeLen } = decodeVarint(buf, offset);
198
- offset += typeLen;
199
- const { value: len, length: lenLen } = decodeVarint(buf, offset);
200
- offset += lenLen;
201
- if (offset + len > buf.length) {
202
- throw new Error(`TLV violation: Length ${len} exceeds buffer`);
203
- }
204
- const value = buf.slice(offset, offset + len);
205
- list.push({ type, value });
206
- offset += len;
207
- }
208
- return list;
209
- }
210
- function decodeTLVs(buf) {
211
- const map2 = /* @__PURE__ */ new Map();
212
- let offset = 0;
213
- let lastType = -1;
214
- while (offset < buf.length) {
215
- const { value: type, length: typeLen } = decodeVarint(buf, offset);
216
- offset += typeLen;
217
- if (type <= lastType) {
218
- throw new Error(
219
- `TLV violation: Unsorted or duplicate type ${type} after ${lastType}`
220
- );
221
- }
222
- lastType = type;
223
- const { value: len, length: lenLen } = decodeVarint(buf, offset);
224
- offset += lenLen;
225
- if (offset + len > buf.length) {
226
- throw new Error(`TLV violation: Length ${len} exceeds buffer`);
227
- }
228
- const value = buf.slice(offset, offset + len);
229
- map2.set(type, value);
230
- offset += len;
231
- }
232
- return map2;
233
- }
234
- function decodeObject(bytes2, depth = 0, limits = { maxDepth: 8, maxItems: 128 }) {
235
- if (depth > limits.maxDepth) {
236
- throw new Error("OBJECT_DEPTH_EXCEEDED");
237
- }
238
- const map2 = decodeTLVs(bytes2);
239
- return map2;
240
- }
241
- function decodeArray(bytes2, itemType, maxItems = 256) {
242
- const list = decodeTLVsList(bytes2, maxItems);
243
- const items = [];
244
- for (const tlv2 of list) {
245
- if (tlv2.type !== itemType) {
246
- throw new Error(`INVALID_ARRAY_ITEM:${tlv2.type}`);
247
- }
248
- items.push(tlv2.value);
249
- }
250
- return items;
251
- }
140
+ import {
141
+ TLV,
142
+ encodeTLVs,
143
+ decodeTLVs,
144
+ decodeTLVsList,
145
+ decodeObject,
146
+ decodeArray
147
+ } from "@nextera.one/axis-protocol";
252
148
 
253
149
  // src/decorators/dto-schema.util.ts
254
150
  function extractDtoSchema(dto) {
255
- const fieldMetas = Reflect.getOwnMetadata(TLV_FIELDS_KEY, dto) || [];
151
+ const fieldMetas = Reflect.getMetadata(TLV_FIELDS_KEY, dto) || [];
256
152
  if (fieldMetas.length === 0) {
257
153
  throw new Error(
258
154
  `DTO class ${dto.name} has no @TlvField decorators \u2014 nothing to validate`
@@ -271,7 +167,7 @@ function extractDtoSchema(dto) {
271
167
  scope: m.options.scope
272
168
  };
273
169
  });
274
- const validatorMetas = Reflect.getOwnMetadata(TLV_VALIDATORS_KEY, dto) || [];
170
+ const validatorMetas = Reflect.getMetadata(TLV_VALIDATORS_KEY, dto) || [];
275
171
  const validators = /* @__PURE__ */ new Map();
276
172
  for (const vm of validatorMetas) {
277
173
  const tag = tagByProp.get(vm.property);
@@ -286,7 +182,7 @@ function extractDtoSchema(dto) {
286
182
  return { fields, validators };
287
183
  }
288
184
  function buildDtoDecoder(dto) {
289
- const fieldMetas = Reflect.getOwnMetadata(TLV_FIELDS_KEY, dto) || [];
185
+ const fieldMetas = Reflect.getMetadata(TLV_FIELDS_KEY, dto) || [];
290
186
  if (fieldMetas.length === 0) {
291
187
  throw new Error(
292
188
  `DTO class ${dto.name} has no @TlvField decorators \u2014 cannot build decoder`
@@ -392,11 +288,146 @@ __decorateClass([
392
288
  ], AxisResponseDto.prototype, "updated_by", 2);
393
289
 
394
290
  // src/engine/intent.router.ts
395
- import { Injectable as Injectable2 } from "@nestjs/common";
291
+ import { Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
292
+
293
+ // src/sensor/axis-sensor.ts
294
+ var Decision = /* @__PURE__ */ ((Decision2) => {
295
+ Decision2["ALLOW"] = "ALLOW";
296
+ Decision2["DENY"] = "DENY";
297
+ Decision2["THROTTLE"] = "THROTTLE";
298
+ Decision2["FLAG"] = "FLAG";
299
+ return Decision2;
300
+ })(Decision || {});
301
+ function normalizeSensorDecision(sensorDecision) {
302
+ if ("action" in sensorDecision) {
303
+ switch (sensorDecision.action) {
304
+ case "ALLOW":
305
+ return {
306
+ allow: true,
307
+ riskScore: 0,
308
+ reasons: [],
309
+ meta: sensorDecision.meta
310
+ };
311
+ case "DENY":
312
+ return {
313
+ allow: false,
314
+ riskScore: 100,
315
+ reasons: [sensorDecision.code, sensorDecision.reason].filter(
316
+ Boolean
317
+ ),
318
+ meta: sensorDecision.meta,
319
+ retryAfterMs: sensorDecision.retryAfterMs
320
+ };
321
+ case "THROTTLE":
322
+ return {
323
+ allow: false,
324
+ riskScore: 50,
325
+ reasons: ["RATE_LIMIT"],
326
+ retryAfterMs: sensorDecision.retryAfterMs,
327
+ meta: sensorDecision.meta
328
+ };
329
+ case "FLAG":
330
+ return {
331
+ allow: true,
332
+ riskScore: sensorDecision.scoreDelta,
333
+ reasons: sensorDecision.reasons,
334
+ meta: sensorDecision.meta
335
+ };
336
+ }
337
+ }
338
+ return {
339
+ allow: sensorDecision.allow,
340
+ riskScore: sensorDecision.riskScore,
341
+ reasons: sensorDecision.reasons,
342
+ tags: sensorDecision.tags,
343
+ meta: sensorDecision.meta,
344
+ tighten: sensorDecision.tighten,
345
+ retryAfterMs: sensorDecision.retryAfterMs
346
+ };
347
+ }
348
+ var SensorDecisions = {
349
+ allow(meta, tags) {
350
+ return {
351
+ decision: "ALLOW" /* ALLOW */,
352
+ allow: true,
353
+ riskScore: 0,
354
+ reasons: [],
355
+ tags,
356
+ meta
357
+ };
358
+ },
359
+ deny(code, reason, meta) {
360
+ return {
361
+ decision: "DENY" /* DENY */,
362
+ allow: false,
363
+ riskScore: 100,
364
+ code,
365
+ reasons: [code, reason].filter(Boolean),
366
+ meta
367
+ };
368
+ },
369
+ throttle(retryAfterMs, meta) {
370
+ return {
371
+ decision: "THROTTLE" /* THROTTLE */,
372
+ allow: false,
373
+ riskScore: 50,
374
+ retryAfterMs,
375
+ code: "RATE_LIMIT",
376
+ reasons: ["RATE_LIMIT"],
377
+ meta
378
+ };
379
+ },
380
+ flag(scoreDelta, reasons, meta) {
381
+ return {
382
+ decision: "FLAG" /* FLAG */,
383
+ allow: true,
384
+ riskScore: scoreDelta,
385
+ scoreDelta,
386
+ reasons,
387
+ meta
388
+ };
389
+ }
390
+ };
391
+
392
+ // src/engine/intent.router.ts
396
393
  var IntentRouter = class {
397
- constructor() {
394
+ constructor(moduleRef) {
395
+ this.moduleRef = moduleRef;
396
+ this.logger = new Logger(IntentRouter.name);
398
397
  /** Internal registry of dynamic intent handlers */
399
398
  this.handlers = /* @__PURE__ */ new Map();
399
+ /** Per-intent sensor classes (resolved at call time) */
400
+ this.intentSensors = /* @__PURE__ */ new Map();
401
+ /** Per-intent body decoders */
402
+ this.intentDecoders = /* @__PURE__ */ new Map();
403
+ /** Per-intent TLV schemas */
404
+ this.intentSchemas = /* @__PURE__ */ new Map();
405
+ /** Per-intent custom validators */
406
+ this.intentValidators = /* @__PURE__ */ new Map();
407
+ /** Per-intent operation kind */
408
+ this.intentKinds = /* @__PURE__ */ new Map();
409
+ }
410
+ getSchema(intent) {
411
+ return this.intentSchemas.get(intent);
412
+ }
413
+ getValidators(intent) {
414
+ return this.intentValidators.get(intent);
415
+ }
416
+ has(intent) {
417
+ return this.handlers.has(intent) || IntentRouter.BUILTIN_INTENTS.has(intent);
418
+ }
419
+ getRegisteredIntents() {
420
+ return [...IntentRouter.BUILTIN_INTENTS, ...this.handlers.keys()];
421
+ }
422
+ getIntentEntry(intent) {
423
+ if (!this.has(intent)) return null;
424
+ return {
425
+ schema: this.intentSchemas.get(intent),
426
+ validators: this.intentValidators.get(intent),
427
+ hasSensors: this.intentSensors.has(intent),
428
+ builtin: IntentRouter.BUILTIN_INTENTS.has(intent),
429
+ kind: this.intentKinds.get(intent)
430
+ };
400
431
  }
401
432
  /**
402
433
  * Registers a handler for a specific intent.
@@ -432,6 +463,16 @@ var IntentRouter = class {
432
463
  } else {
433
464
  this.register(intentName, fn);
434
465
  }
466
+ this.registerIntentMeta(intentName, Object.getPrototypeOf(instance), String(route.methodName));
467
+ }
468
+ const proto = Object.getPrototypeOf(instance);
469
+ for (const key of Object.getOwnPropertyNames(proto)) {
470
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, key);
471
+ if (!meta?.intent) continue;
472
+ if (!this.handlers.has(meta.intent)) {
473
+ this.register(meta.intent, instance[key].bind(instance));
474
+ }
475
+ this.registerIntentMeta(meta.intent, proto, key);
435
476
  }
436
477
  }
437
478
  /**
@@ -455,6 +496,7 @@ var IntentRouter = class {
455
496
  intent = new TextDecoder().decode(intentBytes);
456
497
  let effect;
457
498
  if (intent === "system.ping" || intent === "public.ping") {
499
+ this.logger.debug("PING received");
458
500
  effect = {
459
501
  ok: true,
460
502
  effect: "pong",
@@ -495,6 +537,7 @@ var IntentRouter = class {
495
537
  if (!innerIntent) {
496
538
  throw new Error("INTENT.EXEC missing inner intent");
497
539
  }
540
+ this.logger.debug(`EXEC: routing to inner intent '${innerIntent}'`);
498
541
  const innerFrame = {
499
542
  ...frame,
500
543
  headers: new Map(frame.headers),
@@ -510,8 +553,23 @@ var IntentRouter = class {
510
553
  if (!handler) {
511
554
  throw new Error(`Intent not found: ${intent}`);
512
555
  }
556
+ const sensorClasses = this.intentSensors.get(intent);
557
+ if (sensorClasses && sensorClasses.length > 0) {
558
+ await this.runIntentSensors(sensorClasses, intent, frame);
559
+ }
560
+ const decoder = this.intentDecoders.get(intent);
561
+ let decodedBody = frame.body;
562
+ if (decoder) {
563
+ try {
564
+ decodedBody = decoder(Buffer.from(frame.body));
565
+ } catch (decodeErr) {
566
+ throw new Error(
567
+ `IntentBody decode failed for ${intent}: ${decodeErr.message}`
568
+ );
569
+ }
570
+ }
513
571
  if (typeof handler === "function") {
514
- const resultBody = await handler(frame.body, frame.headers);
572
+ const resultBody = decoder ? await handler(decodedBody, frame.headers) : await handler(frame.body, frame.headers);
515
573
  effect = {
516
574
  ok: true,
517
575
  effect: "complete",
@@ -521,10 +579,7 @@ var IntentRouter = class {
521
579
  if (typeof handler.handle === "function") {
522
580
  effect = await handler.handle(frame);
523
581
  } else if (typeof handler.execute === "function") {
524
- const bodyRes = await handler.execute(
525
- frame.body,
526
- frame.headers
527
- );
582
+ const bodyRes = decoder ? await handler.execute(decodedBody, frame.headers) : await handler.execute(frame.body, frame.headers);
528
583
  effect = {
529
584
  ok: true,
530
585
  effect: "complete",
@@ -537,99 +592,200 @@ var IntentRouter = class {
537
592
  }
538
593
  }
539
594
  }
540
- this.recordLatency(intent, start);
595
+ this.logIntent(intent, start, true);
541
596
  return effect;
542
597
  } catch (e) {
543
- console.error(`Error routing intent ${intent}:`, e.message);
598
+ this.logIntent(intent, start, false, e.message);
544
599
  throw e;
545
600
  }
546
601
  }
547
- recordLatency(intent, start) {
602
+ logIntent(intent, start, ok, error) {
548
603
  const diff = process.hrtime(start);
549
- void diff;
604
+ const ms = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(2);
605
+ if (ok) {
606
+ this.logger.debug(`${intent} completed in ${ms}ms`);
607
+ } else {
608
+ this.logger.warn(`${intent} failed in ${ms}ms - ${error}`);
609
+ }
610
+ }
611
+ registerIntentMeta(intent, proto, methodName) {
612
+ const decoder = Reflect.getMetadata(INTENT_BODY_KEY, proto, methodName);
613
+ if (decoder) {
614
+ this.intentDecoders.set(intent, decoder);
615
+ }
616
+ const sensors = Reflect.getMetadata(INTENT_SENSORS_KEY, proto, methodName);
617
+ if (sensors && Array.isArray(sensors) && sensors.length > 0) {
618
+ this.intentSensors.set(intent, sensors);
619
+ }
620
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, methodName);
621
+ if (meta) {
622
+ this.storeSchema(meta);
623
+ if (meta.kind) {
624
+ this.intentKinds.set(intent, meta.kind);
625
+ }
626
+ }
627
+ }
628
+ async runIntentSensors(sensorClasses, intent, frame) {
629
+ if (!this.moduleRef) return;
630
+ for (const SensorClass of sensorClasses) {
631
+ let sensor;
632
+ try {
633
+ sensor = this.moduleRef.get(SensorClass, { strict: false });
634
+ } catch {
635
+ this.logger.warn(
636
+ `@IntentSensors: could not resolve ${SensorClass.name} for ${intent}`
637
+ );
638
+ continue;
639
+ }
640
+ const sensorInput = {
641
+ rawBytes: frame.body,
642
+ intent,
643
+ body: frame.body,
644
+ headerTLVs: frame.headers,
645
+ metadata: { phase: "intent", intent }
646
+ };
647
+ if (sensor.supports && !sensor.supports(sensorInput)) continue;
648
+ const decision = normalizeSensorDecision(await sensor.run(sensorInput));
649
+ if (!decision.allow) {
650
+ const reason = decision.reasons[0] || `${sensor.name}:DENIED`;
651
+ this.logger.warn(
652
+ `Intent sensor ${sensor.name} denied ${intent}: ${reason}`
653
+ );
654
+ throw new Error(`SENSOR_DENY:${reason}`);
655
+ }
656
+ }
657
+ }
658
+ storeSchema(meta) {
659
+ if (meta.dto) {
660
+ if (meta.tlv && meta.tlv.length > 0) {
661
+ this.logger.warn(
662
+ `${meta.intent}: both 'dto' and 'tlv' specified - using dto, ignoring tlv`
663
+ );
664
+ }
665
+ const extracted = extractDtoSchema(meta.dto);
666
+ const schema2 = {
667
+ intent: meta.intent,
668
+ version: 1,
669
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
670
+ fields: extracted.fields.map((f) => ({
671
+ name: f.name,
672
+ tlv: f.tag,
673
+ kind: f.kind,
674
+ required: f.required,
675
+ maxLen: f.maxLen,
676
+ max: f.max,
677
+ scope: f.scope
678
+ }))
679
+ };
680
+ this.intentSchemas.set(meta.intent, schema2);
681
+ if (extracted.validators.size > 0) {
682
+ this.intentValidators.set(meta.intent, extracted.validators);
683
+ }
684
+ if (!this.intentDecoders.has(meta.intent)) {
685
+ this.intentDecoders.set(meta.intent, buildDtoDecoder(meta.dto));
686
+ }
687
+ return;
688
+ }
689
+ if (!meta.tlv || meta.tlv.length === 0) return;
690
+ const schema = {
691
+ intent: meta.intent,
692
+ version: 1,
693
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
694
+ fields: meta.tlv.map((f) => ({
695
+ name: f.name,
696
+ tlv: f.tag,
697
+ kind: f.kind,
698
+ required: f.required,
699
+ maxLen: f.maxLen,
700
+ max: f.max,
701
+ scope: f.scope
702
+ }))
703
+ };
704
+ this.intentSchemas.set(meta.intent, schema);
550
705
  }
551
706
  };
707
+ /** Intents handled inline in route() — not in `handlers` map */
708
+ IntentRouter.BUILTIN_INTENTS = /* @__PURE__ */ new Set([
709
+ "system.ping",
710
+ "public.ping",
711
+ "system.time",
712
+ "system.echo",
713
+ "INTENT.EXEC",
714
+ "axis.intent.exec"
715
+ ]);
552
716
  IntentRouter = __decorateClass([
553
- Injectable2()
717
+ Injectable2(),
718
+ __decorateParam(0, Optional())
554
719
  ], IntentRouter);
555
720
 
556
721
  // src/core/constants.ts
557
- var AXIS_MAGIC = new Uint8Array([65, 88, 73, 83, 49]);
558
- var AXIS_VERSION = 1;
559
- var MAX_HDR_LEN = 2048;
560
- var MAX_BODY_LEN = 65536;
561
- var MAX_SIG_LEN = 128;
562
- var MAX_FRAME_LEN = 70 * 1024;
563
- var FLAG_BODY_TLV = 1;
564
- var FLAG_CHAIN_REQ = 2;
565
- var FLAG_HAS_WITNESS = 4;
566
- var TLV_PID = 1;
567
- var TLV_TS = 2;
568
- var TLV_INTENT = 3;
569
- var TLV_ACTOR_ID = 4;
570
- var TLV_PROOF_TYPE = 5;
571
- var TLV_PROOF_REF = 6;
572
- var TLV_NONCE = 7;
573
- var TLV_AUD = 8;
574
- var TLV_REALM = TLV_AUD;
575
- var TLV_NODE = 9;
576
- var TLV_TRACE_ID = 10;
577
- var TLV_KID = 11;
578
- var TLV_RID = 15;
579
- var TLV_OK = 16;
580
- var TLV_EFFECT = 17;
581
- var TLV_ERROR_CODE = 18;
582
- var TLV_ERROR_MSG = 19;
583
- var TLV_PREV_HASH = 20;
584
- var TLV_RECEIPT_HASH = 21;
585
- var TLV_NODE_KID = 30;
586
- var TLV_NODE_CERT_HASH = 34;
587
- var TLV_LOOM_PRESENCE_ID = 91;
588
- var TLV_LOOM_WRIT = 92;
589
- var TLV_LOOM_THREAD_HASH = 93;
590
- var TLV_UPLOAD_ID = 70;
591
- var TLV_INDEX = 71;
592
- var TLV_OFFSET = 72;
593
- var TLV_SHA256_CHUNK = 73;
594
- var TLV_CAPSULE = 90;
595
- var TLV_BODY_OBJ = 254;
596
- var TLV_BODY_ARR = 255;
597
- var NCERT_NODE_ID = 1;
598
- var NCERT_KID = 2;
599
- var NCERT_ALG = 3;
600
- var NCERT_PUB = 4;
601
- var NCERT_NBF = 5;
602
- var NCERT_EXP = 6;
603
- var NCERT_SCOPE = 7;
604
- var NCERT_ISSUER_KID = 8;
605
- var NCERT_PAYLOAD = 50;
606
- var NCERT_SIG = 51;
607
- var PROOF_NONE = 0;
608
- var PROOF_CAPSULE = 1;
609
- var PROOF_JWT = 2;
610
- var PROOF_MTLS = 3;
611
- var PROOF_LOOM = 4;
612
- var PROOF_WITNESS = 5;
613
- var ProofType = /* @__PURE__ */ ((ProofType2) => {
614
- ProofType2[ProofType2["NONE"] = 0] = "NONE";
615
- ProofType2[ProofType2["CAPSULE"] = 1] = "CAPSULE";
616
- ProofType2[ProofType2["JWT"] = 2] = "JWT";
617
- ProofType2[ProofType2["MTLS"] = 3] = "MTLS";
618
- ProofType2[ProofType2["LOOM"] = 4] = "LOOM";
619
- ProofType2[ProofType2["WITNESS"] = 5] = "WITNESS";
620
- return ProofType2;
621
- })(ProofType || {});
622
- var BodyProfile = /* @__PURE__ */ ((BodyProfile2) => {
623
- BodyProfile2[BodyProfile2["RAW"] = 0] = "RAW";
624
- BodyProfile2[BodyProfile2["TLV_MAP"] = 1] = "TLV_MAP";
625
- BodyProfile2[BodyProfile2["OBJ"] = 2] = "OBJ";
626
- BodyProfile2[BodyProfile2["ARR"] = 3] = "ARR";
627
- return BodyProfile2;
628
- })(BodyProfile || {});
629
- var ERR_INVALID_PACKET = "INVALID_PACKET";
630
- var ERR_BAD_SIGNATURE = "BAD_SIGNATURE";
631
- var ERR_REPLAY_DETECTED = "REPLAY_DETECTED";
632
- var ERR_CONTRACT_VIOLATION = "CONTRACT_VIOLATION";
722
+ import {
723
+ AXIS_MAGIC,
724
+ AXIS_VERSION,
725
+ MAX_HDR_LEN,
726
+ MAX_BODY_LEN,
727
+ MAX_SIG_LEN,
728
+ MAX_FRAME_LEN,
729
+ FLAG_BODY_TLV,
730
+ FLAG_CHAIN_REQ,
731
+ FLAG_HAS_WITNESS,
732
+ TLV_PID,
733
+ TLV_TS,
734
+ TLV_INTENT,
735
+ TLV_ACTOR_ID,
736
+ TLV_PROOF_TYPE,
737
+ TLV_PROOF_REF,
738
+ TLV_NONCE,
739
+ TLV_AUD,
740
+ TLV_REALM,
741
+ TLV_NODE,
742
+ TLV_TRACE_ID,
743
+ TLV_KID,
744
+ TLV_RID,
745
+ TLV_OK,
746
+ TLV_EFFECT,
747
+ TLV_ERROR_CODE,
748
+ TLV_ERROR_MSG,
749
+ TLV_PREV_HASH,
750
+ TLV_RECEIPT_HASH,
751
+ TLV_NODE_KID,
752
+ TLV_NODE_CERT_HASH,
753
+ TLV_LOOM_PRESENCE_ID,
754
+ TLV_LOOM_WRIT,
755
+ TLV_LOOM_THREAD_HASH,
756
+ TLV_UPLOAD_ID,
757
+ TLV_INDEX,
758
+ TLV_OFFSET,
759
+ TLV_SHA256_CHUNK,
760
+ TLV_CAPSULE,
761
+ TLV_BODY_OBJ,
762
+ TLV_BODY_ARR,
763
+ NCERT_NODE_ID,
764
+ NCERT_KID,
765
+ NCERT_ALG,
766
+ NCERT_PUB,
767
+ NCERT_NBF,
768
+ NCERT_EXP,
769
+ NCERT_SCOPE,
770
+ NCERT_ISSUER_KID,
771
+ NCERT_PAYLOAD,
772
+ NCERT_SIG,
773
+ PROOF_NONE,
774
+ PROOF_CAPSULE,
775
+ PROOF_JWT,
776
+ PROOF_MTLS,
777
+ PROOF_LOOM,
778
+ PROOF_WITNESS,
779
+ ProofType,
780
+ BodyProfile,
781
+ ERR_INVALID_PACKET,
782
+ ERR_BAD_SIGNATURE,
783
+ ERR_REPLAY_DETECTED,
784
+ ERR_CONTRACT_VIOLATION
785
+ } from "@nextera.one/axis-protocol";
786
+
787
+ // src/core/varint.ts
788
+ import { encodeVarint, decodeVarint, varintLength } from "@nextera.one/axis-protocol";
633
789
 
634
790
  // src/core/signature.ts
635
791
  import * as crypto from "crypto";
@@ -1998,105 +2154,6 @@ function buildPacket(hdr, body, sig, flags = 0) {
1998
2154
  };
1999
2155
  }
2000
2156
 
2001
- // src/sensor/axis-sensor.ts
2002
- var Decision = /* @__PURE__ */ ((Decision2) => {
2003
- Decision2["ALLOW"] = "ALLOW";
2004
- Decision2["DENY"] = "DENY";
2005
- Decision2["THROTTLE"] = "THROTTLE";
2006
- Decision2["FLAG"] = "FLAG";
2007
- return Decision2;
2008
- })(Decision || {});
2009
- function normalizeSensorDecision(sensorDecision) {
2010
- if ("action" in sensorDecision) {
2011
- switch (sensorDecision.action) {
2012
- case "ALLOW":
2013
- return {
2014
- allow: true,
2015
- riskScore: 0,
2016
- reasons: [],
2017
- meta: sensorDecision.meta
2018
- };
2019
- case "DENY":
2020
- return {
2021
- allow: false,
2022
- riskScore: 100,
2023
- reasons: [sensorDecision.code, sensorDecision.reason].filter(
2024
- Boolean
2025
- ),
2026
- meta: sensorDecision.meta,
2027
- retryAfterMs: sensorDecision.retryAfterMs
2028
- };
2029
- case "THROTTLE":
2030
- return {
2031
- allow: false,
2032
- riskScore: 50,
2033
- reasons: ["RATE_LIMIT"],
2034
- retryAfterMs: sensorDecision.retryAfterMs,
2035
- meta: sensorDecision.meta
2036
- };
2037
- case "FLAG":
2038
- return {
2039
- allow: true,
2040
- riskScore: sensorDecision.scoreDelta,
2041
- reasons: sensorDecision.reasons,
2042
- meta: sensorDecision.meta
2043
- };
2044
- }
2045
- }
2046
- return {
2047
- allow: sensorDecision.allow,
2048
- riskScore: sensorDecision.riskScore,
2049
- reasons: sensorDecision.reasons,
2050
- tags: sensorDecision.tags,
2051
- meta: sensorDecision.meta,
2052
- tighten: sensorDecision.tighten,
2053
- retryAfterMs: sensorDecision.retryAfterMs
2054
- };
2055
- }
2056
- var SensorDecisions = {
2057
- allow(meta, tags) {
2058
- return {
2059
- decision: "ALLOW" /* ALLOW */,
2060
- allow: true,
2061
- riskScore: 0,
2062
- reasons: [],
2063
- tags,
2064
- meta
2065
- };
2066
- },
2067
- deny(code, reason, meta) {
2068
- return {
2069
- decision: "DENY" /* DENY */,
2070
- allow: false,
2071
- riskScore: 100,
2072
- code,
2073
- reasons: [code, reason].filter(Boolean),
2074
- meta
2075
- };
2076
- },
2077
- throttle(retryAfterMs, meta) {
2078
- return {
2079
- decision: "THROTTLE" /* THROTTLE */,
2080
- allow: false,
2081
- riskScore: 50,
2082
- retryAfterMs,
2083
- code: "RATE_LIMIT",
2084
- reasons: ["RATE_LIMIT"],
2085
- meta
2086
- };
2087
- },
2088
- flag(scoreDelta, reasons, meta) {
2089
- return {
2090
- decision: "FLAG" /* FLAG */,
2091
- allow: true,
2092
- riskScore: scoreDelta,
2093
- scoreDelta,
2094
- reasons,
2095
- meta
2096
- };
2097
- }
2098
- };
2099
-
2100
2157
  // src/security/scopes.ts
2101
2158
  function hasScope(scopes, required) {
2102
2159
  if (!Array.isArray(scopes) || scopes.length === 0) {
@@ -2394,13 +2451,245 @@ function isTimestampValid(ts, skewSeconds = 120) {
2394
2451
  const diff = Math.abs(now - ts);
2395
2452
  return diff <= skewSeconds;
2396
2453
  }
2454
+
2455
+ // src/upload/axis-files.handlers.ts
2456
+ import { Inject, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
2457
+ import * as crypto2 from "crypto";
2458
+
2459
+ // src/upload/upload.tokens.ts
2460
+ var AXIS_UPLOAD_SESSION_STORE = "AXIS_UPLOAD_SESSION_STORE";
2461
+ var AXIS_UPLOAD_FILE_STORE = "AXIS_UPLOAD_FILE_STORE";
2462
+ var AXIS_UPLOAD_RECEIPT_SIGNER = "AXIS_UPLOAD_RECEIPT_SIGNER";
2463
+
2464
+ // src/upload/axis-files.handlers.ts
2465
+ var AxisFilesDownloadHandler = class {
2466
+ constructor(sessions, files) {
2467
+ this.sessions = sessions;
2468
+ this.files = files;
2469
+ this.logger = new Logger2(AxisFilesDownloadHandler.name);
2470
+ this.name = "axis.files.download";
2471
+ this.open = true;
2472
+ this.description = "File download handler";
2473
+ }
2474
+ async execute(body, headers) {
2475
+ const h = headers;
2476
+ if (!h) throw new Error("MISSING_HEADERS");
2477
+ const uploadIdBytes = h.get(20);
2478
+ if (!uploadIdBytes) throw new Error("MISSING_UPLOAD_ID");
2479
+ const uploadId = new TextDecoder().decode(uploadIdBytes);
2480
+ let rangeStart = 0;
2481
+ let rangeLen = -1;
2482
+ const startBytes = h.get(21);
2483
+ if (startBytes) {
2484
+ const { value } = decodeVarint(startBytes);
2485
+ rangeStart = value;
2486
+ }
2487
+ const lenBytes = h.get(22);
2488
+ if (lenBytes) {
2489
+ const { value } = decodeVarint(lenBytes);
2490
+ rangeLen = value;
2491
+ }
2492
+ const session = await this.sessions.findByFileId(uploadId);
2493
+ if (!session) {
2494
+ throw new Error(`SESSION_NOT_FOUND: ${uploadId}`);
2495
+ }
2496
+ if (session.status !== "COMPLETE") {
2497
+ throw new Error(`FILE_NOT_READY: Status is ${session.status}`);
2498
+ }
2499
+ const stat = await this.files.statFinal(
2500
+ uploadId,
2501
+ session.filename
2502
+ );
2503
+ const fileSize = stat.size;
2504
+ if (rangeStart < 0) rangeStart = 0;
2505
+ if (rangeStart >= fileSize) throw new Error("RANGE_OUT_OF_BOUNDS");
2506
+ let end = fileSize;
2507
+ if (rangeLen >= 0) {
2508
+ end = Math.min(rangeStart + rangeLen, fileSize);
2509
+ }
2510
+ const actualLen = end - rangeStart;
2511
+ const buffer = await this.files.readFinalRange(
2512
+ uploadId,
2513
+ session.filename,
2514
+ rangeStart,
2515
+ actualLen
2516
+ );
2517
+ const responseHeaders = /* @__PURE__ */ new Map();
2518
+ responseHeaders.set(30, encodeVarint(fileSize));
2519
+ responseHeaders.set(31, encodeVarint(rangeStart));
2520
+ responseHeaders.set(32, encodeVarint(actualLen));
2521
+ return {
2522
+ ok: true,
2523
+ effect: "FILE_PART",
2524
+ body: buffer,
2525
+ headers: responseHeaders
2526
+ };
2527
+ }
2528
+ };
2529
+ __decorateClass([
2530
+ Intent("file.download", { absolute: true, kind: "read" })
2531
+ ], AxisFilesDownloadHandler.prototype, "execute", 1);
2532
+ AxisFilesDownloadHandler = __decorateClass([
2533
+ Handler("axis.files.download"),
2534
+ Injectable3(),
2535
+ __decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
2536
+ __decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE))
2537
+ ], AxisFilesDownloadHandler);
2538
+ var AxisFilesFinalizeHandler = class {
2539
+ constructor(sessions, files, keyring) {
2540
+ this.sessions = sessions;
2541
+ this.files = files;
2542
+ this.keyring = keyring;
2543
+ this.logger = new Logger2(AxisFilesFinalizeHandler.name);
2544
+ this.name = "axis.files.finalize";
2545
+ this.open = false;
2546
+ this.description = "File upload finalization handler";
2547
+ }
2548
+ async execute(body, headers) {
2549
+ const bodyStr = new TextDecoder().decode(body);
2550
+ const req = JSON.parse(bodyStr);
2551
+ const { fileId, expectedHash } = req;
2552
+ if (!fileId) throw new Error("MISSING_FILE_ID");
2553
+ const session = await this.sessions.findByFileId(fileId);
2554
+ if (!session) throw new Error("SESSION_NOT_FOUND");
2555
+ if (!await this.files.hasTemp(fileId)) {
2556
+ throw new Error("CHUNKS_NOT_FOUND");
2557
+ }
2558
+ const hash = crypto2.createHash("sha256");
2559
+ const rs = this.files.createTempReadStream(fileId);
2560
+ for await (const chunk of rs) {
2561
+ hash.update(chunk);
2562
+ }
2563
+ const finalHash = hash.digest("hex");
2564
+ if (expectedHash && finalHash !== expectedHash) {
2565
+ throw new Error("HASH_MISMATCH");
2566
+ }
2567
+ const finalPath = await this.files.moveTempToFinal(
2568
+ fileId,
2569
+ session.filename
2570
+ );
2571
+ await this.sessions.updateStatus(fileId, "COMPLETE", null);
2572
+ if (!this.keyring) {
2573
+ this.logger.warn("Receipt signer not configured; returning unsigned receipt");
2574
+ return {
2575
+ ok: true,
2576
+ effect: "FILE_FINALIZED",
2577
+ body: new TextEncoder().encode(
2578
+ JSON.stringify({
2579
+ uploadId: fileId,
2580
+ sha256_final: finalHash,
2581
+ totalSize: session.totalSize,
2582
+ tsMs: Date.now(),
2583
+ path: finalPath
2584
+ })
2585
+ )
2586
+ };
2587
+ }
2588
+ const receiptData = {
2589
+ uploadId: fileId,
2590
+ sha256_final: finalHash,
2591
+ totalSize: session.totalSize,
2592
+ tsMs: Date.now()
2593
+ };
2594
+ const receiptJson = JSON.stringify(receiptData);
2595
+ const receiptBody = new TextEncoder().encode(receiptJson);
2596
+ const SIG_PRESENT = 1;
2597
+ const responseFrame = {
2598
+ flags: SIG_PRESENT,
2599
+ headers: /* @__PURE__ */ new Map(),
2600
+ body: receiptBody,
2601
+ sig: new Uint8Array(0)
2602
+ };
2603
+ const signTarget = getSignTarget(responseFrame);
2604
+ const { sig, kid } = this.keyring.signActive(signTarget);
2605
+ responseFrame.sig = sig;
2606
+ return {
2607
+ ok: true,
2608
+ effect: "FILE_FINALIZED",
2609
+ data: encodeFrame(responseFrame),
2610
+ headers: /* @__PURE__ */ new Map([[1, new TextEncoder().encode(kid)]])
2611
+ };
2612
+ }
2613
+ };
2614
+ __decorateClass([
2615
+ Intent("file.finalize", { absolute: true, kind: "action" })
2616
+ ], AxisFilesFinalizeHandler.prototype, "execute", 1);
2617
+ AxisFilesFinalizeHandler = __decorateClass([
2618
+ Handler("axis.files.finalize"),
2619
+ Injectable3(),
2620
+ __decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
2621
+ __decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE)),
2622
+ __decorateParam(2, Optional2()),
2623
+ __decorateParam(2, Inject(AXIS_UPLOAD_RECEIPT_SIGNER))
2624
+ ], AxisFilesFinalizeHandler);
2625
+
2626
+ // src/upload/disk-upload-file.store.ts
2627
+ import * as fs from "fs";
2628
+ import * as path from "path";
2629
+ var DiskUploadFileStore = class {
2630
+ constructor(options) {
2631
+ this.uploadDir = options.uploadDir;
2632
+ this.chunkDir = options.chunkDir;
2633
+ }
2634
+ getFinalPath(fileId, filename) {
2635
+ const safeFilename = filename ? path.basename(filename) : fileId;
2636
+ return path.join(this.uploadDir, safeFilename);
2637
+ }
2638
+ getTempPath(fileId) {
2639
+ const safeId = path.basename(fileId);
2640
+ return path.join(this.chunkDir, safeId);
2641
+ }
2642
+ async statFinal(fileId, filename) {
2643
+ const finalPath = this.getFinalPath(fileId, filename);
2644
+ if (!fs.existsSync(finalPath)) {
2645
+ throw new Error("FILE_MISSING_ON_DISK");
2646
+ }
2647
+ const stat = fs.statSync(finalPath);
2648
+ return { path: finalPath, size: stat.size };
2649
+ }
2650
+ async readFinalRange(fileId, filename, start, length) {
2651
+ const finalPath = this.getFinalPath(fileId, filename);
2652
+ const buffer = Buffer.alloc(length);
2653
+ const fd = fs.openSync(finalPath, "r");
2654
+ try {
2655
+ fs.readSync(fd, buffer, 0, length, start);
2656
+ } finally {
2657
+ fs.closeSync(fd);
2658
+ }
2659
+ return buffer;
2660
+ }
2661
+ async hasTemp(fileId) {
2662
+ const tempPath = this.getTempPath(fileId);
2663
+ return fs.existsSync(tempPath);
2664
+ }
2665
+ async moveTempToFinal(fileId, filename) {
2666
+ const tempPath = this.getTempPath(fileId);
2667
+ const finalPath = this.getFinalPath(fileId, filename);
2668
+ try {
2669
+ await fs.promises.rename(tempPath, finalPath);
2670
+ } catch {
2671
+ await fs.promises.copyFile(tempPath, finalPath);
2672
+ await fs.promises.unlink(tempPath);
2673
+ }
2674
+ return finalPath;
2675
+ }
2676
+ createTempReadStream(fileId) {
2677
+ const tempPath = this.getTempPath(fileId);
2678
+ return fs.createReadStream(tempPath);
2679
+ }
2680
+ };
2397
2681
  export {
2398
2682
  ATS1_HDR,
2399
2683
  ATS1_SCHEMA,
2400
2684
  AXIS_MAGIC,
2401
2685
  AXIS_OPCODES,
2686
+ AXIS_UPLOAD_FILE_STORE,
2687
+ AXIS_UPLOAD_RECEIPT_SIGNER,
2688
+ AXIS_UPLOAD_SESSION_STORE,
2402
2689
  AXIS_VERSION,
2403
2690
  ats1_exports as Ats1Codec,
2691
+ AxisFilesDownloadHandler,
2692
+ AxisFilesFinalizeHandler,
2404
2693
  AxisFrameZ,
2405
2694
  AxisIdDto,
2406
2695
  T as AxisPacketTags,
@@ -2413,6 +2702,7 @@ export {
2413
2702
  DEFAULT_CONTRACTS,
2414
2703
  DEFAULT_TIMEOUT,
2415
2704
  Decision,
2705
+ DiskUploadFileStore,
2416
2706
  ERR_BAD_SIGNATURE,
2417
2707
  ERR_CONTRACT_VIOLATION,
2418
2708
  ERR_INVALID_PACKET,
@@ -2424,14 +2714,18 @@ export {
2424
2714
  FLAG_HAS_WITNESS,
2425
2715
  HANDLER_METADATA_KEY,
2426
2716
  Handler,
2717
+ INTENT_BODY_KEY,
2427
2718
  INTENT_METADATA_KEY,
2428
2719
  INTENT_REQUIREMENTS,
2429
2720
  INTENT_ROUTES_KEY,
2430
2721
  INTENT_SENSITIVITY_MAP,
2722
+ INTENT_SENSORS_KEY,
2431
2723
  INTENT_TIMEOUTS,
2432
2724
  Intent,
2725
+ IntentBody,
2433
2726
  IntentRouter,
2434
2727
  IntentSensitivity,
2728
+ IntentSensors,
2435
2729
  MAX_BODY_LEN,
2436
2730
  MAX_FRAME_LEN,
2437
2731
  MAX_HDR_LEN,
@@ -2465,6 +2759,7 @@ export {
2465
2759
  Schema2012_PasskeyLoginVerifyRes,
2466
2760
  Schema2021_PasskeyRegisterOptionsReq,
2467
2761
  SensorDecisions,
2762
+ TLV,
2468
2763
  TLV_ACTOR_ID,
2469
2764
  TLV_AUD,
2470
2765
  TLV_BODY_ARR,