@aria_asi/cli 0.2.32 → 0.2.33

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 (51) hide show
  1. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
  2. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
  3. package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
  4. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  6. package/dist/aria-connector/src/connectors/codex.js +51 -0
  7. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  8. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  9. package/dist/aria-connector/src/setup-wizard.js +91 -24
  10. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  11. package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
  12. package/dist/assets/hooks/aria-pre-tool-gate.mjs +19 -0
  13. package/dist/assets/hooks/aria-stop-gate.mjs +27 -2
  14. package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
  15. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -0
  16. package/dist/assets/opencode-plugins/harness-gate/index.js +67 -3
  17. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  18. package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
  19. package/dist/assets/opencode-plugins/harness-stop/index.js +61 -1
  20. package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  21. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  22. package/dist/runtime/codex-bridge.mjs +71 -8
  23. package/dist/runtime/harness-daemon.mjs +50 -2
  24. package/dist/runtime/manifest.json +1 -1
  25. package/dist/runtime/sdk/BUNDLED.json +1 -1
  26. package/dist/runtime/sdk/index.d.ts +9 -0
  27. package/dist/runtime/sdk/index.js +23 -1
  28. package/dist/runtime/sdk/index.js.map +1 -1
  29. package/dist/runtime/service.mjs +339 -10
  30. package/dist/sdk/BUNDLED.json +1 -1
  31. package/dist/sdk/index.d.ts +9 -0
  32. package/dist/sdk/index.js +23 -1
  33. package/dist/sdk/index.js.map +1 -1
  34. package/hooks/aria-harness-via-sdk.mjs +10 -5
  35. package/hooks/aria-pre-tool-gate.mjs +19 -0
  36. package/hooks/aria-stop-gate.mjs +27 -2
  37. package/hooks/lib/domain-output-quality.mjs +103 -0
  38. package/hooks/lib/skill-autoload-gate.mjs +1 -0
  39. package/opencode-plugins/harness-gate/index.js +67 -3
  40. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  41. package/opencode-plugins/harness-outcome/index.js +39 -0
  42. package/opencode-plugins/harness-stop/index.js +61 -1
  43. package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  44. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  45. package/package.json +1 -1
  46. package/runtime-src/codex-bridge.mjs +71 -8
  47. package/runtime-src/harness-daemon.mjs +50 -2
  48. package/runtime-src/service.mjs +339 -10
  49. package/src/connectors/codebase-awareness.ts +141 -77
  50. package/src/connectors/codex.ts +51 -0
  51. package/src/setup-wizard.ts +105 -25
@@ -68,6 +68,7 @@ const OFFLINE_BUNDLE_PATH = join(STATE_DIR, 'offline-policy-bundle.enc');
68
68
  const RUNTIME_META_PATH = join(STATE_DIR, 'runtime-meta.json');
69
69
  const AUTONOMY_STATE_PATH = join(STATE_DIR, 'autonomy.json');
70
70
  const COGNITION_STATE_PATH = join(STATE_DIR, 'cognition-state.enc');
71
+ const HIVE_FILE_LEASES_PATH = join(STATE_DIR, 'hive-file-leases.json');
71
72
  const REVOCATION_LOCK_PATH = join(STATE_DIR, 'revoked.json');
72
73
  const CONFIG_PATH = join(process.env.HOME || '', '.aria', 'config.json');
73
74
  const CODEBASE_AWARENESS_STATE_PATH = join(process.env.HOME || '', '.aria', 'codebase-awareness-state.json');
@@ -906,6 +907,248 @@ function writeJsonFile(filePath, payload, mode = 0o600) {
906
907
  writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\n', { mode });
907
908
  }
908
909
 
