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