@astrasyncai/verification-gateway 5.9.0 → 5.11.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.
Files changed (79) hide show
  1. package/dist/adapters/express.js +19 -1
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/express.mjs +19 -1
  4. package/dist/adapters/express.mjs.map +1 -1
  5. package/dist/adapters/mcp.js +19 -1
  6. package/dist/adapters/mcp.js.map +1 -1
  7. package/dist/adapters/mcp.mjs +19 -1
  8. package/dist/adapters/mcp.mjs.map +1 -1
  9. package/dist/adapters/nextjs.js +19 -1
  10. package/dist/adapters/nextjs.js.map +1 -1
  11. package/dist/adapters/nextjs.mjs +19 -1
  12. package/dist/adapters/nextjs.mjs.map +1 -1
  13. package/dist/adapters/sdk.js +19 -1
  14. package/dist/adapters/sdk.js.map +1 -1
  15. package/dist/adapters/sdk.mjs +19 -1
  16. package/dist/adapters/sdk.mjs.map +1 -1
  17. package/dist/agent/index.js +1 -1
  18. package/dist/agent/index.js.map +1 -1
  19. package/dist/agent/index.mjs +1 -1
  20. package/dist/agent/index.mjs.map +1 -1
  21. package/dist/bin/astrasync-claude-hook.js +540 -10
  22. package/dist/bin/astrasync-codex-hook.js +540 -10
  23. package/dist/bin/astrasync-guard.js +629 -65
  24. package/dist/bin/astrasync.js +673 -66
  25. package/dist/browser/background.js +19 -1
  26. package/dist/browser/background.js.map +1 -1
  27. package/dist/browser/background.mjs +19 -1
  28. package/dist/browser/background.mjs.map +1 -1
  29. package/dist/cli/index.d.mts +8 -8
  30. package/dist/cli/index.d.ts +8 -8
  31. package/dist/cli/index.js +22 -64
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/index.mjs +22 -64
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/codex/index.d.mts +5 -0
  36. package/dist/codex/index.d.ts +5 -0
  37. package/dist/codex/index.js +556 -18
  38. package/dist/codex/index.js.map +1 -1
  39. package/dist/codex/index.mjs +558 -20
  40. package/dist/codex/index.mjs.map +1 -1
  41. package/dist/cursor/extension.d.mts +9 -2
  42. package/dist/cursor/extension.d.ts +9 -2
  43. package/dist/cursor/extension.js +522 -5
  44. package/dist/cursor/extension.js.map +1 -1
  45. package/dist/cursor/extension.mjs +524 -5
  46. package/dist/cursor/extension.mjs.map +1 -1
  47. package/dist/edge-config.js +1 -1
  48. package/dist/edge-config.js.map +1 -1
  49. package/dist/edge-config.mjs +1 -1
  50. package/dist/edge-config.mjs.map +1 -1
  51. package/dist/edge-core/index.js +20 -2
  52. package/dist/edge-core/index.js.map +1 -1
  53. package/dist/edge-core/index.mjs +20 -2
  54. package/dist/edge-core/index.mjs.map +1 -1
  55. package/dist/enrolment-store-8DyaIOLe.d.mts +26 -0
  56. package/dist/enrolment-store-8DyaIOLe.d.ts +26 -0
  57. package/dist/gateway/gateway.js +19 -1
  58. package/dist/gateway/gateway.js.map +1 -1
  59. package/dist/gateway/gateway.mjs +19 -1
  60. package/dist/gateway/gateway.mjs.map +1 -1
  61. package/dist/index.d.mts +100 -3
  62. package/dist/index.d.ts +100 -3
  63. package/dist/index.js +3289 -18
  64. package/dist/index.js.map +1 -1
  65. package/dist/index.mjs +3287 -18
  66. package/dist/index.mjs.map +1 -1
  67. package/dist/registration/index.js +1 -1
  68. package/dist/registration/index.js.map +1 -1
  69. package/dist/registration/index.mjs +1 -1
  70. package/dist/registration/index.mjs.map +1 -1
  71. package/dist/transport/index.js +1 -1
  72. package/dist/transport/index.js.map +1 -1
  73. package/dist/transport/index.mjs +1 -1
  74. package/dist/transport/index.mjs.map +1 -1
  75. package/dist/verify.js +19 -1
  76. package/dist/verify.js.map +1 -1
  77. package/dist/verify.mjs +19 -1
  78. package/dist/verify.mjs.map +1 -1
  79. package/package.json +1 -1
@@ -36,7 +36,7 @@ var SDK_VERSION;
36
36
  var init_version = __esm({
37
37
  "src/version.ts"() {
38
38
  "use strict";
39
- SDK_VERSION = "5.9.0";
39
+ SDK_VERSION = "5.11.0";
40
40
  }
41
41
  });
42
42
 