910
+ function readHiveFileLeaseState() {
911
+ const state = readJsonFile(HIVE_FILE_LEASES_PATH, { leases: [] });
912
+ return {
913
+ leases: Array.isArray(state.leases) ? state.leases : [],
914
+ };
915
+ }
916
+
917
+ function writeHiveFileLeaseState(state) {
918
+ writeJsonFile(HIVE_FILE_LEASES_PATH, {
919
+ updatedAt: new Date().toISOString(),
920
+ leases: Array.isArray(state.leases) ? state.leases : [],
921
+ });
922
+ }
923
+
924
+ function normalizeLeasePath(value) {
925
+ const raw = String(value || '').trim();
926
+ if (!raw) return '';
927
+ return raw.replace(/\\/g, '/').replace(/\/+/g, '/');
928
+ }
929
+
930
+ function pathsOverlap(left, right) {
931
+ const a = normalizeLeasePath(left);
932
+ const b = normalizeLeasePath(right);
933
+ if (!a || !b) return false;
934
+ return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
935
+ }
936
+
937
+ function coerceTargetFiles(body) {
938
+ const files = [];
939
+ const push = (value) => {
940
+ const normalized = normalizeLeasePath(value);
941
+ if (normalized && !files.includes(normalized)) files.push(normalized);
942
+ };
943
+
944
+ if (Array.isArray(body.files)) body.files.forEach(push);
945
+ if (Array.isArray(body.targetFiles)) body.targetFiles.forEach(push);
946
+ if (typeof body.filePath === 'string') push(body.filePath);
947
+ if (typeof body.target === 'string') {
948
+ try {
949
+ const parsed = JSON.parse(body.target);
950
+ if (typeof parsed?.filePath === 'string') push(parsed.filePath);
951
+ if (Array.isArray(parsed?.files)) parsed.files.forEach(push);
952
+ if (Array.isArray(parsed?.targetFiles)) parsed.targetFiles.forEach(push);
953
+ } catch {}
954
+ }
955
+ return files;
956
+ }
957
+
958
+ function extractVerifyText(body) {
959
+ const candidates = [body.verifyText, body.verifyBlock, body.text, body.target];
960
+ for (const candidate of candidates) {
961
+ if (typeof candidate === 'string' && candidate.trim()) return candidate;
962
+ }
963
+ return '';
964
+ }
965
+
966
+ function actionRequiresExplicitVerify(action, body) {
967
+ if (body.requireVerify === true) return true;
968
+ return /^(?:delete|deploy|db_mutation|infra_mutation)$/i.test(String(action || ''));
969
+ }
970
+
971
+ function buildObservedVerify(action, files, sessionId, roleProfile) {
972
+ const observations = files.slice(0, 12).map((filePath) => {
973
+ try {
974
+ const stats = statSync(filePath);
975
+ return {
976
+ path: filePath,
977
+ exists: true,
978
+ isFile: stats.isFile(),
979
+ isDirectory: stats.isDirectory(),
980
+ size: stats.size,
981
+ mtimeMs: stats.mtimeMs,
982
+ };
983
+ } catch {
984
+ return { path: filePath, exists: false };
985
+ }
986
+ });
987
+ return {
988
+ target: files,
989
+ role: roleProfile || 'unknown',
990
+ verified: [
991
+ 'runtime observed target file metadata before action',
992
+ 'Hive file lease check completed before mutation',
993
+ ],
994
+ observations,
995
+ rollback: 'Use VCS diff or prior file snapshot for rollback; no destructive rollback was inferred by runtime.',
996
+ axiom: 'truth_over_deception/no_harm/sacred_trust/reflection_before_action',
997
+ sessionId,
998
+ action,
999
+ };
1000
+ }
1001
+
1002
+ function acquireHiveFileLease({ sessionId, actor, roleProfile, action, files, ttlMs = 120000 }) {
1003
+ const now = Date.now();
1004
+ const expiresAt = new Date(now + ttlMs).toISOString();
1005
+ const normalizedFiles = files.map(normalizeLeasePath).filter(Boolean);
1006
+ const state = readHiveFileLeaseState();
1007
+ const activeLeases = state.leases.filter((lease) => Date.parse(lease.expiresAt || '') > now);
1008
+ const conflicts = activeLeases.filter((lease) => {
1009
+ if (lease.sessionId === sessionId) return false;
1010
+ const leaseFiles = Array.isArray(lease.files) ? lease.files : [];
1011
+ return normalizedFiles.some((file) => leaseFiles.some((leaseFile) => pathsOverlap(file, leaseFile)));
1012
+ });
1013
+
1014
+ if (conflicts.length > 0) {
1015
+ writeHiveFileLeaseState({ leases: activeLeases });
1016
+ return {
1017
+ status: 'blocked',
1018
+ leaseId: null,
1019
+ expiresAt: null,
1020
+ conflicts: conflicts.map((lease) => ({
1021
+ leaseId: lease.leaseId,
1022
+ sessionId: lease.sessionId,
1023
+ actor: lease.actor,
1024
+ roleProfile: lease.roleProfile,
1025
+ action: lease.action,
1026
+ files: lease.files,
1027
+ expiresAt: lease.expiresAt,
1028
+ })),
1029
+ };
1030
+ }
1031
+
1032
+ const existing = activeLeases.filter((lease) => lease.sessionId !== sessionId);
1033
+ const lease = {
1034
+ leaseId: `hive_lease_${randomUUID()}`,
1035
+ sessionId,
1036
+ actor: actor || 'unknown',
1037
+ roleProfile: roleProfile || 'unknown',
1038
+ action,
1039
+ files: normalizedFiles,
1040
+ grantedAt: new Date(now).toISOString(),
1041
+ expiresAt,
1042
+ };
1043
+ writeHiveFileLeaseState({ leases: [...existing, lease] });
1044
+ return { status: 'granted', leaseId: lease.leaseId, expiresAt, conflicts: [] };
1045
+ }
1046
+
1047
+ function releaseHiveFileLease(sessionId, files = []) {
1048
+ const normalizedFiles = files.map(normalizeLeasePath).filter(Boolean);
1049
+ const state = readHiveFileLeaseState();
1050
+ const now = Date.now();
1051
+ const leases = state.leases.filter((lease) => {
1052
+ if (Date.parse(lease.expiresAt || '') <= now) return false;
1053
+ if (lease.sessionId !== sessionId) return true;
1054
+ if (normalizedFiles.length === 0) return false;
1055
+ const leaseFiles = Array.isArray(lease.files) ? lease.files : [];
1056
+ return !normalizedFiles.some((file) => leaseFiles.some((leaseFile) => pathsOverlap(file, leaseFile)));
1057
+ });
1058
+ writeHiveFileLeaseState({ leases });
1059
+ return { released: state.leases.length - leases.length, active: leases.length };
1060
+ }
1061
+
1062
+ function validateVerifyAndHive({ req, body, action }) {
1063
+ const sessionId = deriveSessionId(req, body, 'runtime-action');
1064
+ const files = coerceTargetFiles(body);
1065
+ const roleProfile = body.roleProfile || body.role_profile || body?.metadata?.roleProfile || body?.metadata?.role_profile || null;
1066
+ const actor = body.actor || body?.metadata?.actor || req.headers['x-aria-actor'] || 'runtime-client';
1067
+ const explicitVerifyRequired = actionRequiresExplicitVerify(action, body);
1068
+ const verifyText = extractVerifyText(body);
1069
+ const hasVerifyBlock = VERIFY_BLOCK_RX.test(verifyText);
1070
+ const canUseObservedVerify = !explicitVerifyRequired && files.length > 0 && /^(?:write|edit|bash)$/i.test(String(action || ''));
1071
+
1072
+ if (explicitVerifyRequired && !hasVerifyBlock) {
1073
+ return {
1074
+ allowed: false,
1075
+ reason: 'verify block required before this high-risk action. Include <verify> with target, role, verified, rollback, and axiom.',
1076
+ requiredGates: ['verify-block'],
1077
+ verify: { status: 'missing', required: true },
1078
+ hive: { status: 'not_checked', conflicts: [] },
1079
+ };
1080
+ }
1081
+
1082
+ if (!hasVerifyBlock && !canUseObservedVerify && explicitVerifyRequired) {
1083
+ return {
1084
+ allowed: false,
1085
+ reason: 'verify block required before mutation, or target files must be provided so runtime can perform observed verification.',
1086
+ requiredGates: ['verify-block-or-observed-target-files'],
1087
+ verify: { status: 'missing', required: true },
1088
+ hive: { status: 'not_checked', conflicts: [] },
1089
+ };
1090
+ }
1091
+
1092
+ if (!hasVerifyBlock && !canUseObservedVerify && files.length === 0) {
1093
+ return {
1094
+ allowed: true,
1095
+ reason: 'no target files supplied for Hive lease; remote action gate will still evaluate the action',
1096
+ requiredGates: [],
1097
+ verify: { status: 'skipped', required: false },
1098
+ hive: { status: 'skipped', leaseId: null, expiresAt: null, conflicts: [] },
1099
+ };
1100
+ }
1101
+
1102
+ const hive = files.length > 0
1103
+ ? acquireHiveFileLease({ sessionId, actor, roleProfile, action, files })
1104
+ : { status: 'skipped', leaseId: null, expiresAt: null, conflicts: [] };
1105
+ if (hive.status === 'blocked') {
1106
+ const verify = hasVerifyBlock
1107
+ ? { status: 'provided', required: explicitVerifyRequired }
1108
+ : { status: 'observed', required: false, block: buildObservedVerify(action, files, sessionId, roleProfile) };
1109
+ const queued = enqueueAutonomyJob({
1110
+ kind: 'tool_lane:queued_action',
1111
+ surface: 'tool_lane',
1112
+ sessionId,
1113
+ priority: 15,
1114
+ message: 'Tool lane contention queued this action instead of failing it.',
1115
+ payload: { action, files, verify },
1116
+ metadata: {
1117
+ actor,
1118
+ roleProfile,
1119
+ conflicts: hive.conflicts,
1120
+ recovery: {
1121
+ mode: 'queued_not_failed',
1122
+ reason: 'overlapping_hive_file_lease',
1123
+ next: 'Retry when the overlapping Hive lease expires or a tool-lane worker claims this queued action.',
1124
+ },
1125
+ },
1126
+ });
1127
+ return {
1128
+ allowed: false,
1129
+ queued: true,
1130
+ queue: {
1131
+ jobId: queued.job.jobId,
1132
+ kind: queued.job.kind,
1133
+ status: queued.job.status,
1134
+ queueDepth: queued.stats.queued,
1135
+ },
1136
+ reason: `Tool lane queued this action because another active session is editing overlapping file(s): ${hive.conflicts.map((c) => `${c.sessionId}:${(c.files || []).join(',')}`).join(' | ')}`,
1137
+ requiredGates: ['hive-file-lease'],
1138
+ verify,
1139
+ hive,
1140
+ };
1141
+ }
1142
+
1143
+ return {
1144
+ allowed: true,
1145
+ reason: 'verify and Hive pre-action checks passed',
1146
+ requiredGates: [],
1147
+ verify: hasVerifyBlock ? { status: 'provided', required: explicitVerifyRequired } : { status: 'observed', required: false, block: buildObservedVerify(action, files, sessionId, roleProfile) },
1148
+ hive,
1149
+ };
1150
+ }
1151
+
909
1152
  function ensureRuntimeMeta() {
910
1153
  const existing = readJsonFile(RUNTIME_META_PATH, null);
911
1154
  if (existing?.runtimeId) return existing;
@@ -972,6 +1215,33 @@ function queueStats(state) {
972
1215
  };
973
1216
  }
