@nextera.one/axis-server-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ var __decorateClass = (decorators, target, key, kind) => {
33
33
  if (kind && result) __defProp(target, key, result);
34
34
  return result;
35
35
  };
36
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
36
37
 
37
38
  // src/index.ts
38
39
  var index_exports = {};
@@ -41,8 +42,13 @@ __export(index_exports, {
41
42
  ATS1_SCHEMA: () => ATS1_SCHEMA,
42
43
  AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
43
44
  AXIS_OPCODES: () => AXIS_OPCODES,
45
+ AXIS_UPLOAD_FILE_STORE: () => AXIS_UPLOAD_FILE_STORE,
46
+ AXIS_UPLOAD_RECEIPT_SIGNER: () => AXIS_UPLOAD_RECEIPT_SIGNER,
47
+ AXIS_UPLOAD_SESSION_STORE: () => AXIS_UPLOAD_SESSION_STORE,
44
48
  AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
45
49
  Ats1Codec: () => ats1_exports,
50
+ AxisFilesDownloadHandler: () => AxisFilesDownloadHandler,
51
+ AxisFilesFinalizeHandler: () => AxisFilesFinalizeHandler,
46
52
  AxisFrameZ: () => AxisFrameZ,
47
53
  AxisIdDto: () => AxisIdDto,
48
54
  AxisPacketTags: () => T,
@@ -55,6 +61,7 @@ __export(index_exports, {
55
61
  DEFAULT_CONTRACTS: () => DEFAULT_CONTRACTS,
56
62
  DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
57
63
  Decision: () => Decision,
64
+ DiskUploadFileStore: () => DiskUploadFileStore,
58
65
  ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
59
66
  ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
60
67
  ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
@@ -66,14 +73,18 @@ __export(index_exports, {
66
73
  FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
67
74
  HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
68
75
  Handler: () => Handler,
76
+ INTENT_BODY_KEY: () => INTENT_BODY_KEY,
69
77
  INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
70
78
  INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
71
79
  INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
72
80
  INTENT_SENSITIVITY_MAP: () => INTENT_SENSITIVITY_MAP,
81
+ INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
73
82
  INTENT_TIMEOUTS: () => INTENT_TIMEOUTS,
74
83
  Intent: () => Intent,
84
+ IntentBody: () => IntentBody,
75
85
  IntentRouter: () => IntentRouter,
76
86
  IntentSensitivity: () => IntentSensitivity,
87
+ IntentSensors: () => IntentSensors,
77
88
  MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
78
89
  MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
79
90
  MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
@@ -164,6 +175,8 @@ __export(index_exports, {
164
175
  classifyIntent: () => classifyIntent,
165
176
  computeReceiptHash: () => computeReceiptHash,
166
177
  computeSignaturePayload: () => computeSignaturePayload,
178
+ core: () => core_exports,
179
+ crypto: () => crypto_exports,
167
180
  decodeArray: () => import_axis_protocol.decodeArray,
168
181
  decodeAxis1Frame: () => decodeAxis1Frame,
169
182
  decodeFrame: () => decodeFrame,
@@ -171,11 +184,13 @@ __export(index_exports, {
171
184
  decodeTLVs: () => import_axis_protocol.decodeTLVs,
172
185
  decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
173
186
  decodeVarint: () => import_axis_protocol3.decodeVarint,
187
+ decorators: () => decorators_exports,
174
188
  encVarint: () => encVarint,
175
189
  encodeAxis1Frame: () => encodeAxis1Frame,
176
190
  encodeFrame: () => encodeFrame,
177
191
  encodeTLVs: () => import_axis_protocol.encodeTLVs,
178
192
  encodeVarint: () => import_axis_protocol3.encodeVarint,
193
+ engine: () => engine_exports,
179
194
  extractDtoSchema: () => extractDtoSchema,
180
195
  generateEd25519KeyPair: () => generateEd25519KeyPair,
181
196
  getSignTarget: () => getSignTarget,
@@ -183,6 +198,7 @@ __export(index_exports, {
183
198
  isAdminOpcode: () => isAdminOpcode,
184
199
  isKnownOpcode: () => isKnownOpcode,
185
200
  isTimestampValid: () => isTimestampValid,
201
+ loom: () => loom_exports,
186
202
  nonce16: () => nonce16,
187
203
  normalizeSensorDecision: () => normalizeSensorDecision,
188
204
  packPasskeyLoginOptionsReq: () => packPasskeyLoginOptionsReq,
@@ -192,7 +208,10 @@ __export(index_exports, {
192
208
  packPasskeyRegisterOptionsReq: () => packPasskeyRegisterOptionsReq,
193
209
  parseScope: () => parseScope,
194
210
  resolveTimeout: () => resolveTimeout,
211
+ schemas: () => schemas_exports,
212
+ security: () => security_exports,
195
213
  sensitivityName: () => sensitivityName,
214
+ sensors: () => sensors_exports,
196
215
  sha256: () => sha256,
197
216
  signFrame: () => signFrame,
198
217
  tlv: () => tlv,
@@ -201,6 +220,7 @@ __export(index_exports, {
201
220
  unpackPasskeyLoginVerifyReq: () => unpackPasskeyLoginVerifyReq,
202
221
  unpackPasskeyRegisterOptionsReq: () => unpackPasskeyRegisterOptionsReq,
203
222
  utf8: () => utf8,
223
+ utils: () => utils_exports,
204
224
  validateFrameShape: () => validateFrameShape,
205
225
  varintLength: () => import_axis_protocol3.varintLength,
206
226
  varintU: () => varintU,
@@ -245,8 +265,26 @@ function Intent(action, options) {
245
265
  };
246
266
  }
247
267
 
248
- // src/decorators/tlv-field.decorator.ts
268
+ // src/decorators/intent-body.decorator.ts
249
269
  var import_reflect_metadata2 = require("reflect-metadata");
270
+ var INTENT_BODY_KEY = "axis:intent:body";
271
+ function IntentBody(decoder) {
272
+ return (target, propertyKey) => {
273
+ Reflect.defineMetadata(INTENT_BODY_KEY, decoder, target, propertyKey);
274
+ };
275
+ }
276
+
277
+ // src/decorators/intent-sensors.decorator.ts
278
+ var import_reflect_metadata3 = require("reflect-metadata");
279
+ var INTENT_SENSORS_KEY = "axis:intent:sensors";
280
+ function IntentSensors(sensors) {
281
+ return (target, propertyKey) => {
282
+ Reflect.defineMetadata(INTENT_SENSORS_KEY, sensors, target, propertyKey);
283
+ };
284
+ }
285
+
286
+ // src/decorators/tlv-field.decorator.ts
287
+ var import_reflect_metadata4 = require("reflect-metadata");
250
288
  var TLV_FIELDS_KEY = "axis:tlv:fields";
251
289
  var TLV_VALIDATORS_KEY = "axis:tlv:validators";
252
290
  function TlvField(tag, options) {
@@ -304,7 +342,7 @@ function TlvRange(min, max, message) {
304
342
  }
305
343
 
306
344
  // src/decorators/dto-schema.util.ts
307
- var import_reflect_metadata3 = require("reflect-metadata");
345
+ var import_reflect_metadata5 = require("reflect-metadata");
308
346
 
309
347
  // src/core/tlv.ts
310
348
  var import_axis_protocol = require("@nextera.one/axis-protocol");
@@ -405,7 +443,7 @@ __decorateClass([
405
443
  ], AxisIdDto.prototype, "id", 2);
406
444
 
407
445
  // src/base/axis-partial-type.ts
408
- var import_reflect_metadata4 = require("reflect-metadata");
446
+ var import_reflect_metadata6 = require("reflect-metadata");
409
447
  function AxisPartialType(BaseDto) {
410
448
  class PartialDto extends BaseDto {
411
449
  }
@@ -452,10 +490,145 @@ __decorateClass([
452
490
 
453
491
  // src/engine/intent.router.ts
454
492
  var import_common2 = require("@nestjs/common");
493
+
494
+ // src/sensor/axis-sensor.ts
495
+ var Decision = /* @__PURE__ */ ((Decision2) => {
496
+ Decision2["ALLOW"] = "ALLOW";
497
+ Decision2["DENY"] = "DENY";
498
+ Decision2["THROTTLE"] = "THROTTLE";
499
+ Decision2["FLAG"] = "FLAG";
500
+ return Decision2;
501
+ })(Decision || {});
502
+ function normalizeSensorDecision(sensorDecision) {
503
+ if ("action" in sensorDecision) {
504
+ switch (sensorDecision.action) {
505
+ case "ALLOW":
506
+ return {
507
+ allow: true,
508
+ riskScore: 0,
509
+ reasons: [],
510
+ meta: sensorDecision.meta
511
+ };
512
+ case "DENY":
513
+ return {
514
+ allow: false,
515
+ riskScore: 100,
516
+ reasons: [sensorDecision.code, sensorDecision.reason].filter(
517
+ Boolean
518
+ ),
519
+ meta: sensorDecision.meta,
520
+ retryAfterMs: sensorDecision.retryAfterMs
521
+ };
522
+ case "THROTTLE":
523
+ return {
524
+ allow: false,
525
+ riskScore: 50,
526
+ reasons: ["RATE_LIMIT"],
527
+ retryAfterMs: sensorDecision.retryAfterMs,
528
+ meta: sensorDecision.meta
529
+ };
530
+ case "FLAG":
531
+ return {
532
+ allow: true,
533
+ riskScore: sensorDecision.scoreDelta,
534
+ reasons: sensorDecision.reasons,
535
+ meta: sensorDecision.meta
536
+ };
537
+ }
538
+ }
539
+ return {
540
+ allow: sensorDecision.allow,
541
+ riskScore: sensorDecision.riskScore,
542
+ reasons: sensorDecision.reasons,
543
+ tags: sensorDecision.tags,
544
+ meta: sensorDecision.meta,
545
+ tighten: sensorDecision.tighten,
546
+ retryAfterMs: sensorDecision.retryAfterMs
547
+ };
548
+ }
549
+ var SensorDecisions = {
550
+ allow(meta, tags) {
551
+ return {
552
+ decision: "ALLOW" /* ALLOW */,
553
+ allow: true,
554
+ riskScore: 0,
555
+ reasons: [],
556
+ tags,
557
+ meta
558
+ };
559
+ },
560
+ deny(code, reason, meta) {
561
+ return {
562
+ decision: "DENY" /* DENY */,
563
+ allow: false,
564
+ riskScore: 100,
565
+ code,
566
+ reasons: [code, reason].filter(Boolean),
567
+ meta
568
+ };
569
+ },
570
+ throttle(retryAfterMs, meta) {
571
+ return {
572
+ decision: "THROTTLE" /* THROTTLE */,
573
+ allow: false,
574
+ riskScore: 50,
575
+ retryAfterMs,
576
+ code: "RATE_LIMIT",
577
+ reasons: ["RATE_LIMIT"],
578
+ meta
579
+ };
580
+ },
581
+ flag(scoreDelta, reasons, meta) {
582
+ return {
583
+ decision: "FLAG" /* FLAG */,
584
+ allow: true,
585
+ riskScore: scoreDelta,
586
+ scoreDelta,
587
+ reasons,
588
+ meta
589
+ };
590
+ }
591
+ };
592
+
593
+ // src/engine/intent.router.ts
455
594
  var IntentRouter = class {
456
- constructor() {
595
+ constructor(moduleRef) {
596
+ this.moduleRef = moduleRef;
597
+ this.logger = new import_common2.Logger(IntentRouter.name);
457
598
  /** Internal registry of dynamic intent handlers */
458
599
  this.handlers = /* @__PURE__ */ new Map();
600
+ /** Per-intent sensor classes (resolved at call time) */
601
+ this.intentSensors = /* @__PURE__ */ new Map();
602
+ /** Per-intent body decoders */
603
+ this.intentDecoders = /* @__PURE__ */ new Map();
604
+ /** Per-intent TLV schemas */
605
+ this.intentSchemas = /* @__PURE__ */ new Map();
606
+ /** Per-intent custom validators */
607
+ this.intentValidators = /* @__PURE__ */ new Map();
608
+ /** Per-intent operation kind */
609
+ this.intentKinds = /* @__PURE__ */ new Map();
610
+ }
611
+ getSchema(intent) {
612
+ return this.intentSchemas.get(intent);
613
+ }
614
+ getValidators(intent) {
615
+ return this.intentValidators.get(intent);
616
+ }
617
+ has(intent) {
618
+ return this.handlers.has(intent) || IntentRouter.BUILTIN_INTENTS.has(intent);
619
+ }
620
+ getRegisteredIntents() {
621
+ return [...IntentRouter.BUILTIN_INTENTS, ...this.handlers.keys()];
622
+ }
623
+ getIntentEntry(intent) {
624
+ if (!this.has(intent)) return null;
625
+ return {
626
+ schema: this.intentSchemas.get(intent),
627
+ validators: this.intentValidators.get(intent),
628
+ hasSensors: this.intentSensors.has(intent),
629
+ builtin: IntentRouter.BUILTIN_INTENTS.has(intent),
630
+ kind: this.intentKinds.get(intent)
631
+ };
459
632
  }
460
633
  /**
461
634
  * Registers a handler for a specific intent.
@@ -491,6 +664,16 @@ var IntentRouter = class {
491
664
  } else {
492
665
  this.register(intentName, fn);
493
666
  }
667
+ this.registerIntentMeta(intentName, Object.getPrototypeOf(instance), String(route.methodName));
668
+ }
669
+ const proto = Object.getPrototypeOf(instance);
670
+ for (const key of Object.getOwnPropertyNames(proto)) {
671
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, key);
672
+ if (!meta?.intent) continue;
673
+ if (!this.handlers.has(meta.intent)) {
674
+ this.register(meta.intent, instance[key].bind(instance));
675
+ }
676
+ this.registerIntentMeta(meta.intent, proto, key);
494
677
  }
495
678
  }
496
679
  /**
@@ -514,6 +697,7 @@ var IntentRouter = class {
514
697
  intent = new TextDecoder().decode(intentBytes);
515
698
  let effect;
516
699
  if (intent === "system.ping" || intent === "public.ping") {
700
+ this.logger.debug("PING received");
517
701
  effect = {
518
702
  ok: true,
519
703
  effect: "pong",
@@ -554,6 +738,7 @@ var IntentRouter = class {
554
738
  if (!innerIntent) {
555
739
  throw new Error("INTENT.EXEC missing inner intent");
556
740
  }
741
+ this.logger.debug(`EXEC: routing to inner intent '${innerIntent}'`);
557
742
  const innerFrame = {
558
743
  ...frame,
559
744
  headers: new Map(frame.headers),
@@ -569,8 +754,23 @@ var IntentRouter = class {
569
754
  if (!handler) {
570
755
  throw new Error(`Intent not found: ${intent}`);
571
756
  }
757
+ const sensorClasses = this.intentSensors.get(intent);
758
+ if (sensorClasses && sensorClasses.length > 0) {
759
+ await this.runIntentSensors(sensorClasses, intent, frame);
760
+ }
761
+ const decoder = this.intentDecoders.get(intent);
762
+ let decodedBody = frame.body;
763
+ if (decoder) {
764
+ try {
765
+ decodedBody = decoder(Buffer.from(frame.body));
766
+ } catch (decodeErr) {
767
+ throw new Error(
768
+ `IntentBody decode failed for ${intent}: ${decodeErr.message}`
769
+ );
770
+ }
771
+ }
572
772
  if (typeof handler === "function") {
573
- const resultBody = await handler(frame.body, frame.headers);
773
+ const resultBody = decoder ? await handler(decodedBody, frame.headers) : await handler(frame.body, frame.headers);
574
774
  effect = {
575
775
  ok: true,
576
776
  effect: "complete",
@@ -580,10 +780,7 @@ var IntentRouter = class {
580
780
  if (typeof handler.handle === "function") {
581
781
  effect = await handler.handle(frame);
582
782
  } else if (typeof handler.execute === "function") {
583
- const bodyRes = await handler.execute(
584
- frame.body,
585
- frame.headers
586
- );
783
+ const bodyRes = decoder ? await handler.execute(decodedBody, frame.headers) : await handler.execute(frame.body, frame.headers);
587
784
  effect = {
588
785
  ok: true,
589
786
  effect: "complete",
@@ -596,20 +793,130 @@ var IntentRouter = class {
596
793
  }
597
794
  }
598
795
  }
599
- this.recordLatency(intent, start);
796
+ this.logIntent(intent, start, true);
600
797
  return effect;
601
798
  } catch (e) {
602
- console.error(`Error routing intent ${intent}:`, e.message);
799
+ this.logIntent(intent, start, false, e.message);
603
800
  throw e;
604
801
  }
605
802
  }
606
- recordLatency(intent, start) {
803
+ logIntent(intent, start, ok, error) {
607
804
  const diff = process.hrtime(start);
608
- void diff;
805
+ const ms = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(2);
806
+ if (ok) {
807
+ this.logger.debug(`${intent} completed in ${ms}ms`);
808
+ } else {
809
+ this.logger.warn(`${intent} failed in ${ms}ms - ${error}`);
810
+ }
811
+ }
812
+ registerIntentMeta(intent, proto, methodName) {
813
+ const decoder = Reflect.getMetadata(INTENT_BODY_KEY, proto, methodName);
814
+ if (decoder) {
815
+ this.intentDecoders.set(intent, decoder);
816
+ }
817
+ const sensors = Reflect.getMetadata(INTENT_SENSORS_KEY, proto, methodName);
818
+ if (sensors && Array.isArray(sensors) && sensors.length > 0) {
819
+ this.intentSensors.set(intent, sensors);
820
+ }
821
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, methodName);
822
+ if (meta) {
823
+ this.storeSchema(meta);
824
+ if (meta.kind) {
825
+ this.intentKinds.set(intent, meta.kind);
826
+ }
827
+ }
828
+ }
829
+ async runIntentSensors(sensorClasses, intent, frame) {
830
+ if (!this.moduleRef) return;
831
+ for (const SensorClass of sensorClasses) {
832
+ let sensor;
833
+ try {
834
+ sensor = this.moduleRef.get(SensorClass, { strict: false });
835
+ } catch {
836
+ this.logger.warn(
837
+ `@IntentSensors: could not resolve ${SensorClass.name} for ${intent}`
838
+ );
839
+ continue;
840
+ }
841
+ const sensorInput = {
842
+ rawBytes: frame.body,
843
+ intent,
844
+ body: frame.body,
845
+ headerTLVs: frame.headers,
846
+ metadata: { phase: "intent", intent }
847
+ };
848
+ if (sensor.supports && !sensor.supports(sensorInput)) continue;
849
+ const decision = normalizeSensorDecision(await sensor.run(sensorInput));
850
+ if (!decision.allow) {
851
+ const reason = decision.reasons[0] || `${sensor.name}:DENIED`;
852
+ this.logger.warn(
853
+ `Intent sensor ${sensor.name} denied ${intent}: ${reason}`
854
+ );
855
+ throw new Error(`SENSOR_DENY:${reason}`);
856
+ }
857
+ }
858
+ }
859
+ storeSchema(meta) {
860
+ if (meta.dto) {
861
+ if (meta.tlv && meta.tlv.length > 0) {
862
+ this.logger.warn(
863
+ `${meta.intent}: both 'dto' and 'tlv' specified - using dto, ignoring tlv`
864
+ );
865
+ }
866
+ const extracted = extractDtoSchema(meta.dto);
867
+ const schema2 = {
868
+ intent: meta.intent,
869
+ version: 1,
870
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
871
+ fields: extracted.fields.map((f) => ({
872
+ name: f.name,
873
+ tlv: f.tag,
874
+ kind: f.kind,
875
+ required: f.required,
876
+ maxLen: f.maxLen,
877
+ max: f.max,
878
+ scope: f.scope
879
+ }))
880
+ };
881
+ this.intentSchemas.set(meta.intent, schema2);
882
+ if (extracted.validators.size > 0) {
883
+ this.intentValidators.set(meta.intent, extracted.validators);
884
+ }
885
+ if (!this.intentDecoders.has(meta.intent)) {
886
+ this.intentDecoders.set(meta.intent, buildDtoDecoder(meta.dto));
887
+ }
888
+ return;
889
+ }
890
+ if (!meta.tlv || meta.tlv.length === 0) return;
891
+ const schema = {
892
+ intent: meta.intent,
893
+ version: 1,
894
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
895
+ fields: meta.tlv.map((f) => ({
896
+ name: f.name,
897
+ tlv: f.tag,
898
+ kind: f.kind,
899
+ required: f.required,
900
+ maxLen: f.maxLen,
901
+ max: f.max,
902
+ scope: f.scope
903
+ }))
904
+ };
905
+ this.intentSchemas.set(meta.intent, schema);
609
906
  }
610
907
  };
908
+ /** Intents handled inline in route() — not in `handlers` map */
909
+ IntentRouter.BUILTIN_INTENTS = /* @__PURE__ */ new Set([
910
+ "system.ping",
911
+ "public.ping",
912
+ "system.time",
913
+ "system.echo",
914
+ "INTENT.EXEC",
915
+ "axis.intent.exec"
916
+ ]);
611
917
  IntentRouter = __decorateClass([
612
- (0, import_common2.Injectable)()
918
+ (0, import_common2.Injectable)(),
919
+ __decorateParam(0, (0, import_common2.Optional)())
613
920
  ], IntentRouter);
614
921
 
615
922
  // src/core/constants.ts
@@ -1985,105 +2292,6 @@ function buildPacket(hdr, body, sig, flags = 0) {
1985
2292
  };
1986
2293
  }
1987
2294
 
1988
- // src/sensor/axis-sensor.ts
1989
- var Decision = /* @__PURE__ */ ((Decision2) => {
1990
- Decision2["ALLOW"] = "ALLOW";
1991
- Decision2["DENY"] = "DENY";
1992
- Decision2["THROTTLE"] = "THROTTLE";
1993
- Decision2["FLAG"] = "FLAG";
1994
- return Decision2;
1995
- })(Decision || {});
1996
- function normalizeSensorDecision(sensorDecision) {
1997
- if ("action" in sensorDecision) {
1998
- switch (sensorDecision.action) {
1999
- case "ALLOW":
2000
- return {
2001
- allow: true,
2002
- riskScore: 0,
2003
- reasons: [],
2004
- meta: sensorDecision.meta
2005
- };
2006
- case "DENY":
2007
- return {
2008
- allow: false,
2009
- riskScore: 100,
2010
- reasons: [sensorDecision.code, sensorDecision.reason].filter(
2011
- Boolean
2012
- ),
2013
- meta: sensorDecision.meta,
2014
- retryAfterMs: sensorDecision.retryAfterMs
2015
- };
2016
- case "THROTTLE":
2017
- return {
2018
- allow: false,
2019
- riskScore: 50,
2020
- reasons: ["RATE_LIMIT"],
2021
- retryAfterMs: sensorDecision.retryAfterMs,
2022
- meta: sensorDecision.meta
2023
- };
2024
- case "FLAG":
2025
- return {
2026
- allow: true,
2027
- riskScore: sensorDecision.scoreDelta,
2028
- reasons: sensorDecision.reasons,
2029
- meta: sensorDecision.meta
2030
- };
2031
- }
2032
- }
2033
- return {
2034
- allow: sensorDecision.allow,
2035
- riskScore: sensorDecision.riskScore,
2036
- reasons: sensorDecision.reasons,
2037
- tags: sensorDecision.tags,
2038
- meta: sensorDecision.meta,
2039
- tighten: sensorDecision.tighten,
2040
- retryAfterMs: sensorDecision.retryAfterMs
2041
- };
2042
- }
2043
- var SensorDecisions = {
2044
- allow(meta, tags) {
2045
- return {
2046
- decision: "ALLOW" /* ALLOW */,
2047
- allow: true,
2048
- riskScore: 0,
2049
- reasons: [],
2050
- tags,
2051
- meta
2052
- };
2053
- },
2054
- deny(code, reason, meta) {
2055
- return {
2056
- decision: "DENY" /* DENY */,
2057
- allow: false,
2058
- riskScore: 100,
2059
- code,
2060
- reasons: [code, reason].filter(Boolean),
2061
- meta
2062
- };
2063
- },
2064
- throttle(retryAfterMs, meta) {
2065
- return {
2066
- decision: "THROTTLE" /* THROTTLE */,
2067
- allow: false,
2068
- riskScore: 50,
2069
- retryAfterMs,
2070
- code: "RATE_LIMIT",
2071
- reasons: ["RATE_LIMIT"],
2072
- meta
2073
- };
2074
- },
2075
- flag(scoreDelta, reasons, meta) {
2076
- return {
2077
- decision: "FLAG" /* FLAG */,
2078
- allow: true,
2079
- riskScore: scoreDelta,
2080
- scoreDelta,
2081
- reasons,
2082
- meta
2083
- };
2084
- }
2085
- };
2086
-
2087
2295
  // src/security/scopes.ts
2088
2296
  function hasScope(scopes, required) {
2089
2297
  if (!Array.isArray(scopes) || scopes.length === 0) {
@@ -2381,14 +2589,3066 @@ function isTimestampValid(ts, skewSeconds = 120) {
2381
2589
  const diff = Math.abs(now - ts);
2382
2590
  return diff <= skewSeconds;
2383
2591
  }
2592
+
2593
+ // src/upload/axis-files.handlers.ts
2594
+ var import_common3 = require("@nestjs/common");
2595
+ var crypto2 = __toESM(require("crypto"));
2596
+
2597
+ // src/upload/upload.tokens.ts
2598
+ var AXIS_UPLOAD_SESSION_STORE = "AXIS_UPLOAD_SESSION_STORE";
2599
+ var AXIS_UPLOAD_FILE_STORE = "AXIS_UPLOAD_FILE_STORE";
2600
+ var AXIS_UPLOAD_RECEIPT_SIGNER = "AXIS_UPLOAD_RECEIPT_SIGNER";
2601
+
2602
+ // src/upload/axis-files.handlers.ts
2603
+ var AxisFilesDownloadHandler = class {
2604
+ constructor(sessions, files) {
2605
+ this.sessions = sessions;
2606
+ this.files = files;
2607
+ this.logger = new import_common3.Logger(AxisFilesDownloadHandler.name);
2608
+ this.name = "axis.files.download";
2609
+ this.open = true;
2610
+ this.description = "File download handler";
2611
+ }
2612
+ async execute(body, headers) {
2613
+ const h = headers;
2614
+ if (!h) throw new Error("MISSING_HEADERS");
2615
+ const uploadIdBytes = h.get(20);
2616
+ if (!uploadIdBytes) throw new Error("MISSING_UPLOAD_ID");
2617
+ const uploadId = new TextDecoder().decode(uploadIdBytes);
2618
+ let rangeStart = 0;
2619
+ let rangeLen = -1;
2620
+ const startBytes = h.get(21);
2621
+ if (startBytes) {
2622
+ const { value } = (0, import_axis_protocol3.decodeVarint)(startBytes);
2623
+ rangeStart = value;
2624
+ }
2625
+ const lenBytes = h.get(22);
2626
+ if (lenBytes) {
2627
+ const { value } = (0, import_axis_protocol3.decodeVarint)(lenBytes);
2628
+ rangeLen = value;
2629
+ }
2630
+ const session = await this.sessions.findByFileId(uploadId);
2631
+ if (!session) {
2632
+ throw new Error(`SESSION_NOT_FOUND: ${uploadId}`);
2633
+ }
2634
+ if (session.status !== "COMPLETE") {
2635
+ throw new Error(`FILE_NOT_READY: Status is ${session.status}`);
2636
+ }
2637
+ const stat = await this.files.statFinal(
2638
+ uploadId,
2639
+ session.filename
2640
+ );
2641
+ const fileSize = stat.size;
2642
+ if (rangeStart < 0) rangeStart = 0;
2643
+ if (rangeStart >= fileSize) throw new Error("RANGE_OUT_OF_BOUNDS");
2644
+ let end = fileSize;
2645
+ if (rangeLen >= 0) {
2646
+ end = Math.min(rangeStart + rangeLen, fileSize);
2647
+ }
2648
+ const actualLen = end - rangeStart;
2649
+ const buffer = await this.files.readFinalRange(
2650
+ uploadId,
2651
+ session.filename,
2652
+ rangeStart,
2653
+ actualLen
2654
+ );
2655
+ const responseHeaders = /* @__PURE__ */ new Map();
2656
+ responseHeaders.set(30, (0, import_axis_protocol3.encodeVarint)(fileSize));
2657
+ responseHeaders.set(31, (0, import_axis_protocol3.encodeVarint)(rangeStart));
2658
+ responseHeaders.set(32, (0, import_axis_protocol3.encodeVarint)(actualLen));
2659
+ return {
2660
+ ok: true,
2661
+ effect: "FILE_PART",
2662
+ body: buffer,
2663
+ headers: responseHeaders
2664
+ };
2665
+ }
2666
+ };
2667
+ __decorateClass([
2668
+ Intent("file.download", { absolute: true, kind: "read" })
2669
+ ], AxisFilesDownloadHandler.prototype, "execute", 1);
2670
+ AxisFilesDownloadHandler = __decorateClass([
2671
+ Handler("axis.files.download"),
2672
+ (0, import_common3.Injectable)(),
2673
+ __decorateParam(0, (0, import_common3.Inject)(AXIS_UPLOAD_SESSION_STORE)),
2674
+ __decorateParam(1, (0, import_common3.Inject)(AXIS_UPLOAD_FILE_STORE))
2675
+ ], AxisFilesDownloadHandler);
2676
+ var AxisFilesFinalizeHandler = class {
2677
+ constructor(sessions, files, keyring) {
2678
+ this.sessions = sessions;
2679
+ this.files = files;
2680
+ this.keyring = keyring;
2681
+ this.logger = new import_common3.Logger(AxisFilesFinalizeHandler.name);
2682
+ this.name = "axis.files.finalize";
2683
+ this.open = false;
2684
+ this.description = "File upload finalization handler";
2685
+ }
2686
+ async execute(body, headers) {
2687
+ const bodyStr = new TextDecoder().decode(body);
2688
+ const req = JSON.parse(bodyStr);
2689
+ const { fileId, expectedHash } = req;
2690
+ if (!fileId) throw new Error("MISSING_FILE_ID");
2691
+ const session = await this.sessions.findByFileId(fileId);
2692
+ if (!session) throw new Error("SESSION_NOT_FOUND");
2693
+ if (!await this.files.hasTemp(fileId)) {
2694
+ throw new Error("CHUNKS_NOT_FOUND");
2695
+ }
2696
+ const hash = crypto2.createHash("sha256");
2697
+ const rs = this.files.createTempReadStream(fileId);
2698
+ for await (const chunk of rs) {
2699
+ hash.update(chunk);
2700
+ }
2701
+ const finalHash = hash.digest("hex");
2702
+ if (expectedHash && finalHash !== expectedHash) {
2703
+ throw new Error("HASH_MISMATCH");
2704
+ }
2705
+ const finalPath = await this.files.moveTempToFinal(
2706
+ fileId,
2707
+ session.filename
2708
+ );
2709
+ await this.sessions.updateStatus(fileId, "COMPLETE", null);
2710
+ if (!this.keyring) {
2711
+ this.logger.warn("Receipt signer not configured; returning unsigned receipt");
2712
+ return {
2713
+ ok: true,
2714
+ effect: "FILE_FINALIZED",
2715
+ body: new TextEncoder().encode(
2716
+ JSON.stringify({
2717
+ uploadId: fileId,
2718
+ sha256_final: finalHash,
2719
+ totalSize: session.totalSize,
2720
+ tsMs: Date.now(),
2721
+ path: finalPath
2722
+ })
2723
+ )
2724
+ };
2725
+ }
2726
+ const receiptData = {
2727
+ uploadId: fileId,
2728
+ sha256_final: finalHash,
2729
+ totalSize: session.totalSize,
2730
+ tsMs: Date.now()
2731
+ };
2732
+ const receiptJson = JSON.stringify(receiptData);
2733
+ const receiptBody = new TextEncoder().encode(receiptJson);
2734
+ const SIG_PRESENT = 1;
2735
+ const responseFrame = {
2736
+ flags: SIG_PRESENT,
2737
+ headers: /* @__PURE__ */ new Map(),
2738
+ body: receiptBody,
2739
+ sig: new Uint8Array(0)
2740
+ };
2741
+ const signTarget = getSignTarget(responseFrame);
2742
+ const { sig, kid } = this.keyring.signActive(signTarget);
2743
+ responseFrame.sig = sig;
2744
+ return {
2745
+ ok: true,
2746
+ effect: "FILE_FINALIZED",
2747
+ data: encodeFrame(responseFrame),
2748
+ headers: /* @__PURE__ */ new Map([[1, new TextEncoder().encode(kid)]])
2749
+ };
2750
+ }
2751
+ };
2752
+ __decorateClass([
2753
+ Intent("file.finalize", { absolute: true, kind: "action" })
2754
+ ], AxisFilesFinalizeHandler.prototype, "execute", 1);
2755
+ AxisFilesFinalizeHandler = __decorateClass([
2756
+ Handler("axis.files.finalize"),
2757
+ (0, import_common3.Injectable)(),
2758
+ __decorateParam(0, (0, import_common3.Inject)(AXIS_UPLOAD_SESSION_STORE)),
2759
+ __decorateParam(1, (0, import_common3.Inject)(AXIS_UPLOAD_FILE_STORE)),
2760
+ __decorateParam(2, (0, import_common3.Optional)()),
2761
+ __decorateParam(2, (0, import_common3.Inject)(AXIS_UPLOAD_RECEIPT_SIGNER))
2762
+ ], AxisFilesFinalizeHandler);
2763
+
2764
+ // src/upload/disk-upload-file.store.ts
2765
+ var fs = __toESM(require("fs"));
2766
+ var path = __toESM(require("path"));
2767
+ var DiskUploadFileStore = class {
2768
+ constructor(options) {
2769
+ this.uploadDir = options.uploadDir;
2770
+ this.chunkDir = options.chunkDir;
2771
+ }
2772
+ getFinalPath(fileId, filename) {
2773
+ const safeFilename = filename ? path.basename(filename) : fileId;
2774
+ return path.join(this.uploadDir, safeFilename);
2775
+ }
2776
+ getTempPath(fileId) {
2777
+ const safeId = path.basename(fileId);
2778
+ return path.join(this.chunkDir, safeId);
2779
+ }
2780
+ async statFinal(fileId, filename) {
2781
+ const finalPath = this.getFinalPath(fileId, filename);
2782
+ if (!fs.existsSync(finalPath)) {
2783
+ throw new Error("FILE_MISSING_ON_DISK");
2784
+ }
2785
+ const stat = fs.statSync(finalPath);
2786
+ return { path: finalPath, size: stat.size };
2787
+ }
2788
+ async readFinalRange(fileId, filename, start, length) {
2789
+ const finalPath = this.getFinalPath(fileId, filename);
2790
+ const buffer = Buffer.alloc(length);
2791
+ const fd = fs.openSync(finalPath, "r");
2792
+ try {
2793
+ fs.readSync(fd, buffer, 0, length, start);
2794
+ } finally {
2795
+ fs.closeSync(fd);
2796
+ }
2797
+ return buffer;
2798
+ }
2799
+ async hasTemp(fileId) {
2800
+ const tempPath = this.getTempPath(fileId);
2801
+ return fs.existsSync(tempPath);
2802
+ }
2803
+ async moveTempToFinal(fileId, filename) {
2804
+ const tempPath = this.getTempPath(fileId);
2805
+ const finalPath = this.getFinalPath(fileId, filename);
2806
+ try {
2807
+ await fs.promises.rename(tempPath, finalPath);
2808
+ } catch {
2809
+ await fs.promises.copyFile(tempPath, finalPath);
2810
+ await fs.promises.unlink(tempPath);
2811
+ }
2812
+ return finalPath;
2813
+ }
2814
+ createTempReadStream(fileId) {
2815
+ const tempPath = this.getTempPath(fileId);
2816
+ return fs.createReadStream(tempPath);
2817
+ }
2818
+ };
2819
+
2820
+ // src/core/index.ts
2821
+ var core_exports = {};
2822
+ __export(core_exports, {
2823
+ AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
2824
+ AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
2825
+ AxisError: () => AxisError,
2826
+ AxisFrameZ: () => AxisFrameZ,
2827
+ BodyProfile: () => import_axis_protocol2.BodyProfile,
2828
+ ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
2829
+ ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
2830
+ ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
2831
+ ERR_REPLAY_DETECTED: () => import_axis_protocol2.ERR_REPLAY_DETECTED,
2832
+ FLAG_BODY_TLV: () => import_axis_protocol2.FLAG_BODY_TLV,
2833
+ FLAG_CHAIN_REQ: () => import_axis_protocol2.FLAG_CHAIN_REQ,
2834
+ FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
2835
+ MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
2836
+ MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
2837
+ MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
2838
+ MAX_SIG_LEN: () => import_axis_protocol2.MAX_SIG_LEN,
2839
+ NCERT_ALG: () => import_axis_protocol2.NCERT_ALG,
2840
+ NCERT_EXP: () => import_axis_protocol2.NCERT_EXP,
2841
+ NCERT_ISSUER_KID: () => import_axis_protocol2.NCERT_ISSUER_KID,
2842
+ NCERT_KID: () => import_axis_protocol2.NCERT_KID,
2843
+ NCERT_NBF: () => import_axis_protocol2.NCERT_NBF,
2844
+ NCERT_NODE_ID: () => import_axis_protocol2.NCERT_NODE_ID,
2845
+ NCERT_PAYLOAD: () => import_axis_protocol2.NCERT_PAYLOAD,
2846
+ NCERT_PUB: () => import_axis_protocol2.NCERT_PUB,
2847
+ NCERT_SCOPE: () => import_axis_protocol2.NCERT_SCOPE,
2848
+ NCERT_SIG: () => import_axis_protocol2.NCERT_SIG,
2849
+ PROOF_CAPSULE: () => import_axis_protocol2.PROOF_CAPSULE,
2850
+ PROOF_JWT: () => import_axis_protocol2.PROOF_JWT,
2851
+ PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
2852
+ PROOF_MTLS: () => import_axis_protocol2.PROOF_MTLS,
2853
+ PROOF_NONE: () => import_axis_protocol2.PROOF_NONE,
2854
+ PROOF_WITNESS: () => import_axis_protocol2.PROOF_WITNESS,
2855
+ ProofType: () => import_axis_protocol2.ProofType,
2856
+ TLV: () => import_axis_protocol.TLV,
2857
+ TLV_ACTOR_ID: () => import_axis_protocol2.TLV_ACTOR_ID,
2858
+ TLV_AUD: () => import_axis_protocol2.TLV_AUD,
2859
+ TLV_BODY_ARR: () => import_axis_protocol2.TLV_BODY_ARR,
2860
+ TLV_BODY_OBJ: () => import_axis_protocol2.TLV_BODY_OBJ,
2861
+ TLV_CAPSULE: () => import_axis_protocol2.TLV_CAPSULE,
2862
+ TLV_EFFECT: () => import_axis_protocol2.TLV_EFFECT,
2863
+ TLV_ERROR_CODE: () => import_axis_protocol2.TLV_ERROR_CODE,
2864
+ TLV_ERROR_MSG: () => import_axis_protocol2.TLV_ERROR_MSG,
2865
+ TLV_INDEX: () => import_axis_protocol2.TLV_INDEX,
2866
+ TLV_INTENT: () => import_axis_protocol2.TLV_INTENT,
2867
+ TLV_KID: () => import_axis_protocol2.TLV_KID,
2868
+ TLV_LOOM_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
2869
+ TLV_LOOM_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
2870
+ TLV_LOOM_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
2871
+ TLV_NODE: () => import_axis_protocol2.TLV_NODE,
2872
+ TLV_NODE_CERT_HASH: () => import_axis_protocol2.TLV_NODE_CERT_HASH,
2873
+ TLV_NODE_KID: () => import_axis_protocol2.TLV_NODE_KID,
2874
+ TLV_NONCE: () => import_axis_protocol2.TLV_NONCE,
2875
+ TLV_OFFSET: () => import_axis_protocol2.TLV_OFFSET,
2876
+ TLV_OK: () => import_axis_protocol2.TLV_OK,
2877
+ TLV_PID: () => import_axis_protocol2.TLV_PID,
2878
+ TLV_PREV_HASH: () => import_axis_protocol2.TLV_PREV_HASH,
2879
+ TLV_PROOF_REF: () => import_axis_protocol2.TLV_PROOF_REF,
2880
+ TLV_PROOF_TYPE: () => import_axis_protocol2.TLV_PROOF_TYPE,
2881
+ TLV_REALM: () => import_axis_protocol2.TLV_REALM,
2882
+ TLV_RECEIPT_HASH: () => import_axis_protocol2.TLV_RECEIPT_HASH,
2883
+ TLV_RID: () => import_axis_protocol2.TLV_RID,
2884
+ TLV_SHA256_CHUNK: () => import_axis_protocol2.TLV_SHA256_CHUNK,
2885
+ TLV_TRACE_ID: () => import_axis_protocol2.TLV_TRACE_ID,
2886
+ TLV_TS: () => import_axis_protocol2.TLV_TS,
2887
+ TLV_UPLOAD_ID: () => import_axis_protocol2.TLV_UPLOAD_ID,
2888
+ computeReceiptHash: () => computeReceiptHash,
2889
+ computeSignaturePayload: () => computeSignaturePayload,
2890
+ decodeArray: () => import_axis_protocol.decodeArray,
2891
+ decodeFrame: () => decodeFrame,
2892
+ decodeObject: () => import_axis_protocol.decodeObject,
2893
+ decodeTLVs: () => import_axis_protocol.decodeTLVs,
2894
+ decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
2895
+ decodeVarint: () => import_axis_protocol3.decodeVarint,
2896
+ encodeFrame: () => encodeFrame,
2897
+ encodeTLVs: () => import_axis_protocol.encodeTLVs,
2898
+ encodeVarint: () => import_axis_protocol3.encodeVarint,
2899
+ generateEd25519KeyPair: () => generateEd25519KeyPair,
2900
+ getSignTarget: () => getSignTarget,
2901
+ sha256: () => sha256,
2902
+ signFrame: () => signFrame,
2903
+ varintLength: () => import_axis_protocol3.varintLength,
2904
+ verifyFrameSignature: () => verifyFrameSignature
2905
+ });
2906
+
2907
+ // src/core/axis-error.ts
2908
+ var AxisError = class extends Error {
2909
+ constructor(code, message, httpStatus = 400, details) {
2910
+ super(message);
2911
+ this.code = code;
2912
+ this.httpStatus = httpStatus;
2913
+ this.details = details;
2914
+ this.name = "AxisError";
2915
+ }
2916
+ };
2917
+
2918
+ // src/crypto/index.ts
2919
+ var crypto_exports = {};
2920
+ __export(crypto_exports, {
2921
+ ProofVerificationService: () => ProofVerificationService,
2922
+ b64urlDecode: () => b64urlDecode,
2923
+ b64urlDecodeString: () => b64urlDecodeString,
2924
+ b64urlEncode: () => b64urlEncode,
2925
+ b64urlEncodeString: () => b64urlEncodeString,
2926
+ canonicalJson: () => canonicalJson,
2927
+ canonicalJsonExcluding: () => canonicalJsonExcluding
2928
+ });
2929
+
2930
+ // src/crypto/proof-verification.service.ts
2931
+ var import_common4 = require("@nestjs/common");
2932
+ var crypto3 = __toESM(require("crypto"));
2933
+ var nacl = __toESM(require("tweetnacl"));
2934
+ var ProofVerificationService = class {
2935
+ constructor() {
2936
+ this.logger = new import_common4.Logger(ProofVerificationService.name);
2937
+ // Cache of registered device public keys (deviceId -> pubKey)
2938
+ this.deviceKeys = /* @__PURE__ */ new Map();
2939
+ // Cache of trusted mTLS certificate fingerprints
2940
+ this.trustedCerts = /* @__PURE__ */ new Map();
2941
+ }
2942
+ /**
2943
+ * Verifies an authentication proof based on its type.
2944
+ *
2945
+ * **Supported Types:**
2946
+ * - 1 (CAPSULE): Delegated to `verifyCapsuleProof`
2947
+ * - 2 (JWT): Verified by `verifyJWTProof`
2948
+ * - 3 (MTLS_ID): Verified by `verifyMTLSProof`
2949
+ * - 4 (DEVICE_SE): Verified by `verifyDeviceSEProof`
2950
+ *
2951
+ * @param {ProofType} proofType - The numeric AXIS proof type
2952
+ * @param {Uint8Array} proofRef - The binary reference or token for the proof
2953
+ * @param {Object} context - Additional metadata required for specific proof types
2954
+ * @param {Uint8Array} [context.signTarget] - The canonical bytes that were signed (for Ed25519)
2955
+ * @param {Uint8Array} [context.signature] - The signature to verify (for Ed25519)
2956
+ * @param {MTLSContext} [context.mtls] - mTLS certificate data
2957
+ * @param {DeviceSEContext} [context.deviceSE] - Device Secure Element information
2958
+ * @returns {Promise<ProofVerificationResult>} The outcome of the verification
2959
+ */
2960
+ async verifyProof(proofType, proofRef, context) {
2961
+ switch (proofType) {
2962
+ case 1:
2963
+ return this.verifyCapsuleProof(proofRef);
2964
+ case 2:
2965
+ return this.verifyJWTProof(proofRef);
2966
+ case 3:
2967
+ return this.verifyMTLSProof(context.mtls);
2968
+ case 4:
2969
+ return this.verifyDeviceSEProof(
2970
+ context.signTarget,
2971
+ context.signature,
2972
+ context.deviceSE
2973
+ );
2974
+ default:
2975
+ return { valid: false, error: `Unknown proof type: ${proofType}` };
2976
+ }
2977
+ }
2978
+ /**
2979
+ * Verify CAPSULE proof (delegated to CapsuleService)
2980
+ */
2981
+ async verifyCapsuleProof(proofRef) {
2982
+ const capsuleId = new TextDecoder().decode(proofRef);
2983
+ return {
2984
+ valid: true,
2985
+ metadata: { capsuleId, requiresCapsuleValidation: true }
2986
+ };
2987
+ }
2988
+ /**
2989
+ * Verifies a JSON Web Token (JWT) proof.
2990
+ *
2991
+ * **Validation Logic:**
2992
+ * 1. Decodes the token string.
2993
+ * 2. Checks for valid 3-part JWT structure.
2994
+ * 3. Validates `exp` (expiration) and `nbf` (not before) claims.
2995
+ * 4. Extracts `actor_id` or `sub` as the identity.
2996
+ *
2997
+ * @param {Uint8Array} proofRef - Binary representation of the JWT string
2998
+ * @returns {Promise<ProofVerificationResult>} Result including the actor identifier
2999
+ */
3000
+ async verifyJWTProof(proofRef) {
3001
+ try {
3002
+ const token = new TextDecoder().decode(proofRef);
3003
+ const parts = token.split(".");
3004
+ if (parts.length !== 3) {
3005
+ return { valid: false, error: "Invalid JWT format" };
3006
+ }
3007
+ const header = JSON.parse(Buffer.from(parts[0], "base64url").toString());
3008
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3009
+ if (payload.exp && Date.now() / 1e3 > payload.exp) {
3010
+ return { valid: false, error: "JWT expired" };
3011
+ }
3012
+ if (payload.nbf && Date.now() / 1e3 < payload.nbf) {
3013
+ return { valid: false, error: "JWT not yet valid" };
3014
+ }
3015
+ return {
3016
+ valid: true,
3017
+ actorId: payload.sub || payload.actor_id,
3018
+ metadata: { iss: payload.iss, scope: payload.scope }
3019
+ };
3020
+ } catch (e) {
3021
+ const message = e instanceof Error ? e.message : "Unknown error";
3022
+ return { valid: false, error: `JWT parse error: ${message}` };
3023
+ }
3024
+ }
3025
+ /**
3026
+ * Verify mTLS client certificate proof
3027
+ */
3028
+ async verifyMTLSProof(mtls) {
3029
+ if (!mtls) {
3030
+ return { valid: false, error: "No mTLS context provided" };
3031
+ }
3032
+ if (!mtls.verified) {
3033
+ return { valid: false, error: "mTLS not verified by TLS terminator" };
3034
+ }
3035
+ if (mtls.clientCertFingerprint) {
3036
+ const trusted = this.trustedCerts.get(mtls.clientCertFingerprint);
3037
+ if (trusted) {
3038
+ return {
3039
+ valid: true,
3040
+ actorId: trusted.actorId,
3041
+ metadata: {
3042
+ fingerprint: mtls.clientCertFingerprint,
3043
+ subject: mtls.clientCertSubject
3044
+ }
3045
+ };
3046
+ }
3047
+ }
3048
+ if (mtls.clientCertSubject) {
3049
+ const cnMatch = mtls.clientCertSubject.match(/CN=([^,]+)/);
3050
+ if (cnMatch) {
3051
+ return {
3052
+ valid: true,
3053
+ actorId: cnMatch[1],
3054
+ metadata: {
3055
+ subject: mtls.clientCertSubject,
3056
+ issuer: mtls.clientCertIssuer
3057
+ }
3058
+ };
3059
+ }
3060
+ }
3061
+ return { valid: false, error: "Could not extract actor from certificate" };
3062
+ }
3063
+ /**
3064
+ * Verify Device Secure Element signature
3065
+ */
3066
+ async verifyDeviceSEProof(signTarget, signature, deviceSE) {
3067
+ if (!deviceSE || !signTarget || !signature) {
3068
+ return { valid: false, error: "Missing Device SE context" };
3069
+ }
3070
+ let publicKey = deviceSE.publicKey;
3071
+ const registeredKey = this.deviceKeys.get(deviceSE.deviceId);
3072
+ if (registeredKey) {
3073
+ publicKey = registeredKey;
3074
+ }
3075
+ if (!publicKey || publicKey.length !== 32) {
3076
+ return {
3077
+ valid: false,
3078
+ error: "Invalid or unregistered device public key"
3079
+ };
3080
+ }
3081
+ try {
3082
+ const valid = nacl.sign.detached.verify(signTarget, signature, publicKey);
3083
+ if (!valid) {
3084
+ return { valid: false, error: "Device signature verification failed" };
3085
+ }
3086
+ return {
3087
+ valid: true,
3088
+ actorId: deviceSE.deviceId,
3089
+ metadata: { deviceId: deviceSE.deviceId, proofType: "DEVICE_SE" }
3090
+ };
3091
+ } catch (e) {
3092
+ const message = e instanceof Error ? e.message : "Unknown error";
3093
+ return {
3094
+ valid: false,
3095
+ error: `Signature verification error: ${message}`
3096
+ };
3097
+ }
3098
+ }
3099
+ /**
3100
+ * Registers a public key for a trusted device.
3101
+ * This key will be used for future `DEVICE_SE` proof verifications.
3102
+ *
3103
+ * @param {string} deviceId - Unique identifier for the device
3104
+ * @param {Uint8Array} publicKey - 32-byte Ed25519 public key
3105
+ * @throws {Error} If the public key is not 32 bytes
3106
+ */
3107
+ registerDeviceKey(deviceId, publicKey) {
3108
+ if (publicKey.length !== 32) {
3109
+ throw new Error("Device public key must be 32 bytes (Ed25519)");
3110
+ }
3111
+ this.deviceKeys.set(deviceId, publicKey);
3112
+ this.logger.log(`Registered device key for ${deviceId}`);
3113
+ }
3114
+ /**
3115
+ * Unregister a device
3116
+ */
3117
+ unregisterDevice(deviceId) {
3118
+ return this.deviceKeys.delete(deviceId);
3119
+ }
3120
+ /**
3121
+ * Registers a trusted mTLS certificate fingerprint and associates it with an actor.
3122
+ *
3123
+ * @param {string} fingerprint - SHA-256 fingerprint of the client certificate
3124
+ * @param {string} actorId - The actor to associate with this certificate
3125
+ */
3126
+ registerMTLSCert(fingerprint, actorId) {
3127
+ this.trustedCerts.set(fingerprint, { actorId, issuedAt: Date.now() });
3128
+ this.logger.log(`Registered mTLS cert ${fingerprint} for actor ${actorId}`);
3129
+ }
3130
+ /**
3131
+ * Revoke an mTLS certificate
3132
+ */
3133
+ revokeMTLSCert(fingerprint) {
3134
+ return this.trustedCerts.delete(fingerprint);
3135
+ }
3136
+ /**
3137
+ * Calculate certificate fingerprint (SHA-256)
3138
+ */
3139
+ static calculateFingerprint(certPem) {
3140
+ const der = Buffer.from(
3141
+ certPem.replace(/-----BEGIN CERTIFICATE-----/, "").replace(/-----END CERTIFICATE-----/, "").replace(/\s/g, ""),
3142
+ "base64"
3143
+ );
3144
+ return crypto3.createHash("sha256").update(der).digest("hex");
3145
+ }
3146
+ };
3147
+ ProofVerificationService = __decorateClass([
3148
+ (0, import_common4.Injectable)()
3149
+ ], ProofVerificationService);
3150
+
3151
+ // src/decorators/index.ts
3152
+ var decorators_exports = {};
3153
+ __export(decorators_exports, {
3154
+ AxisContext: () => AxisContext,
3155
+ AxisDemoPubkey: () => AxisDemoPubkey,
3156
+ AxisFrame: () => AxisFrame3,
3157
+ AxisIp: () => AxisIp,
3158
+ AxisRaw: () => AxisRaw,
3159
+ HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
3160
+ Handler: () => Handler,
3161
+ INTENT_BODY_KEY: () => INTENT_BODY_KEY,
3162
+ INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
3163
+ INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
3164
+ INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
3165
+ Intent: () => Intent,
3166
+ IntentBody: () => IntentBody,
3167
+ IntentSensors: () => IntentSensors,
3168
+ SENSOR_METADATA_KEY: () => SENSOR_METADATA_KEY,
3169
+ Sensor: () => Sensor,
3170
+ TLV_FIELDS_KEY: () => TLV_FIELDS_KEY,
3171
+ TLV_VALIDATORS_KEY: () => TLV_VALIDATORS_KEY,
3172
+ TlvEnum: () => TlvEnum,
3173
+ TlvField: () => TlvField,
3174
+ TlvMinLen: () => TlvMinLen,
3175
+ TlvRange: () => TlvRange,
3176
+ TlvUtf8Pattern: () => TlvUtf8Pattern,
3177
+ TlvValidate: () => TlvValidate,
3178
+ buildDtoDecoder: () => buildDtoDecoder,
3179
+ extractDtoSchema: () => extractDtoSchema
3180
+ });
3181
+
3182
+ // src/decorators/axis-request.decorator.ts
3183
+ var import_common5 = require("@nestjs/common");
3184
+ function resolveIp(req) {
3185
+ return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.socket.remoteAddress || void 0;
3186
+ }
3187
+ var AxisRaw = (0, import_common5.createParamDecorator)(
3188
+ (_data, ctx) => {
3189
+ const req = ctx.switchToHttp().getRequest();
3190
+ return req.body;
3191
+ }
3192
+ );
3193
+ var AxisIp = (0, import_common5.createParamDecorator)(
3194
+ (_data, ctx) => {
3195
+ const req = ctx.switchToHttp().getRequest();
3196
+ return resolveIp(req);
3197
+ }
3198
+ );
3199
+ var AxisContext = (0, import_common5.createParamDecorator)(
3200
+ (_data, ctx) => {
3201
+ const req = ctx.switchToHttp().getRequest();
3202
+ const axisData = req.axis || {};
3203
+ return {
3204
+ raw: req.body,
3205
+ ip: resolveIp(req),
3206
+ preDecodeInput: axisData.preDecodeInput,
3207
+ frameBytesCount: axisData.frameBytesCount || 0
3208
+ };
3209
+ }
3210
+ );
3211
+ var AxisDemoPubkey = (0, import_common5.createParamDecorator)(
3212
+ (_data, ctx) => {
3213
+ if (process.env.NODE_ENV !== "development") return void 0;
3214
+ const req = ctx.switchToHttp().getRequest();
3215
+ return req.headers["x-demo-pubkey"];
3216
+ }
3217
+ );
3218
+ var AxisFrame3 = (0, import_common5.createParamDecorator)(
3219
+ (_data, ctx) => {
3220
+ const req = ctx.switchToHttp().getRequest();
3221
+ const decoded = req.axisDecoded;
3222
+ if (!decoded) {
3223
+ throw new Error(
3224
+ "@AxisFrame() requires AxisDecodeInterceptor on the route. Add @UseInterceptors(AxisDecodeInterceptor) to use this decorator."
3225
+ );
3226
+ }
3227
+ return decoded;
3228
+ }
3229
+ );
3230
+
3231
+ // src/decorators/sensor.decorator.ts
3232
+ var import_common6 = require("@nestjs/common");
3233
+ var SENSOR_METADATA_KEY = "axis:sensor";
3234
+ function Sensor(options) {
3235
+ return (0, import_common6.SetMetadata)(SENSOR_METADATA_KEY, options ?? true);
3236
+ }
3237
+
3238
+ // src/engine/index.ts
3239
+ var engine_exports = {};
3240
+ __export(engine_exports, {
3241
+ BAND: () => BAND,
3242
+ HandlerDiscoveryService: () => HandlerDiscoveryService,
3243
+ IntentRouter: () => IntentRouter,
3244
+ PRE_DECODE_BOUNDARY: () => PRE_DECODE_BOUNDARY,
3245
+ SensorDiscoveryService: () => SensorDiscoveryService,
3246
+ SensorRegistry: () => SensorRegistry,
3247
+ createObservation: () => createObservation,
3248
+ endStage: () => endStage,
3249
+ finalizeObservation: () => finalizeObservation,
3250
+ recordSensor: () => recordSensor,
3251
+ startStage: () => startStage
3252
+ });
3253
+
3254
+ // src/engine/axis-observation.ts
3255
+ var import_crypto4 = require("crypto");
3256
+ function createObservation(transport, ip) {
3257
+ return {
3258
+ id: (0, import_crypto4.randomBytes)(16).toString("hex"),
3259
+ startMs: Date.now(),
3260
+ transport,
3261
+ ip,
3262
+ stages: [],
3263
+ sensors: [],
3264
+ facts: {}
3265
+ };
3266
+ }
3267
+ function startStage(obs, name) {
3268
+ const stage = { name, status: "ok", startMs: Date.now() };
3269
+ obs.stages.push(stage);
3270
+ return stage;
3271
+ }
3272
+ function endStage(stage, status = "ok", reason, code) {
3273
+ stage.endMs = Date.now();
3274
+ stage.durationMs = stage.endMs - stage.startMs;
3275
+ stage.status = status;
3276
+ if (reason) stage.reason = reason;
3277
+ if (code) stage.code = code;
3278
+ }
3279
+ function recordSensor(obs, name, allowed, riskScore, durationMs, reasons, code) {
3280
+ obs.sensors.push({ name, allowed, riskScore, durationMs, reasons, code });
3281
+ }
3282
+ function finalizeObservation(obs, decision, statusCode, resultCode) {
3283
+ obs.endMs = Date.now();
3284
+ obs.durationMs = obs.endMs - obs.startMs;
3285
+ obs.decision = decision;
3286
+ obs.statusCode = statusCode;
3287
+ if (resultCode) obs.resultCode = resultCode;
3288
+ }
3289
+
3290
+ // src/engine/handler-discovery.service.ts
3291
+ var import_common7 = require("@nestjs/common");
3292
+ var HandlerDiscoveryService = class {
3293
+ constructor(discovery, scanner, router) {
3294
+ this.discovery = discovery;
3295
+ this.scanner = scanner;
3296
+ this.router = router;
3297
+ this.logger = new import_common7.Logger(HandlerDiscoveryService.name);
3298
+ }
3299
+ onModuleInit() {
3300
+ const providers = this.discovery.getProviders();
3301
+ let totalIntents = 0;
3302
+ for (const wrapper of providers) {
3303
+ const { instance, metatype } = wrapper;
3304
+ if (!instance || !metatype) continue;
3305
+ const handlerMeta = Reflect.getMetadata(HANDLER_METADATA_KEY, metatype);
3306
+ if (!handlerMeta) continue;
3307
+ const handlerName = handlerMeta.intent || metatype.name;
3308
+ const proto = Object.getPrototypeOf(instance);
3309
+ const methods = this.scanner.getAllMethodNames(proto);
3310
+ let registered = 0;
3311
+ for (const methodName of methods) {
3312
+ const meta = Reflect.getMetadata(
3313
+ INTENT_METADATA_KEY,
3314
+ proto,
3315
+ methodName
3316
+ );
3317
+ if (!meta?.intent) continue;
3318
+ if (!this.router.has(meta.intent)) {
3319
+ this.router.register(
3320
+ meta.intent,
3321
+ instance[methodName].bind(instance)
3322
+ );
3323
+ registered++;
3324
+ totalIntents++;
3325
+ }
3326
+ this.router.registerIntentMeta(meta.intent, proto, methodName);
3327
+ }
3328
+ if (registered > 0) {
3329
+ this.logger.log(
3330
+ `Auto-registered ${registered} intents from ${handlerName}`
3331
+ );
3332
+ }
3333
+ }
3334
+ this.logger.log(
3335
+ `Handler discovery complete: ${totalIntents} intents auto-registered`
3336
+ );
3337
+ }
3338
+ };
3339
+ HandlerDiscoveryService = __decorateClass([
3340
+ (0, import_common7.Injectable)()
3341
+ ], HandlerDiscoveryService);
3342
+
3343
+ // src/engine/sensor-bands.ts
3344
+ var BAND = {
3345
+ /** Pre-decode: raw byte validation, geo, budget, magic */
3346
+ WIRE: 0,
3347
+ /** Post-decode: identity resolution, capsule, proof */
3348
+ IDENTITY: 40,
3349
+ /** Post-decode: authorization, signature, rate limiting */
3350
+ POLICY: 90,
3351
+ /** Post-decode: content validation, TLV, schema, files */
3352
+ CONTENT: 140,
3353
+ /** Post-decode: business logic sensors, streams, WS */
3354
+ BUSINESS: 200,
3355
+ /** Post-decode: audit, logging (always last) */
3356
+ AUDIT: 900
3357
+ };
3358
+ var PRE_DECODE_BOUNDARY = 40;
3359
+
3360
+ // src/engine/sensor-discovery.service.ts
3361
+ var import_common8 = require("@nestjs/common");
3362
+ var SensorDiscoveryService = class {
3363
+ constructor(discovery, reflector, registry) {
3364
+ this.discovery = discovery;
3365
+ this.reflector = reflector;
3366
+ this.registry = registry;
3367
+ this.logger = new import_common8.Logger(SensorDiscoveryService.name);
3368
+ }
3369
+ onApplicationBootstrap() {
3370
+ const providers = this.discovery.getProviders();
3371
+ let count = 0;
3372
+ for (const wrapper of providers) {
3373
+ const { instance } = wrapper;
3374
+ if (!instance || !instance.constructor) continue;
3375
+ const meta = this.reflector.get(
3376
+ SENSOR_METADATA_KEY,
3377
+ instance.constructor
3378
+ );
3379
+ if (!meta) continue;
3380
+ const sensor = instance;
3381
+ if (!sensor.name || sensor.order === void 0) {
3382
+ this.logger.warn(
3383
+ `@Sensor() on ${instance.constructor.name} missing name or order \u2014 skipped`
3384
+ );
3385
+ continue;
3386
+ }
3387
+ if (!sensor.phase) {
3388
+ const decoratorPhase = meta !== true ? meta.phase : void 0;
3389
+ sensor.phase = decoratorPhase ?? (sensor.order < PRE_DECODE_BOUNDARY ? "PRE_DECODE" : "POST_DECODE");
3390
+ }
3391
+ this.registry.register(sensor);
3392
+ count++;
3393
+ }
3394
+ this.logger.log(`Auto-registered ${count} sensors via @Sensor()`);
3395
+ }
3396
+ };
3397
+ SensorDiscoveryService = __decorateClass([
3398
+ (0, import_common8.Injectable)()
3399
+ ], SensorDiscoveryService);
3400
+
3401
+ // src/engine/registry/sensor.registry.ts
3402
+ var import_common9 = require("@nestjs/common");
3403
+ var SensorRegistry = class {
3404
+ constructor(configService) {
3405
+ this.configService = configService;
3406
+ this.sensors = [];
3407
+ this.logger = new import_common9.Logger(SensorRegistry.name);
3408
+ }
3409
+ /**
3410
+ * Registers a new sensor in the registry.
3411
+ *
3412
+ * Validates that:
3413
+ * - AxisSensor has a unique name
3414
+ * - AxisSensor has an order field
3415
+ * - Pre-decode sensors have order < 40
3416
+ * - Post-decode sensors have order >= 40
3417
+ *
3418
+ * @param {AxisSensor} sensor - The sensor instance to register
3419
+ * @throws Error if validation fails
3420
+ */
3421
+ register(sensor) {
3422
+ if (!sensor.name) {
3423
+ throw new Error("AxisSensor must have a name");
3424
+ }
3425
+ const enabledSensorsStr = this.configService.get("ENABLED_SENSORS");
3426
+ const disabledSensorsStr = this.configService.get("DISABLED_SENSORS");
3427
+ const enabledSensors = enabledSensorsStr ? enabledSensorsStr.split(",").map((s) => s.trim()) : null;
3428
+ const disabledSensors = disabledSensorsStr ? disabledSensorsStr.split(",").map((s) => s.trim()) : [];
3429
+ if (enabledSensors && !enabledSensors.includes(sensor.name)) {
3430
+ this.logger.log(`Skipping disabled sensor (not in ENABLED_SENSORS): ${sensor.name}`);
3431
+ return;
3432
+ }
3433
+ if (disabledSensors.includes(sensor.name)) {
3434
+ this.logger.log(`Skipping disabled sensor (in DISABLED_SENSORS): ${sensor.name}`);
3435
+ return;
3436
+ }
3437
+ if (sensor.order === void 0) {
3438
+ throw new Error(`AxisSensor "${sensor.name}" must have an order field`);
3439
+ }
3440
+ const isPreDecodeSensor = this.isPreDecodeSensor(sensor);
3441
+ const isPostDecodeSensor = this.isPostDecodeSensor(sensor);
3442
+ if (isPreDecodeSensor && sensor.order >= 40) {
3443
+ this.logger.warn(
3444
+ `AxisSensor "${sensor.name}" is marked as PRE_DECODE but has order ${sensor.order} (should be < 40)`
3445
+ );
3446
+ }
3447
+ if (isPostDecodeSensor && sensor.order < 40) {
3448
+ this.logger.warn(
3449
+ `AxisSensor "${sensor.name}" is marked as POST_DECODE but has order ${sensor.order} (should be >= 40)`
3450
+ );
3451
+ }
3452
+ this.sensors.push(sensor);
3453
+ const phaseLabel = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase || "UNKNOWN";
3454
+ this.logger.debug(
3455
+ `Registered sensor: ${sensor.name} (order: ${sensor.order}, phase: ${phaseLabel})`
3456
+ );
3457
+ }
3458
+ /**
3459
+ * Returns all registered sensors, sorted by their execution order.
3460
+ *
3461
+ * @returns {AxisSensor[]} A sorted array of sensors
3462
+ */
3463
+ list() {
3464
+ return [...this.sensors].sort(
3465
+ (a, b) => (a.order ?? 999) - (b.order ?? 999)
3466
+ );
3467
+ }
3468
+ /**
3469
+ * Returns only pre-decode sensors (order < 40).
3470
+ * These sensors run in middleware on raw bytes before frame decoding.
3471
+ *
3472
+ * @returns {AxisPreSensor[]} Pre-decode sensors sorted by order
3473
+ */
3474
+ getPreDecodeSensors() {
3475
+ return this.list().filter((s) => (s.order ?? 999) < 40);
3476
+ }
3477
+ /**
3478
+ * Returns only post-decode sensors (order >= 40).
3479
+ * These sensors run in the controller on fully decoded frames.
3480
+ *
3481
+ * @returns {AxisPostSensor[]} Post-decode sensors sorted by order
3482
+ */
3483
+ getPostDecodeSensors() {
3484
+ return this.list().filter(
3485
+ (s) => (s.order ?? 999) >= 40
3486
+ );
3487
+ }
3488
+ /**
3489
+ * Helper: Check if a sensor is a pre-decode sensor.
3490
+ *
3491
+ * @private
3492
+ * @param {AxisSensor} sensor - The sensor to check
3493
+ * @returns {boolean} True if sensor is pre-decode
3494
+ */
3495
+ isPreDecodeSensor(sensor) {
3496
+ const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
3497
+ return phase === "PRE_DECODE" || (sensor.order ?? 999) < 40;
3498
+ }
3499
+ /**
3500
+ * Helper: Check if a sensor is a post-decode sensor.
3501
+ *
3502
+ * @private
3503
+ * @param {AxisSensor} sensor - The sensor to check
3504
+ * @returns {boolean} True if sensor is post-decode
3505
+ */
3506
+ isPostDecodeSensor(sensor) {
3507
+ const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
3508
+ return phase === "POST_DECODE" || (sensor.order ?? 999) >= 40;
3509
+ }
3510
+ /**
3511
+ * Returns sensor count by phase.
3512
+ * Useful for diagnostics and monitoring.
3513
+ *
3514
+ * @returns {{preDecodeCount: number, postDecodeCount: number}}
3515
+ */
3516
+ getSensorCountByPhase() {
3517
+ return {
3518
+ preDecodeCount: this.getPreDecodeSensors().length,
3519
+ postDecodeCount: this.getPostDecodeSensors().length
3520
+ };
3521
+ }
3522
+ /**
3523
+ * Clears all registered sensors.
3524
+ * Useful for testing.
3525
+ *
3526
+ * @internal
3527
+ */
3528
+ clear() {
3529
+ this.sensors = [];
3530
+ }
3531
+ };
3532
+ SensorRegistry = __decorateClass([
3533
+ (0, import_common9.Injectable)()
3534
+ ], SensorRegistry);
3535
+
3536
+ // src/loom/index.ts
3537
+ var loom_exports = {};
3538
+ __export(loom_exports, {
3539
+ PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
3540
+ TLV_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
3541
+ TLV_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
3542
+ TLV_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
3543
+ canonicalizeGrant: () => canonicalizeGrant,
3544
+ canonicalizeWrit: () => canonicalizeWrit,
3545
+ deriveAnchorReflection: () => deriveAnchorReflection
3546
+ });
3547
+
3548
+ // src/loom/loom.types.ts
3549
+ function deriveAnchorReflection(softid, context = "openlogs", scope = "loom") {
3550
+ return `ar:${context}:${scope}:${softid}`;
3551
+ }
3552
+ function canonicalizeWrit(writ) {
3553
+ const ordered = {
3554
+ head: { tid: writ.head.tid, seq: writ.head.seq },
3555
+ body: {
3556
+ who: writ.body.who,
3557
+ act: writ.body.act,
3558
+ res: writ.body.res,
3559
+ law: writ.body.law
3560
+ },
3561
+ meta: { iat: writ.meta.iat, exp: writ.meta.exp, prev: writ.meta.prev }
3562
+ };
3563
+ return JSON.stringify(ordered);
3564
+ }
3565
+ function canonicalizeGrant(grant) {
3566
+ const ordered = {
3567
+ grant_id: grant.grant_id,
3568
+ issuer: grant.issuer,
3569
+ subject: grant.subject,
3570
+ grant_type: grant.grant_type,
3571
+ caps: grant.caps,
3572
+ meta: grant.meta
3573
+ };
3574
+ return JSON.stringify(ordered);
3575
+ }
3576
+
3577
+ // src/schemas/index.ts
3578
+ var schemas_exports = {};
3579
+ __export(schemas_exports, {
3580
+ AccessProfileZ: () => AccessProfileZ,
3581
+ AxisContextZ: () => AxisContextZ,
3582
+ AxisErrorZ: () => AxisErrorZ,
3583
+ BodyBudgetInputZ: () => BodyBudgetInputZ,
3584
+ BodyBudgetPolicyZ: () => BodyBudgetPolicyZ,
3585
+ BodyProfile: () => BodyProfile2,
3586
+ BodyProfileValidator: () => BodyProfileValidator,
3587
+ BodyProfileZ: () => BodyProfileZ,
3588
+ CapsuleClaimsZ: () => CapsuleClaimsZ,
3589
+ CapsuleValidationResultZ: () => CapsuleValidationResultZ,
3590
+ CapsuleVerifyResultZ: () => CapsuleVerifyResultZ,
3591
+ CapsuleVerifySensorInputZ: () => CapsuleVerifySensorInputZ,
3592
+ CapsuleZ: () => CapsuleZ,
3593
+ ChunkHashInputZ: () => ChunkHashInputZ,
3594
+ CountryBlockDecisionZ: () => CountryBlockDecisionZ,
3595
+ CountryBlockSensorInputZ: () => CountryBlockSensorInputZ,
3596
+ EntropySensorInputZ: () => EntropySensorInputZ,
3597
+ ExecutionMetricsZ: () => ExecutionMetricsZ,
3598
+ IPReputationInputZ: () => IPReputationInputZ,
3599
+ IPReputationZ: () => IPReputationZ,
3600
+ IntentPolicyDecisionZ: () => IntentPolicyDecisionZ,
3601
+ IntentPolicySensorInputZ: () => IntentPolicySensorInputZ,
3602
+ IntentPolicyZ: () => IntentPolicyZ,
3603
+ IntentSchemaZ: () => IntentSchemaZ,
3604
+ PassportZ: () => PassportZ,
3605
+ ProofKindZ: () => ProofKindZ,
3606
+ ProofPresenceInputZ: () => ProofPresenceInputZ,
3607
+ ProofType: () => ProofType2,
3608
+ ProtocolStrictInputZ: () => ProtocolStrictInputZ,
3609
+ RateLimitConfigZ: () => RateLimitConfigZ,
3610
+ RateLimitInputZ: () => RateLimitInputZ,
3611
+ RateLimitProfileZ: () => RateLimitProfileZ,
3612
+ ScanBurstDecisionZ: () => ScanBurstDecisionZ,
3613
+ ScanBurstSensorInputZ: () => ScanBurstSensorInputZ,
3614
+ SchemaFieldKindZ: () => SchemaFieldKindZ,
3615
+ SchemaFieldZ: () => SchemaFieldZ,
3616
+ ScopeZ: () => ScopeZ,
3617
+ SensitivityLevelZ: () => SensitivityLevelZ,
3618
+ SensorChainInputZ: () => SensorChainInputZ,
3619
+ SensorDecisionWithMetadataZ: () => SensorDecisionWithMetadataZ,
3620
+ SensorDecisionZ: () => SensorDecisionZ,
3621
+ SensorResultZ: () => SensorResultZ,
3622
+ UploadSessionZ: () => UploadSessionZ,
3623
+ UploadStatusZ: () => UploadStatusZ,
3624
+ WsHandshakeDecisionZ: () => WsHandshakeDecisionZ,
3625
+ WsHandshakeInputZ: () => WsHandshakeInputZ
3626
+ });
3627
+
3628
+ // src/schemas/axis-schemas.ts
3629
+ var z2 = __toESM(require("zod"));
3630
+ var SensorDecisionZ = z2.union([
3631
+ z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
3632
+ z2.object({
3633
+ action: z2.literal("DENY"),
3634
+ code: z2.string(),
3635
+ reason: z2.string().optional(),
3636
+ meta: z2.any().optional()
3637
+ })
3638
+ ]);
3639
+ var SensorDecisionWithMetadataZ = z2.union([
3640
+ z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
3641
+ z2.object({
3642
+ action: z2.literal("DENY"),
3643
+ code: z2.string(),
3644
+ reason: z2.string().optional(),
3645
+ retryAfterMs: z2.number().int().positive().optional(),
3646
+ meta: z2.any().optional()
3647
+ })
3648
+ ]);
3649
+ var CountryBlockSensorInputZ = z2.object({
3650
+ ip: z2.string().min(1),
3651
+ country: z2.string().length(2).toUpperCase().optional()
3652
+ });
3653
+ var CountryBlockDecisionZ = SensorDecisionZ;
3654
+ var ScanBurstSensorInputZ = z2.object({
3655
+ ip: z2.string().min(1),
3656
+ isFailure: z2.boolean().optional()
3657
+ });
3658
+ var ScanBurstDecisionZ = SensorDecisionWithMetadataZ;
3659
+ var ProofKindZ = z2.enum([
3660
+ "NONE",
3661
+ "CAPSULE",
3662
+ "PASSPORT",
3663
+ "MTLS",
3664
+ "JWT"
3665
+ ]);
3666
+ var AccessProfileZ = z2.enum(["PUBLIC", "PARTNER", "INTERNAL", "NODE"]);
3667
+ var ProofPresenceInputZ = z2.object({
3668
+ profile: AccessProfileZ,
3669
+ visibility: z2.enum(["PUBLIC", "GUARDED"]),
3670
+ requiredProof: z2.array(ProofKindZ).min(1),
3671
+ hasCapsule: z2.boolean(),
3672
+ hasPassportSignature: z2.boolean(),
3673
+ intent: z2.string().min(1)
3674
+ });
3675
+ var SensitivityLevelZ = z2.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]);
3676
+ var IntentPolicyZ = z2.object({
3677
+ intent: z2.string().min(1),
3678
+ sensitivity: SensitivityLevelZ,
3679
+ maxFrameBytes: z2.number().int().positive(),
3680
+ maxHeaderBytes: z2.number().int().positive(),
3681
+ maxBodyBytes: z2.number().int().positive(),
3682
+ maxSigBytes: z2.number().int().positive().optional(),
3683
+ rateLimitPerMinute: z2.number().int().positive().optional(),
3684
+ rateLimitPerHour: z2.number().int().positive().optional(),
3685
+ requiresSignature: z2.boolean(),
3686
+ requiresCapsule: z2.boolean(),
3687
+ timeoutMs: z2.number().int().positive()
3688
+ });
3689
+ var IntentPolicySensorInputZ = z2.object({
3690
+ frame: AxisFrameZ,
3691
+ intent: z2.string().min(1),
3692
+ rawFrameSize: z2.number().int().positive()
3693
+ });
3694
+ var IntentPolicyDecisionZ = z2.union([
3695
+ z2.object({
3696
+ action: z2.literal("ALLOW"),
3697
+ policy: IntentPolicyZ
3698
+ }),
3699
+ z2.object({
3700
+ action: z2.literal("DENY"),
3701
+ reason: z2.string()
3702
+ })
3703
+ ]);
3704
+ var CapsuleClaimsZ = z2.object({
3705
+ capsuleId: z2.string().min(8),
3706
+ allowIntents: z2.array(z2.string()).min(1),
3707
+ limits: z2.object({
3708
+ maxBodyBytes: z2.number().int().positive().optional()
3709
+ }).optional(),
3710
+ scopes: z2.record(z2.string(), z2.any()).optional()
3711
+ });
3712
+ var CapsuleZ = z2.object({
3713
+ id: z2.string(),
3714
+ claims: CapsuleClaimsZ,
3715
+ issuedAt: z2.number().int(),
3716
+ expiresAt: z2.number().int(),
3717
+ tier: z2.enum(["FREE", "STANDARD", "PREMIUM"])
3718
+ });
3719
+ var CapsuleValidationResultZ = z2.object({
3720
+ valid: z2.boolean(),
3721
+ capsule: CapsuleZ.optional(),
3722
+ reason: z2.string().optional(),
3723
+ requiresStepUp: z2.boolean().optional()
3724
+ });
3725
+ var CapsuleVerifySensorInputZ = z2.object({
3726
+ headers: z2.map(
3727
+ z2.number(),
3728
+ z2.custom((v) => v instanceof Uint8Array)
3729
+ ),
3730
+ intent: z2.string().min(1),
3731
+ ctx: z2.any()
3732
+ // AxisContext - avoid circular dependency
3733
+ });
3734
+ var CapsuleVerifyResultZ = z2.object({
3735
+ ok: z2.literal(true),
3736
+ capsule: CapsuleZ
3737
+ });
3738
+ var RateLimitProfileZ = z2.enum([
3739
+ "PUBLIC",
3740
+ "PARTNER",
3741
+ "INTERNAL",
3742
+ "NODE"
3743
+ ]);
3744
+ var RateLimitInputZ = z2.object({
3745
+ ip: z2.string().min(1),
3746
+ userAgent: z2.string().optional(),
3747
+ actorId: z2.string().optional(),
3748
+ capsuleId: z2.string().optional(),
3749
+ intent: z2.string().min(1),
3750
+ profile: RateLimitProfileZ
3751
+ });
3752
+ var RateLimitConfigZ = z2.object({
3753
+ windowSec: z2.number().int().positive(),
3754
+ max: z2.number().int().positive(),
3755
+ key: z2.enum(["ip_fingerprint", "actor_capsule"])
3756
+ });
3757
+ var SensorResultZ = z2.object({
3758
+ ok: z2.literal(true)
3759
+ });
3760
+ var PassportZ = z2.object({
3761
+ id: z2.string(),
3762
+ public_key: z2.custom((v) => Buffer.isBuffer(v)),
3763
+ status: z2.enum(["ACTIVE", "REVOKED", "EXPIRED", "PENDING"]),
3764
+ issuedAt: z2.number().int(),
3765
+ expiresAt: z2.number().int().optional()
3766
+ });
3767
+ var ExecutionMetricsZ = z2.object({
3768
+ dbWrites: z2.number().int(),
3769
+ dbReads: z2.number().int(),
3770
+ externalCalls: z2.number().int(),
3771
+ elapsedMs: z2.number().int().optional()
3772
+ });
3773
+ var SensorChainInputZ = z2.object({
3774
+ ip: z2.string().min(1),
3775
+ path: z2.string().min(1),
3776
+ contentLength: z2.number().int().nonnegative(),
3777
+ peek: z2.instanceof(Uint8Array),
3778
+ country: z2.string().optional()
3779
+ });
3780
+ var EntropySensorInputZ = z2.object({
3781
+ pid: z2.custom((v) => Buffer.isBuffer(v)).optional(),
3782
+ nonce: z2.custom((v) => Buffer.isBuffer(v)).optional(),
3783
+ ip: z2.string().min(1)
3784
+ });
3785
+ var ProtocolStrictInputZ = z2.object({
3786
+ rawBytes: z2.union([z2.custom((v) => Buffer.isBuffer(v)), z2.instanceof(Uint8Array)]).optional(),
3787
+ ip: z2.string().min(1),
3788
+ path: z2.string().min(1),
3789
+ contentLength: z2.number().int().nonnegative(),
3790
+ peek: z2.instanceof(Uint8Array),
3791
+ country: z2.string().optional(),
3792
+ contentType: z2.string().optional()
3793
+ });
3794
+ var SchemaFieldKindZ = z2.enum([
3795
+ "utf8",
3796
+ "u64",
3797
+ "bytes",
3798
+ "bytes16",
3799
+ "bool",
3800
+ "obj",
3801
+ "arr"
3802
+ ]);
3803
+ var ScopeZ = z2.enum(["header", "body"]);
3804
+ var SchemaFieldZ = z2.object({
3805
+ name: z2.string().min(1),
3806
+ tlv: z2.number().int().positive(),
3807
+ kind: SchemaFieldKindZ,
3808
+ required: z2.boolean().optional(),
3809
+ maxLen: z2.number().int().positive().optional(),
3810
+ max: z2.string().optional(),
3811
+ scope: ScopeZ.optional()
3812
+ });
3813
+ var BodyProfileZ = z2.enum(["TLV_MAP", "RAW", "TLV_OBJ", "TLV_ARR"]);
3814
+ var IntentSchemaZ = z2.object({
3815
+ intent: z2.string().min(1),
3816
+ version: z2.number().int().positive(),
3817
+ bodyProfile: BodyProfileZ,
3818
+ fields: z2.array(SchemaFieldZ).min(1)
3819
+ });
3820
+ var WsHandshakeInputZ = z2.object({
3821
+ clientId: z2.string().min(1),
3822
+ isWs: z2.boolean(),
3823
+ ip: z2.string().min(1)
3824
+ });
3825
+ var WsHandshakeDecisionZ = z2.union([
3826
+ z2.object({ action: z2.literal("ALLOW") }),
3827
+ z2.object({ action: z2.literal("DENY"), code: z2.string() })
3828
+ ]);
3829
+ var IPReputationInputZ = z2.object({
3830
+ ip: z2.string().min(1)
3831
+ });
3832
+ var IPReputationZ = z2.object({
3833
+ score: z2.number().min(-100).max(100),
3834
+ lastUpdated: z2.number().int(),
3835
+ totalRequests: z2.number().int().nonnegative(),
3836
+ failedRequests: z2.number().int().nonnegative(),
3837
+ blockedRequests: z2.number().int().nonnegative(),
3838
+ tags: z2.array(z2.string())
3839
+ });
3840
+ var UploadStatusZ = z2.enum([
3841
+ "INIT",
3842
+ "UPLOADING",
3843
+ "FINALIZING",
3844
+ "DONE",
3845
+ "ABORTED"
3846
+ ]);
3847
+ var UploadSessionZ = z2.object({
3848
+ uploadIdHex: z2.string().min(1),
3849
+ fileName: z2.string().min(1),
3850
+ totalSize: z2.number().int().positive(),
3851
+ chunkSize: z2.number().int().positive(),
3852
+ totalChunks: z2.number().int().positive(),
3853
+ receivedCount: z2.number().int().nonnegative(),
3854
+ status: UploadStatusZ
3855
+ });
3856
+ var BodyBudgetInputZ = z2.object({
3857
+ intent: z2.string().min(1),
3858
+ headerLen: z2.number().int().nonnegative(),
3859
+ bodyLen: z2.number().int().nonnegative()
3860
+ });
3861
+ var BodyBudgetPolicyZ = z2.object({
3862
+ maxHeaderBytes: z2.number().int().positive(),
3863
+ maxBodyBytes: z2.number().int().positive()
3864
+ });
3865
+ var ChunkHashInputZ = z2.object({
3866
+ headerTLVs: z2.any(),
3867
+ // Map<number, Uint8Array> - flexible validation for compatibility
3868
+ bodyBytes: z2.any(),
3869
+ // Uint8Array - flexible validation for compatibility
3870
+ intent: z2.string().min(1)
3871
+ });
3872
+ var ProofType2 = /* @__PURE__ */ ((ProofType3) => {
3873
+ ProofType3[ProofType3["CAPSULE"] = 1] = "CAPSULE";
3874
+ ProofType3[ProofType3["JWT"] = 2] = "JWT";
3875
+ ProofType3[ProofType3["MTLS_ID"] = 3] = "MTLS_ID";
3876
+ ProofType3[ProofType3["DEVICE_SE"] = 4] = "DEVICE_SE";
3877
+ ProofType3[ProofType3["WITNESS_SIG"] = 5] = "WITNESS_SIG";
3878
+ return ProofType3;
3879
+ })(ProofType2 || {});
3880
+ var AxisContextZ = z2.object({
3881
+ pid: z2.custom((v) => Buffer.isBuffer(v)),
3882
+ // Process ID
3883
+ ts: z2.bigint(),
3884
+ // Timestamp
3885
+ intent: z2.string().min(1),
3886
+ actorId: z2.custom((v) => Buffer.isBuffer(v)),
3887
+ proofType: z2.enum(ProofType2),
3888
+ proofRef: z2.custom((v) => Buffer.isBuffer(v)),
3889
+ nonce: z2.custom((v) => Buffer.isBuffer(v)),
3890
+ ip: z2.string().min(1),
3891
+ nodeCertHash: z2.string().optional(),
3892
+ capsule: CapsuleZ.optional(),
3893
+ passport: PassportZ.optional(),
3894
+ meter: z2.any().optional()
3895
+ // ExecutionMeter instance - any to avoid circular dependency and allow class instance
3896
+ });
3897
+ var AxisErrorZ = z2.object({
3898
+ code: z2.string(),
3899
+ message: z2.string(),
3900
+ httpStatus: z2.number().int()
3901
+ });
3902
+
3903
+ // src/schemas/body-profile.validator.ts
3904
+ var import_common10 = require("@nestjs/common");
3905
+ var BodyProfile2 = /* @__PURE__ */ ((BodyProfile3) => {
3906
+ BodyProfile3[BodyProfile3["RAW"] = 0] = "RAW";
3907
+ BodyProfile3[BodyProfile3["TLV_MAP"] = 1] = "TLV_MAP";
3908
+ BodyProfile3[BodyProfile3["OBJ"] = 2] = "OBJ";
3909
+ BodyProfile3[BodyProfile3["ARR"] = 3] = "ARR";
3910
+ return BodyProfile3;
3911
+ })(BodyProfile2 || {});
3912
+ var BodyProfileValidator = class {
3913
+ constructor() {
3914
+ this.logger = new import_common10.Logger(BodyProfileValidator.name);
3915
+ }
3916
+ /**
3917
+ * Validate body matches declared profile
3918
+ */
3919
+ validate(body, profile) {
3920
+ switch (profile) {
3921
+ case 0 /* RAW */:
3922
+ return this.validateRaw(body);
3923
+ case 1 /* TLV_MAP */:
3924
+ return this.validateTlvMap(body);
3925
+ case 2 /* OBJ */:
3926
+ return this.validateObj(body);
3927
+ case 3 /* ARR */:
3928
+ return this.validateArr(body);
3929
+ default:
3930
+ return {
3931
+ valid: false,
3932
+ error: `Unknown body profile: ${profile}`,
3933
+ profile
3934
+ };
3935
+ }
3936
+ }
3937
+ /**
3938
+ * RAW profile - no validation, any bytes accepted
3939
+ */
3940
+ validateRaw(body) {
3941
+ return {
3942
+ valid: true,
3943
+ profile: 0 /* RAW */
3944
+ };
3945
+ }
3946
+ /**
3947
+ * TLV_MAP profile - flat TLV list (no nested structures)
3948
+ */
3949
+ validateTlvMap(body) {
3950
+ try {
3951
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
3952
+ for (const tlv2 of tlvs) {
3953
+ if (tlv2.type === 254 || tlv2.type === 255) {
3954
+ return {
3955
+ valid: false,
3956
+ error: "TLV_MAP profile cannot contain nested OBJ/ARR types",
3957
+ profile: 1 /* TLV_MAP */
3958
+ };
3959
+ }
3960
+ }
3961
+ return {
3962
+ valid: true,
3963
+ profile: 1 /* TLV_MAP */
3964
+ };
3965
+ } catch (error) {
3966
+ const message = error instanceof Error ? error.message : "Unknown error";
3967
+ return {
3968
+ valid: false,
3969
+ error: `TLV_MAP decode failed: ${message}`,
3970
+ profile: 1 /* TLV_MAP */
3971
+ };
3972
+ }
3973
+ }
3974
+ /**
3975
+ * OBJ profile - must be valid nested object
3976
+ */
3977
+ validateObj(body) {
3978
+ try {
3979
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
3980
+ const hasObj = tlvs.some((t) => t.type === 254);
3981
+ if (!hasObj && tlvs.length > 0) {
3982
+ return {
3983
+ valid: false,
3984
+ error: "OBJ profile must contain OBJ type (254)",
3985
+ profile: 2 /* OBJ */
3986
+ };
3987
+ }
3988
+ return {
3989
+ valid: true,
3990
+ profile: 2 /* OBJ */
3991
+ };
3992
+ } catch (error) {
3993
+ const message = error instanceof Error ? error.message : "Unknown error";
3994
+ return {
3995
+ valid: false,
3996
+ error: `OBJ decode failed: ${message}`,
3997
+ profile: 2 /* OBJ */
3998
+ };
3999
+ }
4000
+ }
4001
+ /**
4002
+ * ARR profile - must be valid array
4003
+ */
4004
+ validateArr(body) {
4005
+ try {
4006
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
4007
+ const hasArr = tlvs.some((t) => t.type === 255);
4008
+ if (!hasArr && tlvs.length > 0) {
4009
+ return {
4010
+ valid: false,
4011
+ error: "ARR profile must contain ARR type (255)",
4012
+ profile: 3 /* ARR */
4013
+ };
4014
+ }
4015
+ return {
4016
+ valid: true,
4017
+ profile: 3 /* ARR */
4018
+ };
4019
+ } catch (error) {
4020
+ const message = error instanceof Error ? error.message : "Unknown error";
4021
+ return {
4022
+ valid: false,
4023
+ error: `ARR decode failed: ${message}`,
4024
+ profile: 3 /* ARR */
4025
+ };
4026
+ }
4027
+ }
4028
+ };
4029
+ BodyProfileValidator = __decorateClass([
4030
+ (0, import_common10.Injectable)()
4031
+ ], BodyProfileValidator);
4032
+
4033
+ // src/security/index.ts
4034
+ var security_exports = {};
4035
+ __export(security_exports, {
4036
+ CAPABILITIES: () => CAPABILITIES,
4037
+ INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
4038
+ PROOF_CAPABILITIES: () => PROOF_CAPABILITIES,
4039
+ canAccessResource: () => canAccessResource,
4040
+ hasScope: () => hasScope,
4041
+ parseScope: () => parseScope
4042
+ });
4043
+
4044
+ // src/sensors/index.ts
4045
+ var sensors_exports = {};
4046
+ __export(sensors_exports, {
4047
+ AccessProfileResolverSensor: () => AccessProfileResolverSensor,
4048
+ BodyBudgetSensor: () => BodyBudgetSensor,
4049
+ CapabilityEnforcementSensor: () => CapabilityEnforcementSensor,
4050
+ ChunkHashSensor: () => ChunkHashSensor,
4051
+ EntropySensor: () => EntropySensor,
4052
+ ExecutionTimeoutSensor: () => ExecutionTimeoutSensor,
4053
+ FrameBudgetSensor: () => FrameBudgetSensor,
4054
+ FrameHeaderSanitySensor: () => FrameHeaderSanitySensor,
4055
+ HeaderTLVLimitSensor: () => HeaderTLVLimitSensor,
4056
+ IntentAllowlistSensor: () => IntentAllowlistSensor,
4057
+ IntentRegistrySensor: () => IntentRegistrySensor,
4058
+ ProofPresenceSensor: () => ProofPresenceSensor,
4059
+ ProtocolStrictSensor: () => ProtocolStrictSensor,
4060
+ ReceiptPolicySensor: () => ReceiptPolicySensor,
4061
+ SchemaValidationSensor: () => SchemaValidationSensor,
4062
+ StreamScopeSensor: () => StreamScopeSensor,
4063
+ TLVParseSensor: () => TLVParseSensor,
4064
+ VarintHardeningSensor: () => VarintHardeningSensor
4065
+ });
4066
+
4067
+ // src/sensors/access-profile-resolver.sensor.ts
4068
+ var import_common11 = require("@nestjs/common");
4069
+ var AccessProfileResolverSensor = class {
4070
+ constructor() {
4071
+ /** AxisSensor identifier */
4072
+ this.name = "AccessProfileResolverSensor";
4073
+ /**
4074
+ * Execution order - runs early to establish the access profile
4075
+ * for downstream sensors.
4076
+ */
4077
+ this.order = BAND.IDENTITY + 10;
4078
+ }
4079
+ supports() {
4080
+ return true;
4081
+ }
4082
+ async run(input) {
4083
+ const hasCapsule = !!input.metadata?.capsuleId;
4084
+ const hasPassport = !!input.metadata?.passportSig;
4085
+ const hasMTLS = !!input.metadata?.mtlsId;
4086
+ const profile = hasCapsule || hasPassport || hasMTLS ? "GUARDED" : "PUBLIC";
4087
+ if (!input.metadata) input.metadata = {};
4088
+ input.metadata.profile = profile;
4089
+ return { action: "ALLOW" };
4090
+ }
4091
+ };
4092
+ AccessProfileResolverSensor = __decorateClass([
4093
+ Sensor(),
4094
+ (0, import_common11.Injectable)()
4095
+ ], AccessProfileResolverSensor);
4096
+
4097
+ // src/sensors/body-budget.sensor.ts
4098
+ var import_common12 = require("@nestjs/common");
4099
+ var BodyBudgetSensor = class {
4100
+ constructor() {
4101
+ /** AxisSensor identifier */
4102
+ this.name = "BodyBudgetSensor";
4103
+ /**
4104
+ * Execution order - after authentication
4105
+ *
4106
+ * Order 150 ensures:
4107
+ * - Authentication complete
4108
+ * - Runs before full body read
4109
+ * - Before schema validation (170)
4110
+ */
4111
+ this.order = BAND.CONTENT + 10;
4112
+ }
4113
+ /**
4114
+ * Determines if this sensor should process the given input.
4115
+ *
4116
+ * Requires at least 8 bytes of peeked data to read headers.
4117
+ *
4118
+ * @param {SensorInput} input - Incoming request
4119
+ * @returns {boolean} True if sufficient peek data available
4120
+ */
4121
+ supports(input) {
4122
+ return !!input.peek && input.peek.length >= 8;
4123
+ }
4124
+ /**
4125
+ * Validates header and body lengths against configured limits.
4126
+ *
4127
+ * **Frame Parsing:**
4128
+ * - Skip magic (5 bytes)
4129
+ * - Skip version (1 byte)
4130
+ * - Skip flags (1 byte)
4131
+ * - Read HDR_LEN varint
4132
+ * - Read BODY_LEN varint
4133
+ * - Compare against MAX_HDR_LEN and MAX_BODY_LEN
4134
+ *
4135
+ * @param {SensorInput} input - Request with peek data
4136
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on size limits
4137
+ */
4138
+ async run(input) {
4139
+ const { peek } = input;
4140
+ if (!peek || peek.length < 8) {
4141
+ return { action: "ALLOW" };
4142
+ }
4143
+ try {
4144
+ let offset = 5;
4145
+ offset += 1;
4146
+ offset += 1;
4147
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
4148
+ offset += hdrBytes;
4149
+ const { value: bodyLen } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
4150
+ if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
4151
+ return {
4152
+ action: "DENY",
4153
+ code: "HEADER_TOO_LARGE",
4154
+ reason: `Header size ${hdrLen} exceeds limit ${import_axis_protocol2.MAX_HDR_LEN}`
4155
+ };
4156
+ }
4157
+ if (bodyLen > import_axis_protocol2.MAX_BODY_LEN) {
4158
+ return {
4159
+ action: "DENY",
4160
+ code: "BODY_TOO_LARGE",
4161
+ reason: `Body size ${bodyLen} exceeds limit ${import_axis_protocol2.MAX_BODY_LEN}`
4162
+ };
4163
+ }
4164
+ return { action: "ALLOW" };
4165
+ } catch (e) {
4166
+ return { action: "ALLOW" };
4167
+ }
4168
+ }
4169
+ };
4170
+ BodyBudgetSensor = __decorateClass([
4171
+ Sensor(),
4172
+ (0, import_common12.Injectable)()
4173
+ ], BodyBudgetSensor);
4174
+
4175
+ // src/sensors/capability-enforcement.sensor.ts
4176
+ var import_common13 = require("@nestjs/common");
4177
+ var CapabilityEnforcementSensor = class {
4178
+ constructor() {
4179
+ this.logger = new import_common13.Logger(CapabilityEnforcementSensor.name);
4180
+ /** AxisSensor identifier for logging and registry */
4181
+ this.name = "CapabilityEnforcementSensor";
4182
+ /**
4183
+ * Execution order - runs after authentication
4184
+ *
4185
+ * Order 100 ensures:
4186
+ * - Capsule is verified (CapsuleVerifySensor @ 80)
4187
+ * - Signature is verified (SigVerifySensor @ 90)
4188
+ * - We know the proof type for capability lookup
4189
+ */
4190
+ this.order = BAND.POLICY + 10;
4191
+ }
4192
+ /**
4193
+ * Determines if this sensor should process the given input.
4194
+ *
4195
+ * Only activates when an intent is present.
4196
+ *
4197
+ * @param {SensorInput} input - Incoming AXIS request
4198
+ * @returns {boolean} True if intent is present
4199
+ */
4200
+ supports(input) {
4201
+ return !!input.intent;
4202
+ }
4203
+ /**
4204
+ * Enforces capability requirements for the requested intent.
4205
+ *
4206
+ * **Processing Flow:**
4207
+ * 1. Extract proof type from packet (default: 0/NONE)
4208
+ * 2. Look up capabilities granted by this proof type
4209
+ * 3. Look up capabilities required by the intent
4210
+ * 4. If no requirements, ALLOW
4211
+ * 5. Check if all required capabilities are granted
4212
+ * 6. If missing capabilities, DENY with details
4213
+ * 7. Otherwise, ALLOW
4214
+ *
4215
+ * @param {SensorInput} input - Request with intent and packet
4216
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on capabilities
4217
+ */
4218
+ async run(input) {
4219
+ const { intent, packet } = input;
4220
+ if (!intent) {
4221
+ return { action: "ALLOW" };
4222
+ }
4223
+ const proofType = packet?.proofType ?? 0;
4224
+ const grantedCapabilities = PROOF_CAPABILITIES[proofType] || [];
4225
+ const requiredCapabilities = this.getRequiredCapabilities(intent);
4226
+ if (requiredCapabilities.length === 0) {
4227
+ return { action: "ALLOW" };
4228
+ }
4229
+ const missingCapabilities = requiredCapabilities.filter(
4230
+ (cap) => !grantedCapabilities.includes(cap)
4231
+ );
4232
+ if (missingCapabilities.length > 0) {
4233
+ this.logger.warn(
4234
+ `Capability denied for ${intent}: missing ${missingCapabilities.join(", ")} (has: ${grantedCapabilities.join(", ")})`
4235
+ );
4236
+ return {
4237
+ action: "DENY",
4238
+ code: "CAPABILITY_DENIED",
4239
+ reason: `Missing capabilities: ${missingCapabilities.join(", ")}`
4240
+ };
4241
+ }
4242
+ return { action: "ALLOW" };
4243
+ }
4244
+ /**
4245
+ * Gets required capabilities for an intent.
4246
+ *
4247
+ * **Lookup Strategy:**
4248
+ * 1. Check for exact intent match
4249
+ * 2. Check for prefix pattern match (*.suffix)
4250
+ * 3. Default to 'execute' for unknown intents
4251
+ *
4252
+ * @private
4253
+ * @param {string} intent - Intent name to look up
4254
+ * @returns {Capability[]} Array of required capabilities
4255
+ */
4256
+ getRequiredCapabilities(intent) {
4257
+ if (INTENT_REQUIREMENTS[intent]) {
4258
+ return INTENT_REQUIREMENTS[intent];
4259
+ }
4260
+ for (const [pattern, caps] of Object.entries(INTENT_REQUIREMENTS)) {
4261
+ if (pattern.endsWith(".*")) {
4262
+ const prefix = pattern.slice(0, -1);
4263
+ if (intent.startsWith(prefix)) {
4264
+ return caps;
4265
+ }
4266
+ }
4267
+ }
4268
+ return ["execute"];
4269
+ }
4270
+ };
4271
+ CapabilityEnforcementSensor = __decorateClass([
4272
+ Sensor(),
4273
+ (0, import_common13.Injectable)()
4274
+ ], CapabilityEnforcementSensor);
4275
+
4276
+ // src/sensors/chunk-hash.sensor.ts
4277
+ var import_common14 = require("@nestjs/common");
4278
+ var import_crypto5 = require("crypto");
4279
+ var ChunkHashSensor = class {
4280
+ constructor() {
4281
+ /** Sensor identifier */
4282
+ this.name = "ChunkHashSensor";
4283
+ /**
4284
+ * Execution order - after session validation
4285
+ *
4286
+ * Order 190 ensures:
4287
+ * - Session validated (180)
4288
+ * - Chunk parameters verified
4289
+ * - Hash check before storage
4290
+ */
4291
+ this.order = BAND.CONTENT + 50;
4292
+ }
4293
+ /**
4294
+ * Determines if this sensor should process the given input.
4295
+ *
4296
+ * Only processes file.chunk intents.
4297
+ *
4298
+ * @param {SensorInput} input - Incoming request
4299
+ * @returns {boolean} True if intent is 'file.chunk'
4300
+ */
4301
+ supports(input) {
4302
+ return input.intent === "file.chunk";
4303
+ }
4304
+ /**
4305
+ * Validates chunk data against declared SHA-256 hash.
4306
+ *
4307
+ * **Processing Flow:**
4308
+ * 1. Check for required headerTLVs and body
4309
+ * 2. Extract expected hash from TLV 73
4310
+ * 3. Verify hash is exactly 32 bytes
4311
+ * 4. Compute SHA-256 of body
4312
+ * 5. Compare bytes (timing-safe)
4313
+ * 6. DENY on mismatch
4314
+ *
4315
+ * @param {SensorInput} input - Request with chunk body
4316
+ * @returns {Promise<SensorDecision>} ALLOW if hash matches, DENY otherwise
4317
+ */
4318
+ async run(input) {
4319
+ const headerTLVs = input.headerTLVs;
4320
+ const bodyBytes = input.body;
4321
+ if (!headerTLVs || !bodyBytes) {
4322
+ return {
4323
+ action: "DENY",
4324
+ code: "SENSOR_INVALID_INPUT",
4325
+ reason: "Missing headerTLVs or body"
4326
+ };
4327
+ }
4328
+ const TLV_SHA256_CHUNK2 = 73;
4329
+ const expected = headerTLVs.get(TLV_SHA256_CHUNK2);
4330
+ if (!expected || expected.length !== 32) {
4331
+ return {
4332
+ action: "DENY",
4333
+ code: "FILE_CHUNK_HASH_MISSING",
4334
+ reason: "Missing sha256Chunk TLV in header"
4335
+ };
4336
+ }
4337
+ const actual = (0, import_crypto5.createHash)("sha256").update(bodyBytes).digest();
4338
+ if (!Buffer.from(actual).equals(Buffer.from(expected))) {
4339
+ return {
4340
+ action: "DENY",
4341
+ code: "FILE_CHUNK_HASH_MISMATCH",
4342
+ reason: "Chunk hash mismatch - data corrupted"
4343
+ };
4344
+ }
4345
+ return { action: "ALLOW" };
4346
+ }
4347
+ };
4348
+ ChunkHashSensor = __decorateClass([
4349
+ Sensor(),
4350
+ (0, import_common14.Injectable)()
4351
+ ], ChunkHashSensor);
4352
+
4353
+ // src/sensors/entropy.sensor.ts
4354
+ var import_common15 = require("@nestjs/common");
4355
+ var crypto4 = __toESM(require("crypto"));
4356
+ var EntropySensor = class {
4357
+ constructor() {
4358
+ this.logger = new import_common15.Logger(EntropySensor.name);
4359
+ /**
4360
+ * Minimum acceptable entropy in bits per byte.
4361
+ *
4362
+ * 3.0 bits/byte is a conservative threshold:
4363
+ * - Random data: ~7.9 bits/byte
4364
+ * - English text: ~4.5 bits/byte
4365
+ * - Sequential data: ~0-2 bits/byte
4366
+ */
4367
+ this.MIN_ENTROPY_THRESHOLD = 3;
4368
+ /** AxisSensor identifier */
4369
+ this.name = "EntropySensor";
4370
+ /**
4371
+ * Execution order - anomaly detection phase
4372
+ *
4373
+ * Order 130 ensures:
4374
+ * - Replay protection done (120)
4375
+ * - Runs before expensive policy lookups
4376
+ */
4377
+ this.order = BAND.POLICY + 35;
4378
+ }
4379
+ /**
4380
+ * Calculates Shannon entropy of a byte array.
4381
+ *
4382
+ * **Algorithm:**
4383
+ * 1. Count frequency of each byte value (0-255)
4384
+ * 2. Calculate probability p = count / total
4385
+ * 3. Sum: -Σ(p * log2(p))
4386
+ *
4387
+ * @private
4388
+ * @param {Uint8Array} data - Bytes to analyze
4389
+ * @returns {number} Entropy in bits per byte (0-8 scale)
4390
+ */
4391
+ calculateEntropy(data) {
4392
+ if (data.length === 0) return 0;
4393
+ const freq = /* @__PURE__ */ new Map();
4394
+ for (const byte of data) {
4395
+ freq.set(byte, (freq.get(byte) || 0) + 1);
4396
+ }
4397
+ let entropy = 0;
4398
+ const len = data.length;
4399
+ for (const count of freq.values()) {
4400
+ const p = count / len;
4401
+ entropy -= p * Math.log2(p);
4402
+ }
4403
+ return entropy;
4404
+ }
4405
+ /**
4406
+ * Checks for sequential patterns in data.
4407
+ *
4408
+ * Detects sequences like [1,2,3,4...] or [10,9,8,7...].
4409
+ * More than 50% sequential is considered suspicious.
4410
+ *
4411
+ * @private
4412
+ * @param {Uint8Array} data - Bytes to analyze
4413
+ * @returns {boolean} True if sequential pattern detected
4414
+ */
4415
+ hasSequentialPattern(data) {
4416
+ if (data.length < 4) return false;
4417
+ let ascending = 0;
4418
+ let descending = 0;
4419
+ for (let i = 1; i < data.length; i++) {
4420
+ if (data[i] === data[i - 1] + 1) ascending++;
4421
+ if (data[i] === data[i - 1] - 1) descending++;
4422
+ }
4423
+ return ascending > data.length / 2 || descending > data.length / 2;
4424
+ }
4425
+ /**
4426
+ * Checks for repeated patterns in data.
4427
+ *
4428
+ * Detects patterns like [0xAB, 0xCD, 0xAB, 0xCD...].
4429
+ * Checks for 2, 4, and 8 byte repeating patterns.
4430
+ *
4431
+ * @private
4432
+ * @param {Uint8Array} data - Bytes to analyze
4433
+ * @returns {boolean} True if repeated pattern detected
4434
+ */
4435
+ hasRepeatedPattern(data) {
4436
+ if (data.length < 8) return false;
4437
+ for (const patternLen of [2, 4, 8]) {
4438
+ if (data.length % patternLen !== 0) continue;
4439
+ let matches = 0;
4440
+ for (let i = patternLen; i < data.length; i++) {
4441
+ if (data[i] === data[i % patternLen]) matches++;
4442
+ }
4443
+ if (matches > (data.length - patternLen) * 0.9) {
4444
+ return true;
4445
+ }
4446
+ }
4447
+ return false;
4448
+ }
4449
+ /**
4450
+ * Analyzes entropy of PID and nonce in request headers.
4451
+ *
4452
+ * **Processing Flow:**
4453
+ * 1. Extract PID and nonce from header TLVs
4454
+ * 2. Calculate entropy for each
4455
+ * 3. Check for sequential patterns
4456
+ * 4. Check for repeated patterns
4457
+ * 5. Accumulate issues and score delta
4458
+ * 6. Return FLAG if issues found, ALLOW otherwise
4459
+ *
4460
+ * @param {SensorInput} input - Request with header TLVs
4461
+ * @returns {Promise<SensorDecision>} ALLOW or FLAG based on entropy analysis
4462
+ */
4463
+ async run(input) {
4464
+ const headers = input.headerTLVs;
4465
+ if (!headers) {
4466
+ return { action: "ALLOW" };
4467
+ }
4468
+ const pid = headers.get(import_axis_protocol2.TLV_PID);
4469
+ const nonce = headers.get(import_axis_protocol2.TLV_NONCE);
4470
+ const issues = [];
4471
+ let totalDelta = 0;
4472
+ if (pid && pid.length > 0) {
4473
+ const pidEntropy = this.calculateEntropy(pid);
4474
+ if (pidEntropy < this.MIN_ENTROPY_THRESHOLD) {
4475
+ issues.push(`pid_low_entropy:${pidEntropy.toFixed(2)}`);
4476
+ totalDelta -= 3;
4477
+ }
4478
+ if (this.hasSequentialPattern(pid)) {
4479
+ issues.push("pid_sequential");
4480
+ totalDelta -= 5;
4481
+ }
4482
+ if (this.hasRepeatedPattern(pid)) {
4483
+ issues.push("pid_repeated");
4484
+ totalDelta -= 5;
4485
+ }
4486
+ }
4487
+ if (nonce && nonce.length > 0) {
4488
+ const nonceEntropy = this.calculateEntropy(nonce);
4489
+ if (nonceEntropy < this.MIN_ENTROPY_THRESHOLD) {
4490
+ issues.push(`nonce_low_entropy:${nonceEntropy.toFixed(2)}`);
4491
+ totalDelta -= 3;
4492
+ }
4493
+ if (this.hasSequentialPattern(nonce)) {
4494
+ issues.push("nonce_sequential");
4495
+ totalDelta -= 5;
4496
+ }
4497
+ if (this.hasRepeatedPattern(nonce)) {
4498
+ issues.push("nonce_repeated");
4499
+ totalDelta -= 5;
4500
+ }
4501
+ }
4502
+ if (issues.length > 0) {
4503
+ this.logger.warn(`Entropy issues from ${input.ip}: ${issues.join(", ")}`);
4504
+ return {
4505
+ action: "FLAG",
4506
+ scoreDelta: totalDelta,
4507
+ reasons: issues
4508
+ };
4509
+ }
4510
+ return { action: "ALLOW" };
4511
+ }
4512
+ /**
4513
+ * Generates cryptographically secure random bytes.
4514
+ *
4515
+ * Utility method for SDK/client code to ensure proper entropy.
4516
+ * Uses Node.js crypto.randomBytes for secure PRNG.
4517
+ *
4518
+ * @static
4519
+ * @param {number} length - Number of random bytes
4520
+ * @returns {Uint8Array} Cryptographically secure random bytes
4521
+ */
4522
+ static generateSecureRandom(length) {
4523
+ return new Uint8Array(crypto4.randomBytes(length));
4524
+ }
4525
+ };
4526
+ EntropySensor = __decorateClass([
4527
+ Sensor(),
4528
+ (0, import_common15.Injectable)()
4529
+ ], EntropySensor);
4530
+
4531
+ // src/sensors/execution-timeout.sensor.ts
4532
+ var import_common16 = require("@nestjs/common");
4533
+ var ExecutionTimeoutSensor = class {
4534
+ constructor() {
4535
+ this.logger = new import_common16.Logger(ExecutionTimeoutSensor.name);
4536
+ /** AxisSensor identifier */
4537
+ this.name = "ExecutionTimeoutSensor";
4538
+ /**
4539
+ * Execution order - late, near handler execution
4540
+ *
4541
+ * Order 210 ensures:
4542
+ * - All validation complete
4543
+ * - Deadline set just before handler
4544
+ */
4545
+ this.order = BAND.BUSINESS + 10;
4546
+ }
4547
+ /**
4548
+ * Determines if this sensor should process the given input.
4549
+ *
4550
+ * @param {SensorInput} input - Incoming request
4551
+ * @returns {boolean} True if intent is present
4552
+ */
4553
+ supports(input) {
4554
+ return !!input.intent;
4555
+ }
4556
+ /**
4557
+ * Sets execution deadline in the request context.
4558
+ *
4559
+ * **Processing Flow:**
4560
+ * 1. Look up timeout for intent
4561
+ * 2. Calculate absolute deadline
4562
+ * 3. Store in context for handler use
4563
+ * 4. Return ALLOW
4564
+ *
4565
+ * @param {SensorInput} input - Request with intent
4566
+ * @returns {Promise<SensorDecision>} Always ALLOW
4567
+ */
4568
+ async run(input) {
4569
+ const { intent, context } = input;
4570
+ if (!intent) {
4571
+ return { action: "ALLOW" };
4572
+ }
4573
+ const timeout = resolveTimeout(intent);
4574
+ const deadline = Date.now() + timeout;
4575
+ if (context) {
4576
+ context.deadline = deadline;
4577
+ context.timeoutMs = timeout;
4578
+ }
4579
+ this.logger.debug(
4580
+ `Set ${timeout}ms timeout for ${intent} (deadline: ${new Date(deadline).toISOString()})`
4581
+ );
4582
+ return { action: "ALLOW" };
4583
+ }
4584
+ /**
4585
+ * Checks if a deadline has been exceeded.
4586
+ *
4587
+ * Utility method for handler code.
4588
+ *
4589
+ * @static
4590
+ * @param {object} ctx - Context with deadline
4591
+ * @returns {boolean} True if deadline passed
4592
+ */
4593
+ static isExpired(ctx) {
4594
+ if (!ctx.deadline) return false;
4595
+ return Date.now() > ctx.deadline;
4596
+ }
4597
+ /**
4598
+ * Gets remaining time until deadline.
4599
+ *
4600
+ * Utility method for handler code.
4601
+ *
4602
+ * @static
4603
+ * @param {object} ctx - Context with deadline
4604
+ * @returns {number} Remaining milliseconds (0 if expired, Infinity if no deadline)
4605
+ */
4606
+ static getRemainingMs(ctx) {
4607
+ if (!ctx.deadline) return Infinity;
4608
+ return Math.max(0, ctx.deadline - Date.now());
4609
+ }
4610
+ };
4611
+ ExecutionTimeoutSensor = __decorateClass([
4612
+ Sensor(),
4613
+ (0, import_common16.Injectable)()
4614
+ ], ExecutionTimeoutSensor);
4615
+
4616
+ // src/sensors/frame-budget.sensor.ts
4617
+ var import_common17 = require("@nestjs/common");
4618
+ var FrameBudgetSensor = class {
4619
+ constructor(config) {
4620
+ this.config = config;
4621
+ /** AxisSensor identifier for logging and registry */
4622
+ this.name = "FrameBudgetSensor";
4623
+ /**
4624
+ * Execution order - runs after protocol validation
4625
+ *
4626
+ * Order 20 ensures:
4627
+ * - Protocol is valid (ProtocolStrictSensor @ 10)
4628
+ * - Size checked before expensive processing
4629
+ */
4630
+ this.order = BAND.WIRE + 20;
4631
+ }
4632
+ /**
4633
+ * Determines if this sensor should process the given input.
4634
+ *
4635
+ * Only activates when Content-Length header is available.
4636
+ * WebSocket frames may not have Content-Length; they use different size tracking.
4637
+ *
4638
+ * @param {SensorInput} input - Incoming AXIS request
4639
+ * @returns {boolean} True if Content-Length is present
4640
+ */
4641
+ supports(input) {
4642
+ return typeof input.contentLength === "number";
4643
+ }
4644
+ /**
4645
+ * Validates frame size against configured limits.
4646
+ *
4647
+ * **Current Implementation:** Stub that always allows.
4648
+ *
4649
+ * **TODO:** Full implementation should:
4650
+ * 1. Load intent policy for the request
4651
+ * 2. Get maxFrameBytes from policy
4652
+ * 3. Compare against contentLength
4653
+ * 4. DENY if exceeded
4654
+ *
4655
+ * @param {SensorInput} input - Request with contentLength
4656
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on size
4657
+ */
4658
+ async run(input) {
4659
+ const maxBytes = this.config.get("AXIS_MAX_FRAME_SIZE") || 50 * 1024 * 1024;
4660
+ const contentLength = input.contentLength;
4661
+ if (typeof contentLength !== "number") {
4662
+ return { action: "ALLOW" };
4663
+ }
4664
+ if (contentLength > maxBytes) {
4665
+ return {
4666
+ action: "DENY",
4667
+ code: "FRAME_TOO_LARGE",
4668
+ reason: `Frame size ${contentLength} exceeds limit ${maxBytes}`
4669
+ };
4670
+ }
4671
+ return { action: "ALLOW" };
4672
+ }
4673
+ };
4674
+ FrameBudgetSensor = __decorateClass([
4675
+ Sensor({ phase: "PRE_DECODE" }),
4676
+ (0, import_common17.Injectable)()
4677
+ ], FrameBudgetSensor);
4678
+
4679
+ // src/sensors/frame-header-sanity.sensor.ts
4680
+ var import_common18 = require("@nestjs/common");
4681
+ var FrameHeaderSanitySensor = class {
4682
+ constructor() {
4683
+ this.name = "FrameHeaderSanitySensor";
4684
+ this.order = BAND.WIRE + 30;
4685
+ }
4686
+ supports(input) {
4687
+ return !!input.peek && input.peek.length >= 7;
4688
+ }
4689
+ async run(input) {
4690
+ const peek = input.peek;
4691
+ const contentLen = input.contentLength || 0;
4692
+ if (peek.length < 5 || !this.bufferEqual(peek.slice(0, 5), import_axis_protocol2.AXIS_MAGIC)) {
4693
+ return {
4694
+ action: "DENY",
4695
+ code: "INVALID_MAGIC",
4696
+ reason: "Frame magic is not AXIS1"
4697
+ };
4698
+ }
4699
+ if (peek[5] !== import_axis_protocol2.AXIS_VERSION) {
4700
+ return {
4701
+ action: "DENY",
4702
+ code: "UNSUPPORTED_VERSION",
4703
+ reason: `Unsupported version: ${peek[5]}`
4704
+ };
4705
+ }
4706
+ if (contentLen > import_axis_protocol2.MAX_FRAME_LEN) {
4707
+ return {
4708
+ action: "DENY",
4709
+ code: "FRAME_TOO_LARGE",
4710
+ reason: `Frame size ${contentLen} exceeds max ${import_axis_protocol2.MAX_FRAME_LEN}`
4711
+ };
4712
+ }
4713
+ return { action: "ALLOW" };
4714
+ }
4715
+ bufferEqual(a, b) {
4716
+ if (a.length !== b.length) return false;
4717
+ for (let i = 0; i < a.length; i++) {
4718
+ if (a[i] !== b[i]) return false;
4719
+ }
4720
+ return true;
4721
+ }
4722
+ };
4723
+ FrameHeaderSanitySensor = __decorateClass([
4724
+ (0, import_common18.Injectable)(),
4725
+ Sensor({ phase: "PRE_DECODE" })
4726
+ ], FrameHeaderSanitySensor);
4727
+
4728
+ // src/sensors/header-tlv-limit.sensor.ts
4729
+ var import_common19 = require("@nestjs/common");
4730
+ var HeaderTLVLimitSensor = class {
4731
+ constructor() {
4732
+ this.name = "HeaderTLVLimitSensor";
4733
+ this.order = BAND.CONTENT + 0;
4734
+ this.MAX_TLVS = 64;
4735
+ }
4736
+ supports(input) {
4737
+ return !!input.headerTLVs || !!input.packet;
4738
+ }
4739
+ async run(input) {
4740
+ if (input.headerTLVs && input.headerTLVs.size > this.MAX_TLVS) {
4741
+ return {
4742
+ action: "DENY",
4743
+ code: "TOO_MANY_TLVS",
4744
+ reason: `Header TLVs (${input.headerTLVs.size}) exceed max (${this.MAX_TLVS})`
4745
+ };
4746
+ }
4747
+ if (input.packet && input.packet.headerBytes) {
4748
+ const hdrLen = input.packet.headerBytes.length;
4749
+ if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
4750
+ return {
4751
+ action: "DENY",
4752
+ code: "HEADER_TOO_LARGE",
4753
+ reason: `Header size ${hdrLen} exceeds max ${import_axis_protocol2.MAX_HDR_LEN}`
4754
+ };
4755
+ }
4756
+ }
4757
+ return { action: "ALLOW" };
4758
+ }
4759
+ };
4760
+ HeaderTLVLimitSensor = __decorateClass([
4761
+ (0, import_common19.Injectable)(),
4762
+ Sensor()
4763
+ ], HeaderTLVLimitSensor);
4764
+
4765
+ // src/sensors/intent-allowlist.sensor.ts
4766
+ var import_common20 = require("@nestjs/common");
4767
+ var PUBLIC_INTENT_ALLOWLIST = [
4768
+ "public.",
4769
+ "schema.",
4770
+ "catalog.",
4771
+ "health.",
4772
+ "system."
4773
+ ];
4774
+ var IntentAllowlistSensor = class {
4775
+ constructor() {
4776
+ this.name = "IntentAllowlistSensor";
4777
+ this.order = BAND.IDENTITY + 20;
4778
+ }
4779
+ supports(input) {
4780
+ return !!input.intent;
4781
+ }
4782
+ async run(input) {
4783
+ const profile = input.metadata?.profile || "PUBLIC";
4784
+ const intent = input.intent || "";
4785
+ if (profile === "PUBLIC") {
4786
+ const isAllowed = PUBLIC_INTENT_ALLOWLIST.some(
4787
+ (prefix) => intent.startsWith(prefix)
4788
+ );
4789
+ if (!isAllowed) {
4790
+ return {
4791
+ action: "DENY",
4792
+ code: "INTENT_NOT_ALLOWED",
4793
+ reason: `Intent '${intent}' not in public allowlist`
4794
+ };
4795
+ }
4796
+ }
4797
+ return { action: "ALLOW" };
4798
+ }
4799
+ };
4800
+ IntentAllowlistSensor = __decorateClass([
4801
+ (0, import_common20.Injectable)(),
4802
+ Sensor()
4803
+ ], IntentAllowlistSensor);
4804
+
4805
+ // src/sensors/intent-registry.sensor.ts
4806
+ var import_common21 = require("@nestjs/common");
4807
+ var IntentRegistrySensor = class {
4808
+ constructor(router) {
4809
+ this.router = router;
4810
+ this.name = "IntentRegistrySensor";
4811
+ this.order = BAND.IDENTITY + 25;
4812
+ }
4813
+ supports(input) {
4814
+ return !!input.intent;
4815
+ }
4816
+ async run(input) {
4817
+ const intent = input.intent;
4818
+ if (this.router.has(intent)) {
4819
+ return { action: "ALLOW" };
4820
+ }
4821
+ return {
4822
+ action: "DENY",
4823
+ code: "INTENT_NOT_REGISTERED",
4824
+ reason: `Intent '${intent}' is not registered`
4825
+ };
4826
+ }
4827
+ };
4828
+ IntentRegistrySensor = __decorateClass([
4829
+ (0, import_common21.Injectable)(),
4830
+ Sensor({ phase: "POST_DECODE" })
4831
+ ], IntentRegistrySensor);
4832
+
4833
+ // src/sensors/proof-presence.sensor.ts
4834
+ var import_common22 = require("@nestjs/common");
4835
+ var ProofPresenceSensor = class {
4836
+ constructor() {
4837
+ this.name = "ProofPresenceSensor";
4838
+ this.order = BAND.IDENTITY + 30;
4839
+ }
4840
+ supports(input) {
4841
+ return !!input.profile && !!input.visibility;
4842
+ }
4843
+ async run(input) {
4844
+ const validatedInput = ProofPresenceInputZ.safeParse(input);
4845
+ if (!validatedInput.success) {
4846
+ throw new AxisError(
4847
+ "SENSOR_INVALID_INPUT",
4848
+ `Input validation failed: ${validatedInput.error.message}`,
4849
+ 400
4850
+ );
4851
+ }
4852
+ const {
4853
+ visibility,
4854
+ requiredProof,
4855
+ hasCapsule,
4856
+ hasPassportSignature,
4857
+ profile,
4858
+ intent
4859
+ } = validatedInput.data;
4860
+ if (visibility === "PUBLIC") {
4861
+ return { action: "ALLOW" };
4862
+ }
4863
+ if (requiredProof.includes("NONE")) {
4864
+ return { action: "ALLOW" };
4865
+ }
4866
+ const hasCapsuleProof = requiredProof.includes("CAPSULE") && hasCapsule;
4867
+ const hasPassportProof = requiredProof.includes("PASSPORT") && hasPassportSignature;
4868
+ const hasNodeProof = requiredProof.includes("MTLS") && profile === "NODE";
4869
+ const satisfied = hasCapsuleProof || hasPassportProof || hasNodeProof;
4870
+ if (!satisfied) {
4871
+ throw new AxisError(
4872
+ "SENSOR_PROOF_REQUIRED",
4873
+ `Proof required for guarded intent: ${intent}`,
4874
+ 403
4875
+ );
4876
+ }
4877
+ return { action: "ALLOW" };
4878
+ }
4879
+ };
4880
+ ProofPresenceSensor = __decorateClass([
4881
+ Sensor(),
4882
+ (0, import_common22.Injectable)()
4883
+ ], ProofPresenceSensor);
4884
+
4885
+ // src/sensors/protocol-strict.sensor.ts
4886
+ var import_common23 = require("@nestjs/common");
4887
+ var VALID_FLAGS = [
4888
+ 0,
4889
+ // No flags
4890
+ import_axis_protocol2.FLAG_BODY_TLV,
4891
+ // Body contains TLVs
4892
+ import_axis_protocol2.FLAG_CHAIN_REQ,
4893
+ // Requires receipt chaining
4894
+ import_axis_protocol2.FLAG_HAS_WITNESS,
4895
+ // Has witness signatures
4896
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ,
4897
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_HAS_WITNESS,
4898
+ import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS,
4899
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS
4900
+ ];
4901
+ var ProtocolStrictSensor = class {
4902
+ constructor(config) {
4903
+ this.config = config;
4904
+ this.logger = new import_common23.Logger(ProtocolStrictSensor.name);
4905
+ /** Sensor identifier for logging and registry */
4906
+ this.name = "ProtocolStrictSensor";
4907
+ /**
4908
+ * Execution order - FIRST sensor in the chain
4909
+ *
4910
+ * Order 10 ensures:
4911
+ * - Runs before any other processing
4912
+ * - Invalid frames rejected immediately
4913
+ * - Protects all downstream sensors from malformed input
4914
+ */
4915
+ this.order = BAND.WIRE + 10;
4916
+ this.protocolMagic = import_axis_protocol2.AXIS_MAGIC;
4917
+ this.protocolVersion = import_axis_protocol2.AXIS_VERSION;
4918
+ }
4919
+ /**
4920
+ * Static validation for streaming middleware (Fast Check)
4921
+ */
4922
+ static validateMagic(chunk, expected) {
4923
+ if (chunk.length < expected.length) return { valid: true };
4924
+ const actual = chunk.subarray(0, expected.length);
4925
+ const valid = Buffer.from(actual).equals(Buffer.from(expected));
4926
+ return {
4927
+ valid,
4928
+ actual: valid ? void 0 : new TextDecoder().decode(actual)
4929
+ };
4930
+ }
4931
+ static validateVersion(version, expected) {
4932
+ return version === expected;
4933
+ }
4934
+ /**
4935
+ * Lifecycle hook: Registers this sensor in the chain on module initialization.
4936
+ */
4937
+ onModuleInit() {
4938
+ const magicStr = this.config.get("AXIS_PROTOCOL_MAGIC");
4939
+ this.protocolMagic = magicStr ? Buffer.from(magicStr, "ascii") : import_axis_protocol2.AXIS_MAGIC;
4940
+ this.protocolVersion = this.config.get("AXIS_PROTOCOL_VERSION") || import_axis_protocol2.AXIS_VERSION;
4941
+ }
4942
+ /**
4943
+ * Evaluate protocol strictness
4944
+ */
4945
+ async run(input) {
4946
+ const validatedInput = ProtocolStrictInputZ.safeParse(input);
4947
+ if (!validatedInput.success) {
4948
+ this.logger.error(
4949
+ `Invalid input: ${validatedInput.error.message}`,
4950
+ validatedInput.error.issues
4951
+ );
4952
+ return {
4953
+ action: "DENY",
4954
+ code: "INVALID_INPUT",
4955
+ reason: "Protocol validation input failed"
4956
+ };
4957
+ }
4958
+ const { contentType, peek } = validatedInput.data;
4959
+ const issues = [];
4960
+ if (peek.length >= 8) {
4961
+ const hex = Buffer.from(peek.subarray(0, 10)).toString("hex");
4962
+ this.logger.debug(`Raw Frame Header (Hex): ${hex} (IP: ${input.ip})`);
4963
+ }
4964
+ if (contentType !== void 0) {
4965
+ if (!this.isValidContentType(contentType)) {
4966
+ issues.push(`invalid_content_type:${contentType}`);
4967
+ }
4968
+ }
4969
+ if (peek.length < 9) {
4970
+ return {
4971
+ action: "DENY",
4972
+ code: "FRAME_TOO_SHORT",
4973
+ reason: "Frame too short for protocol header"
4974
+ };
4975
+ }
4976
+ const magicCheck = ProtocolStrictSensor.validateMagic(
4977
+ peek,
4978
+ this.protocolMagic
4979
+ );
4980
+ if (!magicCheck.valid) {
4981
+ return {
4982
+ action: "DENY",
4983
+ code: "INVALID_MAGIC",
4984
+ reason: `Expected ${new TextDecoder().decode(this.protocolMagic)} magic, got ${magicCheck.actual}`
4985
+ };
4986
+ }
4987
+ const version = peek[5];
4988
+ if (!ProtocolStrictSensor.validateVersion(version, this.protocolVersion)) {
4989
+ issues.push(`unsupported_version:${version}`);
4990
+ }
4991
+ const flags = peek[6];
4992
+ if (!this.isValidFlags(flags)) {
4993
+ issues.push(`invalid_flags:0x${flags.toString(16)}`);
4994
+ }
4995
+ if (peek.length >= 10) {
4996
+ const lengthCheck = this.checkVarintEncoding(peek.subarray(7));
4997
+ if (!lengthCheck.valid) {
4998
+ issues.push(`non_minimal_varint:${lengthCheck.reason}`);
4999
+ }
5000
+ }
5001
+ if (peek.length >= 20) {
5002
+ const tlvCheck = this.checkTLVOrdering(peek);
5003
+ if (!tlvCheck.valid) {
5004
+ issues.push(`tlv_not_canonical:${tlvCheck.reason}`);
5005
+ }
5006
+ const hasClientVersion = await this.checkForClientVersion(peek);
5007
+ if (!hasClientVersion) {
5008
+ issues.push("missing_client_version");
5009
+ }
5010
+ }
5011
+ if (issues.length > 0) {
5012
+ const critical = issues.some(
5013
+ (i) => i.startsWith("invalid_magic") || i.startsWith("unsupported_version")
5014
+ );
5015
+ if (critical) {
5016
+ return {
5017
+ action: "DENY",
5018
+ code: "PROTOCOL_VIOLATION",
5019
+ reason: issues.join(", ")
5020
+ };
5021
+ }
5022
+ this.logger.warn(
5023
+ `Protocol issues from ${input.ip}: ${issues.join(", ")}`
5024
+ );
5025
+ return {
5026
+ action: "FLAG",
5027
+ scoreDelta: -issues.length * 2,
5028
+ reasons: issues
5029
+ };
5030
+ }
5031
+ return { action: "ALLOW" };
5032
+ }
5033
+ /**
5034
+ * Compare two buffers for equality
5035
+ */
5036
+ buffersEqual(a, b) {
5037
+ if (a.length !== b.length) return false;
5038
+ for (let i = 0; i < a.length; i++) {
5039
+ if (a[i] !== b[i]) return false;
5040
+ }
5041
+ return true;
5042
+ }
5043
+ /**
5044
+ * Check if Content-Type is valid for AXIS
5045
+ */
5046
+ isValidContentType(contentType) {
5047
+ const valid = [
5048
+ "application/axis-bin",
5049
+ "application/octet-stream",
5050
+ "application/x-axis"
5051
+ ];
5052
+ return valid.some((v) => contentType.toLowerCase().includes(v));
5053
+ }
5054
+ /**
5055
+ * Check if flags are a valid combination
5056
+ */
5057
+ isValidFlags(flags) {
5058
+ return VALID_FLAGS.includes(flags);
5059
+ }
5060
+ /**
5061
+ * Check varint encoding is minimal (no leading zeros)
5062
+ */
5063
+ checkVarintEncoding(data) {
5064
+ try {
5065
+ const { value, length: bytesRead } = (0, import_axis_protocol3.decodeVarint)(data, 0);
5066
+ if (value < 128 && bytesRead > 1) {
5067
+ return { valid: false, reason: "non-minimal-small-value" };
5068
+ }
5069
+ if (value < 16384 && bytesRead > 2) {
5070
+ return { valid: false, reason: "non-minimal-medium-value" };
5071
+ }
5072
+ return { valid: true };
5073
+ } catch {
5074
+ return { valid: false, reason: "varint-decode-error" };
5075
+ }
5076
+ }
5077
+ /**
5078
+ * Check TLV ordering is canonical (sorted by type, no duplicates)
5079
+ */
5080
+ checkTLVOrdering(data) {
5081
+ try {
5082
+ let offset = 7;
5083
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5084
+ offset += hdrBytes;
5085
+ const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5086
+ offset += bodyBytes;
5087
+ const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5088
+ offset += sigBytes;
5089
+ const hdrStart = offset;
5090
+ const hdrEnd = hdrStart + Number(hdrLen);
5091
+ if (hdrEnd > data.length) {
5092
+ return { valid: true };
5093
+ }
5094
+ let lastType = -1;
5095
+ let pos = hdrStart;
5096
+ while (pos < hdrEnd && pos < data.length - 2) {
5097
+ const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5098
+ pos += typeBytes;
5099
+ if (pos >= hdrEnd) break;
5100
+ const { value: len, length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5101
+ pos += lenBytes;
5102
+ if (Number(type) <= lastType) {
5103
+ return {
5104
+ valid: false,
5105
+ reason: `type-${type}-after-${lastType}`
5106
+ };
5107
+ }
5108
+ lastType = Number(type);
5109
+ pos += Number(len);
5110
+ }
5111
+ return { valid: true };
5112
+ } catch {
5113
+ return { valid: true };
5114
+ }
5115
+ }
5116
+ /**
5117
+ * Check if TLV 100 (Client Version) exists in the headers
5118
+ */
5119
+ async checkForClientVersion(data) {
5120
+ try {
5121
+ let offset = 7;
5122
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5123
+ offset += hdrBytes;
5124
+ const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5125
+ offset += bodyBytes;
5126
+ const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5127
+ offset += sigBytes;
5128
+ const hdrEnd = offset + Number(hdrLen);
5129
+ let pos = offset;
5130
+ while (pos < hdrEnd && pos < data.length) {
5131
+ const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5132
+ pos += typeBytes;
5133
+ const { length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5134
+ pos += lenBytes;
5135
+ const { value: valLen, length: valLenBytes } = (0, import_axis_protocol3.decodeVarint)(
5136
+ data,
5137
+ pos - lenBytes
5138
+ );
5139
+ }
5140
+ pos = offset;
5141
+ while (pos < hdrEnd && pos < data.length) {
5142
+ const t = (0, import_axis_protocol3.decodeVarint)(data, pos);
5143
+ pos += t.length;
5144
+ const l = (0, import_axis_protocol3.decodeVarint)(data, pos);
5145
+ pos += l.length;
5146
+ if (t.value === 100) return true;
5147
+ pos += Number(l.value);
5148
+ }
5149
+ return false;
5150
+ } catch {
5151
+ return false;
5152
+ }
5153
+ }
5154
+ };
5155
+ ProtocolStrictSensor = __decorateClass([
5156
+ Sensor({ phase: "PRE_DECODE" }),
5157
+ (0, import_common23.Injectable)()
5158
+ ], ProtocolStrictSensor);
5159
+
5160
+ // src/sensors/receipt-policy.sensor.ts
5161
+ var import_common24 = require("@nestjs/common");
5162
+ var ReceiptPolicySensor = class {
5163
+ constructor() {
5164
+ this.name = "ReceiptPolicySensor";
5165
+ this.order = BAND.BUSINESS + 20;
5166
+ }
5167
+ supports() {
5168
+ return true;
5169
+ }
5170
+ async run() {
5171
+ return { action: "ALLOW" };
5172
+ }
5173
+ };
5174
+ ReceiptPolicySensor = __decorateClass([
5175
+ (0, import_common24.Injectable)(),
5176
+ Sensor()
5177
+ ], ReceiptPolicySensor);
5178
+
5179
+ // src/sensors/schema-validation.sensor.ts
5180
+ var import_common25 = require("@nestjs/common");
5181
+ function readU64be(b) {
5182
+ if (b.length !== 8)
5183
+ throw new AxisError("SCHEMA_TYPE_MISMATCH", "u64 must be 8 bytes", 400);
5184
+ let x = 0n;
5185
+ for (const by of b) x = x << 8n | BigInt(by);
5186
+ return x;
5187
+ }
5188
+ var SchemaValidationSensor = class {
5189
+ constructor() {
5190
+ /** Sensor identifier for logging and registry */
5191
+ this.name = "SchemaValidationSensor";
5192
+ /**
5193
+ * Execution order - runs late in the pipeline
5194
+ *
5195
+ * Order 170 ensures:
5196
+ * - All authentication complete
5197
+ * - All policy checks complete
5198
+ * - Data validated before handler execution
5199
+ */
5200
+ this.order = BAND.CONTENT + 35;
5201
+ }
5202
+ /**
5203
+ * Determines if this sensor should process the given input.
5204
+ *
5205
+ * Only activates when a schema is provided for the intent (post-decode phase).
5206
+ *
5207
+ * @param {any} input - Sensor input
5208
+ * @returns {boolean} True if schema exists in metadata
5209
+ */
5210
+ supports(input) {
5211
+ return !!input.metadata?.schema;
5212
+ }
5213
+ /**
5214
+ * Validates TLV fields against the schema definition.
5215
+ *
5216
+ * **Validation Steps:**
5217
+ * 1. Validate the schema itself using Zod
5218
+ * 2. Iterate through each field definition
5219
+ * 3. Check required fields are present
5220
+ * 4. Validate size limits (maxLen)
5221
+ * 5. Validate type-specific rules
5222
+ *
5223
+ * @param {any} input - Standard SensorInput
5224
+ * @returns {{ action: 'ALLOW' } | { action: 'DENY', code: string, reason: string }} Decision
5225
+ */
5226
+ async run(input) {
5227
+ const schema = input.metadata?.schema;
5228
+ const headerTLVs = input.headerTLVs;
5229
+ const bodyTLVs = input.bodyTLVs;
5230
+ if (!schema) {
5231
+ return { action: "ALLOW" };
5232
+ }
5233
+ const validatedSchema = IntentSchemaZ.safeParse(schema);
5234
+ if (!validatedSchema.success) {
5235
+ return {
5236
+ action: "DENY",
5237
+ code: "SCHEMA_INVALID",
5238
+ reason: `Schema validation failed: ${validatedSchema.error.message}`
5239
+ };
5240
+ }
5241
+ try {
5242
+ for (const field of schema.fields) {
5243
+ const scope = field.scope ?? "body";
5244
+ const map3 = scope === "header" ? headerTLVs : bodyTLVs;
5245
+ const val = map3?.get(field.tlv);
5246
+ if (field.required && !val) {
5247
+ throw new AxisError(
5248
+ "SCHEMA_FIELD_MISSING",
5249
+ `Missing required field: ${field.name} (TLV ${field.tlv})`,
5250
+ 400
5251
+ );
5252
+ }
5253
+ if (!val) continue;
5254
+ if (typeof field.maxLen === "number" && val.length > field.maxLen) {
5255
+ throw new AxisError(
5256
+ "SCHEMA_LIMIT_EXCEEDED",
5257
+ `Field ${field.name} too large (${val.length} > ${field.maxLen})`,
5258
+ 413
5259
+ // Payload Too Large
5260
+ );
5261
+ }
5262
+ switch (field.kind) {
5263
+ case "utf8":
5264
+ try {
5265
+ new TextDecoder("utf-8", { fatal: true }).decode(val);
5266
+ } catch {
5267
+ throw new AxisError(
5268
+ "SCHEMA_TYPE_MISMATCH",
5269
+ `Invalid UTF-8 in ${field.name}`,
5270
+ 400
5271
+ );
5272
+ }
5273
+ break;
5274
+ case "bool":
5275
+ if (val.length !== 1 || val[0] !== 0 && val[0] !== 1) {
5276
+ throw new AxisError(
5277
+ "SCHEMA_TYPE_MISMATCH",
5278
+ `Invalid bool: ${field.name}`,
5279
+ 400
5280
+ );
5281
+ }
5282
+ break;
5283
+ case "u64": {
5284
+ const x = readU64be(val);
5285
+ if (field.max) {
5286
+ const mx = BigInt(field.max);
5287
+ if (x > mx) {
5288
+ throw new AxisError(
5289
+ "SCHEMA_LIMIT_EXCEEDED",
5290
+ `u64 ${field.name} exceeds max (${x} > ${mx})`,
5291
+ 400
5292
+ );
5293
+ }
5294
+ }
5295
+ break;
5296
+ }
5297
+ case "bytes16":
5298
+ if (val.length !== 16) {
5299
+ throw new AxisError(
5300
+ "SCHEMA_TYPE_MISMATCH",
5301
+ `bytes16 required for ${field.name}`,
5302
+ 400
5303
+ );
5304
+ }
5305
+ break;
5306
+ case "bytes":
5307
+ break;
5308
+ case "obj":
5309
+ case "arr":
5310
+ break;
5311
+ default:
5312
+ throw new AxisError(
5313
+ "SCHEMA_TYPE_MISMATCH",
5314
+ `Unknown schema kind: ${field.kind}`,
5315
+ 500
5316
+ );
5317
+ }
5318
+ }
5319
+ const validators = input.metadata?.validators;
5320
+ if (validators && validators.size > 0) {
5321
+ for (const field of schema.fields) {
5322
+ const fns = validators.get(field.tlv);
5323
+ if (!fns || fns.length === 0) continue;
5324
+ const scope = field.scope ?? "body";
5325
+ const map3 = scope === "header" ? headerTLVs : bodyTLVs;
5326
+ const val = map3?.get(field.tlv);
5327
+ if (!val) continue;
5328
+ for (const fn of fns) {
5329
+ const error = fn(val, field.name);
5330
+ if (error) {
5331
+ throw new AxisError(
5332
+ "SCHEMA_VALIDATION_FAILED",
5333
+ `${field.name} (TLV ${field.tlv}): ${error}`,
5334
+ 400
5335
+ );
5336
+ }
5337
+ }
5338
+ }
5339
+ }
5340
+ } catch (err) {
5341
+ if (err instanceof AxisError) {
5342
+ return {
5343
+ action: "DENY",
5344
+ code: err.code,
5345
+ reason: err.message
5346
+ };
5347
+ }
5348
+ throw err;
5349
+ }
5350
+ return { action: "ALLOW" };
5351
+ }
5352
+ };
5353
+ SchemaValidationSensor = __decorateClass([
5354
+ Sensor(),
5355
+ (0, import_common25.Injectable)()
5356
+ ], SchemaValidationSensor);
5357
+
5358
+ // src/sensors/stream-scope.sensor.ts
5359
+ var import_common26 = require("@nestjs/common");
5360
+ var StreamScopeSensor = class {
5361
+ constructor() {
5362
+ /** Sensor identifier */
5363
+ this.name = "StreamScopeSensor";
5364
+ /**
5365
+ * Execution order - near handler execution
5366
+ *
5367
+ * Order 200 ensures:
5368
+ * - All authentication complete
5369
+ * - All policy checks complete
5370
+ * - Stream-specific check right before subscription
5371
+ */
5372
+ this.order = BAND.BUSINESS + 0;
5373
+ }
5374
+ /**
5375
+ * Determines if this sensor should process the given input.
5376
+ *
5377
+ * Currently processes all inputs.
5378
+ *
5379
+ * @returns {boolean} Always true
5380
+ */
5381
+ supports() {
5382
+ return true;
5383
+ }
5384
+ /**
5385
+ * Validates stream topic access permissions.
5386
+ *
5387
+ * **Current Implementation:** Stub that always allows.
5388
+ *
5389
+ * **TODO:** Full implementation should:
5390
+ * 1. Check if intent is stream.subscribe or stream.publish
5391
+ * 2. Extract topic from body TLVs
5392
+ * 3. Parse topic into owner/resource pattern
5393
+ * 4. Look up topic ACL from database/cache
5394
+ * 5. Check if actor has required permission (read/write)
5395
+ * 6. DENY if unauthorized
5396
+ *
5397
+ * @returns {Promise<SensorDecision>} ALLOW (stub implementation)
5398
+ */
5399
+ async run() {
5400
+ return { action: "ALLOW" };
5401
+ }
5402
+ };
5403
+ StreamScopeSensor = __decorateClass([
5404
+ Sensor(),
5405
+ (0, import_common26.Injectable)()
5406
+ ], StreamScopeSensor);
5407
+
5408
+ // src/sensors/tlv-parse.sensor.ts
5409
+ var import_common27 = require("@nestjs/common");
5410
+ var TLVParseSensor = class {
5411
+ constructor() {
5412
+ this.name = "TLVParseSensor";
5413
+ this.order = BAND.CONTENT + 20;
5414
+ }
5415
+ supports(input) {
5416
+ return !!input.packet;
5417
+ }
5418
+ async run(input) {
5419
+ const packet = input.packet;
5420
+ if (!packet) return { action: "ALLOW" };
5421
+ const hdrBytes = packet.hdrBytes ?? packet.headerBytes;
5422
+ if (hdrBytes && hdrBytes.length > 0) {
5423
+ const result = this.validateCanonicalTLV(hdrBytes, "header");
5424
+ if (result) return result;
5425
+ }
5426
+ const bodyBytes = packet.bodyBytes ?? input.body;
5427
+ const bodyIsTlv = packet.flags !== void 0 ? (packet.flags & 1) !== 0 : false;
5428
+ const bodyProfile = input.metadata?.schema?.bodyProfile;
5429
+ const skipBody = bodyProfile === "RAW";
5430
+ if (!skipBody && bodyIsTlv && bodyBytes && bodyBytes.length > 0) {
5431
+ const result = this.validateCanonicalTLV(bodyBytes, "body");
5432
+ if (result) return result;
5433
+ }
5434
+ return { action: "ALLOW" };
5435
+ }
5436
+ /**
5437
+ * Validates a TLV buffer for canonical ordering, no duplicates,
5438
+ * valid bounds, and minimal varint encoding.
5439
+ */
5440
+ validateCanonicalTLV(buf, section) {
5441
+ let offset = 0;
5442
+ let lastType = -1;
5443
+ let count = 0;
5444
+ const maxItems = 512;
5445
+ while (offset < buf.length) {
5446
+ if (count >= maxItems) {
5447
+ return {
5448
+ action: "DENY",
5449
+ code: "TLV_LIMIT",
5450
+ reason: `Too many TLVs in ${section}`
5451
+ };
5452
+ }
5453
+ let type;
5454
+ let typeLen;
5455
+ try {
5456
+ const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
5457
+ type = r.value;
5458
+ typeLen = r.length;
5459
+ } catch {
5460
+ return {
5461
+ action: "DENY",
5462
+ code: "TLV_PARSE_ERROR",
5463
+ reason: `Malformed type varint in ${section} at offset ${offset}`
5464
+ };
5465
+ }
5466
+ offset += typeLen;
5467
+ if (type <= 0) {
5468
+ return {
5469
+ action: "DENY",
5470
+ code: "TLV_INVALID_TAG",
5471
+ reason: `Invalid tag ${type} in ${section}`
5472
+ };
5473
+ }
5474
+ if (type <= lastType) {
5475
+ return {
5476
+ action: "DENY",
5477
+ code: "TLV_NOT_CANONICAL",
5478
+ reason: `Non-canonical tag order in ${section}: ${type} after ${lastType}`
5479
+ };
5480
+ }
5481
+ lastType = type;
5482
+ let len;
5483
+ let lenLen;
5484
+ try {
5485
+ const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
5486
+ len = r.value;
5487
+ lenLen = r.length;
5488
+ } catch {
5489
+ return {
5490
+ action: "DENY",
5491
+ code: "TLV_PARSE_ERROR",
5492
+ reason: `Malformed length varint in ${section}`
5493
+ };
5494
+ }
5495
+ offset += lenLen;
5496
+ if (offset + len > buf.length) {
5497
+ return {
5498
+ action: "DENY",
5499
+ code: "TLV_TRUNCATED",
5500
+ reason: `TLV value truncated in ${section}`
5501
+ };
5502
+ }
5503
+ offset += len;
5504
+ count++;
5505
+ }
5506
+ return null;
5507
+ }
5508
+ };
5509
+ TLVParseSensor = __decorateClass([
5510
+ Sensor(),
5511
+ (0, import_common27.Injectable)()
5512
+ ], TLVParseSensor);
5513
+
5514
+ // src/sensors/varint-hardening.sensor.ts
5515
+ var import_common28 = require("@nestjs/common");
5516
+ var VarintHardeningSensor = class {
5517
+ constructor() {
5518
+ /** Sensor identifier */
5519
+ this.name = "VarintHardeningSensor";
5520
+ /**
5521
+ * Execution order - early detection
5522
+ *
5523
+ * Order 40 ensures:
5524
+ * - After protocol magic check
5525
+ * - Before length-based parsing
5526
+ */
5527
+ this.order = BAND.WIRE + 35;
5528
+ /** Maximum allowed bytes for a single varint */
5529
+ this.MAX_VARINT_BYTES = 5;
5530
+ }
5531
+ /**
5532
+ * Determines if this sensor should process the given input.
5533
+ *
5534
+ * Requires at least 7 bytes of peeked data.
5535
+ *
5536
+ * @param {SensorInput} input - Incoming request
5537
+ * @returns {boolean} True if sufficient peek data
5538
+ */
5539
+ supports(input) {
5540
+ return !!input.peek && input.peek.length >= 7;
5541
+ }
5542
+ /**
5543
+ * Validates varint lengths in frame header.
5544
+ *
5545
+ * **Processing Flow:**
5546
+ * 1. Skip to varint section (offset 7)
5547
+ * 2. Scan for continuation bytes (MSB = 1)
5548
+ * 3. Count consecutive continuation bytes
5549
+ * 4. DENY if count exceeds MAX_VARINT_BYTES
5550
+ *
5551
+ * @param {SensorInput} input - Request with peek data
5552
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on varint length
5553
+ */
5554
+ async run(input) {
5555
+ const peek = input.peek;
5556
+ const offset = 7;
5557
+ const maxOffset = Math.min(offset + 15, peek.length);
5558
+ let continuationCount = 0;
5559
+ for (let i = offset; i < maxOffset; i++) {
5560
+ if ((peek[i] & 128) !== 0) {
5561
+ continuationCount++;
5562
+ if (continuationCount > this.MAX_VARINT_BYTES) {
5563
+ return {
5564
+ action: "DENY",
5565
+ code: "VARINT_OVERFLOW",
5566
+ reason: `Varint exceeds ${this.MAX_VARINT_BYTES} bytes`
5567
+ };
5568
+ }
5569
+ } else {
5570
+ continuationCount = 0;
5571
+ }
5572
+ }
5573
+ return { action: "ALLOW" };
5574
+ }
5575
+ };
5576
+ VarintHardeningSensor = __decorateClass([
5577
+ Sensor({ phase: "PRE_DECODE" }),
5578
+ (0, import_common28.Injectable)()
5579
+ ], VarintHardeningSensor);
5580
+
5581
+ // src/utils/index.ts
5582
+ var utils_exports = {};
5583
+ __export(utils_exports, {
5584
+ encodeAxisTlvDto: () => encodeAxisTlvDto
5585
+ });
5586
+
5587
+ // src/utils/axis-tlv-codec.ts
5588
+ function encodeAxisTlvDto(dtoClass, data) {
5589
+ const schema = extractDtoSchema(dtoClass);
5590
+ const items = schema.fields.flatMap((field) => {
5591
+ const value = data[field.name];
5592
+ if (value === void 0 || value === null) {
5593
+ if (field.required) {
5594
+ throw new Error(`Missing required TLV response field: ${field.name}`);
5595
+ }
5596
+ return [];
5597
+ }
5598
+ return [{ type: field.tag, value: encodeField(field, value) }];
5599
+ });
5600
+ return buildTLVs(items);
5601
+ }
5602
+ function encodeField(field, value) {
5603
+ switch (field.kind) {
5604
+ case "utf8":
5605
+ return Buffer.from(String(value), "utf8");
5606
+ case "u64":
5607
+ return encodeU64(value);
5608
+ case "bytes":
5609
+ case "bytes16":
5610
+ return toBuffer(value);
5611
+ case "bool":
5612
+ return Buffer.from([value ? 1 : 0]);
5613
+ case "obj":
5614
+ case "arr":
5615
+ return Buffer.from(JSON.stringify(value), "utf8");
5616
+ default:
5617
+ return toBuffer(value);
5618
+ }
5619
+ }
5620
+ function encodeU64(value) {
5621
+ const encoded = Buffer.alloc(8);
5622
+ encoded.writeBigUInt64BE(
5623
+ typeof value === "bigint" ? value : BigInt(value)
5624
+ );
5625
+ return encoded;
5626
+ }
5627
+ function toBuffer(value) {
5628
+ if (Buffer.isBuffer(value)) {
5629
+ return value;
5630
+ }
5631
+ if (value instanceof Uint8Array) {
5632
+ return Buffer.from(value);
5633
+ }
5634
+ if (typeof value === "string") {
5635
+ return Buffer.from(value, "utf8");
5636
+ }
5637
+ throw new Error(`Unsupported TLV bytes value: ${typeof value}`);
5638
+ }
2384
5639
  // Annotate the CommonJS export names for ESM import in node:
2385
5640
  0 && (module.exports = {
2386
5641
  ATS1_HDR,
2387
5642
  ATS1_SCHEMA,
2388
5643
  AXIS_MAGIC,
2389
5644
  AXIS_OPCODES,
5645
+ AXIS_UPLOAD_FILE_STORE,
5646
+ AXIS_UPLOAD_RECEIPT_SIGNER,
5647
+ AXIS_UPLOAD_SESSION_STORE,
2390
5648
  AXIS_VERSION,
2391
5649
  Ats1Codec,
5650
+ AxisFilesDownloadHandler,
5651
+ AxisFilesFinalizeHandler,
2392
5652
  AxisFrameZ,
2393
5653
  AxisIdDto,
2394
5654
  AxisPacketTags,
@@ -2401,6 +5661,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
2401
5661
  DEFAULT_CONTRACTS,
2402
5662
  DEFAULT_TIMEOUT,
2403
5663
  Decision,
5664
+ DiskUploadFileStore,
2404
5665
  ERR_BAD_SIGNATURE,
2405
5666
  ERR_CONTRACT_VIOLATION,
2406
5667
  ERR_INVALID_PACKET,
@@ -2412,14 +5673,18 @@ function isTimestampValid(ts, skewSeconds = 120) {
2412
5673
  FLAG_HAS_WITNESS,
2413
5674
  HANDLER_METADATA_KEY,
2414
5675
  Handler,
5676
+ INTENT_BODY_KEY,
2415
5677
  INTENT_METADATA_KEY,
2416
5678
  INTENT_REQUIREMENTS,
2417
5679
  INTENT_ROUTES_KEY,
2418
5680
  INTENT_SENSITIVITY_MAP,
5681
+ INTENT_SENSORS_KEY,
2419
5682
  INTENT_TIMEOUTS,
2420
5683
  Intent,
5684
+ IntentBody,
2421
5685
  IntentRouter,
2422
5686
  IntentSensitivity,
5687
+ IntentSensors,
2423
5688
  MAX_BODY_LEN,
2424
5689
  MAX_FRAME_LEN,
2425
5690
  MAX_HDR_LEN,
@@ -2510,6 +5775,8 @@ function isTimestampValid(ts, skewSeconds = 120) {
2510
5775
  classifyIntent,
2511
5776
  computeReceiptHash,
2512
5777
  computeSignaturePayload,
5778
+ core,
5779
+ crypto,
2513
5780
  decodeArray,
2514
5781
  decodeAxis1Frame,
2515
5782
  decodeFrame,
@@ -2517,11 +5784,13 @@ function isTimestampValid(ts, skewSeconds = 120) {
2517
5784
  decodeTLVs,
2518
5785
  decodeTLVsList,
2519
5786
  decodeVarint,
5787
+ decorators,
2520
5788
  encVarint,
2521
5789
  encodeAxis1Frame,
2522
5790
  encodeFrame,
2523
5791
  encodeTLVs,
2524
5792
  encodeVarint,
5793
+ engine,
2525
5794
  extractDtoSchema,
2526
5795
  generateEd25519KeyPair,
2527
5796
  getSignTarget,
@@ -2529,6 +5798,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
2529
5798
  isAdminOpcode,
2530
5799
  isKnownOpcode,
2531
5800
  isTimestampValid,
5801
+ loom,
2532
5802
  nonce16,
2533
5803
  normalizeSensorDecision,
2534
5804
  packPasskeyLoginOptionsReq,
@@ -2538,7 +5808,10 @@ function isTimestampValid(ts, skewSeconds = 120) {
2538
5808
  packPasskeyRegisterOptionsReq,
2539
5809
  parseScope,
2540
5810
  resolveTimeout,
5811
+ schemas,
5812
+ security,
2541
5813
  sensitivityName,
5814
+ sensors,
2542
5815
  sha256,
2543
5816
  signFrame,
2544
5817
  tlv,
@@ -2547,6 +5820,7 @@ function isTimestampValid(ts, skewSeconds = 120) {
2547
5820
  unpackPasskeyLoginVerifyReq,
2548
5821
  unpackPasskeyRegisterOptionsReq,
2549
5822
  utf8,
5823
+ utils,
2550
5824
  validateFrameShape,
2551
5825
  varintLength,
2552
5826
  varintU,