@@ -4095,7 +4095,6 @@ var init_upgrade = __esm({
4095
4095
  "src/cli/upgrade.ts"() {
4096
4096
  "use strict";
4097
4097
  init_http();
4098
- init_pdlss_parser();
4099
4098
  UpgradeFlow = class {
4100
4099
  constructor(config, callbacks, trigger) {
4101
4100
  this.config = config;
@@ -4155,27 +4154,23 @@ var init_upgrade = __esm({
4155
4154
  */
4156
4155
  async continueUpgrade(apiKey, kydId) {
4157
4156
  const { print, prompt } = this.callbacks;
4158
- print("\n[3/5] Migrate local policy to cloud");
4159
- print(` Policy: ${this.config.policyPath}`);
4160
- print(` Name: ${this.config.policy.name}`);
4161
- print(` Rules: ${this.config.policy.purposes.length} purpose rules`);
4162
- if (this.config.policy.scope) {
4163
- const scope = this.config.policy.scope;
4164
- const domains = [
4165
- ...(scope.allowedDomains || []).length ? [`${scope.allowedDomains.length} allowed`] : [],
4166
- ...(scope.blockedDomains || []).length ? [`${scope.blockedDomains.length} blocked`] : []
4167
- ];
4168
- if (domains.length) print(` Scope: ${domains.join(", ")} domains`);
4169
- }
4170
- print("");
4171
- print(" Uploading YAML -> Cloud PDLSS boundary...");
4172
- const uploadResult = await this.uploadPolicy(apiKey);
4173
- if (!uploadResult.success) {
4174
- print(` Upload failed: ${uploadResult.error}`);
4175
- return uploadResult;
4176
- }
4177
- print(" Policy uploaded. Same rules, now with trust scoring and audit.");
4178
- print(` Agent registered: ${uploadResult.agentId}`);
4157
+ print("\n[3/5] Link your agent");
4158
+ print(
4159
+ ` Local policy ${this.config.policyPath} stays on this machine (${this.config.policy.purposes.length} purpose rules).`
4160
+ );
4161
+ print(" Register the agent in the AstraSync dashboard (or with `astrasync register`) and");
4162
+ print(" paste its ASTRA- id so synced decisions are attributed to it.\n");
4163
+ const agentInput = (await prompt(" Agent ASTRA- id (Enter to skip): ")).trim();
4164
+ let agentId;
4165
+ if (agentInput) {
4166
+ if (!/^ASTRA-[A-Za-z0-9_-]+$/.test(agentInput)) {
4167
+ return { success: false, error: `Not an ASTRA- agent id: ${agentInput}` };
4168
+ }
4169
+ agentId = agentInput;
4170
+ print(` Agent: ${agentId}`);
4171
+ } else {
4172
+ print(" No agent linked \u2014 decisions sync under your API key only.");
4173
+ }
4179
4174
  if (kydId) {
4180
4175
  print(` Developer: ${kydId}`);
4181
4176
  }
@@ -4186,23 +4181,23 @@ var init_upgrade = __esm({
4186
4181
  const mode = modeInput.trim().toLowerCase() === "online" ? "online" : "hybrid";
4187
4182
  print(`
4188
4183
  [5/5] Activating ${mode} verification`);
4189
- await this.writeLocalConfig(apiKey, uploadResult.agentId, mode, kydId);
4184
+ await this.writeLocalConfig(apiKey, agentId, mode, kydId);
4190
4185
  print(` Config written to ${this.configDir}/config`);
4191
4186
  print(` Mode: ${mode}`);
4192
4187
  print(" Done!\n");
4193
- print(` Your agent (${uploadResult.agentId}) is verified on the AstraSync network.`);
4188
+ if (agentId) print(` Your agent (${agentId}) is verified on the AstraSync network.`);
4194
4189
  if (mode === "hybrid") {
4195
4190
  print(" Local evaluation active for speed. Cloud sync for audit and trust scoring.");
4196
4191
  } else {
4197
4192
  print(" Trust scoring, audit trails, and blockchain recording are active.");
4198
4193
  }
4199
4194
  print(`
4200
- Dashboard: ${this.platformUrl}/agents/${uploadResult.agentId}
4195
+ Dashboard: ${this.platformUrl}/agents${agentId ? `/${agentId}` : ""}
4201
4196
  `);
4202
4197
  return {
4203
4198
  success: true,
4204
4199
  apiKey,
4205
- agentId: uploadResult.agentId,
4200
+ agentId,
4206
4201
  kydId,
4207
4202
  mode
4208
4203
  };
@@ -4255,44 +4250,6 @@ var init_upgrade = __esm({
4255
4250
  return { valid: true, error: `Could not reach platform (proceeding anyway)` };
4256
4251
  }
4257
4252
  }
4258
- /**
4259
- * Upload local policy to the AstraSync cloud.
4260
- * YAML -> cloud PDLSS boundary, 1:1 mapping, no lossy transformation.
4261
- */
4262
- async uploadPolicy(apiKey) {
4263
- const policyYaml = toYaml(this.config.policy);
4264
- try {
4265
- const response = await sdkFetch(`${this.platformUrl}/api/v1/agents/policies/upload`, {
4266
- method: "POST",
4267
- headers: {
4268
- "Content-Type": "application/json",
4269
- Authorization: `Bearer ${apiKey}`
4270
- },
4271
- body: JSON.stringify({
4272
- policy: policyYaml,
4273
- policyObject: this.config.policy,
4274
- format: "yaml",
4275
- source: "local-guard-upgrade",
4276
- name: this.config.policy.name
4277
- })
4278
- });
4279
- if (!response.ok) {
4280
- const body = await response.text();
4281
- return { success: false, error: `Upload failed (${response.status}): ${body}` };
4282
- }
4283
- const data = await response.json();
4284
- return {
4285
- success: true,
4286
- agentId: data.agentId || "ASTRA-PENDING",
4287
- kydId: data.kydId
4288
- };
4289
- } catch (err) {
4290
- return {
4291
- success: false,
4292
- error: `Network error: ${err instanceof Error ? err.message : String(err)}`
4293
- };
4294
- }
4295
- }
4296
4253
  /**
4297
4254
  * Write the config file for the chosen mode.
4298
4255
  */
@@ -5379,6 +5336,24 @@ async function callVerifyAccessAPI(config, request2) {
5379
5336
  }
5380
5337
  };
5381
5338
  }
5339
+ if (response.status === 400 && data?.error === "INVALID_AGENT_ID") {
5340
+ const message = typeof data?.message === "string" ? data.message : "Agent identifier must be a canonical ASTRA-* id.";
5341
+ return {
5342
+ success: true,
5343
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
5344
+ access: {
5345
+ allowed: false,
5346
+ reason: "invalid_agent_id",
5347
+ failures: [
5348
+ {
5349
+ dimension: "identity.invalid_agent_id",
5350
+ message,
5351
+ guidance: typeof data?.guidance === "string" ? data.guidance : "Present your canonical ASTRA-* id (discover it via GET /api/agents/request-registration/{requestId} after owner approval)."
5352
+ }
5353
+ ]
5354
+ }
5355
+ };
5356
+ }
5382
5357
  if (!response.ok) {
5383
5358
  if (response.status === 401) {
5384
5359
  return {
@@ -6220,6 +6195,8 @@ var init_src = __esm({
6220
6195
  init_runtime_signatures();
6221
6196
  init_verified_hop();
6222
6197
  init_http();
6198
+ init_pdlss_parser();
6199
+ init_schema();
6223
6200
  init_version();
6224
6201
  }
6225
6202
  });
@@ -7098,7 +7075,15 @@ var init_settings_installer = __esm({
7098
7075
  "~/.claude/settings.json",
7099
7076
  "~/.claude/settings.local.json",
7100
7077
  "**/.astrasync/**",
7101
- "~/.astrasync/**"
7078
+ "~/.astrasync/**",
7079
+ // 5.11.0 fleet mode: enrolment + signed-policy cache (user scope is inside
7080
+ // ~/.astrasync already; listed explicitly so the intent survives edits) and
7081
+ // the machine-scope enrolment dirs. `//` = absolute from filesystem root;
7082
+ // Windows paths are matched in POSIX form (C:\ → /c/).
7083
+ "~/.astrasync/fleet/**",
7084
+ "//etc/astrasync/**",
7085
+ "//Library/Application Support/AstraSync/**",
7086
+ "//c/ProgramData/AstraSync/**"
7102
7087
  ];
7103
7088
  GUARD_PERMISSION_DENY_RULES = PERMISSION_DENY_PATHS.map(
7104
7089
  (p) => `Edit(${p})`
@@ -7140,6 +7125,545 @@ var init_settings_installer2 = __esm({
7140
7125
  }
7141
7126
  });
7142
7127
 
7128
+ // src/fleet/client.ts
7129
+ function base(url) {
7130
+ return url.replace(/\/+$/, "");
7131
+ }
7132
+ async function call(url, init, timeoutMs) {
7133
+ const controller = new AbortController();
7134
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
7135
+ try {
7136
+ const res = await sdkFetch(url, { ...init, signal: controller.signal });
7137
+ const text = await res.text();
7138
+ let body = text;
7139
+ try {
7140
+ body = text ? JSON.parse(text) : null;
7141
+ } catch {
7142
+ }
7143
+ return { status: res.status, body, headers: res.headers };
7144
+ } finally {
7145
+ clearTimeout(timer);
7146
+ }
7147
+ }
7148
+ function apiError(status, body, what) {
7149
+ const b = body ?? {};
7150
+ const code = b.code ?? (typeof b.error === "string" ? b.error : void 0);
7151
+ return new FleetApiError(`${what} failed (${status}${code ? ` ${code}` : ""})`, status, code);
7152
+ }
7153
+ async function enrolDevice(apiBaseUrl, request2, timeoutMs = 15e3) {
7154
+ const r = await call(
7155
+ `${base(apiBaseUrl)}/fleet/enrol`,
7156
+ {
7157
+ method: "POST",
7158
+ headers: { "Content-Type": "application/json" },
7159
+ body: JSON.stringify(request2)
7160
+ },
7161
+ timeoutMs
7162
+ );
7163
+ if (r.status !== 201 && r.status !== 200) throw apiError(r.status, r.body, "Enrolment");
7164
+ const data = r.body;
7165
+ if (!data?.deviceId || !data.credential || !data.jwks || !data.policyJws) {
7166
+ throw new FleetApiError("Enrolment response is missing required fields", r.status);
7167
+ }
7168
+ return data;
7169
+ }
7170
+ var FleetApiError;
7171
+ var init_client2 = __esm({
7172
+ "src/fleet/client.ts"() {
7173
+ "use strict";
7174
+ init_http();
7175
+ FleetApiError = class extends Error {
7176
+ constructor(message, status, code) {
7177
+ super(message);
7178
+ this.status = status;
7179
+ this.code = code;
7180
+ this.name = "FleetApiError";
7181
+ }
7182
+ };
7183
+ }
7184
+ });
7185
+
7186
+ // src/fleet/enrolment-store.ts
7187
+ function machineScopeDir(platform2 = process.platform, env = process.env) {
7188
+ if (platform2 === "darwin") return "/Library/Application Support/AstraSync";
7189
+ if (platform2 === "win32") return (0, import_path5.join)(env.ProgramData || "C:\\ProgramData", "AstraSync");
7190
+ return "/etc/astrasync";
7191
+ }
7192
+ function fleetPaths(options = {}) {
7193
+ const home = options.home ?? (0, import_os.homedir)();
7194
+ const machineDir = options.machineDir ?? options.env?.ASTRASYNC_FLEET_MACHINE_DIR ?? machineScopeDir(options.platform, options.env);
7195
+ const userDir = (0, import_path5.join)(home, ".astrasync", "fleet");
7196
+ return {
7197
+ machineDevice: (0, import_path5.join)(machineDir, "fleet.json"),
7198
+ machinePolicy: (0, import_path5.join)(machineDir, "policy.jws"),
7199
+ userDir,
7200
+ userDevice: (0, import_path5.join)(userDir, "device.json"),
7201
+ userPolicy: (0, import_path5.join)(userDir, "policy.jws"),
7202
+ userKeyChain: (0, import_path5.join)(userDir, "keychain.json"),
7203
+ state: (0, import_path5.join)(userDir, "state.json")
7204
+ };
7205
+ }
7206
+ function isEnrolment(value) {
7207
+ const v = value;
7208
+ return !!v && typeof v.deviceId === "string" && typeof v.credential === "string" && typeof v.apiBaseUrl === "string" && !!v.org && typeof v.org.ownerId === "string" && !!v.jwks && Array.isArray(v.jwks.keys);
7209
+ }
7210
+ function loadEnrolment(paths) {
7211
+ const candidates = [
7212
+ [paths.machineDevice, "machine"],
7213
+ [paths.userDevice, "user"]
7214
+ ];
7215
+ for (const [path2, scope] of candidates) {
7216
+ if (!(0, import_fs6.existsSync)(path2)) continue;
7217
+ try {
7218
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
7219
+ if (isEnrolment(parsed)) return { enrolment: parsed, scope };
7220
+ } catch {
7221
+ }
7222
+ return { corrupt: true, path: path2 };
7223
+ }
7224
+ return null;
7225
+ }
7226
+ function writeFileMode(path2, content, mode) {
7227
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path2), { recursive: true, mode: 448 });
7228
+ (0, import_fs6.writeFileSync)(path2, content, { encoding: "utf-8", mode });
7229
+ try {
7230
+ (0, import_fs6.chmodSync)(path2, mode);
7231
+ } catch {
7232
+ }
7233
+ }
7234
+ function saveEnrolment(paths, enrolment, scope) {
7235
+ const path2 = scope === "machine" ? paths.machineDevice : paths.userDevice;
7236
+ writeFileMode(path2, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
7237
+ return path2;
7238
+ }
7239
+ function readCachedPolicies(paths) {
7240
+ const out = [];
7241
+ for (const p of [paths.userPolicy, paths.machinePolicy]) {
7242
+ try {
7243
+ if ((0, import_fs6.existsSync)(p)) out.push((0, import_fs6.readFileSync)(p, "utf-8").trim());
7244
+ } catch {
7245
+ }
7246
+ }
7247
+ return out.filter(Boolean);
7248
+ }
7249
+ function writeCachedPolicy(paths, jws) {
7250
+ writeFileMode(paths.userPolicy, jws, 384);
7251
+ }
7252
+ function readKeyChain(paths) {
7253
+ try {
7254
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(paths.userKeyChain, "utf-8"));
7255
+ return Array.isArray(parsed) ? parsed.filter((j) => typeof j === "string") : [];
7256
+ } catch {
7257
+ return [];
7258
+ }
7259
+ }
7260
+ function writeKeyChain(paths, chain) {
7261
+ writeFileMode(paths.userKeyChain, JSON.stringify(chain), 384);
7262
+ }
7263
+ function writeMachinePolicy(paths, jws) {
7264
+ writeFileMode(paths.machinePolicy, jws, 420);
7265
+ }
7266
+ function readState(paths) {
7267
+ try {
7268
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(paths.state, "utf-8"));
7269
+ const c = parsed.counts;
7270
+ return {
7271
+ ...parsed,
7272
+ counts: { allow: c?.allow ?? 0, deny: c?.deny ?? 0, ask: c?.ask ?? 0 }
7273
+ };
7274
+ } catch {
7275
+ return { counts: { allow: 0, deny: 0, ask: 0 } };
7276
+ }
7277
+ }
7278
+ function writeState(paths, state) {
7279
+ try {
7280
+ writeFileMode(paths.state, JSON.stringify(state, null, 2), 384);
7281
+ } catch {
7282
+ }
7283
+ }
7284
+ var import_fs6, import_os, import_path5;
7285
+ var init_enrolment_store = __esm({
7286
+ "src/fleet/enrolment-store.ts"() {
7287
+ "use strict";
7288
+ import_fs6 = require("fs");
7289
+ import_os = require("os");
7290
+ import_path5 = require("path");
7291
+ }
7292
+ });
7293
+
7294
+ // src/fleet/types.ts
7295
+ var FLEET_POLICY_TYP, FLEET_POLICY_AUD, FLEET_POLICY_ISS, FLEET_KEYS_TYP, FLEET_KEYS_AUD, DEFAULT_HEARTBEAT_INTERVAL_SEC;
7296
+ var init_types3 = __esm({
7297
+ "src/fleet/types.ts"() {
7298
+ "use strict";
7299
+ FLEET_POLICY_TYP = "fleet-policy+jwt";
7300
+ FLEET_POLICY_AUD = "astrasync:fleet-policy";
7301
+ FLEET_POLICY_ISS = "astrasync.ai";
7302
+ FLEET_KEYS_TYP = "fleet-keys+jwt";
7303
+ FLEET_KEYS_AUD = "astrasync:fleet-keys";
7304
+ DEFAULT_HEARTBEAT_INTERVAL_SEC = 900;
7305
+ }
7306
+ });
7307
+
7308
+ // src/fleet/policy-verify.ts
7309
+ function sha256Hex(text) {
7310
+ return (0, import_crypto.createHash)("sha256").update(text, "utf8").digest("hex");
7311
+ }
7312
+ function parseSettings(raw) {
7313
+ if (!raw || typeof raw !== "object") throw new FleetPolicyError("settings missing");
7314
+ const s = raw;
7315
+ if (s.posture !== "active" && s.posture !== "passive") {
7316
+ throw new FleetPolicyError("settings.posture must be active or passive");
7317
+ }
7318
+ if (s.failOpen !== void 0 && typeof s.failOpen !== "boolean") {
7319
+ throw new FleetPolicyError("settings.failOpen must be a boolean");
7320
+ }
7321
+ if (s.governMcp !== void 0 && typeof s.governMcp !== "boolean") {
7322
+ throw new FleetPolicyError("settings.governMcp must be a boolean");
7323
+ }
7324
+ if (s.skipTools !== void 0 && !(Array.isArray(s.skipTools) && s.skipTools.every((t) => typeof t === "string"))) {
7325
+ throw new FleetPolicyError("settings.skipTools must be a string array");
7326
+ }
7327
+ const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
7328
+ return {
7329
+ posture: s.posture,
7330
+ failOpen: s.failOpen === true,
7331
+ ...typeof s.governMcp === "boolean" && { governMcp: s.governMcp },
7332
+ ...Array.isArray(s.skipTools) && { skipTools: s.skipTools },
7333
+ ...num(s.heartbeatIntervalSec) && { heartbeatIntervalSec: num(s.heartbeatIntervalSec) },
7334
+ ...num(s.staleAfterSec) && { staleAfterSec: num(s.staleAfterSec) }
7335
+ };
7336
+ }
7337
+ async function verifyFleetPolicy(jws, jwks, options) {
7338
+ let payloadBytes;
7339
+ let header;
7340
+ try {
7341
+ const keySet = (0, import_jose2.createLocalJWKSet)(jwks);
7342
+ const result = await (0, import_jose2.compactVerify)(jws, keySet, { algorithms: ["ES256"] });
7343
+ payloadBytes = result.payload;
7344
+ header = result.protectedHeader;
7345
+ } catch (err) {
7346
+ throw new FleetPolicyError(
7347
+ `signature verification failed (${err instanceof Error ? err.message : String(err)})`
7348
+ );
7349
+ }
7350
+ if (header.typ !== FLEET_POLICY_TYP) throw new FleetPolicyError(`unexpected typ ${header.typ}`);
7351
+ let claims;
7352
+ try {
7353
+ claims = JSON.parse(new TextDecoder().decode(payloadBytes));
7354
+ } catch {
7355
+ throw new FleetPolicyError("payload is not JSON");
7356
+ }
7357
+ if (claims.aud !== FLEET_POLICY_AUD) throw new FleetPolicyError(`unexpected aud ${claims.aud}`);
7358
+ if (claims.iss !== FLEET_POLICY_ISS) throw new FleetPolicyError(`unexpected iss ${claims.iss}`);
7359
+ if (!options.expectedSubs.includes(claims.sub)) {
7360
+ throw new FleetPolicyError("policy was issued for a different organisation or device");
7361
+ }
7362
+ if (!Number.isInteger(claims.ver) || claims.ver < 1) {
7363
+ throw new FleetPolicyError("ver must be a positive integer");
7364
+ }
7365
+ if (options.minVer !== void 0 && claims.ver < options.minVer) {
7366
+ throw new FleetPolicyError(
7367
+ `policy version ${claims.ver} is older than the accepted version ${options.minVer} (rollback)`
7368
+ );
7369
+ }
7370
+ if (typeof claims.yaml !== "string" || sha256Hex(claims.yaml) !== claims.sha256) {
7371
+ throw new FleetPolicyError("policy hash mismatch");
7372
+ }
7373
+ const settings = parseSettings(claims.settings);
7374
+ const parsed = parseYaml(claims.yaml);
7375
+ if (!parsed.policy) {
7376
+ const first = parsed.validation.errors[0];
7377
+ throw new FleetPolicyError(
7378
+ `signed policy is invalid${first ? ` (${first.path}: ${first.message})` : ""}`
7379
+ );
7380
+ }
7381
+ return { kid: header.kid, claims: { ...claims, settings }, policy: parsed.policy, settings, jws };
7382
+ }
7383
+ async function resolveFleetJwks(pinned, chain, ownerId) {
7384
+ let trusted = pinned;
7385
+ let lastSeq = 0;
7386
+ for (const link of chain) {
7387
+ try {
7388
+ const { payload, protectedHeader } = await (0, import_jose2.compactVerify)(
7389
+ link,
7390
+ (0, import_jose2.createLocalJWKSet)(trusted),
7391
+ { algorithms: ["ES256"] }
7392
+ );
7393
+ if (protectedHeader.typ !== FLEET_KEYS_TYP) continue;
7394
+ const c = JSON.parse(new TextDecoder().decode(payload));
7395
+ if (c.aud !== FLEET_KEYS_AUD || c.iss !== FLEET_POLICY_ISS || c.sub !== ownerId) continue;
7396
+ if (!Number.isInteger(c.seq) || c.seq <= lastSeq) continue;
7397
+ const keys = Array.isArray(c.keys) ? c.keys.filter((k) => k && k.kty === "EC" && k.crv === "P-256" && !("d" in k)) : [];
7398
+ if (keys.length === 0) continue;
7399
+ trusted = { keys };
7400
+ lastSeq = c.seq;
7401
+ } catch {
7402
+ }
7403
+ }
7404
+ return trusted;
7405
+ }
7406
+ async function pickNewestValid(candidates, jwks, options) {
7407
+ let best = null;
7408
+ let lastError = new FleetPolicyError("no cached fleet policy");
7409
+ for (const jws of candidates) {
7410
+ try {
7411
+ const v = await verifyFleetPolicy(jws, jwks, options);
7412
+ if (!best || v.claims.ver > best.claims.ver) best = v;
7413
+ } catch (err) {
7414
+ lastError = err instanceof Error ? err : new FleetPolicyError(String(err));
7415
+ }
7416
+ }
7417
+ if (!best) throw lastError;
7418
+ return best;
7419
+ }
7420
+ var import_crypto, import_jose2, FleetPolicyError;
7421
+ var init_policy_verify = __esm({
7422
+ "src/fleet/policy-verify.ts"() {
7423
+ "use strict";
7424
+ import_crypto = require("crypto");
7425
+ import_jose2 = require("jose");
7426
+ init_pdlss_parser();
7427
+ init_types3();
7428
+ FleetPolicyError = class extends Error {
7429
+ constructor(message) {
7430
+ super(message);
7431
+ this.name = "FleetPolicyError";
7432
+ }
7433
+ };
7434
+ }
7435
+ });
7436
+
7437
+ // src/fleet/cli.ts
7438
+ function machineFingerprint() {
7439
+ const candidates = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
7440
+ let raw = "";
7441
+ for (const c of candidates) {
7442
+ try {
7443
+ if ((0, import_fs7.existsSync)(c)) {
7444
+ raw = (0, import_fs7.readFileSync)(c, "utf-8").trim();
7445
+ if (raw) break;
7446
+ }
7447
+ } catch {
7448
+ }
7449
+ }
7450
+ if (!raw) raw = `${(0, import_os2.hostname)()}|${(0, import_os2.platform)()}|${(0, import_os2.arch)()}`;
7451
+ return (0, import_crypto2.createHash)("sha256").update(`astrasync-fleet:${raw}`).digest("hex");
7452
+ }
7453
+ async function runEnroll(options) {
7454
+ const print = options.print ?? ((l) => console.log(l));
7455
+ const token = options.token.trim();
7456
+ if (!token)
7457
+ throw new Error("Usage: astrasync guard enroll <token> [--api-base-url <url>] [--system]");
7458
+ const apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_FLEET_API_BASE_URL).replace(/\/+$/, "");
7459
+ const paths = options.paths ?? fleetPaths();
7460
+ const response = await enrolDevice(apiBaseUrl, {
7461
+ token,
7462
+ fingerprint: options.fingerprint ?? machineFingerprint(),
7463
+ hostname: (0, import_os2.hostname)(),
7464
+ os: { platform: (0, import_os2.platform)(), release: (0, import_os2.release)(), arch: (0, import_os2.arch)() },
7465
+ guardVersion: SDK_VERSION,
7466
+ adapters: options.adapters ?? [
7467
+ { kind: "claude-code", managed: false },
7468
+ { kind: "codex", managed: false }
7469
+ ]
7470
+ });
7471
+ const enrolment = {
7472
+ version: 1,
7473
+ deviceId: response.deviceId,
7474
+ credential: response.credential,
7475
+ org: response.org,
7476
+ apiBaseUrl: response.apiBaseUrl || apiBaseUrl,
7477
+ jwks: response.jwks,
7478
+ heartbeatIntervalSec: response.heartbeatIntervalSec || DEFAULT_HEARTBEAT_INTERVAL_SEC,
7479
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
7480
+ };
7481
+ const verified = await verifyFleetPolicy(response.policyJws, enrolment.jwks, {
7482
+ expectedSubs: [enrolment.org.ownerId, enrolment.deviceId]
7483
+ });
7484
+ const scope = options.system ? "machine" : "user";
7485
+ const savedTo = saveEnrolment(paths, enrolment, scope);
7486
+ if (options.system) writeMachinePolicy(paths, response.policyJws);
7487
+ writeCachedPolicy(paths, response.policyJws);
7488
+ writeKeyChain(paths, []);
7489
+ const state = readState(paths);
7490
+ writeState(paths, {
7491
+ ...state,
7492
+ appliedSha256: verified.claims.sha256,
7493
+ maxVer: Math.max(state.maxVer ?? 0, verified.claims.ver),
7494
+ revoked: false
7495
+ });
7496
+ print(
7497
+ `Enrolled in ${enrolment.org.name ?? enrolment.org.ownerId} (device ${enrolment.deviceId}).`
7498
+ );
7499
+ print(` Enrolment: ${savedTo} (${scope} scope)`);
7500
+ print(` Policy: v${verified.claims.ver}, posture ${verified.settings.posture}`);
7501
+ print(
7502
+ " Local policy.yaml / guard.json files are now ignored \u2014 the organisation policy governs."
7503
+ );
7504
+ return enrolment;
7505
+ }
7506
+ async function getFleetStatus(paths = fleetPaths()) {
7507
+ const loaded = loadEnrolment(paths);
7508
+ if (!loaded) return { enrolled: false };
7509
+ if ("corrupt" in loaded) return { enrolled: true, corrupt: loaded.path };
7510
+ const { enrolment, scope } = loaded;
7511
+ const state = readState(paths);
7512
+ let policy;
7513
+ try {
7514
+ const jwks = await resolveFleetJwks(enrolment.jwks, readKeyChain(paths), enrolment.org.ownerId);
7515
+ const v = await pickNewestValid(readCachedPolicies(paths), jwks, {
7516
+ expectedSubs: [enrolment.org.ownerId, enrolment.deviceId],
7517
+ minVer: state.maxVer
7518
+ });
7519
+ policy = {
7520
+ ver: v.claims.ver,
7521
+ sha256: v.claims.sha256,
7522
+ posture: v.settings.posture,
7523
+ failOpen: v.settings.failOpen
7524
+ };
7525
+ } catch (err) {
7526
+ policy = { error: err instanceof Error ? err.message : String(err) };
7527
+ }
7528
+ return {
7529
+ enrolled: true,
7530
+ scope,
7531
+ org: enrolment.org,
7532
+ deviceId: enrolment.deviceId,
7533
+ apiBaseUrl: enrolment.apiBaseUrl,
7534
+ policy,
7535
+ lastHeartbeatAt: state.lastHeartbeatAt,
7536
+ revoked: state.revoked === true,
7537
+ lastError: state.lastError,
7538
+ pendingCounts: state.counts
7539
+ };
7540
+ }
7541
+ function formatFleetStatus(status) {
7542
+ if (!status.enrolled) return ["Fleet: not enrolled (local policy mode)."];
7543
+ if (status.corrupt) {
7544
+ return [`Fleet: enrolment file ${status.corrupt} is unreadable \u2014 the guard fails closed.`];
7545
+ }
7546
+ const lines = [
7547
+ `Fleet: enrolled in ${status.org?.name ?? status.org?.ownerId} (${status.scope} scope)`,
7548
+ ` Device: ${status.deviceId}`,
7549
+ ` Platform: ${status.apiBaseUrl}`
7550
+ ];
7551
+ if (status.policy && "error" in status.policy) {
7552
+ lines.push(` Policy: NO VALID SIGNED POLICY \u2014 ${status.policy.error} (guard denies)`);
7553
+ } else if (status.policy) {
7554
+ lines.push(
7555
+ ` Policy: v${status.policy.ver} (${status.policy.sha256.slice(0, 12)}\u2026), posture ${status.policy.posture}${status.policy.failOpen ? ", fail-open" : ""}`
7556
+ );
7557
+ }
7558
+ lines.push(` Heartbeat: ${status.lastHeartbeatAt ?? "never"}`);
7559
+ if (status.revoked) {
7560
+ lines.push(" Status: REVOKED by the organisation \u2014 enforcing the last good policy");
7561
+ }
7562
+ if (status.lastError) lines.push(` Last sync error: ${status.lastError}`);
7563
+ return lines;
7564
+ }
7565
+ function managedHookCommand(target, os2) {
7566
+ const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
7567
+ if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
7568
+ return `/usr/local/bin/${bin}`;
7569
+ }
7570
+ function buildClaudeManagedSettings(os2) {
7571
+ return {
7572
+ allowManagedHooksOnly: true,
7573
+ env: { ASTRASYNC_FLEET_REQUIRED: "1" },
7574
+ hooks: {
7575
+ PreToolUse: [
7576
+ {
7577
+ matcher: HOOK_MATCHER,
7578
+ hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
7579
+ }
7580
+ ]
7581
+ },
7582
+ permissions: { deny: [...GUARD_PERMISSION_DENY_RULES] }
7583
+ };
7584
+ }
7585
+ function tomlString(value) {
7586
+ return JSON.stringify(value);
7587
+ }
7588
+ function buildCodexRequirementsToml(os2) {
7589
+ const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
7590
+ return [
7591
+ "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
7592
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
7593
+ "allow_managed_hooks_only = true",
7594
+ "",
7595
+ "[features]",
7596
+ "hooks = true",
7597
+ "",
7598
+ "[[hooks.PreToolUse]]",
7599
+ `matcher = ${tomlString(CODEX_HOOK_MATCHER)}`,
7600
+ "",
7601
+ "[[hooks.PreToolUse.hooks]]",
7602
+ 'type = "command"',
7603
+ `command = ${tomlString(command)}`,
7604
+ "timeout = 10",
7605
+ 'statusMessage = "AstraSync Local Guard"',
7606
+ ""
7607
+ ].join("\n");
7608
+ }
7609
+ function buildManagedSettingsFiles(targets) {
7610
+ const out = [];
7611
+ for (const target of targets) {
7612
+ for (const os2 of ["darwin", "linux", "win32"]) {
7613
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os2), null, 2)}
7614
+ ` : buildCodexRequirementsToml(os2);
7615
+ out.push({
7616
+ target,
7617
+ os: os2,
7618
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
7619
+ fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
7620
+ content
7621
+ });
7622
+ }
7623
+ }
7624
+ return out;
7625
+ }
7626
+ function writeManagedSettings(files, outDir) {
7627
+ (0, import_fs7.mkdirSync)(outDir, { recursive: true });
7628
+ return files.map((f) => {
7629
+ const p = (0, import_path6.join)(outDir, f.fileName);
7630
+ (0, import_fs7.writeFileSync)(p, f.content, "utf-8");
7631
+ return p;
7632
+ });
7633
+ }
7634
+ var import_crypto2, import_fs7, import_os2, import_path6, DEFAULT_FLEET_API_BASE_URL, MANAGED_SETTINGS_LOCATIONS;
7635
+ var init_cli = __esm({
7636
+ "src/fleet/cli.ts"() {
7637
+ "use strict";
7638
+ import_crypto2 = require("crypto");
7639
+ import_fs7 = require("fs");
7640
+ import_os2 = require("os");
7641
+ import_path6 = require("path");
7642
+ init_settings_installer();
7643
+ init_settings_installer2();
7644
+ init_version();
7645
+ init_client2();
7646
+ init_enrolment_store();
7647
+ init_policy_verify();
7648
+ init_types3();
7649
+ DEFAULT_FLEET_API_BASE_URL = "https://astrasync.ai/api";
7650
+ MANAGED_SETTINGS_LOCATIONS = {
7651
+ // code.claude.com/docs/en/managed-settings
7652
+ claude: {
7653
+ darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
7654
+ linux: "/etc/claude-code/managed-settings.json",
7655
+ win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
7656
+ },
7657
+ // learn.chatgpt.com/docs/enterprise/managed-configuration
7658
+ codex: {
7659
+ darwin: "/etc/codex/requirements.toml",
7660
+ linux: "/etc/codex/requirements.toml",
7661
+ win32: "%ProgramData%\\OpenAI\\Codex\\requirements.toml"
7662
+ }
7663
+ };
7664
+ }
7665
+ });
7666
+
7143
7667
  // src/cli/guard.ts
7144
7668
  var guard_exports = {};
7145
7669
  function createPrompt() {
@@ -7278,6 +7802,9 @@ async function cmdStatus() {
7278
7802
  }
7279
7803
  });
7280
7804
  console.log("\nAstraSync Local Guard Status\n");
7805
+ for (const line of formatFleetStatus(await getFleetStatus(fleetPaths()))) {
7806
+ console.log(` ${line}`);
7807
+ }
7281
7808
  if (upgraded) {
7282
7809
  const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
7283
7810
  console.log(` Mode: ${config.mode}`);
@@ -7633,6 +8160,66 @@ function printClaudeCodeStatus() {
7633
8160
  }
7634
8161
  }
7635
8162
  }
8163
+ function flagValue(flag) {
8164
+ const i = process.argv.indexOf(flag);
8165
+ return i !== -1 ? process.argv[i + 1] : void 0;
8166
+ }
8167
+ function positionalAfter(command) {
8168
+ const argv = process.argv;
8169
+ const start = argv.indexOf(command) + 1;
8170
+ for (let i = start; i < argv.length; i++) {
8171
+ if (argv[i].startsWith("--")) {
8172
+ if (argv[i] === "--api-base-url" || argv[i] === "--out") i++;
8173
+ continue;
8174
+ }
8175
+ return argv[i];
8176
+ }
8177
+ return void 0;
8178
+ }
8179
+ async function cmdEnroll() {
8180
+ const token = positionalAfter("enroll") ?? "";
8181
+ try {
8182
+ await runEnroll({
8183
+ token,
8184
+ apiBaseUrl: flagValue("--api-base-url"),
8185
+ system: process.argv.includes("--system")
8186
+ });
8187
+ } catch (err) {
8188
+ console.error(`Enrolment failed: ${err instanceof Error ? err.message : String(err)}`);
8189
+ if (process.argv.includes("--system")) {
8190
+ console.error(
8191
+ " (--system writes the machine-scope enrolment and needs administrator rights)"
8192
+ );
8193
+ }
8194
+ process.exit(1);
8195
+ }
8196
+ }
8197
+ async function cmdFleetStatus() {
8198
+ const status = await getFleetStatus(fleetPaths());
8199
+ for (const line of formatFleetStatus(status)) console.log(line);
8200
+ }
8201
+ async function cmdManagedSettings() {
8202
+ const targets = [];
8203
+ if (process.argv.includes("--claude")) targets.push("claude");
8204
+ if (process.argv.includes("--codex")) targets.push("codex");
8205
+ if (targets.length === 0) targets.push("claude", "codex");
8206
+ const files = buildManagedSettingsFiles(targets);
8207
+ const outDir = flagValue("--out");
8208
+ if (outDir) {
8209
+ const written = writeManagedSettings(files, outDir);
8210
+ console.log("Managed-settings templates written:");
8211
+ files.forEach((f, i) => console.log(` ${written[i]} \u2192 deploy to ${f.installPath}`));
8212
+ } else {
8213
+ for (const f of files) {
8214
+ console.log(`===== ${f.target} \xB7 ${f.os} \u2192 ${f.installPath}`);
8215
+ console.log(f.content);
8216
+ }
8217
+ }
8218
+ console.log(
8219
+ "Hook commands assume npm\u2019s default global prefix; adjust if your MDM installs elsewhere."
8220
+ );
8221
+ console.log("Machines must also be enrolled: astrasync guard enroll <token> --system");
8222
+ }
7636
8223
  async function main() {
7637
8224
  const command = process.argv[2] === "guard" ? process.argv[3] : process.argv[2];
7638
8225
  switch (command) {
@@ -7669,6 +8256,16 @@ async function main() {
7669
8256
  case "uninstall-codex":
7670
8257
  await cmdUninstallCodex();
7671
8258
  break;
8259
+ case "enroll":
8260
+ case "enrol":
8261
+ await cmdEnroll();
8262
+ break;
8263
+ case "fleet-status":
8264
+ await cmdFleetStatus();
8265
+ break;
8266
+ case "managed-settings":
8267
+ await cmdManagedSettings();
8268
+ break;
7672
8269
  default:
7673
8270
  console.log("AstraSync Local Guard\n");
7674
8271
  console.log("Usage: astrasync guard <command>\n");
@@ -7689,6 +8286,10 @@ async function main() {
7689
8286
  console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
7690
8287
  console.log(" (same flags; policy shared with Claude Code)");
7691
8288
  console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
8289
+ console.log(" enroll <token> Enrol this machine in your organisation fleet");
8290
+ console.log(" (--api-base-url <url>, --system for machine scope)");
8291
+ console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");
8292
+ console.log(" managed-settings Print MDM templates (--claude, --codex, --out <dir>)");
7692
8293
  console.log("");
7693
8294
  process.exit(command ? 1 : 0);
7694
8295
  }
@@ -7709,6 +8310,8 @@ var init_guard = __esm({
7709
8310
  init_git_hooks();
7710
8311
  init_settings_installer();
7711
8312
  init_settings_installer2();
8313
+ init_cli();
8314
+ init_enrolment_store();
7712
8315
  DEFAULT_POLICY_PATH = "./local-policy.yaml";
7713
8316
  DEFAULT_CONFIG_DIR = ".astrasync";
7714
8317
  main().catch((err) => {
@@ -7741,6 +8344,10 @@ COMMANDS:
7741
8344
  verify <id> Look up an agent by ASTRA ID or UUID
7742
8345
  health Check API health
7743
8346
  guard <subcommand> Local Guard CLI (setup, start, status, install-claude-code, ...)
8347
+ guard enroll <token> Enrol this machine in your organisation's fleet
8348
+ (--api-base-url <url>, --system for machine scope)
8349
+ guard fleet-status Fleet enrolment, signed policy and heartbeat state
8350
+ guard managed-settings MDM templates for Claude Code / Codex (--claude, --codex, --out <dir>)
7744
8351
 
7745
8352
  REGISTER OPTIONS:
7746
8353
  --name <name> Agent name (required)