974
1217
 
1218
+ function enqueueAutonomyJob(jobDef) {
1219
+ const state = sweepAutonomyState(loadAutonomyState());
1220
+ const now = new Date().toISOString();
1221
+ const job = {
1222
+ jobId: randomUUID(),
1223
+ kind: jobDef.kind,
1224
+ surface: jobDef.surface || 'tool_lane',
1225
+ sessionId: jobDef.sessionId || null,
1226
+ payload: jobDef.payload || {},
1227
+ metadata: jobDef.metadata || {},
1228
+ priority: Number.isFinite(Number(jobDef.priority)) ? Number(jobDef.priority) : 25,
1229
+ status: 'queued',
1230
+ attempts: 0,
1231
+ maxAttempts: Number.isFinite(Number(jobDef.maxAttempts)) ? Number(jobDef.maxAttempts) : 3,
1232
+ createdAt: now,
1233
+ updatedAt: now,
1234
+ workerId: null,
1235
+ claimedAt: null,
1236
+ claimExpiresAt: null,
1237
+ progress: [{ at: now, status: 'queued', message: jobDef.message || 'Queued for tool lane execution.' }],
1238
+ garden: null,
1239
+ };
1240
+ state.jobs.push(job);
1241
+ saveAutonomyState(state);
1242
+ return { job, stats: queueStats(state) };
1243
+ }
1244
+
975
1245
  function asJsonPayload(value) {
976
1246
  if (value == null) return null;
977
1247
  if (typeof value === 'string') {
@@ -1459,7 +1729,7 @@ function buildOwnerBypassPacket(message, reason = 'owner-local-bypass') {
1459
1729
  }
1460
1730
 
1461
1731
  async function loadRuntimePacket(req, body, client, packetRequest, message) {
1462
- if (body.packet) return body.packet;
1732
+ if (body.packet) return enrichPacketWithCodebaseSnapshot(body.packet);
1463
1733
  const apiKey = resolveApiKey(req, body);
1464
1734
  ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey));
1465
1735
  try {
@@ -1474,7 +1744,7 @@ async function loadRuntimePacket(req, body, client, packetRequest, message) {
1474
1744
  doctrineBundleHash: lease?.claims?.doctrine_bundle_hash || null,
1475
1745
  lastUpstreamError: null,
1476
1746
  });
1477
- return packet;
1747
+ return enrichPacketWithCodebaseSnapshot(packet);
1478
1748
  } catch (error) {
1479
1749
  const bundle = ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey)) || loadEncryptedOfflineBundle(apiKey);
