@nextera.one/axis-server-sdk 1.2.1 → 1.4.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
@@ -166,34 +166,45 @@ __export(index_exports, {
166
166
  buildAts1Hdr: () => buildAts1Hdr,
167
167
  buildDtoDecoder: () => buildDtoDecoder,
168
168
  buildPacket: () => buildPacket,
169
+ buildQueueMessage: () => buildQueueMessage,
169
170
  buildReceiptHash: () => buildReceiptHash,
170
171
  buildTLVs: () => buildTLVs,
172
+ buildUnsignedWitness: () => buildUnsignedWitness,
171
173
  bytes: () => bytes,
172
174
  canAccessResource: () => canAccessResource,
173
175
  canonicalJson: () => canonicalJson,
174
176
  canonicalJsonExcluding: () => canonicalJsonExcluding,
177
+ canonicalizeObservation: () => canonicalizeObservation,
175
178
  classifyIntent: () => classifyIntent,
176
179
  computeReceiptHash: () => computeReceiptHash,
177
180
  computeSignaturePayload: () => computeSignaturePayload,
181
+ core: () => core_exports,
182
+ crypto: () => crypto_exports,
178
183
  decodeArray: () => import_axis_protocol.decodeArray,
179
184
  decodeAxis1Frame: () => decodeAxis1Frame,
180
185
  decodeFrame: () => decodeFrame,
181
186
  decodeObject: () => import_axis_protocol.decodeObject,
187
+ decodeQueueMessage: () => decodeQueueMessage,
182
188
  decodeTLVs: () => import_axis_protocol.decodeTLVs,
183
189
  decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
184
190
  decodeVarint: () => import_axis_protocol3.decodeVarint,
191
+ decorators: () => decorators_exports,
185
192
  encVarint: () => encVarint,
186
193
  encodeAxis1Frame: () => encodeAxis1Frame,
187
194
  encodeFrame: () => encodeFrame,
195
+ encodeQueueMessage: () => encodeQueueMessage,
188
196
  encodeTLVs: () => import_axis_protocol.encodeTLVs,
189
197
  encodeVarint: () => import_axis_protocol3.encodeVarint,
198
+ engine: () => engine_exports,
190
199
  extractDtoSchema: () => extractDtoSchema,
191
200
  generateEd25519KeyPair: () => generateEd25519KeyPair,
192
201
  getSignTarget: () => getSignTarget,
193
202
  hasScope: () => hasScope,
203
+ hashObservation: () => hashObservation,
194
204
  isAdminOpcode: () => isAdminOpcode,
195
205
  isKnownOpcode: () => isKnownOpcode,
196
206
  isTimestampValid: () => isTimestampValid,
207
+ loom: () => loom_exports,
197
208
  nonce16: () => nonce16,
198
209
  normalizeSensorDecision: () => normalizeSensorDecision,
199
210
  packPasskeyLoginOptionsReq: () => packPasskeyLoginOptionsReq,
@@ -201,21 +212,29 @@ __export(index_exports, {
201
212
  packPasskeyLoginVerifyReq: () => packPasskeyLoginVerifyReq,
202
213
  packPasskeyLoginVerifyRes: () => packPasskeyLoginVerifyRes,
203
214
  packPasskeyRegisterOptionsReq: () => packPasskeyRegisterOptionsReq,
215
+ parseAutoClaimEntries: () => parseAutoClaimEntries,
204
216
  parseScope: () => parseScope,
217
+ parseStreamEntries: () => parseStreamEntries,
205
218
  resolveTimeout: () => resolveTimeout,
219
+ schemas: () => schemas_exports,
220
+ security: () => security_exports,
206
221
  sensitivityName: () => sensitivityName,
222
+ sensors: () => sensors_exports,
207
223
  sha256: () => sha256,
208
224
  signFrame: () => signFrame,
225
+ stableJsonStringify: () => stableJsonStringify,
209
226
  tlv: () => tlv,
210
227
  u64be: () => u64be,
211
228
  unpackPasskeyLoginOptionsReq: () => unpackPasskeyLoginOptionsReq,
212
229
  unpackPasskeyLoginVerifyReq: () => unpackPasskeyLoginVerifyReq,
213
230
  unpackPasskeyRegisterOptionsReq: () => unpackPasskeyRegisterOptionsReq,
214
231
  utf8: () => utf8,
232
+ utils: () => utils_exports,
215
233
  validateFrameShape: () => validateFrameShape,
216
234
  varintLength: () => import_axis_protocol3.varintLength,
217
235
  varintU: () => varintU,
218
- verifyFrameSignature: () => verifyFrameSignature
236
+ verifyFrameSignature: () => verifyFrameSignature,
237
+ verifyResponse: () => verifyResponse
219
238
  });
220
239
  module.exports = __toCommonJS(index_exports);
221
240
 
@@ -910,9 +929,213 @@ IntentRouter = __decorateClass([
910
929
  __decorateParam(0, (0, import_common2.Optional)())
911
930
  ], IntentRouter);
912
931
 
932
+ // src/engine/observation/stable-json.ts
933
+ function normalize(value) {
934
+ if (Array.isArray(value)) {
935
+ return value.map((item) => normalize(item));
936
+ }
937
+ if (value && typeof value === "object") {
938
+ const entries = Object.entries(value).filter(([, nested]) => nested !== void 0).sort(([left], [right]) => left.localeCompare(right));
939
+ const normalized = {};
940
+ for (const [key, nested] of entries) {
941
+ normalized[key] = normalize(nested);
942
+ }
943
+ return normalized;
944
+ }
945
+ return value;
946
+ }
947
+ function stableJsonStringify(value) {
948
+ return JSON.stringify(normalize(value));
949
+ }
950
+
951
+ // src/engine/observation/observation-queue.codec.ts
952
+ function buildQueueMessage(observation, sourceNodeId, previous, lastError) {
953
+ const now = Date.now();
954
+ return {
955
+ v: 1,
956
+ observation,
957
+ attempts: previous ? previous.attempts + 1 : 0,
958
+ firstEnqueuedAt: previous?.firstEnqueuedAt ?? now,
959
+ lastEnqueuedAt: now,
960
+ sourceNodeId,
961
+ lastError
962
+ };
963
+ }
964
+ function encodeQueueMessage(message) {
965
+ return JSON.stringify(message);
966
+ }
967
+ function decodeQueueMessage(raw) {
968
+ try {
969
+ const parsed = JSON.parse(raw);
970
+ if (!parsed || parsed.v !== 1 || !parsed.observation?.id) {
971
+ return null;
972
+ }
973
+ return parsed;
974
+ } catch {
975
+ return null;
976
+ }
977
+ }
978
+ function parseStreamEntries(raw) {
979
+ if (!Array.isArray(raw)) {
980
+ return [];
981
+ }
982
+ const entries = [];
983
+ for (const streamRow of raw) {
984
+ if (!Array.isArray(streamRow) || streamRow.length < 2) {
985
+ continue;
986
+ }
987
+ const messageRows = streamRow[1];
988
+ if (!Array.isArray(messageRows)) {
989
+ continue;
990
+ }
991
+ for (const row of messageRows) {
992
+ if (!Array.isArray(row) || row.length < 2) {
993
+ continue;
994
+ }
995
+ const id = String(row[0]);
996
+ const fields = Array.isArray(row[1]) ? row[1] : [];
997
+ const fieldMap = fieldsToMap(fields);
998
+ const payload = fieldMap.get("payload");
999
+ if (!payload) {
1000
+ continue;
1001
+ }
1002
+ const message = decodeQueueMessage(payload);
1003
+ if (!message) {
1004
+ continue;
1005
+ }
1006
+ entries.push({ id, message });
1007
+ }
1008
+ }
1009
+ return entries;
1010
+ }
1011
+ function parseAutoClaimEntries(raw) {
1012
+ if (!Array.isArray(raw) || raw.length < 2) {
1013
+ return [];
1014
+ }
1015
+ const rows = Array.isArray(raw[1]) ? raw[1] : [];
1016
+ return parseStreamEntries([["stream", rows]]);
1017
+ }
1018
+ function fieldsToMap(fields) {
1019
+ const map3 = /* @__PURE__ */ new Map();
1020
+ for (let i = 0; i < fields.length; i += 2) {
1021
+ const key = fields[i];
1022
+ const value = fields[i + 1];
1023
+ if (key !== void 0 && value !== void 0) {
1024
+ map3.set(String(key), String(value));
1025
+ }
1026
+ }
1027
+ return map3;
1028
+ }
1029
+
1030
+ // src/engine/observation/observation-hash.ts
1031
+ var import_crypto = require("crypto");
1032
+ function canonicalizeObservation(obs) {
1033
+ const obj = {
1034
+ id: obs.id,
1035
+ startMs: obs.startMs,
1036
+ endMs: obs.endMs,
1037
+ transport: obs.transport,
1038
+ ip: obs.ip,
1039
+ intent: obs.intent,
1040
+ actorId: obs.actorId,
1041
+ capsuleId: obs.capsuleId,
1042
+ decision: obs.decision,
1043
+ resultCode: obs.resultCode,
1044
+ statusCode: obs.statusCode,
1045
+ durationMs: obs.durationMs,
1046
+ stages: obs.stages.map((s) => ({
1047
+ name: s.name,
1048
+ status: s.status,
1049
+ startMs: s.startMs,
1050
+ endMs: s.endMs,
1051
+ durationMs: s.durationMs,
1052
+ reason: s.reason,
1053
+ code: s.code
1054
+ })),
1055
+ sensors: obs.sensors.map((s) => ({
1056
+ name: s.name,
1057
+ allowed: s.allowed,
1058
+ riskScore: s.riskScore,
1059
+ durationMs: s.durationMs,
1060
+ reasons: s.reasons,
1061
+ code: s.code
1062
+ }))
1063
+ };
1064
+ return stableJsonStringify(obj);
1065
+ }
1066
+ function hashObservation(obs) {
1067
+ const canonical = canonicalizeObservation(obs);
1068
+ return (0, import_crypto.createHash)("sha256").update(canonical).digest("hex");
1069
+ }
1070
+ function buildUnsignedWitness(obs) {
1071
+ if (!obs.decision || !obs.endMs) {
1072
+ return null;
1073
+ }
1074
+ return {
1075
+ v: 1,
1076
+ observationId: obs.id,
1077
+ payloadHash: hashObservation(obs),
1078
+ sealedAt: Date.now(),
1079
+ summary: {
1080
+ intent: obs.intent,
1081
+ actorId: obs.actorId,
1082
+ decision: obs.decision,
1083
+ statusCode: obs.statusCode,
1084
+ durationMs: obs.durationMs,
1085
+ sensorCount: obs.sensors.length,
1086
+ stageCount: obs.stages.length
1087
+ }
1088
+ };
1089
+ }
1090
+
913
1091
  // src/core/constants.ts
914
1092
  var import_axis_protocol2 = require("@nextera.one/axis-protocol");
915
1093
 
1094
+ // src/engine/observation/response-observer.ts
1095
+ var SENSITIVE_RESPONSE_TAGS = [4, 5, 6];
1096
+ function verifyResponse(ctx, response) {
1097
+ if (!response.effect || typeof response.effect !== "string") {
1098
+ return {
1099
+ passed: false,
1100
+ code: "OBSERVER_INVALID_EFFECT",
1101
+ reason: "Response effect is missing or invalid"
1102
+ };
1103
+ }
1104
+ if (response.ok && (!response.body || response.body.length === 0)) {
1105
+ return {
1106
+ passed: false,
1107
+ code: "OBSERVER_EMPTY_BODY",
1108
+ reason: "Successful response must contain a body"
1109
+ };
1110
+ }
1111
+ if (response.body && response.body.length > import_axis_protocol2.MAX_BODY_LEN) {
1112
+ return {
1113
+ passed: false,
1114
+ code: "OBSERVER_BODY_OVERFLOW",
1115
+ reason: `Response body exceeds ${import_axis_protocol2.MAX_BODY_LEN} bytes`
1116
+ };
1117
+ }
1118
+ if (response.headers) {
1119
+ for (const tag of SENSITIVE_RESPONSE_TAGS) {
1120
+ if (response.headers.has(tag)) {
1121
+ return {
1122
+ passed: false,
1123
+ code: "OBSERVER_DATA_LEAK",
1124
+ reason: `Response must not contain sensitive TLV tag ${tag}`
1125
+ };
1126
+ }
1127
+ }
1128
+ }
1129
+ if (response.effect.includes("Error:") || response.effect.includes("stack") || response.effect.includes("at /")) {
1130
+ return {
1131
+ passed: false,
1132
+ code: "OBSERVER_INFO_LEAK",
1133
+ reason: "Response effect may contain internal error details"
1134
+ };
1135
+ }
1136
+ return { passed: true };
1137
+ }
1138
+
916
1139
  // src/core/varint.ts
917
1140
  var import_axis_protocol3 = require("@nextera.one/axis-protocol");
918
1141
 
@@ -1188,7 +1411,7 @@ __export(ats1_exports, {
1188
1411
  tlvsToMap: () => tlvsToMap,
1189
1412
  validateTLVsAgainstSchema: () => validateTLVsAgainstSchema
1190
1413
  });
1191
- var import_crypto = require("crypto");
1414
+ var import_crypto2 = require("crypto");
1192
1415
  var DEFAULT_LIMITS = {
1193
1416
  maxVarintBytes: 10,
1194
1417
  maxTlvCount: 512,
@@ -1238,7 +1461,7 @@ function decodeU64BE(buf) {
1238
1461
  return buf.readBigUInt64BE(0);
1239
1462
  }
1240
1463
  function sha2562(data) {
1241
- return (0, import_crypto.createHash)("sha256").update(data).digest();
1464
+ return (0, import_crypto2.createHash)("sha256").update(data).digest();
1242
1465
  }
1243
1466
  function encodeTLV(tag, value) {
1244
1467
  if (!Number.isInteger(tag) || tag <= 0)
@@ -1827,7 +2050,7 @@ function packPasskeyLoginVerifyRes(params) {
1827
2050
  }
1828
2051
 
1829
2052
  // src/codec/tlv.encode.ts
1830
- var import_crypto2 = require("crypto");
2053
+ var import_crypto3 = require("crypto");
1831
2054
  function encVarint(x) {
1832
2055
  if (x < 0n) throw new Error("VARINT_NEG");
1833
2056
  const out = [];
@@ -1855,7 +2078,7 @@ function bytes(b) {
1855
2078
  return Buffer.isBuffer(b) ? b : Buffer.from(b);
1856
2079
  }
1857
2080
  function nonce16() {
1858
- return (0, import_crypto2.randomBytes)(16);
2081
+ return (0, import_crypto3.randomBytes)(16);
1859
2082
  }
1860
2083
  function tlv(type, value) {
1861
2084
  if (!Number.isSafeInteger(type) || type < 0) throw new Error("TLV_BAD_TYPE");
@@ -2401,9 +2624,9 @@ function isAdminOpcode(op) {
2401
2624
  }
2402
2625
 
2403
2626
  // src/core/receipt.ts
2404
- var import_crypto3 = require("crypto");
2627
+ var import_crypto4 = require("crypto");
2405
2628
  function buildReceiptHash(prevHash, pid, actorId, intent, effect, ts) {
2406
- const h = (0, import_crypto3.createHash)("sha256");
2629
+ const h = (0, import_crypto4.createHash)("sha256");
2407
2630
  if (prevHash) h.update(prevHash);
2408
2631
  h.update(pid);
2409
2632
  h.update(Buffer.from(actorId, "utf8"));
@@ -2807,6 +3030,2842 @@ var DiskUploadFileStore = class {
2807
3030
  return fs.createReadStream(tempPath);
2808
3031
  }
2809
3032
  };
3033
+
3034
+ // src/core/index.ts
3035
+ var core_exports = {};
3036
+ __export(core_exports, {
3037
+ AXIS_MAGIC: () => import_axis_protocol2.AXIS_MAGIC,
3038
+ AXIS_VERSION: () => import_axis_protocol2.AXIS_VERSION,
3039
+ AxisError: () => AxisError,
3040
+ AxisFrameZ: () => AxisFrameZ,
3041
+ BodyProfile: () => import_axis_protocol2.BodyProfile,
3042
+ ERR_BAD_SIGNATURE: () => import_axis_protocol2.ERR_BAD_SIGNATURE,
3043
+ ERR_CONTRACT_VIOLATION: () => import_axis_protocol2.ERR_CONTRACT_VIOLATION,
3044
+ ERR_INVALID_PACKET: () => import_axis_protocol2.ERR_INVALID_PACKET,
3045
+ ERR_REPLAY_DETECTED: () => import_axis_protocol2.ERR_REPLAY_DETECTED,
3046
+ FLAG_BODY_TLV: () => import_axis_protocol2.FLAG_BODY_TLV,
3047
+ FLAG_CHAIN_REQ: () => import_axis_protocol2.FLAG_CHAIN_REQ,
3048
+ FLAG_HAS_WITNESS: () => import_axis_protocol2.FLAG_HAS_WITNESS,
3049
+ MAX_BODY_LEN: () => import_axis_protocol2.MAX_BODY_LEN,
3050
+ MAX_FRAME_LEN: () => import_axis_protocol2.MAX_FRAME_LEN,
3051
+ MAX_HDR_LEN: () => import_axis_protocol2.MAX_HDR_LEN,
3052
+ MAX_SIG_LEN: () => import_axis_protocol2.MAX_SIG_LEN,
3053
+ NCERT_ALG: () => import_axis_protocol2.NCERT_ALG,
3054
+ NCERT_EXP: () => import_axis_protocol2.NCERT_EXP,
3055
+ NCERT_ISSUER_KID: () => import_axis_protocol2.NCERT_ISSUER_KID,
3056
+ NCERT_KID: () => import_axis_protocol2.NCERT_KID,
3057
+ NCERT_NBF: () => import_axis_protocol2.NCERT_NBF,
3058
+ NCERT_NODE_ID: () => import_axis_protocol2.NCERT_NODE_ID,
3059
+ NCERT_PAYLOAD: () => import_axis_protocol2.NCERT_PAYLOAD,
3060
+ NCERT_PUB: () => import_axis_protocol2.NCERT_PUB,
3061
+ NCERT_SCOPE: () => import_axis_protocol2.NCERT_SCOPE,
3062
+ NCERT_SIG: () => import_axis_protocol2.NCERT_SIG,
3063
+ PROOF_CAPSULE: () => import_axis_protocol2.PROOF_CAPSULE,
3064
+ PROOF_JWT: () => import_axis_protocol2.PROOF_JWT,
3065
+ PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
3066
+ PROOF_MTLS: () => import_axis_protocol2.PROOF_MTLS,
3067
+ PROOF_NONE: () => import_axis_protocol2.PROOF_NONE,
3068
+ PROOF_WITNESS: () => import_axis_protocol2.PROOF_WITNESS,
3069
+ ProofType: () => import_axis_protocol2.ProofType,
3070
+ TLV: () => import_axis_protocol.TLV,
3071
+ TLV_ACTOR_ID: () => import_axis_protocol2.TLV_ACTOR_ID,
3072
+ TLV_AUD: () => import_axis_protocol2.TLV_AUD,
3073
+ TLV_BODY_ARR: () => import_axis_protocol2.TLV_BODY_ARR,
3074
+ TLV_BODY_OBJ: () => import_axis_protocol2.TLV_BODY_OBJ,
3075
+ TLV_CAPSULE: () => import_axis_protocol2.TLV_CAPSULE,
3076
+ TLV_EFFECT: () => import_axis_protocol2.TLV_EFFECT,
3077
+ TLV_ERROR_CODE: () => import_axis_protocol2.TLV_ERROR_CODE,
3078
+ TLV_ERROR_MSG: () => import_axis_protocol2.TLV_ERROR_MSG,
3079
+ TLV_INDEX: () => import_axis_protocol2.TLV_INDEX,
3080
+ TLV_INTENT: () => import_axis_protocol2.TLV_INTENT,
3081
+ TLV_KID: () => import_axis_protocol2.TLV_KID,
3082
+ TLV_LOOM_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
3083
+ TLV_LOOM_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
3084
+ TLV_LOOM_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
3085
+ TLV_NODE: () => import_axis_protocol2.TLV_NODE,
3086
+ TLV_NODE_CERT_HASH: () => import_axis_protocol2.TLV_NODE_CERT_HASH,
3087
+ TLV_NODE_KID: () => import_axis_protocol2.TLV_NODE_KID,
3088
+ TLV_NONCE: () => import_axis_protocol2.TLV_NONCE,
3089
+ TLV_OFFSET: () => import_axis_protocol2.TLV_OFFSET,
3090
+ TLV_OK: () => import_axis_protocol2.TLV_OK,
3091
+ TLV_PID: () => import_axis_protocol2.TLV_PID,
3092
+ TLV_PREV_HASH: () => import_axis_protocol2.TLV_PREV_HASH,
3093
+ TLV_PROOF_REF: () => import_axis_protocol2.TLV_PROOF_REF,
3094
+ TLV_PROOF_TYPE: () => import_axis_protocol2.TLV_PROOF_TYPE,
3095
+ TLV_REALM: () => import_axis_protocol2.TLV_REALM,
3096
+ TLV_RECEIPT_HASH: () => import_axis_protocol2.TLV_RECEIPT_HASH,
3097
+ TLV_RID: () => import_axis_protocol2.TLV_RID,
3098
+ TLV_SHA256_CHUNK: () => import_axis_protocol2.TLV_SHA256_CHUNK,
3099
+ TLV_TRACE_ID: () => import_axis_protocol2.TLV_TRACE_ID,
3100
+ TLV_TS: () => import_axis_protocol2.TLV_TS,
3101
+ TLV_UPLOAD_ID: () => import_axis_protocol2.TLV_UPLOAD_ID,
3102
+ computeReceiptHash: () => computeReceiptHash,
3103
+ computeSignaturePayload: () => computeSignaturePayload,
3104
+ decodeArray: () => import_axis_protocol.decodeArray,
3105
+ decodeFrame: () => decodeFrame,
3106
+ decodeObject: () => import_axis_protocol.decodeObject,
3107
+ decodeTLVs: () => import_axis_protocol.decodeTLVs,
3108
+ decodeTLVsList: () => import_axis_protocol.decodeTLVsList,
3109
+ decodeVarint: () => import_axis_protocol3.decodeVarint,
3110
+ encodeFrame: () => encodeFrame,
3111
+ encodeTLVs: () => import_axis_protocol.encodeTLVs,
3112
+ encodeVarint: () => import_axis_protocol3.encodeVarint,
3113
+ generateEd25519KeyPair: () => generateEd25519KeyPair,
3114
+ getSignTarget: () => getSignTarget,
3115
+ sha256: () => sha256,
3116
+ signFrame: () => signFrame,
3117
+ varintLength: () => import_axis_protocol3.varintLength,
3118
+ verifyFrameSignature: () => verifyFrameSignature
3119
+ });
3120
+
3121
+ // src/core/axis-error.ts
3122
+ var AxisError = class extends Error {
3123
+ constructor(code, message, httpStatus = 400, details) {
3124
+ super(message);
3125
+ this.code = code;
3126
+ this.httpStatus = httpStatus;
3127
+ this.details = details;
3128
+ this.name = "AxisError";
3129
+ }
3130
+ };
3131
+
3132
+ // src/crypto/index.ts
3133
+ var crypto_exports = {};
3134
+ __export(crypto_exports, {
3135
+ ProofVerificationService: () => ProofVerificationService,
3136
+ b64urlDecode: () => b64urlDecode,
3137
+ b64urlDecodeString: () => b64urlDecodeString,
3138
+ b64urlEncode: () => b64urlEncode,
3139
+ b64urlEncodeString: () => b64urlEncodeString,
3140
+ canonicalJson: () => canonicalJson,
3141
+ canonicalJsonExcluding: () => canonicalJsonExcluding
3142
+ });
3143
+
3144
+ // src/crypto/proof-verification.service.ts
3145
+ var import_common4 = require("@nestjs/common");
3146
+ var crypto3 = __toESM(require("crypto"));
3147
+ var nacl = __toESM(require("tweetnacl"));
3148
+ var ProofVerificationService = class {
3149
+ constructor() {
3150
+ this.logger = new import_common4.Logger(ProofVerificationService.name);
3151
+ // Cache of registered device public keys (deviceId -> pubKey)
3152
+ this.deviceKeys = /* @__PURE__ */ new Map();
3153
+ // Cache of trusted mTLS certificate fingerprints
3154
+ this.trustedCerts = /* @__PURE__ */ new Map();
3155
+ }
3156
+ /**
3157
+ * Verifies an authentication proof based on its type.
3158
+ *
3159
+ * **Supported Types:**
3160
+ * - 1 (CAPSULE): Delegated to `verifyCapsuleProof`
3161
+ * - 2 (JWT): Verified by `verifyJWTProof`
3162
+ * - 3 (MTLS_ID): Verified by `verifyMTLSProof`
3163
+ * - 4 (DEVICE_SE): Verified by `verifyDeviceSEProof`
3164
+ *
3165
+ * @param {ProofType} proofType - The numeric AXIS proof type
3166
+ * @param {Uint8Array} proofRef - The binary reference or token for the proof
3167
+ * @param {Object} context - Additional metadata required for specific proof types
3168
+ * @param {Uint8Array} [context.signTarget] - The canonical bytes that were signed (for Ed25519)
3169
+ * @param {Uint8Array} [context.signature] - The signature to verify (for Ed25519)
3170
+ * @param {MTLSContext} [context.mtls] - mTLS certificate data
3171
+ * @param {DeviceSEContext} [context.deviceSE] - Device Secure Element information
3172
+ * @returns {Promise<ProofVerificationResult>} The outcome of the verification
3173
+ */
3174
+ async verifyProof(proofType, proofRef, context) {
3175
+ switch (proofType) {
3176
+ case 1:
3177
+ return this.verifyCapsuleProof(proofRef);
3178
+ case 2:
3179
+ return this.verifyJWTProof(proofRef);
3180
+ case 3:
3181
+ return this.verifyMTLSProof(context.mtls);
3182
+ case 4:
3183
+ return this.verifyDeviceSEProof(
3184
+ context.signTarget,
3185
+ context.signature,
3186
+ context.deviceSE
3187
+ );
3188
+ default:
3189
+ return { valid: false, error: `Unknown proof type: ${proofType}` };
3190
+ }
3191
+ }
3192
+ /**
3193
+ * Verify CAPSULE proof (delegated to CapsuleService)
3194
+ */
3195
+ async verifyCapsuleProof(proofRef) {
3196
+ const capsuleId = new TextDecoder().decode(proofRef);
3197
+ return {
3198
+ valid: true,
3199
+ metadata: { capsuleId, requiresCapsuleValidation: true }
3200
+ };
3201
+ }
3202
+ /**
3203
+ * Verifies a JSON Web Token (JWT) proof.
3204
+ *
3205
+ * **Validation Logic:**
3206
+ * 1. Decodes the token string.
3207
+ * 2. Checks for valid 3-part JWT structure.
3208
+ * 3. Validates `exp` (expiration) and `nbf` (not before) claims.
3209
+ * 4. Extracts `actor_id` or `sub` as the identity.
3210
+ *
3211
+ * @param {Uint8Array} proofRef - Binary representation of the JWT string
3212
+ * @returns {Promise<ProofVerificationResult>} Result including the actor identifier
3213
+ */
3214
+ async verifyJWTProof(proofRef) {
3215
+ try {
3216
+ const token = new TextDecoder().decode(proofRef);
3217
+ const parts = token.split(".");
3218
+ if (parts.length !== 3) {
3219
+ return { valid: false, error: "Invalid JWT format" };
3220
+ }
3221
+ const header = JSON.parse(Buffer.from(parts[0], "base64url").toString());
3222
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3223
+ if (payload.exp && Date.now() / 1e3 > payload.exp) {
3224
+ return { valid: false, error: "JWT expired" };
3225
+ }
3226
+ if (payload.nbf && Date.now() / 1e3 < payload.nbf) {
3227
+ return { valid: false, error: "JWT not yet valid" };
3228
+ }
3229
+ return {
3230
+ valid: true,
3231
+ actorId: payload.sub || payload.actor_id,
3232
+ metadata: { iss: payload.iss, scope: payload.scope }
3233
+ };
3234
+ } catch (e) {
3235
+ const message = e instanceof Error ? e.message : "Unknown error";
3236
+ return { valid: false, error: `JWT parse error: ${message}` };
3237
+ }
3238
+ }
3239
+ /**
3240
+ * Verify mTLS client certificate proof
3241
+ */
3242
+ async verifyMTLSProof(mtls) {
3243
+ if (!mtls) {
3244
+ return { valid: false, error: "No mTLS context provided" };
3245
+ }
3246
+ if (!mtls.verified) {
3247
+ return { valid: false, error: "mTLS not verified by TLS terminator" };
3248
+ }
3249
+ if (mtls.clientCertFingerprint) {
3250
+ const trusted = this.trustedCerts.get(mtls.clientCertFingerprint);
3251
+ if (trusted) {
3252
+ return {
3253
+ valid: true,
3254
+ actorId: trusted.actorId,
3255
+ metadata: {
3256
+ fingerprint: mtls.clientCertFingerprint,
3257
+ subject: mtls.clientCertSubject
3258
+ }
3259
+ };
3260
+ }
3261
+ }
3262
+ if (mtls.clientCertSubject) {
3263
+ const cnMatch = mtls.clientCertSubject.match(/CN=([^,]+)/);
3264
+ if (cnMatch) {
3265
+ return {
3266
+ valid: true,
3267
+ actorId: cnMatch[1],
3268
+ metadata: {
3269
+ subject: mtls.clientCertSubject,
3270
+ issuer: mtls.clientCertIssuer
3271
+ }
3272
+ };
3273
+ }
3274
+ }
3275
+ return { valid: false, error: "Could not extract actor from certificate" };
3276
+ }
3277
+ /**
3278
+ * Verify Device Secure Element signature
3279
+ */
3280
+ async verifyDeviceSEProof(signTarget, signature, deviceSE) {
3281
+ if (!deviceSE || !signTarget || !signature) {
3282
+ return { valid: false, error: "Missing Device SE context" };
3283
+ }
3284
+ let publicKey = deviceSE.publicKey;
3285
+ const registeredKey = this.deviceKeys.get(deviceSE.deviceId);
3286
+ if (registeredKey) {
3287
+ publicKey = registeredKey;
3288
+ }
3289
+ if (!publicKey || publicKey.length !== 32) {
3290
+ return {
3291
+ valid: false,
3292
+ error: "Invalid or unregistered device public key"
3293
+ };
3294
+ }
3295
+ try {
3296
+ const valid = nacl.sign.detached.verify(signTarget, signature, publicKey);
3297
+ if (!valid) {
3298
+ return { valid: false, error: "Device signature verification failed" };
3299
+ }
3300
+ return {
3301
+ valid: true,
3302
+ actorId: deviceSE.deviceId,
3303
+ metadata: { deviceId: deviceSE.deviceId, proofType: "DEVICE_SE" }
3304
+ };
3305
+ } catch (e) {
3306
+ const message = e instanceof Error ? e.message : "Unknown error";
3307
+ return {
3308
+ valid: false,
3309
+ error: `Signature verification error: ${message}`
3310
+ };
3311
+ }
3312
+ }
3313
+ /**
3314
+ * Registers a public key for a trusted device.
3315
+ * This key will be used for future `DEVICE_SE` proof verifications.
3316
+ *
3317
+ * @param {string} deviceId - Unique identifier for the device
3318
+ * @param {Uint8Array} publicKey - 32-byte Ed25519 public key
3319
+ * @throws {Error} If the public key is not 32 bytes
3320
+ */
3321
+ registerDeviceKey(deviceId, publicKey) {
3322
+ if (publicKey.length !== 32) {
3323
+ throw new Error("Device public key must be 32 bytes (Ed25519)");
3324
+ }
3325
+ this.deviceKeys.set(deviceId, publicKey);
3326
+ this.logger.log(`Registered device key for ${deviceId}`);
3327
+ }
3328
+ /**
3329
+ * Unregister a device
3330
+ */
3331
+ unregisterDevice(deviceId) {
3332
+ return this.deviceKeys.delete(deviceId);
3333
+ }
3334
+ /**
3335
+ * Registers a trusted mTLS certificate fingerprint and associates it with an actor.
3336
+ *
3337
+ * @param {string} fingerprint - SHA-256 fingerprint of the client certificate
3338
+ * @param {string} actorId - The actor to associate with this certificate
3339
+ */
3340
+ registerMTLSCert(fingerprint, actorId) {
3341
+ this.trustedCerts.set(fingerprint, { actorId, issuedAt: Date.now() });
3342
+ this.logger.log(`Registered mTLS cert ${fingerprint} for actor ${actorId}`);
3343
+ }
3344
+ /**
3345
+ * Revoke an mTLS certificate
3346
+ */
3347
+ revokeMTLSCert(fingerprint) {
3348
+ return this.trustedCerts.delete(fingerprint);
3349
+ }
3350
+ /**
3351
+ * Calculate certificate fingerprint (SHA-256)
3352
+ */
3353
+ static calculateFingerprint(certPem) {
3354
+ const der = Buffer.from(
3355
+ certPem.replace(/-----BEGIN CERTIFICATE-----/, "").replace(/-----END CERTIFICATE-----/, "").replace(/\s/g, ""),
3356
+ "base64"
3357
+ );
3358
+ return crypto3.createHash("sha256").update(der).digest("hex");
3359
+ }
3360
+ };
3361
+ ProofVerificationService = __decorateClass([
3362
+ (0, import_common4.Injectable)()
3363
+ ], ProofVerificationService);
3364
+
3365
+ // src/decorators/index.ts
3366
+ var decorators_exports = {};
3367
+ __export(decorators_exports, {
3368
+ AxisContext: () => AxisContext,
3369
+ AxisDemoPubkey: () => AxisDemoPubkey,
3370
+ AxisFrame: () => AxisFrame3,
3371
+ AxisIp: () => AxisIp,
3372
+ AxisRaw: () => AxisRaw,
3373
+ HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
3374
+ Handler: () => Handler,
3375
+ INTENT_BODY_KEY: () => INTENT_BODY_KEY,
3376
+ INTENT_METADATA_KEY: () => INTENT_METADATA_KEY,
3377
+ INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
3378
+ INTENT_SENSORS_KEY: () => INTENT_SENSORS_KEY,
3379
+ Intent: () => Intent,
3380
+ IntentBody: () => IntentBody,
3381
+ IntentSensors: () => IntentSensors,
3382
+ SENSOR_METADATA_KEY: () => SENSOR_METADATA_KEY,
3383
+ Sensor: () => Sensor,
3384
+ TLV_FIELDS_KEY: () => TLV_FIELDS_KEY,
3385
+ TLV_VALIDATORS_KEY: () => TLV_VALIDATORS_KEY,
3386
+ TlvEnum: () => TlvEnum,
3387
+ TlvField: () => TlvField,
3388
+ TlvMinLen: () => TlvMinLen,
3389
+ TlvRange: () => TlvRange,
3390
+ TlvUtf8Pattern: () => TlvUtf8Pattern,
3391
+ TlvValidate: () => TlvValidate,
3392
+ buildDtoDecoder: () => buildDtoDecoder,
3393
+ extractDtoSchema: () => extractDtoSchema
3394
+ });
3395
+
3396
+ // src/decorators/axis-request.decorator.ts
3397
+ var import_common5 = require("@nestjs/common");
3398
+ function resolveIp(req) {
3399
+ return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.headers["x-real-ip"] || req.socket.remoteAddress || void 0;
3400
+ }
3401
+ var AxisRaw = (0, import_common5.createParamDecorator)(
3402
+ (_data, ctx) => {
3403
+ const req = ctx.switchToHttp().getRequest();
3404
+ return req.body;
3405
+ }
3406
+ );
3407
+ var AxisIp = (0, import_common5.createParamDecorator)(
3408
+ (_data, ctx) => {
3409
+ const req = ctx.switchToHttp().getRequest();
3410
+ return resolveIp(req);
3411
+ }
3412
+ );
3413
+ var AxisContext = (0, import_common5.createParamDecorator)(
3414
+ (_data, ctx) => {
3415
+ const req = ctx.switchToHttp().getRequest();
3416
+ const axisData = req.axis || {};
3417
+ return {
3418
+ raw: req.body,
3419
+ ip: resolveIp(req),
3420
+ preDecodeInput: axisData.preDecodeInput,
3421
+ frameBytesCount: axisData.frameBytesCount || 0
3422
+ };
3423
+ }
3424
+ );
3425
+ var AxisDemoPubkey = (0, import_common5.createParamDecorator)(
3426
+ (_data, ctx) => {
3427
+ if (process.env.NODE_ENV !== "development") return void 0;
3428
+ const req = ctx.switchToHttp().getRequest();
3429
+ return req.headers["x-demo-pubkey"];
3430
+ }
3431
+ );
3432
+ var AxisFrame3 = (0, import_common5.createParamDecorator)(
3433
+ (_data, ctx) => {
3434
+ const req = ctx.switchToHttp().getRequest();
3435
+ const decoded = req.axisDecoded;
3436
+ if (!decoded) {
3437
+ throw new Error(
3438
+ "@AxisFrame() requires AxisDecodeInterceptor on the route. Add @UseInterceptors(AxisDecodeInterceptor) to use this decorator."
3439
+ );
3440
+ }
3441
+ return decoded;
3442
+ }
3443
+ );
3444
+
3445
+ // src/decorators/sensor.decorator.ts
3446
+ var import_common6 = require("@nestjs/common");
3447
+ var SENSOR_METADATA_KEY = "axis:sensor";
3448
+ function Sensor(options) {
3449
+ return (0, import_common6.SetMetadata)(SENSOR_METADATA_KEY, options ?? true);
3450
+ }
3451
+
3452
+ // src/engine/index.ts
3453
+ var engine_exports = {};
3454
+ __export(engine_exports, {
3455
+ BAND: () => BAND,
3456
+ HandlerDiscoveryService: () => HandlerDiscoveryService,
3457
+ IntentRouter: () => IntentRouter,
3458
+ PRE_DECODE_BOUNDARY: () => PRE_DECODE_BOUNDARY,
3459
+ SensorDiscoveryService: () => SensorDiscoveryService,
3460
+ SensorRegistry: () => SensorRegistry,
3461
+ createObservation: () => createObservation,
3462
+ endStage: () => endStage,
3463
+ finalizeObservation: () => finalizeObservation,
3464
+ observation: () => observation_exports,
3465
+ recordSensor: () => recordSensor,
3466
+ startStage: () => startStage
3467
+ });
3468
+
3469
+ // src/engine/axis-observation.ts
3470
+ var import_crypto5 = require("crypto");
3471
+ function createObservation(transport, ip) {
3472
+ return {
3473
+ id: (0, import_crypto5.randomBytes)(16).toString("hex"),
3474
+ startMs: Date.now(),
3475
+ transport,
3476
+ ip,
3477
+ stages: [],
3478
+ sensors: [],
3479
+ facts: {}
3480
+ };
3481
+ }
3482
+ function startStage(obs, name) {
3483
+ const stage = { name, status: "ok", startMs: Date.now() };
3484
+ obs.stages.push(stage);
3485
+ return stage;
3486
+ }
3487
+ function endStage(stage, status = "ok", reason, code) {
3488
+ stage.endMs = Date.now();
3489
+ stage.durationMs = stage.endMs - stage.startMs;
3490
+ stage.status = status;
3491
+ if (reason) stage.reason = reason;
3492
+ if (code) stage.code = code;
3493
+ }
3494
+ function recordSensor(obs, name, allowed, riskScore, durationMs, reasons, code) {
3495
+ obs.sensors.push({ name, allowed, riskScore, durationMs, reasons, code });
3496
+ }
3497
+ function finalizeObservation(obs, decision, statusCode, resultCode) {
3498
+ obs.endMs = Date.now();
3499
+ obs.durationMs = obs.endMs - obs.startMs;
3500
+ obs.decision = decision;
3501
+ obs.statusCode = statusCode;
3502
+ if (resultCode) obs.resultCode = resultCode;
3503
+ }
3504
+
3505
+ // src/engine/handler-discovery.service.ts
3506
+ var import_common7 = require("@nestjs/common");
3507
+ var HandlerDiscoveryService = class {
3508
+ constructor(discovery, scanner, router) {
3509
+ this.discovery = discovery;
3510
+ this.scanner = scanner;
3511
+ this.router = router;
3512
+ this.logger = new import_common7.Logger(HandlerDiscoveryService.name);
3513
+ }
3514
+ onModuleInit() {
3515
+ const providers = this.discovery.getProviders();
3516
+ let totalIntents = 0;
3517
+ for (const wrapper of providers) {
3518
+ const { instance, metatype } = wrapper;
3519
+ if (!instance || !metatype) continue;
3520
+ const handlerMeta = Reflect.getMetadata(HANDLER_METADATA_KEY, metatype);
3521
+ if (!handlerMeta) continue;
3522
+ const handlerName = handlerMeta.intent || metatype.name;
3523
+ const proto = Object.getPrototypeOf(instance);
3524
+ const methods = this.scanner.getAllMethodNames(proto);
3525
+ let registered = 0;
3526
+ for (const methodName of methods) {
3527
+ const meta = Reflect.getMetadata(
3528
+ INTENT_METADATA_KEY,
3529
+ proto,
3530
+ methodName
3531
+ );
3532
+ if (!meta?.intent) continue;
3533
+ if (!this.router.has(meta.intent)) {
3534
+ this.router.register(
3535
+ meta.intent,
3536
+ instance[methodName].bind(instance)
3537
+ );
3538
+ registered++;
3539
+ totalIntents++;
3540
+ }
3541
+ this.router.registerIntentMeta(meta.intent, proto, methodName);
3542
+ }
3543
+ if (registered > 0) {
3544
+ this.logger.log(
3545
+ `Auto-registered ${registered} intents from ${handlerName}`
3546
+ );
3547
+ }
3548
+ }
3549
+ this.logger.log(
3550
+ `Handler discovery complete: ${totalIntents} intents auto-registered`
3551
+ );
3552
+ }
3553
+ };
3554
+ HandlerDiscoveryService = __decorateClass([
3555
+ (0, import_common7.Injectable)()
3556
+ ], HandlerDiscoveryService);
3557
+
3558
+ // src/engine/sensor-bands.ts
3559
+ var BAND = {
3560
+ /** Pre-decode: raw byte validation, geo, budget, magic */
3561
+ WIRE: 0,
3562
+ /** Post-decode: identity resolution, capsule, proof */
3563
+ IDENTITY: 40,
3564
+ /** Post-decode: authorization, signature, rate limiting */
3565
+ POLICY: 90,
3566
+ /** Post-decode: content validation, TLV, schema, files */
3567
+ CONTENT: 140,
3568
+ /** Post-decode: business logic sensors, streams, WS */
3569
+ BUSINESS: 200,
3570
+ /** Post-decode: audit, logging (always last) */
3571
+ AUDIT: 900
3572
+ };
3573
+ var PRE_DECODE_BOUNDARY = 40;
3574
+
3575
+ // src/engine/sensor-discovery.service.ts
3576
+ var import_common8 = require("@nestjs/common");
3577
+ var SensorDiscoveryService = class {
3578
+ constructor(discovery, reflector, registry) {
3579
+ this.discovery = discovery;
3580
+ this.reflector = reflector;
3581
+ this.registry = registry;
3582
+ this.logger = new import_common8.Logger(SensorDiscoveryService.name);
3583
+ }
3584
+ onApplicationBootstrap() {
3585
+ const providers = this.discovery.getProviders();
3586
+ let count = 0;
3587
+ for (const wrapper of providers) {
3588
+ const { instance } = wrapper;
3589
+ if (!instance || !instance.constructor) continue;
3590
+ const meta = this.reflector.get(
3591
+ SENSOR_METADATA_KEY,
3592
+ instance.constructor
3593
+ );
3594
+ if (!meta) continue;
3595
+ const sensor = instance;
3596
+ if (!sensor.name || sensor.order === void 0) {
3597
+ this.logger.warn(
3598
+ `@Sensor() on ${instance.constructor.name} missing name or order \u2014 skipped`
3599
+ );
3600
+ continue;
3601
+ }
3602
+ if (!sensor.phase) {
3603
+ const decoratorPhase = meta !== true ? meta.phase : void 0;
3604
+ sensor.phase = decoratorPhase ?? (sensor.order < PRE_DECODE_BOUNDARY ? "PRE_DECODE" : "POST_DECODE");
3605
+ }
3606
+ this.registry.register(sensor);
3607
+ count++;
3608
+ }
3609
+ this.logger.log(`Auto-registered ${count} sensors via @Sensor()`);
3610
+ }
3611
+ };
3612
+ SensorDiscoveryService = __decorateClass([
3613
+ (0, import_common8.Injectable)()
3614
+ ], SensorDiscoveryService);
3615
+
3616
+ // src/engine/registry/sensor.registry.ts
3617
+ var import_common9 = require("@nestjs/common");
3618
+ var SensorRegistry = class {
3619
+ constructor(configService) {
3620
+ this.configService = configService;
3621
+ this.sensors = [];
3622
+ this.logger = new import_common9.Logger(SensorRegistry.name);
3623
+ }
3624
+ /**
3625
+ * Registers a new sensor in the registry.
3626
+ *
3627
+ * Validates that:
3628
+ * - AxisSensor has a unique name
3629
+ * - AxisSensor has an order field
3630
+ * - Pre-decode sensors have order < 40
3631
+ * - Post-decode sensors have order >= 40
3632
+ *
3633
+ * @param {AxisSensor} sensor - The sensor instance to register
3634
+ * @throws Error if validation fails
3635
+ */
3636
+ register(sensor) {
3637
+ if (!sensor.name) {
3638
+ throw new Error("AxisSensor must have a name");
3639
+ }
3640
+ const enabledSensorsStr = this.configService.get("ENABLED_SENSORS");
3641
+ const disabledSensorsStr = this.configService.get("DISABLED_SENSORS");
3642
+ const enabledSensors = enabledSensorsStr ? enabledSensorsStr.split(",").map((s) => s.trim()) : null;
3643
+ const disabledSensors = disabledSensorsStr ? disabledSensorsStr.split(",").map((s) => s.trim()) : [];
3644
+ if (enabledSensors && !enabledSensors.includes(sensor.name)) {
3645
+ this.logger.log(`Skipping disabled sensor (not in ENABLED_SENSORS): ${sensor.name}`);
3646
+ return;
3647
+ }
3648
+ if (disabledSensors.includes(sensor.name)) {
3649
+ this.logger.log(`Skipping disabled sensor (in DISABLED_SENSORS): ${sensor.name}`);
3650
+ return;
3651
+ }
3652
+ if (sensor.order === void 0) {
3653
+ throw new Error(`AxisSensor "${sensor.name}" must have an order field`);
3654
+ }
3655
+ const isPreDecodeSensor = this.isPreDecodeSensor(sensor);
3656
+ const isPostDecodeSensor = this.isPostDecodeSensor(sensor);
3657
+ if (isPreDecodeSensor && sensor.order >= 40) {
3658
+ this.logger.warn(
3659
+ `AxisSensor "${sensor.name}" is marked as PRE_DECODE but has order ${sensor.order} (should be < 40)`
3660
+ );
3661
+ }
3662
+ if (isPostDecodeSensor && sensor.order < 40) {
3663
+ this.logger.warn(
3664
+ `AxisSensor "${sensor.name}" is marked as POST_DECODE but has order ${sensor.order} (should be >= 40)`
3665
+ );
3666
+ }
3667
+ this.sensors.push(sensor);
3668
+ const phaseLabel = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase || "UNKNOWN";
3669
+ this.logger.debug(
3670
+ `Registered sensor: ${sensor.name} (order: ${sensor.order}, phase: ${phaseLabel})`
3671
+ );
3672
+ }
3673
+ /**
3674
+ * Returns all registered sensors, sorted by their execution order.
3675
+ *
3676
+ * @returns {AxisSensor[]} A sorted array of sensors
3677
+ */
3678
+ list() {
3679
+ return [...this.sensors].sort(
3680
+ (a, b) => (a.order ?? 999) - (b.order ?? 999)
3681
+ );
3682
+ }
3683
+ /**
3684
+ * Returns only pre-decode sensors (order < 40).
3685
+ * These sensors run in middleware on raw bytes before frame decoding.
3686
+ *
3687
+ * @returns {AxisPreSensor[]} Pre-decode sensors sorted by order
3688
+ */
3689
+ getPreDecodeSensors() {
3690
+ return this.list().filter((s) => (s.order ?? 999) < 40);
3691
+ }
3692
+ /**
3693
+ * Returns only post-decode sensors (order >= 40).
3694
+ * These sensors run in the controller on fully decoded frames.
3695
+ *
3696
+ * @returns {AxisPostSensor[]} Post-decode sensors sorted by order
3697
+ */
3698
+ getPostDecodeSensors() {
3699
+ return this.list().filter(
3700
+ (s) => (s.order ?? 999) >= 40
3701
+ );
3702
+ }
3703
+ /**
3704
+ * Helper: Check if a sensor is a pre-decode sensor.
3705
+ *
3706
+ * @private
3707
+ * @param {AxisSensor} sensor - The sensor to check
3708
+ * @returns {boolean} True if sensor is pre-decode
3709
+ */
3710
+ isPreDecodeSensor(sensor) {
3711
+ const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
3712
+ return phase === "PRE_DECODE" || (sensor.order ?? 999) < 40;
3713
+ }
3714
+ /**
3715
+ * Helper: Check if a sensor is a post-decode sensor.
3716
+ *
3717
+ * @private
3718
+ * @param {AxisSensor} sensor - The sensor to check
3719
+ * @returns {boolean} True if sensor is post-decode
3720
+ */
3721
+ isPostDecodeSensor(sensor) {
3722
+ const phase = typeof sensor.phase === "string" ? sensor.phase : sensor.phase?.phase;
3723
+ return phase === "POST_DECODE" || (sensor.order ?? 999) >= 40;
3724
+ }
3725
+ /**
3726
+ * Returns sensor count by phase.
3727
+ * Useful for diagnostics and monitoring.
3728
+ *
3729
+ * @returns {{preDecodeCount: number, postDecodeCount: number}}
3730
+ */
3731
+ getSensorCountByPhase() {
3732
+ return {
3733
+ preDecodeCount: this.getPreDecodeSensors().length,
3734
+ postDecodeCount: this.getPostDecodeSensors().length
3735
+ };
3736
+ }
3737
+ /**
3738
+ * Clears all registered sensors.
3739
+ * Useful for testing.
3740
+ *
3741
+ * @internal
3742
+ */
3743
+ clear() {
3744
+ this.sensors = [];
3745
+ }
3746
+ };
3747
+ SensorRegistry = __decorateClass([
3748
+ (0, import_common9.Injectable)()
3749
+ ], SensorRegistry);
3750
+
3751
+ // src/engine/observation/index.ts
3752
+ var observation_exports = {};
3753
+ __export(observation_exports, {
3754
+ buildQueueMessage: () => buildQueueMessage,
3755
+ buildUnsignedWitness: () => buildUnsignedWitness,
3756
+ canonicalizeObservation: () => canonicalizeObservation,
3757
+ decodeQueueMessage: () => decodeQueueMessage,
3758
+ encodeQueueMessage: () => encodeQueueMessage,
3759
+ hashObservation: () => hashObservation,
3760
+ parseAutoClaimEntries: () => parseAutoClaimEntries,
3761
+ parseStreamEntries: () => parseStreamEntries,
3762
+ stableJsonStringify: () => stableJsonStringify,
3763
+ verifyResponse: () => verifyResponse
3764
+ });
3765
+
3766
+ // src/loom/index.ts
3767
+ var loom_exports = {};
3768
+ __export(loom_exports, {
3769
+ PROOF_LOOM: () => import_axis_protocol2.PROOF_LOOM,
3770
+ TLV_PRESENCE_ID: () => import_axis_protocol2.TLV_LOOM_PRESENCE_ID,
3771
+ TLV_THREAD_HASH: () => import_axis_protocol2.TLV_LOOM_THREAD_HASH,
3772
+ TLV_WRIT: () => import_axis_protocol2.TLV_LOOM_WRIT,
3773
+ canonicalizeGrant: () => canonicalizeGrant,
3774
+ canonicalizeWrit: () => canonicalizeWrit,
3775
+ deriveAnchorReflection: () => deriveAnchorReflection
3776
+ });
3777
+
3778
+ // src/loom/loom.types.ts
3779
+ function deriveAnchorReflection(softid, context = "openlogs", scope = "loom") {
3780
+ return `ar:${context}:${scope}:${softid}`;
3781
+ }
3782
+ function canonicalizeWrit(writ) {
3783
+ const ordered = {
3784
+ head: { tid: writ.head.tid, seq: writ.head.seq },
3785
+ body: {
3786
+ who: writ.body.who,
3787
+ act: writ.body.act,
3788
+ res: writ.body.res,
3789
+ law: writ.body.law
3790
+ },
3791
+ meta: { iat: writ.meta.iat, exp: writ.meta.exp, prev: writ.meta.prev }
3792
+ };
3793
+ return JSON.stringify(ordered);
3794
+ }
3795
+ function canonicalizeGrant(grant) {
3796
+ const ordered = {
3797
+ grant_id: grant.grant_id,
3798
+ issuer: grant.issuer,
3799
+ subject: grant.subject,
3800
+ grant_type: grant.grant_type,
3801
+ caps: grant.caps,
3802
+ meta: grant.meta
3803
+ };
3804
+ return JSON.stringify(ordered);
3805
+ }
3806
+
3807
+ // src/schemas/index.ts
3808
+ var schemas_exports = {};
3809
+ __export(schemas_exports, {
3810
+ AccessProfileZ: () => AccessProfileZ,
3811
+ AxisContextZ: () => AxisContextZ,
3812
+ AxisErrorZ: () => AxisErrorZ,
3813
+ BodyBudgetInputZ: () => BodyBudgetInputZ,
3814
+ BodyBudgetPolicyZ: () => BodyBudgetPolicyZ,
3815
+ BodyProfile: () => BodyProfile2,
3816
+ BodyProfileValidator: () => BodyProfileValidator,
3817
+ BodyProfileZ: () => BodyProfileZ,
3818
+ CapsuleClaimsZ: () => CapsuleClaimsZ,
3819
+ CapsuleValidationResultZ: () => CapsuleValidationResultZ,
3820
+ CapsuleVerifyResultZ: () => CapsuleVerifyResultZ,
3821
+ CapsuleVerifySensorInputZ: () => CapsuleVerifySensorInputZ,
3822
+ CapsuleZ: () => CapsuleZ,
3823
+ ChunkHashInputZ: () => ChunkHashInputZ,
3824
+ CountryBlockDecisionZ: () => CountryBlockDecisionZ,
3825
+ CountryBlockSensorInputZ: () => CountryBlockSensorInputZ,
3826
+ EntropySensorInputZ: () => EntropySensorInputZ,
3827
+ ExecutionMetricsZ: () => ExecutionMetricsZ,
3828
+ IPReputationInputZ: () => IPReputationInputZ,
3829
+ IPReputationZ: () => IPReputationZ,
3830
+ IntentPolicyDecisionZ: () => IntentPolicyDecisionZ,
3831
+ IntentPolicySensorInputZ: () => IntentPolicySensorInputZ,
3832
+ IntentPolicyZ: () => IntentPolicyZ,
3833
+ IntentSchemaZ: () => IntentSchemaZ,
3834
+ PassportZ: () => PassportZ,
3835
+ ProofKindZ: () => ProofKindZ,
3836
+ ProofPresenceInputZ: () => ProofPresenceInputZ,
3837
+ ProofType: () => ProofType2,
3838
+ ProtocolStrictInputZ: () => ProtocolStrictInputZ,
3839
+ RateLimitConfigZ: () => RateLimitConfigZ,
3840
+ RateLimitInputZ: () => RateLimitInputZ,
3841
+ RateLimitProfileZ: () => RateLimitProfileZ,
3842
+ ScanBurstDecisionZ: () => ScanBurstDecisionZ,
3843
+ ScanBurstSensorInputZ: () => ScanBurstSensorInputZ,
3844
+ SchemaFieldKindZ: () => SchemaFieldKindZ,
3845
+ SchemaFieldZ: () => SchemaFieldZ,
3846
+ ScopeZ: () => ScopeZ,
3847
+ SensitivityLevelZ: () => SensitivityLevelZ,
3848
+ SensorChainInputZ: () => SensorChainInputZ,
3849
+ SensorDecisionWithMetadataZ: () => SensorDecisionWithMetadataZ,
3850
+ SensorDecisionZ: () => SensorDecisionZ,
3851
+ SensorResultZ: () => SensorResultZ,
3852
+ UploadSessionZ: () => UploadSessionZ,
3853
+ UploadStatusZ: () => UploadStatusZ,
3854
+ WsHandshakeDecisionZ: () => WsHandshakeDecisionZ,
3855
+ WsHandshakeInputZ: () => WsHandshakeInputZ
3856
+ });
3857
+
3858
+ // src/schemas/axis-schemas.ts
3859
+ var z2 = __toESM(require("zod"));
3860
+ var SensorDecisionZ = z2.union([
3861
+ z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
3862
+ z2.object({
3863
+ action: z2.literal("DENY"),
3864
+ code: z2.string(),
3865
+ reason: z2.string().optional(),
3866
+ meta: z2.any().optional()
3867
+ })
3868
+ ]);
3869
+ var SensorDecisionWithMetadataZ = z2.union([
3870
+ z2.object({ action: z2.literal("ALLOW"), meta: z2.any().optional() }),
3871
+ z2.object({
3872
+ action: z2.literal("DENY"),
3873
+ code: z2.string(),
3874
+ reason: z2.string().optional(),
3875
+ retryAfterMs: z2.number().int().positive().optional(),
3876
+ meta: z2.any().optional()
3877
+ })
3878
+ ]);
3879
+ var CountryBlockSensorInputZ = z2.object({
3880
+ ip: z2.string().min(1),
3881
+ country: z2.string().length(2).toUpperCase().optional()
3882
+ });
3883
+ var CountryBlockDecisionZ = SensorDecisionZ;
3884
+ var ScanBurstSensorInputZ = z2.object({
3885
+ ip: z2.string().min(1),
3886
+ isFailure: z2.boolean().optional()
3887
+ });
3888
+ var ScanBurstDecisionZ = SensorDecisionWithMetadataZ;
3889
+ var ProofKindZ = z2.enum([
3890
+ "NONE",
3891
+ "CAPSULE",
3892
+ "PASSPORT",
3893
+ "MTLS",
3894
+ "JWT"
3895
+ ]);
3896
+ var AccessProfileZ = z2.enum(["PUBLIC", "PARTNER", "INTERNAL", "NODE"]);
3897
+ var ProofPresenceInputZ = z2.object({
3898
+ profile: AccessProfileZ,
3899
+ visibility: z2.enum(["PUBLIC", "GUARDED"]),
3900
+ requiredProof: z2.array(ProofKindZ).min(1),
3901
+ hasCapsule: z2.boolean(),
3902
+ hasPassportSignature: z2.boolean(),
3903
+ intent: z2.string().min(1)
3904
+ });
3905
+ var SensitivityLevelZ = z2.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]);
3906
+ var IntentPolicyZ = z2.object({
3907
+ intent: z2.string().min(1),
3908
+ sensitivity: SensitivityLevelZ,
3909
+ maxFrameBytes: z2.number().int().positive(),
3910
+ maxHeaderBytes: z2.number().int().positive(),
3911
+ maxBodyBytes: z2.number().int().positive(),
3912
+ maxSigBytes: z2.number().int().positive().optional(),
3913
+ rateLimitPerMinute: z2.number().int().positive().optional(),
3914
+ rateLimitPerHour: z2.number().int().positive().optional(),
3915
+ requiresSignature: z2.boolean(),
3916
+ requiresCapsule: z2.boolean(),
3917
+ timeoutMs: z2.number().int().positive()
3918
+ });
3919
+ var IntentPolicySensorInputZ = z2.object({
3920
+ frame: AxisFrameZ,
3921
+ intent: z2.string().min(1),
3922
+ rawFrameSize: z2.number().int().positive()
3923
+ });
3924
+ var IntentPolicyDecisionZ = z2.union([
3925
+ z2.object({
3926
+ action: z2.literal("ALLOW"),
3927
+ policy: IntentPolicyZ
3928
+ }),
3929
+ z2.object({
3930
+ action: z2.literal("DENY"),
3931
+ reason: z2.string()
3932
+ })
3933
+ ]);
3934
+ var CapsuleClaimsZ = z2.object({
3935
+ capsuleId: z2.string().min(8),
3936
+ allowIntents: z2.array(z2.string()).min(1),
3937
+ limits: z2.object({
3938
+ maxBodyBytes: z2.number().int().positive().optional()
3939
+ }).optional(),
3940
+ scopes: z2.record(z2.string(), z2.any()).optional()
3941
+ });
3942
+ var CapsuleZ = z2.object({
3943
+ id: z2.string(),
3944
+ claims: CapsuleClaimsZ,
3945
+ issuedAt: z2.number().int(),
3946
+ expiresAt: z2.number().int(),
3947
+ tier: z2.enum(["FREE", "STANDARD", "PREMIUM"])
3948
+ });
3949
+ var CapsuleValidationResultZ = z2.object({
3950
+ valid: z2.boolean(),
3951
+ capsule: CapsuleZ.optional(),
3952
+ reason: z2.string().optional(),
3953
+ requiresStepUp: z2.boolean().optional()
3954
+ });
3955
+ var CapsuleVerifySensorInputZ = z2.object({
3956
+ headers: z2.map(
3957
+ z2.number(),
3958
+ z2.custom((v) => v instanceof Uint8Array)
3959
+ ),
3960
+ intent: z2.string().min(1),
3961
+ ctx: z2.any()
3962
+ // AxisContext - avoid circular dependency
3963
+ });
3964
+ var CapsuleVerifyResultZ = z2.object({
3965
+ ok: z2.literal(true),
3966
+ capsule: CapsuleZ
3967
+ });
3968
+ var RateLimitProfileZ = z2.enum([
3969
+ "PUBLIC",
3970
+ "PARTNER",
3971
+ "INTERNAL",
3972
+ "NODE"
3973
+ ]);
3974
+ var RateLimitInputZ = z2.object({
3975
+ ip: z2.string().min(1),
3976
+ userAgent: z2.string().optional(),
3977
+ actorId: z2.string().optional(),
3978
+ capsuleId: z2.string().optional(),
3979
+ intent: z2.string().min(1),
3980
+ profile: RateLimitProfileZ
3981
+ });
3982
+ var RateLimitConfigZ = z2.object({
3983
+ windowSec: z2.number().int().positive(),
3984
+ max: z2.number().int().positive(),
3985
+ key: z2.enum(["ip_fingerprint", "actor_capsule"])
3986
+ });
3987
+ var SensorResultZ = z2.object({
3988
+ ok: z2.literal(true)
3989
+ });
3990
+ var PassportZ = z2.object({
3991
+ id: z2.string(),
3992
+ public_key: z2.custom((v) => Buffer.isBuffer(v)),
3993
+ status: z2.enum(["ACTIVE", "REVOKED", "EXPIRED", "PENDING"]),
3994
+ issuedAt: z2.number().int(),
3995
+ expiresAt: z2.number().int().optional()
3996
+ });
3997
+ var ExecutionMetricsZ = z2.object({
3998
+ dbWrites: z2.number().int(),
3999
+ dbReads: z2.number().int(),
4000
+ externalCalls: z2.number().int(),
4001
+ elapsedMs: z2.number().int().optional()
4002
+ });
4003
+ var SensorChainInputZ = z2.object({
4004
+ ip: z2.string().min(1),
4005
+ path: z2.string().min(1),
4006
+ contentLength: z2.number().int().nonnegative(),
4007
+ peek: z2.instanceof(Uint8Array),
4008
+ country: z2.string().optional()
4009
+ });
4010
+ var EntropySensorInputZ = z2.object({
4011
+ pid: z2.custom((v) => Buffer.isBuffer(v)).optional(),
4012
+ nonce: z2.custom((v) => Buffer.isBuffer(v)).optional(),
4013
+ ip: z2.string().min(1)
4014
+ });
4015
+ var ProtocolStrictInputZ = z2.object({
4016
+ rawBytes: z2.union([z2.custom((v) => Buffer.isBuffer(v)), z2.instanceof(Uint8Array)]).optional(),
4017
+ ip: z2.string().min(1),
4018
+ path: z2.string().min(1),
4019
+ contentLength: z2.number().int().nonnegative(),
4020
+ peek: z2.instanceof(Uint8Array),
4021
+ country: z2.string().optional(),
4022
+ contentType: z2.string().optional()
4023
+ });
4024
+ var SchemaFieldKindZ = z2.enum([
4025
+ "utf8",
4026
+ "u64",
4027
+ "bytes",
4028
+ "bytes16",
4029
+ "bool",
4030
+ "obj",
4031
+ "arr"
4032
+ ]);
4033
+ var ScopeZ = z2.enum(["header", "body"]);
4034
+ var SchemaFieldZ = z2.object({
4035
+ name: z2.string().min(1),
4036
+ tlv: z2.number().int().positive(),
4037
+ kind: SchemaFieldKindZ,
4038
+ required: z2.boolean().optional(),
4039
+ maxLen: z2.number().int().positive().optional(),
4040
+ max: z2.string().optional(),
4041
+ scope: ScopeZ.optional()
4042
+ });
4043
+ var BodyProfileZ = z2.enum(["TLV_MAP", "RAW", "TLV_OBJ", "TLV_ARR"]);
4044
+ var IntentSchemaZ = z2.object({
4045
+ intent: z2.string().min(1),
4046
+ version: z2.number().int().positive(),
4047
+ bodyProfile: BodyProfileZ,
4048
+ fields: z2.array(SchemaFieldZ).min(1)
4049
+ });
4050
+ var WsHandshakeInputZ = z2.object({
4051
+ clientId: z2.string().min(1),
4052
+ isWs: z2.boolean(),
4053
+ ip: z2.string().min(1)
4054
+ });
4055
+ var WsHandshakeDecisionZ = z2.union([
4056
+ z2.object({ action: z2.literal("ALLOW") }),
4057
+ z2.object({ action: z2.literal("DENY"), code: z2.string() })
4058
+ ]);
4059
+ var IPReputationInputZ = z2.object({
4060
+ ip: z2.string().min(1)
4061
+ });
4062
+ var IPReputationZ = z2.object({
4063
+ score: z2.number().min(-100).max(100),
4064
+ lastUpdated: z2.number().int(),
4065
+ totalRequests: z2.number().int().nonnegative(),
4066
+ failedRequests: z2.number().int().nonnegative(),
4067
+ blockedRequests: z2.number().int().nonnegative(),
4068
+ tags: z2.array(z2.string())
4069
+ });
4070
+ var UploadStatusZ = z2.enum([
4071
+ "INIT",
4072
+ "UPLOADING",
4073
+ "FINALIZING",
4074
+ "DONE",
4075
+ "ABORTED"
4076
+ ]);
4077
+ var UploadSessionZ = z2.object({
4078
+ uploadIdHex: z2.string().min(1),
4079
+ fileName: z2.string().min(1),
4080
+ totalSize: z2.number().int().positive(),
4081
+ chunkSize: z2.number().int().positive(),
4082
+ totalChunks: z2.number().int().positive(),
4083
+ receivedCount: z2.number().int().nonnegative(),
4084
+ status: UploadStatusZ
4085
+ });
4086
+ var BodyBudgetInputZ = z2.object({
4087
+ intent: z2.string().min(1),
4088
+ headerLen: z2.number().int().nonnegative(),
4089
+ bodyLen: z2.number().int().nonnegative()
4090
+ });
4091
+ var BodyBudgetPolicyZ = z2.object({
4092
+ maxHeaderBytes: z2.number().int().positive(),
4093
+ maxBodyBytes: z2.number().int().positive()
4094
+ });
4095
+ var ChunkHashInputZ = z2.object({
4096
+ headerTLVs: z2.any(),
4097
+ // Map<number, Uint8Array> - flexible validation for compatibility
4098
+ bodyBytes: z2.any(),
4099
+ // Uint8Array - flexible validation for compatibility
4100
+ intent: z2.string().min(1)
4101
+ });
4102
+ var ProofType2 = /* @__PURE__ */ ((ProofType3) => {
4103
+ ProofType3[ProofType3["CAPSULE"] = 1] = "CAPSULE";
4104
+ ProofType3[ProofType3["JWT"] = 2] = "JWT";
4105
+ ProofType3[ProofType3["MTLS_ID"] = 3] = "MTLS_ID";
4106
+ ProofType3[ProofType3["DEVICE_SE"] = 4] = "DEVICE_SE";
4107
+ ProofType3[ProofType3["WITNESS_SIG"] = 5] = "WITNESS_SIG";
4108
+ return ProofType3;
4109
+ })(ProofType2 || {});
4110
+ var AxisContextZ = z2.object({
4111
+ pid: z2.custom((v) => Buffer.isBuffer(v)),
4112
+ // Process ID
4113
+ ts: z2.bigint(),
4114
+ // Timestamp
4115
+ intent: z2.string().min(1),
4116
+ actorId: z2.custom((v) => Buffer.isBuffer(v)),
4117
+ proofType: z2.enum(ProofType2),
4118
+ proofRef: z2.custom((v) => Buffer.isBuffer(v)),
4119
+ nonce: z2.custom((v) => Buffer.isBuffer(v)),
4120
+ ip: z2.string().min(1),
4121
+ nodeCertHash: z2.string().optional(),
4122
+ capsule: CapsuleZ.optional(),
4123
+ passport: PassportZ.optional(),
4124
+ meter: z2.any().optional()
4125
+ // ExecutionMeter instance - any to avoid circular dependency and allow class instance
4126
+ });
4127
+ var AxisErrorZ = z2.object({
4128
+ code: z2.string(),
4129
+ message: z2.string(),
4130
+ httpStatus: z2.number().int()
4131
+ });
4132
+
4133
+ // src/schemas/body-profile.validator.ts
4134
+ var import_common10 = require("@nestjs/common");
4135
+ var BodyProfile2 = /* @__PURE__ */ ((BodyProfile3) => {
4136
+ BodyProfile3[BodyProfile3["RAW"] = 0] = "RAW";
4137
+ BodyProfile3[BodyProfile3["TLV_MAP"] = 1] = "TLV_MAP";
4138
+ BodyProfile3[BodyProfile3["OBJ"] = 2] = "OBJ";
4139
+ BodyProfile3[BodyProfile3["ARR"] = 3] = "ARR";
4140
+ return BodyProfile3;
4141
+ })(BodyProfile2 || {});
4142
+ var BodyProfileValidator = class {
4143
+ constructor() {
4144
+ this.logger = new import_common10.Logger(BodyProfileValidator.name);
4145
+ }
4146
+ /**
4147
+ * Validate body matches declared profile
4148
+ */
4149
+ validate(body, profile) {
4150
+ switch (profile) {
4151
+ case 0 /* RAW */:
4152
+ return this.validateRaw(body);
4153
+ case 1 /* TLV_MAP */:
4154
+ return this.validateTlvMap(body);
4155
+ case 2 /* OBJ */:
4156
+ return this.validateObj(body);
4157
+ case 3 /* ARR */:
4158
+ return this.validateArr(body);
4159
+ default:
4160
+ return {
4161
+ valid: false,
4162
+ error: `Unknown body profile: ${profile}`,
4163
+ profile
4164
+ };
4165
+ }
4166
+ }
4167
+ /**
4168
+ * RAW profile - no validation, any bytes accepted
4169
+ */
4170
+ validateRaw(body) {
4171
+ return {
4172
+ valid: true,
4173
+ profile: 0 /* RAW */
4174
+ };
4175
+ }
4176
+ /**
4177
+ * TLV_MAP profile - flat TLV list (no nested structures)
4178
+ */
4179
+ validateTlvMap(body) {
4180
+ try {
4181
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
4182
+ for (const tlv2 of tlvs) {
4183
+ if (tlv2.type === 254 || tlv2.type === 255) {
4184
+ return {
4185
+ valid: false,
4186
+ error: "TLV_MAP profile cannot contain nested OBJ/ARR types",
4187
+ profile: 1 /* TLV_MAP */
4188
+ };
4189
+ }
4190
+ }
4191
+ return {
4192
+ valid: true,
4193
+ profile: 1 /* TLV_MAP */
4194
+ };
4195
+ } catch (error) {
4196
+ const message = error instanceof Error ? error.message : "Unknown error";
4197
+ return {
4198
+ valid: false,
4199
+ error: `TLV_MAP decode failed: ${message}`,
4200
+ profile: 1 /* TLV_MAP */
4201
+ };
4202
+ }
4203
+ }
4204
+ /**
4205
+ * OBJ profile - must be valid nested object
4206
+ */
4207
+ validateObj(body) {
4208
+ try {
4209
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
4210
+ const hasObj = tlvs.some((t) => t.type === 254);
4211
+ if (!hasObj && tlvs.length > 0) {
4212
+ return {
4213
+ valid: false,
4214
+ error: "OBJ profile must contain OBJ type (254)",
4215
+ profile: 2 /* OBJ */
4216
+ };
4217
+ }
4218
+ return {
4219
+ valid: true,
4220
+ profile: 2 /* OBJ */
4221
+ };
4222
+ } catch (error) {
4223
+ const message = error instanceof Error ? error.message : "Unknown error";
4224
+ return {
4225
+ valid: false,
4226
+ error: `OBJ decode failed: ${message}`,
4227
+ profile: 2 /* OBJ */
4228
+ };
4229
+ }
4230
+ }
4231
+ /**
4232
+ * ARR profile - must be valid array
4233
+ */
4234
+ validateArr(body) {
4235
+ try {
4236
+ const tlvs = (0, import_axis_protocol.decodeTLVsList)(body);
4237
+ const hasArr = tlvs.some((t) => t.type === 255);
4238
+ if (!hasArr && tlvs.length > 0) {
4239
+ return {
4240
+ valid: false,
4241
+ error: "ARR profile must contain ARR type (255)",
4242
+ profile: 3 /* ARR */
4243
+ };
4244
+ }
4245
+ return {
4246
+ valid: true,
4247
+ profile: 3 /* ARR */
4248
+ };
4249
+ } catch (error) {
4250
+ const message = error instanceof Error ? error.message : "Unknown error";
4251
+ return {
4252
+ valid: false,
4253
+ error: `ARR decode failed: ${message}`,
4254
+ profile: 3 /* ARR */
4255
+ };
4256
+ }
4257
+ }
4258
+ };
4259
+ BodyProfileValidator = __decorateClass([
4260
+ (0, import_common10.Injectable)()
4261
+ ], BodyProfileValidator);
4262
+
4263
+ // src/security/index.ts
4264
+ var security_exports = {};
4265
+ __export(security_exports, {
4266
+ CAPABILITIES: () => CAPABILITIES,
4267
+ INTENT_REQUIREMENTS: () => INTENT_REQUIREMENTS,
4268
+ PROOF_CAPABILITIES: () => PROOF_CAPABILITIES,
4269
+ canAccessResource: () => canAccessResource,
4270
+ hasScope: () => hasScope,
4271
+ parseScope: () => parseScope
4272
+ });
4273
+
4274
+ // src/sensors/index.ts
4275
+ var sensors_exports = {};
4276
+ __export(sensors_exports, {
4277
+ AccessProfileResolverSensor: () => AccessProfileResolverSensor,
4278
+ BodyBudgetSensor: () => BodyBudgetSensor,
4279
+ CapabilityEnforcementSensor: () => CapabilityEnforcementSensor,
4280
+ ChunkHashSensor: () => ChunkHashSensor,
4281
+ EntropySensor: () => EntropySensor,
4282
+ ExecutionTimeoutSensor: () => ExecutionTimeoutSensor,
4283
+ FrameBudgetSensor: () => FrameBudgetSensor,
4284
+ FrameHeaderSanitySensor: () => FrameHeaderSanitySensor,
4285
+ HeaderTLVLimitSensor: () => HeaderTLVLimitSensor,
4286
+ IntentAllowlistSensor: () => IntentAllowlistSensor,
4287
+ IntentRegistrySensor: () => IntentRegistrySensor,
4288
+ ProofPresenceSensor: () => ProofPresenceSensor,
4289
+ ProtocolStrictSensor: () => ProtocolStrictSensor,
4290
+ ReceiptPolicySensor: () => ReceiptPolicySensor,
4291
+ SchemaValidationSensor: () => SchemaValidationSensor,
4292
+ StreamScopeSensor: () => StreamScopeSensor,
4293
+ TLVParseSensor: () => TLVParseSensor,
4294
+ VarintHardeningSensor: () => VarintHardeningSensor
4295
+ });
4296
+
4297
+ // src/sensors/access-profile-resolver.sensor.ts
4298
+ var import_common11 = require("@nestjs/common");
4299
+ var AccessProfileResolverSensor = class {
4300
+ constructor() {
4301
+ /** AxisSensor identifier */
4302
+ this.name = "AccessProfileResolverSensor";
4303
+ /**
4304
+ * Execution order - runs early to establish the access profile
4305
+ * for downstream sensors.
4306
+ */
4307
+ this.order = BAND.IDENTITY + 10;
4308
+ }
4309
+ supports() {
4310
+ return true;
4311
+ }
4312
+ async run(input) {
4313
+ const hasCapsule = !!input.metadata?.capsuleId;
4314
+ const hasPassport = !!input.metadata?.passportSig;
4315
+ const hasMTLS = !!input.metadata?.mtlsId;
4316
+ const profile = hasCapsule || hasPassport || hasMTLS ? "GUARDED" : "PUBLIC";
4317
+ if (!input.metadata) input.metadata = {};
4318
+ input.metadata.profile = profile;
4319
+ return { action: "ALLOW" };
4320
+ }
4321
+ };
4322
+ AccessProfileResolverSensor = __decorateClass([
4323
+ Sensor(),
4324
+ (0, import_common11.Injectable)()
4325
+ ], AccessProfileResolverSensor);
4326
+
4327
+ // src/sensors/body-budget.sensor.ts
4328
+ var import_common12 = require("@nestjs/common");
4329
+ var BodyBudgetSensor = class {
4330
+ constructor() {
4331
+ /** AxisSensor identifier */
4332
+ this.name = "BodyBudgetSensor";
4333
+ /**
4334
+ * Execution order - after authentication
4335
+ *
4336
+ * Order 150 ensures:
4337
+ * - Authentication complete
4338
+ * - Runs before full body read
4339
+ * - Before schema validation (170)
4340
+ */
4341
+ this.order = BAND.CONTENT + 10;
4342
+ }
4343
+ /**
4344
+ * Determines if this sensor should process the given input.
4345
+ *
4346
+ * Requires at least 8 bytes of peeked data to read headers.
4347
+ *
4348
+ * @param {SensorInput} input - Incoming request
4349
+ * @returns {boolean} True if sufficient peek data available
4350
+ */
4351
+ supports(input) {
4352
+ return !!input.peek && input.peek.length >= 8;
4353
+ }
4354
+ /**
4355
+ * Validates header and body lengths against configured limits.
4356
+ *
4357
+ * **Frame Parsing:**
4358
+ * - Skip magic (5 bytes)
4359
+ * - Skip version (1 byte)
4360
+ * - Skip flags (1 byte)
4361
+ * - Read HDR_LEN varint
4362
+ * - Read BODY_LEN varint
4363
+ * - Compare against MAX_HDR_LEN and MAX_BODY_LEN
4364
+ *
4365
+ * @param {SensorInput} input - Request with peek data
4366
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on size limits
4367
+ */
4368
+ async run(input) {
4369
+ const { peek } = input;
4370
+ if (!peek || peek.length < 8) {
4371
+ return { action: "ALLOW" };
4372
+ }
4373
+ try {
4374
+ let offset = 5;
4375
+ offset += 1;
4376
+ offset += 1;
4377
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
4378
+ offset += hdrBytes;
4379
+ const { value: bodyLen } = (0, import_axis_protocol3.decodeVarint)(peek, offset);
4380
+ if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
4381
+ return {
4382
+ action: "DENY",
4383
+ code: "HEADER_TOO_LARGE",
4384
+ reason: `Header size ${hdrLen} exceeds limit ${import_axis_protocol2.MAX_HDR_LEN}`
4385
+ };
4386
+ }
4387
+ if (bodyLen > import_axis_protocol2.MAX_BODY_LEN) {
4388
+ return {
4389
+ action: "DENY",
4390
+ code: "BODY_TOO_LARGE",
4391
+ reason: `Body size ${bodyLen} exceeds limit ${import_axis_protocol2.MAX_BODY_LEN}`
4392
+ };
4393
+ }
4394
+ return { action: "ALLOW" };
4395
+ } catch (e) {
4396
+ return { action: "ALLOW" };
4397
+ }
4398
+ }
4399
+ };
4400
+ BodyBudgetSensor = __decorateClass([
4401
+ Sensor(),
4402
+ (0, import_common12.Injectable)()
4403
+ ], BodyBudgetSensor);
4404
+
4405
+ // src/sensors/capability-enforcement.sensor.ts
4406
+ var import_common13 = require("@nestjs/common");
4407
+ var CapabilityEnforcementSensor = class {
4408
+ constructor() {
4409
+ this.logger = new import_common13.Logger(CapabilityEnforcementSensor.name);
4410
+ /** AxisSensor identifier for logging and registry */
4411
+ this.name = "CapabilityEnforcementSensor";
4412
+ /**
4413
+ * Execution order - runs after authentication
4414
+ *
4415
+ * Order 100 ensures:
4416
+ * - Capsule is verified (CapsuleVerifySensor @ 80)
4417
+ * - Signature is verified (SigVerifySensor @ 90)
4418
+ * - We know the proof type for capability lookup
4419
+ */
4420
+ this.order = BAND.POLICY + 10;
4421
+ }
4422
+ /**
4423
+ * Determines if this sensor should process the given input.
4424
+ *
4425
+ * Only activates when an intent is present.
4426
+ *
4427
+ * @param {SensorInput} input - Incoming AXIS request
4428
+ * @returns {boolean} True if intent is present
4429
+ */
4430
+ supports(input) {
4431
+ return !!input.intent;
4432
+ }
4433
+ /**
4434
+ * Enforces capability requirements for the requested intent.
4435
+ *
4436
+ * **Processing Flow:**
4437
+ * 1. Extract proof type from packet (default: 0/NONE)
4438
+ * 2. Look up capabilities granted by this proof type
4439
+ * 3. Look up capabilities required by the intent
4440
+ * 4. If no requirements, ALLOW
4441
+ * 5. Check if all required capabilities are granted
4442
+ * 6. If missing capabilities, DENY with details
4443
+ * 7. Otherwise, ALLOW
4444
+ *
4445
+ * @param {SensorInput} input - Request with intent and packet
4446
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on capabilities
4447
+ */
4448
+ async run(input) {
4449
+ const { intent, packet } = input;
4450
+ if (!intent) {
4451
+ return { action: "ALLOW" };
4452
+ }
4453
+ const proofType = packet?.proofType ?? 0;
4454
+ const grantedCapabilities = PROOF_CAPABILITIES[proofType] || [];
4455
+ const requiredCapabilities = this.getRequiredCapabilities(intent);
4456
+ if (requiredCapabilities.length === 0) {
4457
+ return { action: "ALLOW" };
4458
+ }
4459
+ const missingCapabilities = requiredCapabilities.filter(
4460
+ (cap) => !grantedCapabilities.includes(cap)
4461
+ );
4462
+ if (missingCapabilities.length > 0) {
4463
+ this.logger.warn(
4464
+ `Capability denied for ${intent}: missing ${missingCapabilities.join(", ")} (has: ${grantedCapabilities.join(", ")})`
4465
+ );
4466
+ return {
4467
+ action: "DENY",
4468
+ code: "CAPABILITY_DENIED",
4469
+ reason: `Missing capabilities: ${missingCapabilities.join(", ")}`
4470
+ };
4471
+ }
4472
+ return { action: "ALLOW" };
4473
+ }
4474
+ /**
4475
+ * Gets required capabilities for an intent.
4476
+ *
4477
+ * **Lookup Strategy:**
4478
+ * 1. Check for exact intent match
4479
+ * 2. Check for prefix pattern match (*.suffix)
4480
+ * 3. Default to 'execute' for unknown intents
4481
+ *
4482
+ * @private
4483
+ * @param {string} intent - Intent name to look up
4484
+ * @returns {Capability[]} Array of required capabilities
4485
+ */
4486
+ getRequiredCapabilities(intent) {
4487
+ if (INTENT_REQUIREMENTS[intent]) {
4488
+ return INTENT_REQUIREMENTS[intent];
4489
+ }
4490
+ for (const [pattern, caps] of Object.entries(INTENT_REQUIREMENTS)) {
4491
+ if (pattern.endsWith(".*")) {
4492
+ const prefix = pattern.slice(0, -1);
4493
+ if (intent.startsWith(prefix)) {
4494
+ return caps;
4495
+ }
4496
+ }
4497
+ }
4498
+ return ["execute"];
4499
+ }
4500
+ };
4501
+ CapabilityEnforcementSensor = __decorateClass([
4502
+ Sensor(),
4503
+ (0, import_common13.Injectable)()
4504
+ ], CapabilityEnforcementSensor);
4505
+
4506
+ // src/sensors/chunk-hash.sensor.ts
4507
+ var import_common14 = require("@nestjs/common");
4508
+ var import_crypto6 = require("crypto");
4509
+ var ChunkHashSensor = class {
4510
+ constructor() {
4511
+ /** Sensor identifier */
4512
+ this.name = "ChunkHashSensor";
4513
+ /**
4514
+ * Execution order - after session validation
4515
+ *
4516
+ * Order 190 ensures:
4517
+ * - Session validated (180)
4518
+ * - Chunk parameters verified
4519
+ * - Hash check before storage
4520
+ */
4521
+ this.order = BAND.CONTENT + 50;
4522
+ }
4523
+ /**
4524
+ * Determines if this sensor should process the given input.
4525
+ *
4526
+ * Only processes file.chunk intents.
4527
+ *
4528
+ * @param {SensorInput} input - Incoming request
4529
+ * @returns {boolean} True if intent is 'file.chunk'
4530
+ */
4531
+ supports(input) {
4532
+ return input.intent === "file.chunk";
4533
+ }
4534
+ /**
4535
+ * Validates chunk data against declared SHA-256 hash.
4536
+ *
4537
+ * **Processing Flow:**
4538
+ * 1. Check for required headerTLVs and body
4539
+ * 2. Extract expected hash from TLV 73
4540
+ * 3. Verify hash is exactly 32 bytes
4541
+ * 4. Compute SHA-256 of body
4542
+ * 5. Compare bytes (timing-safe)
4543
+ * 6. DENY on mismatch
4544
+ *
4545
+ * @param {SensorInput} input - Request with chunk body
4546
+ * @returns {Promise<SensorDecision>} ALLOW if hash matches, DENY otherwise
4547
+ */
4548
+ async run(input) {
4549
+ const headerTLVs = input.headerTLVs;
4550
+ const bodyBytes = input.body;
4551
+ if (!headerTLVs || !bodyBytes) {
4552
+ return {
4553
+ action: "DENY",
4554
+ code: "SENSOR_INVALID_INPUT",
4555
+ reason: "Missing headerTLVs or body"
4556
+ };
4557
+ }
4558
+ const TLV_SHA256_CHUNK2 = 73;
4559
+ const expected = headerTLVs.get(TLV_SHA256_CHUNK2);
4560
+ if (!expected || expected.length !== 32) {
4561
+ return {
4562
+ action: "DENY",
4563
+ code: "FILE_CHUNK_HASH_MISSING",
4564
+ reason: "Missing sha256Chunk TLV in header"
4565
+ };
4566
+ }
4567
+ const actual = (0, import_crypto6.createHash)("sha256").update(bodyBytes).digest();
4568
+ if (!Buffer.from(actual).equals(Buffer.from(expected))) {
4569
+ return {
4570
+ action: "DENY",
4571
+ code: "FILE_CHUNK_HASH_MISMATCH",
4572
+ reason: "Chunk hash mismatch - data corrupted"
4573
+ };
4574
+ }
4575
+ return { action: "ALLOW" };
4576
+ }
4577
+ };
4578
+ ChunkHashSensor = __decorateClass([
4579
+ Sensor(),
4580
+ (0, import_common14.Injectable)()
4581
+ ], ChunkHashSensor);
4582
+
4583
+ // src/sensors/entropy.sensor.ts
4584
+ var import_common15 = require("@nestjs/common");
4585
+ var crypto4 = __toESM(require("crypto"));
4586
+ var EntropySensor = class {
4587
+ constructor() {
4588
+ this.logger = new import_common15.Logger(EntropySensor.name);
4589
+ /**
4590
+ * Minimum acceptable entropy in bits per byte.
4591
+ *
4592
+ * 3.0 bits/byte is a conservative threshold:
4593
+ * - Random data: ~7.9 bits/byte
4594
+ * - English text: ~4.5 bits/byte
4595
+ * - Sequential data: ~0-2 bits/byte
4596
+ */
4597
+ this.MIN_ENTROPY_THRESHOLD = 3;
4598
+ /** AxisSensor identifier */
4599
+ this.name = "EntropySensor";
4600
+ /**
4601
+ * Execution order - anomaly detection phase
4602
+ *
4603
+ * Order 130 ensures:
4604
+ * - Replay protection done (120)
4605
+ * - Runs before expensive policy lookups
4606
+ */
4607
+ this.order = BAND.POLICY + 35;
4608
+ }
4609
+ /**
4610
+ * Calculates Shannon entropy of a byte array.
4611
+ *
4612
+ * **Algorithm:**
4613
+ * 1. Count frequency of each byte value (0-255)
4614
+ * 2. Calculate probability p = count / total
4615
+ * 3. Sum: -Σ(p * log2(p))
4616
+ *
4617
+ * @private
4618
+ * @param {Uint8Array} data - Bytes to analyze
4619
+ * @returns {number} Entropy in bits per byte (0-8 scale)
4620
+ */
4621
+ calculateEntropy(data) {
4622
+ if (data.length === 0) return 0;
4623
+ const freq = /* @__PURE__ */ new Map();
4624
+ for (const byte of data) {
4625
+ freq.set(byte, (freq.get(byte) || 0) + 1);
4626
+ }
4627
+ let entropy = 0;
4628
+ const len = data.length;
4629
+ for (const count of freq.values()) {
4630
+ const p = count / len;
4631
+ entropy -= p * Math.log2(p);
4632
+ }
4633
+ return entropy;
4634
+ }
4635
+ /**
4636
+ * Checks for sequential patterns in data.
4637
+ *
4638
+ * Detects sequences like [1,2,3,4...] or [10,9,8,7...].
4639
+ * More than 50% sequential is considered suspicious.
4640
+ *
4641
+ * @private
4642
+ * @param {Uint8Array} data - Bytes to analyze
4643
+ * @returns {boolean} True if sequential pattern detected
4644
+ */
4645
+ hasSequentialPattern(data) {
4646
+ if (data.length < 4) return false;
4647
+ let ascending = 0;
4648
+ let descending = 0;
4649
+ for (let i = 1; i < data.length; i++) {
4650
+ if (data[i] === data[i - 1] + 1) ascending++;
4651
+ if (data[i] === data[i - 1] - 1) descending++;
4652
+ }
4653
+ return ascending > data.length / 2 || descending > data.length / 2;
4654
+ }
4655
+ /**
4656
+ * Checks for repeated patterns in data.
4657
+ *
4658
+ * Detects patterns like [0xAB, 0xCD, 0xAB, 0xCD...].
4659
+ * Checks for 2, 4, and 8 byte repeating patterns.
4660
+ *
4661
+ * @private
4662
+ * @param {Uint8Array} data - Bytes to analyze
4663
+ * @returns {boolean} True if repeated pattern detected
4664
+ */
4665
+ hasRepeatedPattern(data) {
4666
+ if (data.length < 8) return false;
4667
+ for (const patternLen of [2, 4, 8]) {
4668
+ if (data.length % patternLen !== 0) continue;
4669
+ let matches = 0;
4670
+ for (let i = patternLen; i < data.length; i++) {
4671
+ if (data[i] === data[i % patternLen]) matches++;
4672
+ }
4673
+ if (matches > (data.length - patternLen) * 0.9) {
4674
+ return true;
4675
+ }
4676
+ }
4677
+ return false;
4678
+ }
4679
+ /**
4680
+ * Analyzes entropy of PID and nonce in request headers.
4681
+ *
4682
+ * **Processing Flow:**
4683
+ * 1. Extract PID and nonce from header TLVs
4684
+ * 2. Calculate entropy for each
4685
+ * 3. Check for sequential patterns
4686
+ * 4. Check for repeated patterns
4687
+ * 5. Accumulate issues and score delta
4688
+ * 6. Return FLAG if issues found, ALLOW otherwise
4689
+ *
4690
+ * @param {SensorInput} input - Request with header TLVs
4691
+ * @returns {Promise<SensorDecision>} ALLOW or FLAG based on entropy analysis
4692
+ */
4693
+ async run(input) {
4694
+ const headers = input.headerTLVs;
4695
+ if (!headers) {
4696
+ return { action: "ALLOW" };
4697
+ }
4698
+ const pid = headers.get(import_axis_protocol2.TLV_PID);
4699
+ const nonce = headers.get(import_axis_protocol2.TLV_NONCE);
4700
+ const issues = [];
4701
+ let totalDelta = 0;
4702
+ if (pid && pid.length > 0) {
4703
+ const pidEntropy = this.calculateEntropy(pid);
4704
+ if (pidEntropy < this.MIN_ENTROPY_THRESHOLD) {
4705
+ issues.push(`pid_low_entropy:${pidEntropy.toFixed(2)}`);
4706
+ totalDelta -= 3;
4707
+ }
4708
+ if (this.hasSequentialPattern(pid)) {
4709
+ issues.push("pid_sequential");
4710
+ totalDelta -= 5;
4711
+ }
4712
+ if (this.hasRepeatedPattern(pid)) {
4713
+ issues.push("pid_repeated");
4714
+ totalDelta -= 5;
4715
+ }
4716
+ }
4717
+ if (nonce && nonce.length > 0) {
4718
+ const nonceEntropy = this.calculateEntropy(nonce);
4719
+ if (nonceEntropy < this.MIN_ENTROPY_THRESHOLD) {
4720
+ issues.push(`nonce_low_entropy:${nonceEntropy.toFixed(2)}`);
4721
+ totalDelta -= 3;
4722
+ }
4723
+ if (this.hasSequentialPattern(nonce)) {
4724
+ issues.push("nonce_sequential");
4725
+ totalDelta -= 5;
4726
+ }
4727
+ if (this.hasRepeatedPattern(nonce)) {
4728
+ issues.push("nonce_repeated");
4729
+ totalDelta -= 5;
4730
+ }
4731
+ }
4732
+ if (issues.length > 0) {
4733
+ this.logger.warn(`Entropy issues from ${input.ip}: ${issues.join(", ")}`);
4734
+ return {
4735
+ action: "FLAG",
4736
+ scoreDelta: totalDelta,
4737
+ reasons: issues
4738
+ };
4739
+ }
4740
+ return { action: "ALLOW" };
4741
+ }
4742
+ /**
4743
+ * Generates cryptographically secure random bytes.
4744
+ *
4745
+ * Utility method for SDK/client code to ensure proper entropy.
4746
+ * Uses Node.js crypto.randomBytes for secure PRNG.
4747
+ *
4748
+ * @static
4749
+ * @param {number} length - Number of random bytes
4750
+ * @returns {Uint8Array} Cryptographically secure random bytes
4751
+ */
4752
+ static generateSecureRandom(length) {
4753
+ return new Uint8Array(crypto4.randomBytes(length));
4754
+ }
4755
+ };
4756
+ EntropySensor = __decorateClass([
4757
+ Sensor(),
4758
+ (0, import_common15.Injectable)()
4759
+ ], EntropySensor);
4760
+
4761
+ // src/sensors/execution-timeout.sensor.ts
4762
+ var import_common16 = require("@nestjs/common");
4763
+ var ExecutionTimeoutSensor = class {
4764
+ constructor() {
4765
+ this.logger = new import_common16.Logger(ExecutionTimeoutSensor.name);
4766
+ /** AxisSensor identifier */
4767
+ this.name = "ExecutionTimeoutSensor";
4768
+ /**
4769
+ * Execution order - late, near handler execution
4770
+ *
4771
+ * Order 210 ensures:
4772
+ * - All validation complete
4773
+ * - Deadline set just before handler
4774
+ */
4775
+ this.order = BAND.BUSINESS + 10;
4776
+ }
4777
+ /**
4778
+ * Determines if this sensor should process the given input.
4779
+ *
4780
+ * @param {SensorInput} input - Incoming request
4781
+ * @returns {boolean} True if intent is present
4782
+ */
4783
+ supports(input) {
4784
+ return !!input.intent;
4785
+ }
4786
+ /**
4787
+ * Sets execution deadline in the request context.
4788
+ *
4789
+ * **Processing Flow:**
4790
+ * 1. Look up timeout for intent
4791
+ * 2. Calculate absolute deadline
4792
+ * 3. Store in context for handler use
4793
+ * 4. Return ALLOW
4794
+ *
4795
+ * @param {SensorInput} input - Request with intent
4796
+ * @returns {Promise<SensorDecision>} Always ALLOW
4797
+ */
4798
+ async run(input) {
4799
+ const { intent, context } = input;
4800
+ if (!intent) {
4801
+ return { action: "ALLOW" };
4802
+ }
4803
+ const timeout = resolveTimeout(intent);
4804
+ const deadline = Date.now() + timeout;
4805
+ if (context) {
4806
+ context.deadline = deadline;
4807
+ context.timeoutMs = timeout;
4808
+ }
4809
+ this.logger.debug(
4810
+ `Set ${timeout}ms timeout for ${intent} (deadline: ${new Date(deadline).toISOString()})`
4811
+ );
4812
+ return { action: "ALLOW" };
4813
+ }
4814
+ /**
4815
+ * Checks if a deadline has been exceeded.
4816
+ *
4817
+ * Utility method for handler code.
4818
+ *
4819
+ * @static
4820
+ * @param {object} ctx - Context with deadline
4821
+ * @returns {boolean} True if deadline passed
4822
+ */
4823
+ static isExpired(ctx) {
4824
+ if (!ctx.deadline) return false;
4825
+ return Date.now() > ctx.deadline;
4826
+ }
4827
+ /**
4828
+ * Gets remaining time until deadline.
4829
+ *
4830
+ * Utility method for handler code.
4831
+ *
4832
+ * @static
4833
+ * @param {object} ctx - Context with deadline
4834
+ * @returns {number} Remaining milliseconds (0 if expired, Infinity if no deadline)
4835
+ */
4836
+ static getRemainingMs(ctx) {
4837
+ if (!ctx.deadline) return Infinity;
4838
+ return Math.max(0, ctx.deadline - Date.now());
4839
+ }
4840
+ };
4841
+ ExecutionTimeoutSensor = __decorateClass([
4842
+ Sensor(),
4843
+ (0, import_common16.Injectable)()
4844
+ ], ExecutionTimeoutSensor);
4845
+
4846
+ // src/sensors/frame-budget.sensor.ts
4847
+ var import_common17 = require("@nestjs/common");
4848
+ var FrameBudgetSensor = class {
4849
+ constructor(config) {
4850
+ this.config = config;
4851
+ /** AxisSensor identifier for logging and registry */
4852
+ this.name = "FrameBudgetSensor";
4853
+ /**
4854
+ * Execution order - runs after protocol validation
4855
+ *
4856
+ * Order 20 ensures:
4857
+ * - Protocol is valid (ProtocolStrictSensor @ 10)
4858
+ * - Size checked before expensive processing
4859
+ */
4860
+ this.order = BAND.WIRE + 20;
4861
+ }
4862
+ /**
4863
+ * Determines if this sensor should process the given input.
4864
+ *
4865
+ * Only activates when Content-Length header is available.
4866
+ * WebSocket frames may not have Content-Length; they use different size tracking.
4867
+ *
4868
+ * @param {SensorInput} input - Incoming AXIS request
4869
+ * @returns {boolean} True if Content-Length is present
4870
+ */
4871
+ supports(input) {
4872
+ return typeof input.contentLength === "number";
4873
+ }
4874
+ /**
4875
+ * Validates frame size against configured limits.
4876
+ *
4877
+ * **Current Implementation:** Stub that always allows.
4878
+ *
4879
+ * **TODO:** Full implementation should:
4880
+ * 1. Load intent policy for the request
4881
+ * 2. Get maxFrameBytes from policy
4882
+ * 3. Compare against contentLength
4883
+ * 4. DENY if exceeded
4884
+ *
4885
+ * @param {SensorInput} input - Request with contentLength
4886
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on size
4887
+ */
4888
+ async run(input) {
4889
+ const maxBytes = this.config.get("AXIS_MAX_FRAME_SIZE") || 50 * 1024 * 1024;
4890
+ const contentLength = input.contentLength;
4891
+ if (typeof contentLength !== "number") {
4892
+ return { action: "ALLOW" };
4893
+ }
4894
+ if (contentLength > maxBytes) {
4895
+ return {
4896
+ action: "DENY",
4897
+ code: "FRAME_TOO_LARGE",
4898
+ reason: `Frame size ${contentLength} exceeds limit ${maxBytes}`
4899
+ };
4900
+ }
4901
+ return { action: "ALLOW" };
4902
+ }
4903
+ };
4904
+ FrameBudgetSensor = __decorateClass([
4905
+ Sensor({ phase: "PRE_DECODE" }),
4906
+ (0, import_common17.Injectable)()
4907
+ ], FrameBudgetSensor);
4908
+
4909
+ // src/sensors/frame-header-sanity.sensor.ts
4910
+ var import_common18 = require("@nestjs/common");
4911
+ var FrameHeaderSanitySensor = class {
4912
+ constructor() {
4913
+ this.name = "FrameHeaderSanitySensor";
4914
+ this.order = BAND.WIRE + 30;
4915
+ }
4916
+ supports(input) {
4917
+ return !!input.peek && input.peek.length >= 7;
4918
+ }
4919
+ async run(input) {
4920
+ const peek = input.peek;
4921
+ const contentLen = input.contentLength || 0;
4922
+ if (peek.length < 5 || !this.bufferEqual(peek.slice(0, 5), import_axis_protocol2.AXIS_MAGIC)) {
4923
+ return {
4924
+ action: "DENY",
4925
+ code: "INVALID_MAGIC",
4926
+ reason: "Frame magic is not AXIS1"
4927
+ };
4928
+ }
4929
+ if (peek[5] !== import_axis_protocol2.AXIS_VERSION) {
4930
+ return {
4931
+ action: "DENY",
4932
+ code: "UNSUPPORTED_VERSION",
4933
+ reason: `Unsupported version: ${peek[5]}`
4934
+ };
4935
+ }
4936
+ if (contentLen > import_axis_protocol2.MAX_FRAME_LEN) {
4937
+ return {
4938
+ action: "DENY",
4939
+ code: "FRAME_TOO_LARGE",
4940
+ reason: `Frame size ${contentLen} exceeds max ${import_axis_protocol2.MAX_FRAME_LEN}`
4941
+ };
4942
+ }
4943
+ return { action: "ALLOW" };
4944
+ }
4945
+ bufferEqual(a, b) {
4946
+ if (a.length !== b.length) return false;
4947
+ for (let i = 0; i < a.length; i++) {
4948
+ if (a[i] !== b[i]) return false;
4949
+ }
4950
+ return true;
4951
+ }
4952
+ };
4953
+ FrameHeaderSanitySensor = __decorateClass([
4954
+ (0, import_common18.Injectable)(),
4955
+ Sensor({ phase: "PRE_DECODE" })
4956
+ ], FrameHeaderSanitySensor);
4957
+
4958
+ // src/sensors/header-tlv-limit.sensor.ts
4959
+ var import_common19 = require("@nestjs/common");
4960
+ var HeaderTLVLimitSensor = class {
4961
+ constructor() {
4962
+ this.name = "HeaderTLVLimitSensor";
4963
+ this.order = BAND.CONTENT + 0;
4964
+ this.MAX_TLVS = 64;
4965
+ }
4966
+ supports(input) {
4967
+ return !!input.headerTLVs || !!input.packet;
4968
+ }
4969
+ async run(input) {
4970
+ if (input.headerTLVs && input.headerTLVs.size > this.MAX_TLVS) {
4971
+ return {
4972
+ action: "DENY",
4973
+ code: "TOO_MANY_TLVS",
4974
+ reason: `Header TLVs (${input.headerTLVs.size}) exceed max (${this.MAX_TLVS})`
4975
+ };
4976
+ }
4977
+ if (input.packet && input.packet.headerBytes) {
4978
+ const hdrLen = input.packet.headerBytes.length;
4979
+ if (hdrLen > import_axis_protocol2.MAX_HDR_LEN) {
4980
+ return {
4981
+ action: "DENY",
4982
+ code: "HEADER_TOO_LARGE",
4983
+ reason: `Header size ${hdrLen} exceeds max ${import_axis_protocol2.MAX_HDR_LEN}`
4984
+ };
4985
+ }
4986
+ }
4987
+ return { action: "ALLOW" };
4988
+ }
4989
+ };
4990
+ HeaderTLVLimitSensor = __decorateClass([
4991
+ (0, import_common19.Injectable)(),
4992
+ Sensor()
4993
+ ], HeaderTLVLimitSensor);
4994
+
4995
+ // src/sensors/intent-allowlist.sensor.ts
4996
+ var import_common20 = require("@nestjs/common");
4997
+ var PUBLIC_INTENT_ALLOWLIST = [
4998
+ "public.",
4999
+ "schema.",
5000
+ "catalog.",
5001
+ "health.",
5002
+ "system."
5003
+ ];
5004
+ var IntentAllowlistSensor = class {
5005
+ constructor() {
5006
+ this.name = "IntentAllowlistSensor";
5007
+ this.order = BAND.IDENTITY + 20;
5008
+ }
5009
+ supports(input) {
5010
+ return !!input.intent;
5011
+ }
5012
+ async run(input) {
5013
+ const profile = input.metadata?.profile || "PUBLIC";
5014
+ const intent = input.intent || "";
5015
+ if (profile === "PUBLIC") {
5016
+ const isAllowed = PUBLIC_INTENT_ALLOWLIST.some(
5017
+ (prefix) => intent.startsWith(prefix)
5018
+ );
5019
+ if (!isAllowed) {
5020
+ return {
5021
+ action: "DENY",
5022
+ code: "INTENT_NOT_ALLOWED",
5023
+ reason: `Intent '${intent}' not in public allowlist`
5024
+ };
5025
+ }
5026
+ }
5027
+ return { action: "ALLOW" };
5028
+ }
5029
+ };
5030
+ IntentAllowlistSensor = __decorateClass([
5031
+ (0, import_common20.Injectable)(),
5032
+ Sensor()
5033
+ ], IntentAllowlistSensor);
5034
+
5035
+ // src/sensors/intent-registry.sensor.ts
5036
+ var import_common21 = require("@nestjs/common");
5037
+ var IntentRegistrySensor = class {
5038
+ constructor(router) {
5039
+ this.router = router;
5040
+ this.name = "IntentRegistrySensor";
5041
+ this.order = BAND.IDENTITY + 25;
5042
+ }
5043
+ supports(input) {
5044
+ return !!input.intent;
5045
+ }
5046
+ async run(input) {
5047
+ const intent = input.intent;
5048
+ if (this.router.has(intent)) {
5049
+ return { action: "ALLOW" };
5050
+ }
5051
+ return {
5052
+ action: "DENY",
5053
+ code: "INTENT_NOT_REGISTERED",
5054
+ reason: `Intent '${intent}' is not registered`
5055
+ };
5056
+ }
5057
+ };
5058
+ IntentRegistrySensor = __decorateClass([
5059
+ (0, import_common21.Injectable)(),
5060
+ Sensor({ phase: "POST_DECODE" })
5061
+ ], IntentRegistrySensor);
5062
+
5063
+ // src/sensors/proof-presence.sensor.ts
5064
+ var import_common22 = require("@nestjs/common");
5065
+ var ProofPresenceSensor = class {
5066
+ constructor() {
5067
+ this.name = "ProofPresenceSensor";
5068
+ this.order = BAND.IDENTITY + 30;
5069
+ }
5070
+ supports(input) {
5071
+ return !!input.profile && !!input.visibility;
5072
+ }
5073
+ async run(input) {
5074
+ const validatedInput = ProofPresenceInputZ.safeParse(input);
5075
+ if (!validatedInput.success) {
5076
+ throw new AxisError(
5077
+ "SENSOR_INVALID_INPUT",
5078
+ `Input validation failed: ${validatedInput.error.message}`,
5079
+ 400
5080
+ );
5081
+ }
5082
+ const {
5083
+ visibility,
5084
+ requiredProof,
5085
+ hasCapsule,
5086
+ hasPassportSignature,
5087
+ profile,
5088
+ intent
5089
+ } = validatedInput.data;
5090
+ if (visibility === "PUBLIC") {
5091
+ return { action: "ALLOW" };
5092
+ }
5093
+ if (requiredProof.includes("NONE")) {
5094
+ return { action: "ALLOW" };
5095
+ }
5096
+ const hasCapsuleProof = requiredProof.includes("CAPSULE") && hasCapsule;
5097
+ const hasPassportProof = requiredProof.includes("PASSPORT") && hasPassportSignature;
5098
+ const hasNodeProof = requiredProof.includes("MTLS") && profile === "NODE";
5099
+ const satisfied = hasCapsuleProof || hasPassportProof || hasNodeProof;
5100
+ if (!satisfied) {
5101
+ throw new AxisError(
5102
+ "SENSOR_PROOF_REQUIRED",
5103
+ `Proof required for guarded intent: ${intent}`,
5104
+ 403
5105
+ );
5106
+ }
5107
+ return { action: "ALLOW" };
5108
+ }
5109
+ };
5110
+ ProofPresenceSensor = __decorateClass([
5111
+ Sensor(),
5112
+ (0, import_common22.Injectable)()
5113
+ ], ProofPresenceSensor);
5114
+
5115
+ // src/sensors/protocol-strict.sensor.ts
5116
+ var import_common23 = require("@nestjs/common");
5117
+ var VALID_FLAGS = [
5118
+ 0,
5119
+ // No flags
5120
+ import_axis_protocol2.FLAG_BODY_TLV,
5121
+ // Body contains TLVs
5122
+ import_axis_protocol2.FLAG_CHAIN_REQ,
5123
+ // Requires receipt chaining
5124
+ import_axis_protocol2.FLAG_HAS_WITNESS,
5125
+ // Has witness signatures
5126
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ,
5127
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_HAS_WITNESS,
5128
+ import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS,
5129
+ import_axis_protocol2.FLAG_BODY_TLV | import_axis_protocol2.FLAG_CHAIN_REQ | import_axis_protocol2.FLAG_HAS_WITNESS
5130
+ ];
5131
+ var ProtocolStrictSensor = class {
5132
+ constructor(config) {
5133
+ this.config = config;
5134
+ this.logger = new import_common23.Logger(ProtocolStrictSensor.name);
5135
+ /** Sensor identifier for logging and registry */
5136
+ this.name = "ProtocolStrictSensor";
5137
+ /**
5138
+ * Execution order - FIRST sensor in the chain
5139
+ *
5140
+ * Order 10 ensures:
5141
+ * - Runs before any other processing
5142
+ * - Invalid frames rejected immediately
5143
+ * - Protects all downstream sensors from malformed input
5144
+ */
5145
+ this.order = BAND.WIRE + 10;
5146
+ this.protocolMagic = import_axis_protocol2.AXIS_MAGIC;
5147
+ this.protocolVersion = import_axis_protocol2.AXIS_VERSION;
5148
+ }
5149
+ /**
5150
+ * Static validation for streaming middleware (Fast Check)
5151
+ */
5152
+ static validateMagic(chunk, expected) {
5153
+ if (chunk.length < expected.length) return { valid: true };
5154
+ const actual = chunk.subarray(0, expected.length);
5155
+ const valid = Buffer.from(actual).equals(Buffer.from(expected));
5156
+ return {
5157
+ valid,
5158
+ actual: valid ? void 0 : new TextDecoder().decode(actual)
5159
+ };
5160
+ }
5161
+ static validateVersion(version, expected) {
5162
+ return version === expected;
5163
+ }
5164
+ /**
5165
+ * Lifecycle hook: Registers this sensor in the chain on module initialization.
5166
+ */
5167
+ onModuleInit() {
5168
+ const magicStr = this.config.get("AXIS_PROTOCOL_MAGIC");
5169
+ this.protocolMagic = magicStr ? Buffer.from(magicStr, "ascii") : import_axis_protocol2.AXIS_MAGIC;
5170
+ this.protocolVersion = this.config.get("AXIS_PROTOCOL_VERSION") || import_axis_protocol2.AXIS_VERSION;
5171
+ }
5172
+ /**
5173
+ * Evaluate protocol strictness
5174
+ */
5175
+ async run(input) {
5176
+ const validatedInput = ProtocolStrictInputZ.safeParse(input);
5177
+ if (!validatedInput.success) {
5178
+ this.logger.error(
5179
+ `Invalid input: ${validatedInput.error.message}`,
5180
+ validatedInput.error.issues
5181
+ );
5182
+ return {
5183
+ action: "DENY",
5184
+ code: "INVALID_INPUT",
5185
+ reason: "Protocol validation input failed"
5186
+ };
5187
+ }
5188
+ const { contentType, peek } = validatedInput.data;
5189
+ const issues = [];
5190
+ if (peek.length >= 8) {
5191
+ const hex = Buffer.from(peek.subarray(0, 10)).toString("hex");
5192
+ this.logger.debug(`Raw Frame Header (Hex): ${hex} (IP: ${input.ip})`);
5193
+ }
5194
+ if (contentType !== void 0) {
5195
+ if (!this.isValidContentType(contentType)) {
5196
+ issues.push(`invalid_content_type:${contentType}`);
5197
+ }
5198
+ }
5199
+ if (peek.length < 9) {
5200
+ return {
5201
+ action: "DENY",
5202
+ code: "FRAME_TOO_SHORT",
5203
+ reason: "Frame too short for protocol header"
5204
+ };
5205
+ }
5206
+ const magicCheck = ProtocolStrictSensor.validateMagic(
5207
+ peek,
5208
+ this.protocolMagic
5209
+ );
5210
+ if (!magicCheck.valid) {
5211
+ return {
5212
+ action: "DENY",
5213
+ code: "INVALID_MAGIC",
5214
+ reason: `Expected ${new TextDecoder().decode(this.protocolMagic)} magic, got ${magicCheck.actual}`
5215
+ };
5216
+ }
5217
+ const version = peek[5];
5218
+ if (!ProtocolStrictSensor.validateVersion(version, this.protocolVersion)) {
5219
+ issues.push(`unsupported_version:${version}`);
5220
+ }
5221
+ const flags = peek[6];
5222
+ if (!this.isValidFlags(flags)) {
5223
+ issues.push(`invalid_flags:0x${flags.toString(16)}`);
5224
+ }
5225
+ if (peek.length >= 10) {
5226
+ const lengthCheck = this.checkVarintEncoding(peek.subarray(7));
5227
+ if (!lengthCheck.valid) {
5228
+ issues.push(`non_minimal_varint:${lengthCheck.reason}`);
5229
+ }
5230
+ }
5231
+ if (peek.length >= 20) {
5232
+ const tlvCheck = this.checkTLVOrdering(peek);
5233
+ if (!tlvCheck.valid) {
5234
+ issues.push(`tlv_not_canonical:${tlvCheck.reason}`);
5235
+ }
5236
+ const hasClientVersion = await this.checkForClientVersion(peek);
5237
+ if (!hasClientVersion) {
5238
+ issues.push("missing_client_version");
5239
+ }
5240
+ }
5241
+ if (issues.length > 0) {
5242
+ const critical = issues.some(
5243
+ (i) => i.startsWith("invalid_magic") || i.startsWith("unsupported_version")
5244
+ );
5245
+ if (critical) {
5246
+ return {
5247
+ action: "DENY",
5248
+ code: "PROTOCOL_VIOLATION",
5249
+ reason: issues.join(", ")
5250
+ };
5251
+ }
5252
+ this.logger.warn(
5253
+ `Protocol issues from ${input.ip}: ${issues.join(", ")}`
5254
+ );
5255
+ return {
5256
+ action: "FLAG",
5257
+ scoreDelta: -issues.length * 2,
5258
+ reasons: issues
5259
+ };
5260
+ }
5261
+ return { action: "ALLOW" };
5262
+ }
5263
+ /**
5264
+ * Compare two buffers for equality
5265
+ */
5266
+ buffersEqual(a, b) {
5267
+ if (a.length !== b.length) return false;
5268
+ for (let i = 0; i < a.length; i++) {
5269
+ if (a[i] !== b[i]) return false;
5270
+ }
5271
+ return true;
5272
+ }
5273
+ /**
5274
+ * Check if Content-Type is valid for AXIS
5275
+ */
5276
+ isValidContentType(contentType) {
5277
+ const valid = [
5278
+ "application/axis-bin",
5279
+ "application/octet-stream",
5280
+ "application/x-axis"
5281
+ ];
5282
+ return valid.some((v) => contentType.toLowerCase().includes(v));
5283
+ }
5284
+ /**
5285
+ * Check if flags are a valid combination
5286
+ */
5287
+ isValidFlags(flags) {
5288
+ return VALID_FLAGS.includes(flags);
5289
+ }
5290
+ /**
5291
+ * Check varint encoding is minimal (no leading zeros)
5292
+ */
5293
+ checkVarintEncoding(data) {
5294
+ try {
5295
+ const { value, length: bytesRead } = (0, import_axis_protocol3.decodeVarint)(data, 0);
5296
+ if (value < 128 && bytesRead > 1) {
5297
+ return { valid: false, reason: "non-minimal-small-value" };
5298
+ }
5299
+ if (value < 16384 && bytesRead > 2) {
5300
+ return { valid: false, reason: "non-minimal-medium-value" };
5301
+ }
5302
+ return { valid: true };
5303
+ } catch {
5304
+ return { valid: false, reason: "varint-decode-error" };
5305
+ }
5306
+ }
5307
+ /**
5308
+ * Check TLV ordering is canonical (sorted by type, no duplicates)
5309
+ */
5310
+ checkTLVOrdering(data) {
5311
+ try {
5312
+ let offset = 7;
5313
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5314
+ offset += hdrBytes;
5315
+ const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5316
+ offset += bodyBytes;
5317
+ const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5318
+ offset += sigBytes;
5319
+ const hdrStart = offset;
5320
+ const hdrEnd = hdrStart + Number(hdrLen);
5321
+ if (hdrEnd > data.length) {
5322
+ return { valid: true };
5323
+ }
5324
+ let lastType = -1;
5325
+ let pos = hdrStart;
5326
+ while (pos < hdrEnd && pos < data.length - 2) {
5327
+ const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5328
+ pos += typeBytes;
5329
+ if (pos >= hdrEnd) break;
5330
+ const { value: len, length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5331
+ pos += lenBytes;
5332
+ if (Number(type) <= lastType) {
5333
+ return {
5334
+ valid: false,
5335
+ reason: `type-${type}-after-${lastType}`
5336
+ };
5337
+ }
5338
+ lastType = Number(type);
5339
+ pos += Number(len);
5340
+ }
5341
+ return { valid: true };
5342
+ } catch {
5343
+ return { valid: true };
5344
+ }
5345
+ }
5346
+ /**
5347
+ * Check if TLV 100 (Client Version) exists in the headers
5348
+ */
5349
+ async checkForClientVersion(data) {
5350
+ try {
5351
+ let offset = 7;
5352
+ const { value: hdrLen, length: hdrBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5353
+ offset += hdrBytes;
5354
+ const { length: bodyBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5355
+ offset += bodyBytes;
5356
+ const { length: sigBytes } = (0, import_axis_protocol3.decodeVarint)(data, offset);
5357
+ offset += sigBytes;
5358
+ const hdrEnd = offset + Number(hdrLen);
5359
+ let pos = offset;
5360
+ while (pos < hdrEnd && pos < data.length) {
5361
+ const { value: type, length: typeBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5362
+ pos += typeBytes;
5363
+ const { length: lenBytes } = (0, import_axis_protocol3.decodeVarint)(data, pos);
5364
+ pos += lenBytes;
5365
+ const { value: valLen, length: valLenBytes } = (0, import_axis_protocol3.decodeVarint)(
5366
+ data,
5367
+ pos - lenBytes
5368
+ );
5369
+ }
5370
+ pos = offset;
5371
+ while (pos < hdrEnd && pos < data.length) {
5372
+ const t = (0, import_axis_protocol3.decodeVarint)(data, pos);
5373
+ pos += t.length;
5374
+ const l = (0, import_axis_protocol3.decodeVarint)(data, pos);
5375
+ pos += l.length;
5376
+ if (t.value === 100) return true;
5377
+ pos += Number(l.value);
5378
+ }
5379
+ return false;
5380
+ } catch {
5381
+ return false;
5382
+ }
5383
+ }
5384
+ };
5385
+ ProtocolStrictSensor = __decorateClass([
5386
+ Sensor({ phase: "PRE_DECODE" }),
5387
+ (0, import_common23.Injectable)()
5388
+ ], ProtocolStrictSensor);
5389
+
5390
+ // src/sensors/receipt-policy.sensor.ts
5391
+ var import_common24 = require("@nestjs/common");
5392
+ var ReceiptPolicySensor = class {
5393
+ constructor() {
5394
+ this.name = "ReceiptPolicySensor";
5395
+ this.order = BAND.BUSINESS + 20;
5396
+ }
5397
+ supports() {
5398
+ return true;
5399
+ }
5400
+ async run() {
5401
+ return { action: "ALLOW" };
5402
+ }
5403
+ };
5404
+ ReceiptPolicySensor = __decorateClass([
5405
+ (0, import_common24.Injectable)(),
5406
+ Sensor()
5407
+ ], ReceiptPolicySensor);
5408
+
5409
+ // src/sensors/schema-validation.sensor.ts
5410
+ var import_common25 = require("@nestjs/common");
5411
+ function readU64be(b) {
5412
+ if (b.length !== 8)
5413
+ throw new AxisError("SCHEMA_TYPE_MISMATCH", "u64 must be 8 bytes", 400);
5414
+ let x = 0n;
5415
+ for (const by of b) x = x << 8n | BigInt(by);
5416
+ return x;
5417
+ }
5418
+ var SchemaValidationSensor = class {
5419
+ constructor() {
5420
+ /** Sensor identifier for logging and registry */
5421
+ this.name = "SchemaValidationSensor";
5422
+ /**
5423
+ * Execution order - runs late in the pipeline
5424
+ *
5425
+ * Order 170 ensures:
5426
+ * - All authentication complete
5427
+ * - All policy checks complete
5428
+ * - Data validated before handler execution
5429
+ */
5430
+ this.order = BAND.CONTENT + 35;
5431
+ }
5432
+ /**
5433
+ * Determines if this sensor should process the given input.
5434
+ *
5435
+ * Only activates when a schema is provided for the intent (post-decode phase).
5436
+ *
5437
+ * @param {any} input - Sensor input
5438
+ * @returns {boolean} True if schema exists in metadata
5439
+ */
5440
+ supports(input) {
5441
+ return !!input.metadata?.schema;
5442
+ }
5443
+ /**
5444
+ * Validates TLV fields against the schema definition.
5445
+ *
5446
+ * **Validation Steps:**
5447
+ * 1. Validate the schema itself using Zod
5448
+ * 2. Iterate through each field definition
5449
+ * 3. Check required fields are present
5450
+ * 4. Validate size limits (maxLen)
5451
+ * 5. Validate type-specific rules
5452
+ *
5453
+ * @param {any} input - Standard SensorInput
5454
+ * @returns {{ action: 'ALLOW' } | { action: 'DENY', code: string, reason: string }} Decision
5455
+ */
5456
+ async run(input) {
5457
+ const schema = input.metadata?.schema;
5458
+ const headerTLVs = input.headerTLVs;
5459
+ const bodyTLVs = input.bodyTLVs;
5460
+ if (!schema) {
5461
+ return { action: "ALLOW" };
5462
+ }
5463
+ const validatedSchema = IntentSchemaZ.safeParse(schema);
5464
+ if (!validatedSchema.success) {
5465
+ return {
5466
+ action: "DENY",
5467
+ code: "SCHEMA_INVALID",
5468
+ reason: `Schema validation failed: ${validatedSchema.error.message}`
5469
+ };
5470
+ }
5471
+ try {
5472
+ for (const field of schema.fields) {
5473
+ const scope = field.scope ?? "body";
5474
+ const map3 = scope === "header" ? headerTLVs : bodyTLVs;
5475
+ const val = map3?.get(field.tlv);
5476
+ if (field.required && !val) {
5477
+ throw new AxisError(
5478
+ "SCHEMA_FIELD_MISSING",
5479
+ `Missing required field: ${field.name} (TLV ${field.tlv})`,
5480
+ 400
5481
+ );
5482
+ }
5483
+ if (!val) continue;
5484
+ if (typeof field.maxLen === "number" && val.length > field.maxLen) {
5485
+ throw new AxisError(
5486
+ "SCHEMA_LIMIT_EXCEEDED",
5487
+ `Field ${field.name} too large (${val.length} > ${field.maxLen})`,
5488
+ 413
5489
+ // Payload Too Large
5490
+ );
5491
+ }
5492
+ switch (field.kind) {
5493
+ case "utf8":
5494
+ try {
5495
+ new TextDecoder("utf-8", { fatal: true }).decode(val);
5496
+ } catch {
5497
+ throw new AxisError(
5498
+ "SCHEMA_TYPE_MISMATCH",
5499
+ `Invalid UTF-8 in ${field.name}`,
5500
+ 400
5501
+ );
5502
+ }
5503
+ break;
5504
+ case "bool":
5505
+ if (val.length !== 1 || val[0] !== 0 && val[0] !== 1) {
5506
+ throw new AxisError(
5507
+ "SCHEMA_TYPE_MISMATCH",
5508
+ `Invalid bool: ${field.name}`,
5509
+ 400
5510
+ );
5511
+ }
5512
+ break;
5513
+ case "u64": {
5514
+ const x = readU64be(val);
5515
+ if (field.max) {
5516
+ const mx = BigInt(field.max);
5517
+ if (x > mx) {
5518
+ throw new AxisError(
5519
+ "SCHEMA_LIMIT_EXCEEDED",
5520
+ `u64 ${field.name} exceeds max (${x} > ${mx})`,
5521
+ 400
5522
+ );
5523
+ }
5524
+ }
5525
+ break;
5526
+ }
5527
+ case "bytes16":
5528
+ if (val.length !== 16) {
5529
+ throw new AxisError(
5530
+ "SCHEMA_TYPE_MISMATCH",
5531
+ `bytes16 required for ${field.name}`,
5532
+ 400
5533
+ );
5534
+ }
5535
+ break;
5536
+ case "bytes":
5537
+ break;
5538
+ case "obj":
5539
+ case "arr":
5540
+ break;
5541
+ default:
5542
+ throw new AxisError(
5543
+ "SCHEMA_TYPE_MISMATCH",
5544
+ `Unknown schema kind: ${field.kind}`,
5545
+ 500
5546
+ );
5547
+ }
5548
+ }
5549
+ const validators = input.metadata?.validators;
5550
+ if (validators && validators.size > 0) {
5551
+ for (const field of schema.fields) {
5552
+ const fns = validators.get(field.tlv);
5553
+ if (!fns || fns.length === 0) continue;
5554
+ const scope = field.scope ?? "body";
5555
+ const map3 = scope === "header" ? headerTLVs : bodyTLVs;
5556
+ const val = map3?.get(field.tlv);
5557
+ if (!val) continue;
5558
+ for (const fn of fns) {
5559
+ const error = fn(val, field.name);
5560
+ if (error) {
5561
+ throw new AxisError(
5562
+ "SCHEMA_VALIDATION_FAILED",
5563
+ `${field.name} (TLV ${field.tlv}): ${error}`,
5564
+ 400
5565
+ );
5566
+ }
5567
+ }
5568
+ }
5569
+ }
5570
+ } catch (err) {
5571
+ if (err instanceof AxisError) {
5572
+ return {
5573
+ action: "DENY",
5574
+ code: err.code,
5575
+ reason: err.message
5576
+ };
5577
+ }
5578
+ throw err;
5579
+ }
5580
+ return { action: "ALLOW" };
5581
+ }
5582
+ };
5583
+ SchemaValidationSensor = __decorateClass([
5584
+ Sensor(),
5585
+ (0, import_common25.Injectable)()
5586
+ ], SchemaValidationSensor);
5587
+
5588
+ // src/sensors/stream-scope.sensor.ts
5589
+ var import_common26 = require("@nestjs/common");
5590
+ var StreamScopeSensor = class {
5591
+ constructor() {
5592
+ /** Sensor identifier */
5593
+ this.name = "StreamScopeSensor";
5594
+ /**
5595
+ * Execution order - near handler execution
5596
+ *
5597
+ * Order 200 ensures:
5598
+ * - All authentication complete
5599
+ * - All policy checks complete
5600
+ * - Stream-specific check right before subscription
5601
+ */
5602
+ this.order = BAND.BUSINESS + 0;
5603
+ }
5604
+ /**
5605
+ * Determines if this sensor should process the given input.
5606
+ *
5607
+ * Currently processes all inputs.
5608
+ *
5609
+ * @returns {boolean} Always true
5610
+ */
5611
+ supports() {
5612
+ return true;
5613
+ }
5614
+ /**
5615
+ * Validates stream topic access permissions.
5616
+ *
5617
+ * **Current Implementation:** Stub that always allows.
5618
+ *
5619
+ * **TODO:** Full implementation should:
5620
+ * 1. Check if intent is stream.subscribe or stream.publish
5621
+ * 2. Extract topic from body TLVs
5622
+ * 3. Parse topic into owner/resource pattern
5623
+ * 4. Look up topic ACL from database/cache
5624
+ * 5. Check if actor has required permission (read/write)
5625
+ * 6. DENY if unauthorized
5626
+ *
5627
+ * @returns {Promise<SensorDecision>} ALLOW (stub implementation)
5628
+ */
5629
+ async run() {
5630
+ return { action: "ALLOW" };
5631
+ }
5632
+ };
5633
+ StreamScopeSensor = __decorateClass([
5634
+ Sensor(),
5635
+ (0, import_common26.Injectable)()
5636
+ ], StreamScopeSensor);
5637
+
5638
+ // src/sensors/tlv-parse.sensor.ts
5639
+ var import_common27 = require("@nestjs/common");
5640
+ var TLVParseSensor = class {
5641
+ constructor() {
5642
+ this.name = "TLVParseSensor";
5643
+ this.order = BAND.CONTENT + 20;
5644
+ }
5645
+ supports(input) {
5646
+ return !!input.packet;
5647
+ }
5648
+ async run(input) {
5649
+ const packet = input.packet;
5650
+ if (!packet) return { action: "ALLOW" };
5651
+ const hdrBytes = packet.hdrBytes ?? packet.headerBytes;
5652
+ if (hdrBytes && hdrBytes.length > 0) {
5653
+ const result = this.validateCanonicalTLV(hdrBytes, "header");
5654
+ if (result) return result;
5655
+ }
5656
+ const bodyBytes = packet.bodyBytes ?? input.body;
5657
+ const bodyIsTlv = packet.flags !== void 0 ? (packet.flags & 1) !== 0 : false;
5658
+ const bodyProfile = input.metadata?.schema?.bodyProfile;
5659
+ const skipBody = bodyProfile === "RAW";
5660
+ if (!skipBody && bodyIsTlv && bodyBytes && bodyBytes.length > 0) {
5661
+ const result = this.validateCanonicalTLV(bodyBytes, "body");
5662
+ if (result) return result;
5663
+ }
5664
+ return { action: "ALLOW" };
5665
+ }
5666
+ /**
5667
+ * Validates a TLV buffer for canonical ordering, no duplicates,
5668
+ * valid bounds, and minimal varint encoding.
5669
+ */
5670
+ validateCanonicalTLV(buf, section) {
5671
+ let offset = 0;
5672
+ let lastType = -1;
5673
+ let count = 0;
5674
+ const maxItems = 512;
5675
+ while (offset < buf.length) {
5676
+ if (count >= maxItems) {
5677
+ return {
5678
+ action: "DENY",
5679
+ code: "TLV_LIMIT",
5680
+ reason: `Too many TLVs in ${section}`
5681
+ };
5682
+ }
5683
+ let type;
5684
+ let typeLen;
5685
+ try {
5686
+ const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
5687
+ type = r.value;
5688
+ typeLen = r.length;
5689
+ } catch {
5690
+ return {
5691
+ action: "DENY",
5692
+ code: "TLV_PARSE_ERROR",
5693
+ reason: `Malformed type varint in ${section} at offset ${offset}`
5694
+ };
5695
+ }
5696
+ offset += typeLen;
5697
+ if (type <= 0) {
5698
+ return {
5699
+ action: "DENY",
5700
+ code: "TLV_INVALID_TAG",
5701
+ reason: `Invalid tag ${type} in ${section}`
5702
+ };
5703
+ }
5704
+ if (type <= lastType) {
5705
+ return {
5706
+ action: "DENY",
5707
+ code: "TLV_NOT_CANONICAL",
5708
+ reason: `Non-canonical tag order in ${section}: ${type} after ${lastType}`
5709
+ };
5710
+ }
5711
+ lastType = type;
5712
+ let len;
5713
+ let lenLen;
5714
+ try {
5715
+ const r = (0, import_axis_protocol3.decodeVarint)(buf, offset);
5716
+ len = r.value;
5717
+ lenLen = r.length;
5718
+ } catch {
5719
+ return {
5720
+ action: "DENY",
5721
+ code: "TLV_PARSE_ERROR",
5722
+ reason: `Malformed length varint in ${section}`
5723
+ };
5724
+ }
5725
+ offset += lenLen;
5726
+ if (offset + len > buf.length) {
5727
+ return {
5728
+ action: "DENY",
5729
+ code: "TLV_TRUNCATED",
5730
+ reason: `TLV value truncated in ${section}`
5731
+ };
5732
+ }
5733
+ offset += len;
5734
+ count++;
5735
+ }
5736
+ return null;
5737
+ }
5738
+ };
5739
+ TLVParseSensor = __decorateClass([
5740
+ Sensor(),
5741
+ (0, import_common27.Injectable)()
5742
+ ], TLVParseSensor);
5743
+
5744
+ // src/sensors/varint-hardening.sensor.ts
5745
+ var import_common28 = require("@nestjs/common");
5746
+ var VarintHardeningSensor = class {
5747
+ constructor() {
5748
+ /** Sensor identifier */
5749
+ this.name = "VarintHardeningSensor";
5750
+ /**
5751
+ * Execution order - early detection
5752
+ *
5753
+ * Order 40 ensures:
5754
+ * - After protocol magic check
5755
+ * - Before length-based parsing
5756
+ */
5757
+ this.order = BAND.WIRE + 35;
5758
+ /** Maximum allowed bytes for a single varint */
5759
+ this.MAX_VARINT_BYTES = 5;
5760
+ }
5761
+ /**
5762
+ * Determines if this sensor should process the given input.
5763
+ *
5764
+ * Requires at least 7 bytes of peeked data.
5765
+ *
5766
+ * @param {SensorInput} input - Incoming request
5767
+ * @returns {boolean} True if sufficient peek data
5768
+ */
5769
+ supports(input) {
5770
+ return !!input.peek && input.peek.length >= 7;
5771
+ }
5772
+ /**
5773
+ * Validates varint lengths in frame header.
5774
+ *
5775
+ * **Processing Flow:**
5776
+ * 1. Skip to varint section (offset 7)
5777
+ * 2. Scan for continuation bytes (MSB = 1)
5778
+ * 3. Count consecutive continuation bytes
5779
+ * 4. DENY if count exceeds MAX_VARINT_BYTES
5780
+ *
5781
+ * @param {SensorInput} input - Request with peek data
5782
+ * @returns {Promise<SensorDecision>} ALLOW or DENY based on varint length
5783
+ */
5784
+ async run(input) {
5785
+ const peek = input.peek;
5786
+ const offset = 7;
5787
+ const maxOffset = Math.min(offset + 15, peek.length);
5788
+ let continuationCount = 0;
5789
+ for (let i = offset; i < maxOffset; i++) {
5790
+ if ((peek[i] & 128) !== 0) {
5791
+ continuationCount++;
5792
+ if (continuationCount > this.MAX_VARINT_BYTES) {
5793
+ return {
5794
+ action: "DENY",
5795
+ code: "VARINT_OVERFLOW",
5796
+ reason: `Varint exceeds ${this.MAX_VARINT_BYTES} bytes`
5797
+ };
5798
+ }
5799
+ } else {
5800
+ continuationCount = 0;
5801
+ }
5802
+ }
5803
+ return { action: "ALLOW" };
5804
+ }
5805
+ };
5806
+ VarintHardeningSensor = __decorateClass([
5807
+ Sensor({ phase: "PRE_DECODE" }),
5808
+ (0, import_common28.Injectable)()
5809
+ ], VarintHardeningSensor);
5810
+
5811
+ // src/utils/index.ts
5812
+ var utils_exports = {};
5813
+ __export(utils_exports, {
5814
+ encodeAxisTlvDto: () => encodeAxisTlvDto
5815
+ });
5816
+
5817
+ // src/utils/axis-tlv-codec.ts
5818
+ function encodeAxisTlvDto(dtoClass, data) {
5819
+ const schema = extractDtoSchema(dtoClass);
5820
+ const items = schema.fields.flatMap((field) => {
5821
+ const value = data[field.name];
5822
+ if (value === void 0 || value === null) {
5823
+ if (field.required) {
5824
+ throw new Error(`Missing required TLV response field: ${field.name}`);
5825
+ }
5826
+ return [];
5827
+ }
5828
+ return [{ type: field.tag, value: encodeField(field, value) }];
5829
+ });
5830
+ return buildTLVs(items);
5831
+ }
5832
+ function encodeField(field, value) {
5833
+ switch (field.kind) {
5834
+ case "utf8":
5835
+ return Buffer.from(String(value), "utf8");
5836
+ case "u64":
5837
+ return encodeU64(value);
5838
+ case "bytes":
5839
+ case "bytes16":
5840
+ return toBuffer(value);
5841
+ case "bool":
5842
+ return Buffer.from([value ? 1 : 0]);
5843
+ case "obj":
5844
+ case "arr":
5845
+ return Buffer.from(JSON.stringify(value), "utf8");
5846
+ default:
5847
+ return toBuffer(value);
5848
+ }
5849
+ }
5850
+ function encodeU64(value) {
5851
+ const encoded = Buffer.alloc(8);
5852
+ encoded.writeBigUInt64BE(
5853
+ typeof value === "bigint" ? value : BigInt(value)
5854
+ );
5855
+ return encoded;
5856
+ }
5857
+ function toBuffer(value) {
5858
+ if (Buffer.isBuffer(value)) {
5859
+ return value;
5860
+ }
5861
+ if (value instanceof Uint8Array) {
5862
+ return Buffer.from(value);
5863
+ }
5864
+ if (typeof value === "string") {
5865
+ return Buffer.from(value, "utf8");
5866
+ }
5867
+ throw new Error(`Unsupported TLV bytes value: ${typeof value}`);
5868
+ }
2810
5869
  // Annotate the CommonJS export names for ESM import in node:
2811
5870
  0 && (module.exports = {
2812
5871
  ATS1_HDR,
@@ -2937,34 +5996,45 @@ var DiskUploadFileStore = class {
2937
5996
  buildAts1Hdr,
2938
5997
  buildDtoDecoder,
2939
5998
  buildPacket,
5999
+ buildQueueMessage,
2940
6000
  buildReceiptHash,
2941
6001
  buildTLVs,
6002
+ buildUnsignedWitness,
2942
6003
  bytes,
2943
6004
  canAccessResource,
2944
6005
  canonicalJson,
2945
6006
  canonicalJsonExcluding,
6007
+ canonicalizeObservation,
2946
6008
  classifyIntent,
2947
6009
  computeReceiptHash,
2948
6010
  computeSignaturePayload,
6011
+ core,
6012
+ crypto,
2949
6013
  decodeArray,
2950
6014
  decodeAxis1Frame,
2951
6015
  decodeFrame,
2952
6016
  decodeObject,
6017
+ decodeQueueMessage,
2953
6018
  decodeTLVs,
2954
6019
  decodeTLVsList,
2955
6020
  decodeVarint,
6021
+ decorators,
2956
6022
  encVarint,
2957
6023
  encodeAxis1Frame,
2958
6024
  encodeFrame,
6025
+ encodeQueueMessage,
2959
6026
  encodeTLVs,
2960
6027
  encodeVarint,
6028
+ engine,
2961
6029
  extractDtoSchema,
2962
6030
  generateEd25519KeyPair,
2963
6031
  getSignTarget,
2964
6032
  hasScope,
6033
+ hashObservation,
2965
6034
  isAdminOpcode,
2966
6035
  isKnownOpcode,
2967
6036
  isTimestampValid,
6037
+ loom,
2968
6038
  nonce16,
2969
6039
  normalizeSensorDecision,
2970
6040
  packPasskeyLoginOptionsReq,
@@ -2972,20 +6042,28 @@ var DiskUploadFileStore = class {
2972
6042
  packPasskeyLoginVerifyReq,
2973
6043
  packPasskeyLoginVerifyRes,
2974
6044
  packPasskeyRegisterOptionsReq,
6045
+ parseAutoClaimEntries,
2975
6046
  parseScope,
6047
+ parseStreamEntries,
2976
6048
  resolveTimeout,
6049
+ schemas,
6050
+ security,
2977
6051
  sensitivityName,
6052
+ sensors,
2978
6053
  sha256,
2979
6054
  signFrame,
6055
+ stableJsonStringify,
2980
6056
  tlv,
2981
6057
  u64be,
2982
6058
  unpackPasskeyLoginOptionsReq,
2983
6059
  unpackPasskeyLoginVerifyReq,
2984
6060
  unpackPasskeyRegisterOptionsReq,
2985
6061
  utf8,
6062
+ utils,
2986
6063
  validateFrameShape,
2987
6064
  varintLength,
2988
6065
  varintU,
2989
- verifyFrameSignature
6066
+ verifyFrameSignature,
6067
+ verifyResponse
2990
6068
  });
2991
6069
  //# sourceMappingURL=index.js.map