1480
1750
  const bundleStatus = computeOfflineBundleStatus(bundle);
@@ -1486,19 +1756,52 @@ async function loadRuntimePacket(req, body, client, packetRequest, message) {
1486
1756
  lastUpstreamError: error instanceof Error ? error.message : String(error),
1487
1757
  lastUpstreamOkAt: bundle.lastUpstreamOkAt || bundle.cachedAt || new Date().toISOString(),
1488
1758
  });
1489
- return fallbackPacket;
1759
+ return enrichPacketWithCodebaseSnapshot(fallbackPacket);
1490
1760
  }
1491
1761
  }
1492
1762
  if (!isOwnerBypassRequest(req, body)) {
1493
1763
  throw error;
1494
1764
  }
1495
- return buildOwnerBypassPacket(
1765
+ return enrichPacketWithCodebaseSnapshot(buildOwnerBypassPacket(
1496
1766
  message || packetRequest?.message || '',
1497
1767
  error instanceof Error ? error.message : 'owner-local-bypass',
1498
- );
1768
+ ));
1499
1769
  }
1500
1770
  }
1501
1771
 
1772
+ function enrichPacketWithCodebaseSnapshot(packet) {
1773
+ if (!packet || typeof packet !== 'object') return packet;
1774
+ const codebase = compactCodebaseSnapshot();
1775
+ return {
1776
+ ...packet,
1777
+ runtime: {
1778
+ ...(packet.runtime && typeof packet.runtime === 'object' ? packet.runtime : {}),
1779
+ codebase_awareness: codebase,
1780
+ },
1781
+ };
1782
+ }
1783
+
1784
+ function buildMizanPacketRequest(body = {}) {
1785
+ const context = body.context && typeof body.context === 'object' ? body.context : body;
1786
+ const existing = body.packetRequest && typeof body.packetRequest === 'object' ? body.packetRequest : {};
1787
+ const message = String(
1788
+ context.message ||
1789
+ context.intendedAction ||
1790
+ body.message ||
1791
+ body.text ||
1792
+ ''
1793
+ );
1794
+ const platform = String(context.platform || context.surface || body.platform || body.client || 'mounted-runtime');
1795
+ return {
1796
+ stage: 'preflight',
1797
+ actor: String(context.actor || platform || 'mizan'),
1798
+ system: String(context.system || platform || 'aria-mounted-runtime'),
1799
+ platform,
1800
+ message,
1801
+ ...existing,
1802
+ };
1803
+ }
1804
+
1502
1805
  async function buildRuntimeTurnContext(req, body, client) {
1503
1806
  const sessionId = deriveSessionId(req, body, body.provider === 'anthropic' ? 'anthropic' : 'openai');
1504
1807
  const userId = deriveUserId(req, body);
@@ -2100,6 +2403,8 @@ function compactCodebaseSnapshot() {
2100
2403
  ? config.schemaImages
2101
2404
  : {};
2102
2405
  const repoSnapshots = Array.isArray(awareness.repoSnapshots) ? awareness.repoSnapshots : [];
2406
+ const heartbeatAt = awareness?.daemon?.heartbeatAt || null;
2407
+ const heartbeatAgeMs = heartbeatAt ? Date.now() - Date.parse(heartbeatAt) : null;
2103
2408
  return {
2104
2409
  repositories: repositories.slice(0, 6).map((repo) => ({
2105
2410
  name: repo?.name || null,
@@ -2112,6 +2417,8 @@ function compactCodebaseSnapshot() {
2112
2417
  awareness: {
2113
2418
  status: awareness.status || 'idle',
2114
2419
  updatedAt: awareness.updatedAt || null,
2420
+ daemon: awareness.daemon || null,
2421
+ stale: typeof heartbeatAgeMs === 'number' ? heartbeatAgeMs > 120000 : true,
2115
2422
  repoSnapshots: repoSnapshots.slice(0, 4).map((repo) => ({
2116
2423
  repoName: repo?.repoName || repo?.name || null,
2117
2424
  filesIndexed: repo?.filesIndexed || repo?.fileCount || null,
@@ -3365,7 +3672,8 @@ async function handleRoute(req, res) {
3365
3672
 
3366
3673
  if (url.pathname === '/phase/pre' || url.pathname === '/mizan/pre') {
3367
3674
  const apiKey = resolveApiKey(req, body);
3368
- const packet = body.packet || await client.getHarnessPacket(body.packetRequest || {});
3675
+ const packetRequest = buildMizanPacketRequest(body);
3676
+ const packet = body.packet || await loadRuntimePacket(req, body, client, packetRequest, packetRequest.message || body.text || '');
3369
3677
  const bundle = evaluateMizanPre(packet, body.context || body, {
3370
3678
  sessionId: body.sessionId || body.context?.sessionId || deriveSessionId(req, body, 'mizan-pre'),
3371
3679
  runtimeId: ensureRuntimeMeta().runtimeId,
@@ -3385,7 +3693,8 @@ async function handleRoute(req, res) {
3385
3693
 
3386
3694
  if (url.pathname === '/phase/mid' || url.pathname === '/mizan/mid') {
3387
3695
  const apiKey = resolveApiKey(req, body);
3388
- const packet = body.packet || null;
3696
+ const packetRequest = buildMizanPacketRequest(body);
3697
+ const packet = body.packet || await loadRuntimePacket(req, body, client, packetRequest, packetRequest.message || body.message || '');
3389
3698
  const bundle = evaluateMizanMid(body.message || '', body.plannedApproach || '', packet, body.context || body, {
3390
3699
  sessionId: body.sessionId || body.context?.sessionId || deriveSessionId(req, body, 'mizan-mid'),
3391
3700
  runtimeId: ensureRuntimeMeta().runtimeId,
@@ -3405,7 +3714,8 @@ async function handleRoute(req, res) {
3405
3714
 
3406
3715
  if (url.pathname === '/phase/post' || url.pathname === '/mizan/post') {
3407
3716
  const apiKey = resolveApiKey(req, body);
3408
- const packet = body.packet || null;
3717
+ const packetRequest = buildMizanPacketRequest(body);
3718
+ const packet = body.packet || await loadRuntimePacket(req, body, client, packetRequest, packetRequest.message || body.text || '');
3409
3719
  const bundle = evaluateMizanPost(body.text || '', body.evidence || {}, packet, body.context || body, {
3410
3720
  sessionId: body.sessionId || body.context?.sessionId || deriveSessionId(req, body, 'mizan-post'),
3411
3721
  runtimeId: ensureRuntimeMeta().runtimeId,
@@ -3595,8 +3905,27 @@ async function handleRoute(req, res) {
3595
3905
  }
3596
3906
 
3597
3907
  if (url.pathname === '/check-action') {
3598
- const result = await client.checkAction(body.action, body.target || '');
3599
- return json(res, 200, { ok: true, ...result });
3908
+ const action = body.action || 'write';
3909
+ const localGate = validateVerifyAndHive({ req, body, action });
3910
+ if (!localGate.allowed) {
3911
+ return json(res, 200, { ok: true, allowed: false, ...localGate });
3912
+ }
3913
+ let result;
3914
+ try {
3915
+ result = await client.checkAction(action, body.target || '');
3916
+ } catch (error) {
3917
+ releaseHiveFileLease(deriveSessionId(req, body, 'runtime-action'), coerceTargetFiles(body));
3918
+ throw error;
3919
+ }
3920
+ if (result?.allowed === false) {
3921
+ releaseHiveFileLease(deriveSessionId(req, body, 'runtime-action'), coerceTargetFiles(body));
3922
+ }
3923
+ return json(res, 200, { ok: true, ...result, verify: localGate.verify, hive: localGate.hive });
3924
+ }
3925
+
3926
+ if (url.pathname === '/hive/release') {
3927
+ const sessionId = deriveSessionId(req, body, 'runtime-action');
3928
+ return json(res, 200, { ok: true, hive: releaseHiveFileLease(sessionId, coerceTargetFiles(body)) });
3600
3929
  }
3601
3930
 
3602
3931
  if (url.pathname === '/validate-output' || url.pathname === '/api/harness/validate') {