@zaip-ai/cli 4.9.0 → 5.1.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 (213) hide show
  1. package/dist/services/adapter-hook-sources.d.ts +3 -0
  2. package/dist/services/adapter-hook-sources.d.ts.map +1 -1
  3. package/dist/services/adapter-hook-sources.js +3 -0
  4. package/dist/services/adapter-hook-sources.js.map +1 -1
  5. package/dist/services/adapters.d.ts +1 -1
  6. package/dist/services/adapters.d.ts.map +1 -1
  7. package/dist/services/adapters.js +5 -2
  8. package/dist/services/adapters.js.map +1 -1
  9. package/dist/services/adapters.test.js +1 -1
  10. package/dist/services/adapters.test.js.map +1 -1
  11. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
  12. package/dist/services/codex-conversation-lifecycle-hook.js +16 -6
  13. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
  14. package/dist/services/codex-hook-activation.d.ts +1 -1
  15. package/dist/services/codex-hook-activation.d.ts.map +1 -1
  16. package/dist/services/codex-hook-activation.js +116 -10
  17. package/dist/services/codex-hook-activation.js.map +1 -1
  18. package/dist/services/codex-pre-tool-cli-capture-template.d.ts +2 -0
  19. package/dist/services/codex-pre-tool-cli-capture-template.d.ts.map +1 -0
  20. package/dist/services/codex-pre-tool-cli-capture-template.js +104 -0
  21. package/dist/services/codex-pre-tool-cli-capture-template.js.map +1 -0
  22. package/dist/services/codex-pre-tool-cloud-template.d.ts +2 -0
  23. package/dist/services/codex-pre-tool-cloud-template.d.ts.map +1 -0
  24. package/dist/services/codex-pre-tool-cloud-template.js +190 -0
  25. package/dist/services/codex-pre-tool-cloud-template.js.map +1 -0
  26. package/dist/services/codex-pre-tool-dependencies.test-support.d.ts +2 -0
  27. package/dist/services/codex-pre-tool-dependencies.test-support.d.ts.map +1 -0
  28. package/dist/services/codex-pre-tool-dependencies.test-support.js +9 -0
  29. package/dist/services/codex-pre-tool-dependencies.test-support.js.map +1 -0
  30. package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts +5 -0
  31. package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts.map +1 -0
  32. package/dist/services/codex-pre-tool-hook-fixture.test-support.js +19 -0
  33. package/dist/services/codex-pre-tool-hook-fixture.test-support.js.map +1 -0
  34. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts +2 -0
  35. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts.map +1 -0
  36. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js +107 -0
  37. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js.map +1 -0
  38. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  39. package/dist/services/codex-pre-tool-hook-template.js +96 -210
  40. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  41. package/dist/services/codex-pre-tool-hook-template.test.js +32 -80
  42. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  43. package/dist/services/codex-pre-tool-host-context-template.d.ts +2 -0
  44. package/dist/services/codex-pre-tool-host-context-template.d.ts.map +1 -0
  45. package/dist/services/codex-pre-tool-host-context-template.js +139 -0
  46. package/dist/services/codex-pre-tool-host-context-template.js.map +1 -0
  47. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  48. package/dist/services/codex-pre-tool-support-template.js +61 -101
  49. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  50. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  51. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  52. package/dist/services/codex-subagent-hook-template.js +13 -2
  53. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  54. package/dist/services/codex-validation-runtime-template.d.ts.map +1 -1
  55. package/dist/services/codex-validation-runtime-template.js +10 -4
  56. package/dist/services/codex-validation-runtime-template.js.map +1 -1
  57. package/dist/services/runtime-template-test-support.d.ts +1 -0
  58. package/dist/services/runtime-template-test-support.d.ts.map +1 -1
  59. package/dist/services/runtime-template-test-support.js +23 -2
  60. package/dist/services/runtime-template-test-support.js.map +1 -1
  61. package/dist/services/runtime-template.test.js +4 -23
  62. package/dist/services/runtime-template.test.js.map +1 -1
  63. package/package.json +1 -1
  64. package/templates/.agents/README-ENG.md +58 -7
  65. package/templates/.agents/README.md +46 -10
  66. package/templates/.agents/config/agents/discovery.json +1 -1
  67. package/templates/.agents/dashboard/.zaip-managed.json +174 -86
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BW7Vy1cp.js → CostCalculationGuidePage-uR-asJv_.js} +1 -1
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-Cp_hEVd8.js → DashboardHelpDialog-CngnpoLL.js} +87 -13
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Prcs-ydy.js → PricingSettingsDialog-Bszw3ekd.js} +1 -1
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-Cu40wAFF.js → ProjectSettingsPage-IJ1znG4w.js} +1 -1
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-Bh3YBd3C.js → TraceabilityWorkspace-CsBpHqq_.js} +1 -1
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-B0Jwz_rk.js +68 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-CF7B1Hjn.js → dashboard-localization-D41g5rdo.js} +58 -7
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DOhy6D2-.js → icons-vendor-CjGGux6h.js} +1 -1
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Bedlm0f7.js +2 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CkPC-yMz.js → react-vendor-B9jZSAKf.js} +1 -1
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-BEdGoIDm.js → traceability-DFpwb0TN.js} +1 -1
  79. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  80. package/templates/.agents/dashboard/apps/frontend/package.json +2 -2
  81. package/templates/.agents/dashboard/apps/frontend/src/AppDashboardContent.tsx +3 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +16 -7
  83. package/templates/.agents/dashboard/apps/frontend/src/__tests__/app-composition-support.ts +1 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +1 -1
  85. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +2 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.navigation.test.tsx +125 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +67 -9
  88. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +15 -15
  89. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-locale.ts +18 -0
  90. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  91. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +10 -3
  92. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +5 -1
  93. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostGroupsTable.period-values.test.tsx +2 -2
  94. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-keeps-legacy-codex-identity-fields-blank.test.ts +5 -5
  95. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +36 -43
  96. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-loaders.ts +1 -0
  97. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-formatters.ts +6 -4
  98. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +2 -2
  99. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +1 -1
  100. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +11 -25
  101. package/templates/.agents/dashboard/modules/cost-monitor/src/server/cost-summary-defaults.ts +26 -0
  102. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  103. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +11 -16
  104. package/templates/.agents/dashboard/modules/operations/src/planning-contracts.ts +14 -0
  105. package/templates/.agents/dashboard/modules/operations/src/react/DecisionTable.tsx +65 -0
  106. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +5 -71
  107. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +4 -39
  108. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.navigation.test.tsx +150 -0
  109. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +7 -3
  110. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +14 -43
  111. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +3 -0
  112. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-contract.ts +51 -0
  113. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +12 -3
  114. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +1 -0
  115. package/templates/.agents/dashboard/modules/operations/src/react/use-operations-default-range.ts +16 -0
  116. package/templates/.agents/dashboard/modules/operations/src/react/use-operations-filter-sync.ts +66 -0
  117. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  118. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  119. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  120. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  121. package/templates/.agents/dashboard/packages/local-runtime/dashboard-build-fingerprint.mjs +87 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/dashboard-build-fingerprint.test.mjs +48 -0
  123. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +193 -191
  124. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +169 -169
  125. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  126. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-frontend.mjs +29 -0
  127. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +5 -0
  128. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +193 -191
  129. package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.test.ts +38 -0
  130. package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.ts +35 -0
  131. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +53 -2
  132. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +18 -1
  133. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +8 -2
  134. package/templates/.agents/dashboard/packages/local-runtime/src/server-capture-projection-order.test.ts +118 -0
  135. package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.test.ts +90 -0
  136. package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.ts +86 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-routes.ts +8 -1
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server-projection-loaders.ts +13 -0
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +30 -74
  140. package/templates/.agents/dashboard/packages/local-runtime/src/server-route-handlers.ts +42 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/server-routes.test.ts +60 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +12 -2
  143. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +29 -4
  144. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +7 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +51 -54
  146. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +28 -45
  147. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-filter-matrix.test.ts +5 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +49 -0
  149. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +44 -1
  150. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-cached-cost-page.ts +11 -10
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +1 -1
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +18 -1
  153. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-build.mjs +12 -5
  154. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-paths.mjs +8 -0
  155. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +1 -0
  156. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  157. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  158. package/templates/.agents/dashboard/tests/e2e/qualify-top-five-trace.mjs +53 -0
  159. package/templates/.agents/dashboard/tests/e2e/verify-top-five-served.mjs +78 -0
  160. package/templates/.agents/design/.zaip-managed.json +14 -6
  161. package/templates/.agents/design/apps/catalog/dist/assets/index-BGxXxXTo.js +72 -0
  162. package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
  163. package/templates/.agents/design/apps/catalog/package.json +1 -1
  164. package/templates/.agents/design/apps/catalog/src/components/TopEntitiesSpecimen.tsx +45 -0
  165. package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +3 -1
  166. package/templates/.agents/design/apps/catalog/src/stories/Components.stories.tsx +2 -0
  167. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  168. package/templates/.agents/requirements/autonomous-assurance.md +11 -2
  169. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  170. package/templates/.agents/requirements/comparative-audit.md +1 -1
  171. package/templates/.agents/requirements/context-engine.md +1 -1
  172. package/templates/.agents/requirements/cost-observability.md +1 -1
  173. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  174. package/templates/.agents/requirements/data-export.md +1 -1
  175. package/templates/.agents/requirements/design-system.md +1 -1
  176. package/templates/.agents/requirements/execution-engine.md +1 -1
  177. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  178. package/templates/.agents/requirements/model-orchestration.md +1 -1
  179. package/templates/.agents/requirements/public-distribution.md +1 -1
  180. package/templates/.agents/requirements/software-architecture.md +1 -1
  181. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  182. package/templates/.agents/requirements/work-lifecycle.md +2 -1
  183. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  184. package/templates/.agents/rules/run-lifecycle-closure.md +2 -1
  185. package/templates/.agents/rules/workflow.md +1 -0
  186. package/templates/.agents/runtime/VERSION +1 -1
  187. package/templates/.agents/runtime/bin/zaip.mjs +1178 -764
  188. package/templates/.agents/runtime/checksums.sha256 +6 -6
  189. package/templates/.agents/runtime/manifest.json +1 -1
  190. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  191. package/templates/.agents/runtime/provenance.json +5 -5
  192. package/templates/.agents/runtime/sbom.cdx.json +59 -59
  193. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +1 -1
  194. package/templates/.agents/skills/zaip-skill-execution/references/controlled-release-recovery.md +14 -0
  195. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +1 -1
  196. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -1
  197. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract-cli.test.mjs +53 -0
  198. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +12 -0
  199. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +26 -32
  200. package/templates/.agents/skills/zaip-skill-maintenance-enter/SKILL.md +23 -0
  201. package/templates/.agents/skills/zaip-skill-maintenance-enter/agents/openai.yaml +4 -0
  202. package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.mjs +213 -0
  203. package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.test.mjs +207 -0
  204. package/templates/.agents/skills/zaip-skill-maintenance-exit/SKILL.md +21 -0
  205. package/templates/.agents/skills/zaip-skill-maintenance-exit/agents/openai.yaml +4 -0
  206. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +2 -0
  207. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +37 -8
  208. package/templates/.agents/zaip-aios/VERSION +1 -1
  209. package/templates/.agents/zaip-aios/guides/10-seguranca.md +2 -2
  210. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +38 -0
  211. package/templates/template-manifest.json +259 -135
  212. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Co2Jiuzh.js +0 -68
  213. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-qVSJnJ0_.js +0 -2
@@ -5,12 +5,15 @@ import { lstat, readFile, realpath } from "node:fs/promises";
5
5
  import { dirname, isAbsolute, relative, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { promisify } from "node:util";
8
- import { captureCliTurn, checkDestructivePatterns, configuredDeploymentBranches, gitInvocation, inside, isLiteralReadSequence, requiresShellMutationRun, reviewedContextReadPaths, reviewedReleasePaths, rewriteCliInput, selectHookExecutionWorkspace, shellSyntax, tokenizeLiteralCommand, validateInlineExecution } from "./zaip-pre-tool-support.mjs";
8
+ import { captureCliTurn, checkDestructivePatterns, configuredDeploymentBranches, configuredPermissionProfile, gitInvocation, inside, isLiteralReadSequence, parseHookEvent, readBoundedCanonicalText, requiresShellMutationRun, reviewedContextReadPaths, reviewedReleasePaths, rewriteCliInput, selectHookExecutionWorkspace, selectHookRepositoryRoot, shellSyntax, tokenizeLiteralCommand, validateInlineExecution } from "./zaip-pre-tool-support.mjs";
9
+ import { createCloudValidators } from "./zaip-pre-tool-cloud.mjs";
9
10
  const execute = promisify(execFile);
10
11
  const nativeAuthority = await import("./zaip-native-tool-authority.mjs").catch(() => null);
11
12
  const activation = await import("./zaip-hook-activation.mjs").catch(() => null);
12
13
  const installedRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
13
14
  let repositoryRoot = installedRoot;
15
+ let principalFullAccess = false;
16
+ let principalFullNetwork = false;
14
17
  const deletionCommandPattern = /\b(?:Remove-Item|ri|rm|rmdir|rd|del|erase)\b/i;
15
18
  const allowedExplicitNetworkHosts = new Set([
16
19
  "127.0.0.1", "api.github.com", "github.com", "localhost", "objects.githubusercontent.com", "releases.hashicorp.com", "::1",
@@ -32,6 +35,10 @@ const homeReferencePattern = /(?:\$HOME\b|\$env:(?:USERPROFILE|HOMEDRIVE|HOMEPAT
32
35
  const credentialReferencePattern = /(?:^|[\\/\s"'=])(?:\.ssh|\.aws|\.azure|\.kube|\.docker|\.gnupg|\.git-credentials|\.npmrc|\.pypirc|id_(?:rsa|ed25519)|[^\\/\s"']+\.(?:pem|key|p12|pfx))(?:[\\/\s"'=]|$)|(?:^|[\\/\s"'=])\.env(?:\.[A-Za-z0-9_-]+)*(?:[\\/\s"']|$)/i;
33
36
  const sensitiveEnvironmentPattern = /(?:\$env:|%|\$)[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|CREDENTIAL)[A-Z0-9_]*%?/i;
34
37
  const destructivePatterns = [
38
+ {
39
+ pattern: /\b(?:Restart-Computer|Stop-Computer|Restart-Host|shutdown(?:\.exe)?|reboot|poweroff|halt|Suspend-Computer|hibernate|logoff|Win32Shutdown)\b/i,
40
+ reason: "Local computer restart, shutdown, suspension, hibernation and session termination are blocked.",
41
+ },
35
42
  {
36
43
  pattern: /\b(?:Clear-Content|shred)\b/i,
37
44
  reason: "Unbounded content destruction is blocked.",
@@ -148,7 +155,7 @@ function repositoryPath(path) {
148
155
  }
149
156
  function isGitPath(path) {
150
157
  const scoped = repositoryPath(path).toLowerCase();
151
- return scoped === ".git" || scoped.startsWith(".git/");
158
+ return scoped.split("/").includes(".git");
152
159
  }
153
160
 
154
161
  function unquote(value) {
@@ -172,7 +179,18 @@ function explicitPathCandidates(command) {
172
179
  }
173
180
  return [...candidates].map(unquote).filter(Boolean);
174
181
  }
175
- function validateExplicitFilesystemReferences(command, cwd, reviewedPaths = []) {
182
+ async function assertCanonicalLiteralPath(path, cwd = repositoryRoot) {
183
+ if (path.split(/[\\/]/).includes("..")) deny("Filesystem traversal is blocked.");
184
+ for (let cursor = resolve(cwd, path);; cursor = dirname(cursor)) {
185
+ try {
186
+ const info = await lstat(cursor);
187
+ if (info.isSymbolicLink() || (await realpath(cursor)).toLowerCase() !== cursor.toLowerCase())
188
+ deny("Filesystem links and aliases are blocked.");
189
+ } catch (error) { if (error?.code !== "ENOENT") deny("Filesystem path could not be safely verified."); }
190
+ if (dirname(cursor) === cursor) break;
191
+ }
192
+ }
193
+ async function validateExplicitFilesystemReferences(command, cwd, reviewedPaths = []) {
176
194
  const checked = reviewedPaths.reduce((text, path) => text.replaceAll(path, "REVIEWED_RELEASE_PATH"), command);
177
195
  if (homeReferencePattern.test(checked)) {
178
196
  deny("Direct user-profile path references are blocked; use repository-scoped paths.");
@@ -180,7 +198,7 @@ function validateExplicitFilesystemReferences(command, cwd, reviewedPaths = [])
180
198
  if (credentialReferencePattern.test(checked) || sensitiveEnvironmentPattern.test(checked)) {
181
199
  deny("Direct credential and secret access is blocked.");
182
200
  }
183
- if (/\b(?:Get-ChildItem|gci|dir)\s+Env:|\b(?:printenv|set)\s*$/i.test(checked) || /GetEnvironmentVariables\s*\(/i.test(checked)) {
201
+ if (/\b(?:Get-ChildItem|gci|dir|Get-Item|gi)(?:\s+-(?:LiteralPath|Path))?\s+(?:Env|Variable|Function|Alias):|\b(?:printenv|set)\s*$/i.test(checked) || /GetEnvironmentVariables\s*\(/i.test(checked)) {
184
202
  deny("Bulk environment enumeration is blocked.");
185
203
  }
186
204
  if (/(?:^|[\s"'=(,]|-[A-Za-z][A-Za-z0-9_-]*:)(?:(?:\\\\){1,2}|\/\/)[^\\/\s"']+[\\/]/.test(checked)) {
@@ -191,13 +209,16 @@ function validateExplicitFilesystemReferences(command, cwd, reviewedPaths = [])
191
209
  ? checked.replace(/(^|\s)--(?:agent-id|parent-agent-id)\s+\/root(?:\/[a-z][a-z0-9_]*_zaip_agent(?:_[a-z0-9]+)?)+(?=\s|$)/g, "$1--native-agent-id zaip-agent")
192
210
  : checked; if (/[\r\n]/.test(checked)) deny("Multiline shell commands are blocked.");
193
211
  for (const match of osPathCommand.matchAll(/(["'])(\/(?:etc|home|Users|root|var|opt|private|mnt|proc|sys|dev|tmp)(?:\/[^\r\n]*?)?)\1|(?:^|[\s=(,]|-[A-Za-z][A-Za-z0-9_-]*:)(\/(?:etc|home|Users|root|var|opt|private|mnt|proc|sys|dev|tmp)(?:\/[^\s"'\x60;,)|&]*)?)/gi)) {
194
- const path = match[2] ?? match[3], before = osPathCommand[match.index - 1] ?? "", after = osPathCommand[match.index + match[0].length] ?? "", jsonPath = /(?:^|\s)'\{"path":$/.test(osPathCommand.slice(0, match.index)) && /^}'(?:\s|$)/.test(osPathCommand.slice(match.index + match[0].length)); if (jsonPath) { if (path.includes("\\") || !isWithinRepository(resolve(path))) deny("Explicit filesystem path escapes the repository boundary."); continue; }
212
+ const path = match[2] ?? match[3], before = osPathCommand[match.index - 1] ?? "", after = osPathCommand[match.index + match[0].length] ?? "", jsonPath = /(?:^|\s)'\{"path":$/.test(osPathCommand.slice(0, match.index)) && /^}'(?:\s|$)/.test(osPathCommand.slice(match.index + match[0].length));
213
+ if (principalFullAccess) { await assertCanonicalLiteralPath(path); continue; }
214
+ if (jsonPath) { if (path.includes("\\") || !isWithinRepository(resolve(path))) deny("Explicit filesystem path escapes the repository boundary."); continue; }
195
215
  if (path.includes("\\") || !isWithinRepository(resolve(path)) || (match[2] && before && !(/\s/.test(before) || before === "=" || jsonPath)) || (after && !/\s/.test(after) && !jsonPath)) deny("Explicit operating-system paths outside the repository are blocked.");
196
216
  }
197
217
  if (process.platform === "win32" && /(?:^|[\s"'=(,])\\(?!\\)[^\s\\/"'<>|;]+(?:\\[^\s"'<>|;]+|\.[A-Za-z0-9]{1,10})/.test(checked)) {
198
218
  deny("Drive-rooted filesystem paths outside the repository are blocked.");
199
219
  }
200
220
  for (const candidate of explicitPathCandidates(checked)) {
221
+ if (principalFullAccess) { await assertCanonicalLiteralPath(candidate, cwd); continue; }
201
222
  if (process.platform !== "win32" && /^[A-Za-z]:[\\/]/.test(candidate)) deny("Explicit filesystem path escapes the repository boundary.");
202
223
  const absolute = resolve(cwd, candidate);
203
224
  if (!isWithinRepository(absolute)) {
@@ -328,7 +349,7 @@ function validateExplicitNetwork(command) {
328
349
  deny("Malformed network target was rejected.");
329
350
  }
330
351
  const host = target.hostname.toLowerCase();
331
- if (!allowedExplicitNetworkHosts.has(host)) {
352
+ if (!principalFullNetwork && !allowedExplicitNetworkHosts.has(host)) {
332
353
  deny("Explicit network target is outside the ZAIP allowlist.");
333
354
  }
334
355
  if (host === "releases.hashicorp.com") {
@@ -339,188 +360,6 @@ function validateExplicitNetwork(command) {
339
360
  }
340
361
  }
341
362
 
342
- function optionMap(tokens) {
343
- if (tokens.length % 2 !== 0) return null;
344
- const values = new Map();
345
- for (let index = 0; index < tokens.length; index += 2) {
346
- const key = tokens[index];
347
- const value = tokens[index + 1];
348
- if (!key.startsWith("--") || value.startsWith("--") || values.has(key)) return null;
349
- values.set(key, value);
350
- }
351
- return values;
352
- }
353
-
354
- function requireExactOptions(options, allowed, required) {
355
- if ([...options.keys()].some((key) => !allowed.has(key))) return false;
356
- return [...required].every((key) => typeof options.get(key) === "string" && options.get(key).length > 0);
357
- }
358
-
359
- function isStringArray(value) {
360
- return Array.isArray(value) && value.every((item) => typeof item === "string" && item.length > 0);
361
- }
362
-
363
- async function readCloudContract(options) {
364
- if (options.get("--contract") !== cloudContractPath) {
365
- deny("Cloud runner must use the canonical autonomous-cloud contract path.");
366
- }
367
- const absolute = resolve(repositoryRoot, cloudContractPath);
368
- if (!isWithinRepository(absolute)) deny("Cloud runner contract escapes the repository boundary.");
369
- let contract;
370
- try {
371
- contract = JSON.parse(await readFile(absolute, "utf8"));
372
- } catch {
373
- deny("Cloud runner contract is absent or malformed.");
374
- }
375
- if (
376
- contract === null ||
377
- typeof contract !== "object" ||
378
- contract.version !== 1 ||
379
- contract.profile !== "autonomous-cloud" ||
380
- contract.cloudflare === null ||
381
- typeof contract.cloudflare !== "object" ||
382
- contract.hostinger === null ||
383
- typeof contract.hostinger !== "object"
384
- ) {
385
- deny("Cloud runner contract has no valid autonomous-cloud profile.");
386
- }
387
- return contract;
388
- }
389
-
390
- function cloudflareTargetMatches(contract, options, operation) {
391
- const cloudflare = contract.cloudflare;
392
- const record = options.get("--dns-record");
393
- const managed = isStringArray(cloudflare.dns_records) && cloudflare.dns_records.includes(record);
394
- const direct = cloudflare.direct_dns_records !== null && typeof cloudflare.direct_dns_records === "object" && Object.hasOwn(cloudflare.direct_dns_records, record);
395
- return (
396
- typeof cloudflare.account_id === "string" &&
397
- typeof cloudflare.zone_id === "string" &&
398
- isStringArray(cloudflare.allowed_operations) &&
399
- cloudflare.account_id === options.get("--account-id") &&
400
- cloudflare.zone_id === options.get("--zone-id") &&
401
- cloudflare.allowed_operations.includes(operation) &&
402
- (operation === "cloudflare:dns:read" ? managed || direct : direct && !managed)
403
- );
404
- }
405
-
406
- function hostingerTargetMatches(contract, options, operation) {
407
- const hostinger = contract.hostinger;
408
- const role = options.get("--vps-role");
409
- if (
410
- typeof hostinger.account_id !== "string" ||
411
- !isStringArray(hostinger.allowed_operations) ||
412
- hostinger.account_id !== options.get("--account-id") ||
413
- !hostinger.allowed_operations.includes(operation) ||
414
- (role !== "edge" && role !== "application") ||
415
- hostinger.vps === null ||
416
- typeof hostinger.vps !== "object"
417
- ) {
418
- return false;
419
- }
420
- const target = hostinger.vps[role];
421
- if (target === null || typeof target !== "object") return false;
422
-
423
- if (typeof target.id !== "string" || target.id !== options.get("--vps-id")) return false;
424
- if (operation.startsWith("hostinger:ssh:")) {
425
- return typeof target.host_key_sha256 === "string" && target.host_key_sha256 === options.get("--host-key-sha256");
426
- }
427
- return true;
428
- }
429
-
430
- function terraformTargetMatches(contract, options, operation) {
431
- const terraform = contract.terraform;
432
- if (
433
- terraform === null ||
434
- typeof terraform !== "object" ||
435
- !isStringArray(terraform.allowed_operations) ||
436
- !terraform.allowed_operations.includes(operation)
437
- ) return false;
438
- if (operation === "terraform:plan") return terraform.working_directory === options.get("--terraform-directory");
439
- if (operation === "terraform:apply") {
440
- const plan = terraform.plan;
441
- return plan !== null && typeof plan === "object" && typeof plan.path === "string" && typeof plan.sha256 === "string" && typeof plan.policy_result_sha256 === "string" && plan.path === options.get("--plan-path") && plan.sha256 === options.get("--plan-sha256") && plan.policy_result_sha256 === options.get("--policy-result-sha256");
442
- }
443
- return terraform.import_targets !== null && typeof terraform.import_targets === "object" && terraform.import_targets[options.get("--resource-address")] === options.get("--resource-id");
444
- }
445
-
446
- function validateDirectCloudProviderMutation(command) {
447
- const tokens = tokenizeLiteralCommand(command);
448
- if (/^(?:node|node\.exe)$/i.test(tokens?.[0] ?? "") && tokens?.[1] === cloudRunnerScript) return;
449
- if (/\bterraform(?:\.exe)?\s+(?:apply|destroy|import)\b/i.test(command)) deny("Direct Terraform mutation is blocked; use the contract-bound cloud runner.");
450
- const provider = /^(?:wrangler|cloudflare|hostinger)(?:\.exe|\.cmd|\.js|\.mjs)?$/i;
451
- const wrapperProvider = /^(?:wrangler|cloudflare|hostinger)(?:\.exe|\.cmd|\.js|\.mjs|@[A-Za-z0-9][A-Za-z0-9._-]*)?$/i;
452
- const base = (token) => token?.split(/[\\/]/).at(-1) ?? "";
453
- if (tokens === null) {
454
- // Complex shell syntax stays fail-closed; literal arguments are inspected as tokens below.
455
- if (/\b(?:wrangler|cloudflare|hostinger)(?:\.exe)?\b/i.test(command)) deny("Direct Cloudflare or Hostinger commands are blocked; use the contract-bound cloud runner.");
456
- return;
457
- }
458
- let index = 0;
459
- const launcher = base(tokens[0]);
460
- if (/^(?:npx|pnpm|npm|yarn|bunx)(?:\.cmd|\.exe)?$/i.test(launcher)) {
461
- // Package-manager options can move the executable. Inspect exact provider
462
- // tokens across this recognized wrapper; paths and document names remain inert.
463
- if (tokens.slice(1).some((token) => wrapperProvider.test(token))) deny("Direct Cloudflare or Hostinger commands are blocked; use the contract-bound cloud runner.");
464
- index = 1;
465
- if (/^npx(?:\.cmd|\.exe)?$/i.test(launcher) && /^(?:--yes|-y)$/.test(tokens[index] ?? "")) index += 1;
466
- if (/^pnpm(?:\.cmd|\.exe)?$/i.test(launcher) && /^(?:--filter|--dir)$/.test(tokens[index] ?? "") && tokens[index + 1]) index += 2;
467
- if (/^(?:exec|dlx)$/.test(tokens[index] ?? "")) index += 1;
468
- if (tokens[index] === "--") index += 1;
469
- if (/^(?:exec|dlx)$/.test(tokens[index] ?? "")) index += 1;
470
- } else if (/^(?:node|node\.exe)$/i.test(launcher)) index = 1;
471
- else if (/^(?:powershell|pwsh|cmd|bash|sh)(?:\.exe)?$/i.test(launcher)) {
472
- if (/\b(?:wrangler|cloudflare|hostinger)(?:\.exe)?\b/i.test(command)) deny("Direct Cloudflare or Hostinger commands are blocked; use the contract-bound cloud runner.");
473
- return;
474
- }
475
- if (provider.test(base(tokens[index]))) deny("Direct Cloudflare or Hostinger commands are blocked; use the contract-bound cloud runner.");
476
- }
477
-
478
- async function validateCloudRunner(command) {
479
- const tokens = tokenizeLiteralCommand(command);
480
- if (tokens === null || tokens.length < 2 || !/^(?:node|node\.exe)$/i.test(tokens[0]) || tokens[1] !== cloudRunnerScript) {
481
- return false;
482
- }
483
- const options = optionMap(tokens.slice(2));
484
- if (options === null) deny("Cloud runner requires literal, named arguments.");
485
- const operation = options.get("--operation");
486
- if (operation === "contract:derive") {
487
- if (!requireExactOptions(options, new Set(["--operation", "--credentials-source"]), new Set(["--operation", "--credentials-source"]))) {
488
- deny("Cloud contract derivation has unrecognized arguments.");
489
- }
490
- if (options.get("--credentials-source") !== "root") {
491
- deny("Cloud contract derivation requires the selected root credential source.");
492
- }
493
- return true;
494
- }
495
- const provider = cloudOperationProviders.get(operation);
496
- if (provider === undefined) deny("Cloud runner operation is unrecognized or destructive.");
497
- const common = new Set(["--contract", "--operation", "--provider", "--account-id", "--credentials-source", "--zone-id", "--dns-record", "--vps-role", "--vps-id", "--host-key-sha256", "--terraform-directory", "--plan-path", "--plan-sha256", "--policy-result-sha256", "--resource-address", "--resource-id"]);
498
- const baseRequired = provider === "terraform"
499
- ? new Set(["--contract", "--operation", "--credentials-source"])
500
- : new Set(["--contract", "--operation", "--provider", "--account-id", "--credentials-source"]);
501
- if (!requireExactOptions(options, common, baseRequired)) {
502
- deny("Cloud runner invocation has unrecognized arguments.");
503
- }
504
- if ((provider !== "terraform" && options.get("--provider") !== provider) || !["root", "agents"].includes(options.get("--credentials-source"))) {
505
- deny("Cloud runner provider or credential source is outside the approved contract.");
506
- }
507
- const contract = await readCloudContract(options);
508
- const valid = provider === "cloudflare"
509
- ? requireExactOptions(options, common, new Set(["--contract", "--operation", "--provider", "--account-id", "--credentials-source", "--zone-id", "--dns-record"])) && cloudflareTargetMatches(contract, options, operation)
510
- : provider === "hostinger"
511
- ? requireExactOptions(options, common, new Set(["--contract", "--operation", "--provider", "--account-id", "--credentials-source", "--vps-role"])) && hostingerTargetMatches(contract, options, operation)
512
- : requireExactOptions(
513
- options,
514
- common,
515
- operation === "terraform:plan"
516
- ? new Set(["--contract", "--operation", "--credentials-source", "--terraform-directory"])
517
- : operation === "terraform:apply"
518
- ? new Set(["--contract", "--operation", "--credentials-source", "--plan-path", "--plan-sha256", "--policy-result-sha256"])
519
- : new Set(["--contract", "--operation", "--credentials-source", "--resource-address", "--resource-id"]),
520
- ) && terraformTargetMatches(contract, options, operation);
521
- if (!valid) deny("Cloud runner target, operation, or host identity is outside the approved contract.");
522
- return true;
523
- }
524
363
 
525
364
  ${CODEX_STALE_GIT_LOCK_HELPER}
526
365
 
@@ -597,6 +436,10 @@ async function validateShellDeletion(command, cwd) {
597
436
  }
598
437
 
599
438
  async function validatePatch(command) {
439
+ for (const match of command.matchAll(/^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$|^\*\*\* Move to:\s*(.+)$/gm)) {
440
+ const rawPath = (match[1] ?? match[2]).trim().replace(/^["']|["']$/g, "");
441
+ if (principalFullAccess) await assertCanonicalLiteralPath(rawPath);
442
+ }
600
443
  const paths = patchPaths(command);
601
444
  const lifecycleRecordTouched = paths.some((path) =>
602
445
  /^\.agents[\\/]records[\\/](?:prds|specs|tasks)[\\/]/i.test(path));
@@ -605,8 +448,10 @@ async function validatePatch(command) {
605
448
  }
606
449
  for (const path of paths) {
607
450
  const absolute = resolve(repositoryRoot, path);
608
- if (!isWithinRepository(absolute)) deny("Patch path escapes the repository boundary.");
609
- if (!await nativeAuthority?.isSafePatchPath(absolute, repositoryRoot)) deny("Patch path traverses a link or could not be safely verified.");
451
+ if (credentialReferencePattern.test(absolute)) deny("Direct credential and secret access is blocked.");
452
+ if (!isWithinRepository(absolute) && !principalFullAccess) deny("Patch path escapes the repository boundary.");
453
+ if (principalFullAccess) await assertCanonicalLiteralPath(absolute);
454
+ else if (!await nativeAuthority?.isSafePatchPath(absolute, repositoryRoot)) deny("Patch path traverses a link or could not be safely verified.");
610
455
  if (repositoryPath(absolute) === "") deny("Patching the repository root is blocked.");
611
456
  if (protectedTaskWorkspacePath(repositoryPath(absolute))) deny("Task workspace claims are managed only by the canonical CLI.");
612
457
  if (isGitPath(absolute)) {
@@ -628,13 +473,25 @@ async function requiresOrchestratorRun(event, command) {
628
473
  return paths.length === 0 || paths.some((path) => !lifecycleExemptPathPattern.test(path));
629
474
  }
630
475
  if (event.tool_name === "Edit" || event.tool_name === "Write") {
631
- if (!await nativeAuthority?.isSafePatchPath(resolve(event.cwd ?? repositoryRoot, command), repositoryRoot)) deny("File write path traverses a link or could not be safely verified.");
476
+ if (principalFullAccess) await assertCanonicalLiteralPath(command, event.cwd ?? repositoryRoot);
477
+ else if (!await nativeAuthority?.isSafePatchPath(resolve(event.cwd ?? repositoryRoot, command), repositoryRoot)) deny("File write path traverses a link or could not be safely verified.");
632
478
  const path = repositoryPath(resolve(event.cwd ?? repositoryRoot, command));
633
479
  if (protectedTaskWorkspacePath(path)) deny("Task workspace claims are managed only by the canonical CLI.");
634
480
  return !lifecycleExemptPathPattern.test(path);
635
481
  }
636
482
  // Only single literal writes claim exemptions; compound commands remain gated.
637
483
  const tokens = tokenizeLiteralCommand(command);
484
+ // The public CLI validates the immutable post-publication receipt, observed
485
+ // invocation, completed maintenance Gui and exact target before any write.
486
+ // Its first call necessarily follows the maintenance root's terminal Stop.
487
+ if (tokens && !Object.values(shellSyntax(command)).some(Boolean) &&
488
+ /^(?:node|node\.exe)$/i.test(tokens[0] ?? "") &&
489
+ /^\.agents[\\/]runtime[\\/]bin[\\/]zaip\.mjs$/.test(tokens[1] ?? "") &&
490
+ tokens[2] === "task" && tokens[3] === "resume-post-publication" &&
491
+ /^TASK-[A-Z0-9-]+$/.test(tokens[4] ?? "") && tokens[5] === "--receipt" &&
492
+ /^\.local[\\/]recovery[\\/]post-publication[\\/][a-f0-9]{64}\.json$/.test(tokens[6] ?? "") &&
493
+ (tokens.length === 7 || (tokens.length === 8 && tokens[7] === "--json"))) return false;
494
+ if (tokens && tokens[2] === "task" && tokens[3] === "resume-post-publication") return true;
638
495
  if (tokens && /^(?:Set-Content|Add-Content|Out-File|New-Item|Copy-Item|Move-Item|Remove-Item|Rename-Item|sc|ac|ni|cp|mv|mi|ri|rm|git(?:\.exe)?)$/i.test(tokens[0] ?? "") &&
639
496
  tokens.some(token => token.toLowerCase().includes("task-workspaces") &&
640
497
  protectedTaskWorkspacePath(repositoryPath(resolve(event.cwd ?? repositoryRoot, token)))))
@@ -649,7 +506,8 @@ async function requiresOrchestratorRun(event, command) {
649
506
  }
650
507
  if (valid && options.has("-literalpath") && options.has("-value")) {
651
508
  const path = repositoryPath(resolve(event.cwd ?? repositoryRoot, options.get("-literalpath")));
652
- if (!await nativeAuthority?.isSafePatchPath(resolve(repositoryRoot, path), repositoryRoot)) deny("Shell write path traverses a link or could not be safely verified.");
509
+ if (principalFullAccess) await assertCanonicalLiteralPath(options.get("-literalpath"), event.cwd ?? repositoryRoot);
510
+ else if (!await nativeAuthority?.isSafePatchPath(resolve(repositoryRoot, path), repositoryRoot)) deny("Shell write path traverses a link or could not be safely verified.");
653
511
  if (protectedTaskWorkspacePath(path)) deny("Task workspace claims are managed only by the canonical CLI.");
654
512
  if (lifecycleExemptPathPattern.test(path)) return false;
655
513
  }
@@ -664,37 +522,65 @@ async function requiresOrchestratorRun(event, command) {
664
522
  try { optIn = JSON.parse(await readFile(resolve(repositoryRoot, ".local/validation-runtime.json"), "utf8")); }
665
523
  catch { optIn = null; }
666
524
  const reviewed = await (nativeAuthority?.reviewedValidationRuntimePaths?.(command, tokenizeLiteralCommand, repositoryRoot) ?? []);
667
- if (optIn?.executable === tokens[0] && reviewed.length === 1 && reviewed[0] === tokens[0]) return false;
525
+ if (typeof optIn?.executable === "string" &&
526
+ tokens[0].replaceAll("/", "\\").toLowerCase() === optIn.executable.replaceAll("/", "\\").toLowerCase() &&
527
+ reviewed.length === 1 && reviewed[0] === tokens[0]) return false;
668
528
  }
669
529
  return requiresShellMutationRun(command, tokens, event.cwd ?? repositoryRoot, repositoryRoot);
670
530
  }
671
531
 
672
- async function boundedCanonicalText(path, maximumBytes, authorityRoot = repositoryRoot) {
673
- const info = await lstat(path);
674
- if (!info.isFile() || info.isSymbolicLink() || info.size > maximumBytes) throw new Error("Unsafe Run lookup file");
675
- const physical = await realpath(path);
676
- const identity = value => process.platform === "win32" ? value.toLowerCase() : value;
677
- if (!inside(physical, authorityRoot) || identity(physical) !== identity(resolve(path))) throw new Error("Run lookup must not follow links");
678
- return readFile(path, "utf8");
679
- }
532
+ const boundedCanonicalText = (path, maximumBytes, authorityRoot = repositoryRoot) => readBoundedCanonicalText(path, maximumBytes, authorityRoot);
680
533
 
681
534
  let raw = "";
682
535
  for await (const chunk of process.stdin) raw += chunk;
683
536
 
684
537
  let event;
685
- try {
686
- event = JSON.parse(raw);
687
- } catch {
688
- deny("Malformed hook input was rejected.");
689
- }
538
+ event = parseHookEvent(raw, deny);
690
539
 
691
540
  const toolInput = event?.tool_input;
692
- try { const scope = await selectHookExecutionWorkspace(installedRoot, event); repositoryRoot = scope.repositoryRoot; event = { ...event, cwd: scope.cwd }; }
541
+ const candidateWorkdir = typeof toolInput?.workdir === "string" && isAbsolute(toolInput.workdir) ? toolInput.workdir : event.cwd;
542
+ try { repositoryRoot = await selectHookRepositoryRoot(installedRoot, candidateWorkdir); }
543
+ catch { repositoryRoot = installedRoot; }
544
+ if (typeof activation?.hookPolicies !== "function") {
545
+ let projectConfig;
546
+ try {
547
+ projectConfig = await readBoundedCanonicalText(resolve(repositoryRoot, ".agents/config/project.yaml"), 65536, repositoryRoot);
548
+ } catch (error) {
549
+ if (error?.code === "ENOENT") { writeSync(1, "{}\n"); process.exit(0); }
550
+ deny("ZAIP project policy cannot be verified without hook activation.");
551
+ }
552
+ const sections = [...projectConfig.matchAll(/^zaip:([^\r\n]*)$/gm)];
553
+ const inlineDisabled = sections.length === 1 && /^\s*\{\s*enabled:\s*false\s*\}(?:\s*#.*)?$/.test(sections[0][1]);
554
+ const following = sections.length === 1 ? projectConfig.slice(sections[0].index + sections[0][0].length).split(/(?=^[^\s#])/m)[0] : "";
555
+ const enabledEntries = following.match(/^\s+enabled:/gm) ?? [];
556
+ const blockDisabled = sections.length === 1 && /^\s*(?:#.*)?$/.test(sections[0][1]) && enabledEntries.length === 1 && /^\s+enabled:\s*false(?:\s*#.*)?$/m.test(following);
557
+ if (inlineDisabled || blockDisabled) { writeSync(1, "{}\n"); process.exit(0); }
558
+ deny("ZAIP hook activation module is unavailable for an enabled or invalid project policy.");
559
+ }
560
+ const hookPolicy = await activation.hookPolicies(repositoryRoot, (path, maximumBytes) => readBoundedCanonicalText(path, maximumBytes, repositoryRoot));
561
+ activation?.recordPolicyDiagnostic?.(repositoryRoot, hookPolicy);
562
+ if (hookPolicy.tracking) activation?.recordHookObservation?.(repositoryRoot, event, hookPolicy.governance);
563
+ if (!hookPolicy.governance) {
564
+ if (hookPolicy.diagnostic && !["config_missing", "policy_missing"].includes(hookPolicy.diagnostic))
565
+ deny("ZAIP project policy is invalid or unavailable; restore the canonical activation and project configuration.");
566
+ writeSync(1, "{}\n");
567
+ process.exit(0);
568
+ }
569
+ const configuredPolicy = await configuredPermissionProfile(repositoryRoot);
570
+ const principal = [event.agent_id, event.agent_type, event.agent_path, event.parent_agent_id].every(value => value === undefined) && event.permission_mode !== "plan";
571
+ principalFullAccess = principal && configuredPolicy.filesystem;
572
+ principalFullNetwork = principal && configuredPolicy.network;
573
+ try { const scope = await selectHookExecutionWorkspace(installedRoot, event, principalFullAccess); repositoryRoot = scope.repositoryRoot; event = { ...event, cwd: scope.cwd }; }
693
574
  catch (error) { deny(error.message); }
694
- if (!isWithinRepository(event.cwd)) {
575
+ if (credentialReferencePattern.test(event.cwd)) deny("Direct credential and secret access is blocked.");
576
+ if (!principalFullAccess && !isWithinRepository(event.cwd)) {
695
577
  deny("Tool execution outside the ZAIP-AIOS repository is blocked.");
696
578
  }
697
579
 
580
+ const { validateCloudRunner, validateDirectCloudProviderMutation } = createCloudValidators({
581
+ tokenizeLiteralCommand, deny, repositoryRoot, isWithinRepository, cloudRunnerScript, cloudContractPath, cloudOperationProviders,
582
+ });
583
+
698
584
  const commandFromCmd = toolInput?.cmd;
699
585
  const commandFromCommand = toolInput?.command;
700
586
  if (typeof commandFromCmd === "string" && typeof commandFromCommand === "string" && commandFromCmd !== commandFromCommand) {
@@ -703,7 +589,7 @@ if (typeof commandFromCmd === "string" && typeof commandFromCommand === "string"
703
589
  const command = typeof commandFromCmd === "string" ? commandFromCmd : commandFromCommand;
704
590
  if (typeof command !== "string") deny("A matched tool call without a command was rejected.");
705
591
 
706
- if (await requiresOrchestratorRun(event, command) && !(await activation?.isZaipDisabled?.(repositoryRoot, boundedCanonicalText))) {
592
+ if (await requiresOrchestratorRun(event, command)) {
707
593
  const directRun = await nativeAuthority?.hasCurrentTaskOrchestratorRun(event, installedRoot, boundedCanonicalText);
708
594
  const primaryRead = (path, maximumBytes) => boundedCanonicalText(path, maximumBytes, installedRoot);
709
595
  const delegated = directRun ? false : await nativeAuthority?.hasNativeDelegatedPatch(event, repositoryRoot, primaryRead, installedRoot);
@@ -722,7 +608,7 @@ else {
722
608
  ...await reviewedReleasePaths(command, event.cwd, repositoryRoot, credentialReferencePattern, deny),
723
609
  ...await reviewedContextReadPaths(command, credentialReferencePattern, installedRoot), ...await (nativeAuthority?.reviewedValidationRuntimePaths?.(command, tokenizeLiteralCommand, repositoryRoot) ?? []),
724
610
  ];
725
- validateExplicitFilesystemReferences(command, event.cwd, reviewedPaths);
611
+ await validateExplicitFilesystemReferences(command, event.cwd, reviewedPaths);
726
612
  validateInlineExecution(command, deny);
727
613
  if (!isLiteralReadSequence(command)) {
728
614
  await validateGitPublication(command);
@@ -1 +1 @@
1
- {"version":3,"file":"codex-pre-tool-hook-template.js","sourceRoot":"","sources":["../../src/services/codex-pre-tool-hook-template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAEjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2gB3C,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsN5B,CAAC"}
1
+ {"version":3,"file":"codex-pre-tool-hook-template.js","sourceRoot":"","sources":["../../src/services/codex-pre-tool-hook-template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAEjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0W3C,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqQ5B,CAAC"}
@@ -3,22 +3,21 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { afterEach, expect, it } from "vitest";
6
- import { CODEX_HOOK_ACTIVATION } from "./codex-hook-activation.js";
7
- import { CODEX_NATIVE_TOOL_AUTHORITY_HOOK } from "./codex-native-tool-authority-template.js";
8
- import { CODEX_PRE_TOOL_HOOK } from "./codex-pre-tool-hook-template.js";
9
- import { CODEX_PRE_TOOL_SUPPORT_HOOK } from "./codex-pre-tool-support-template.js";
6
+ import { installPreToolHook } from "./codex-pre-tool-hook-fixture.test-support.js";
10
7
  const roots = [];
8
+ async function enableZaip(root) {
9
+ const config = join(root, ".agents", "config");
10
+ await mkdir(config, { recursive: true });
11
+ await writeFile(join(config, "project.yaml"), "zaip:\n enabled: true\ntracking:\n enabled: false\n");
12
+ }
11
13
  afterEach(async () => {
12
14
  await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
13
15
  });
14
16
  it("portable hook restricts HashiCorp release reads to the exact HTTPS host and GET or HEAD", async () => {
15
17
  const root = await mkdtemp(join(tmpdir(), "zaip-cli-hashicorp-read-"));
16
18
  roots.push(root);
17
- const hooks = join(root, ".codex", "hooks");
18
- await mkdir(hooks, { recursive: true });
19
- await writeFile(join(hooks, "zaip-pre-tool-use.mjs"), CODEX_PRE_TOOL_HOOK);
20
- await writeFile(join(hooks, "zaip-pre-tool-support.mjs"), CODEX_PRE_TOOL_SUPPORT_HOOK);
21
- await writeFile(join(hooks, "zaip-native-tool-authority.mjs"), CODEX_NATIVE_TOOL_AUTHORITY_HOOK);
19
+ await enableZaip(root);
20
+ const { hooks } = await installPreToolHook(root);
22
21
  const invoke = (cmd) => spawnSync(process.execPath, [join(hooks, "zaip-pre-tool-use.mjs")], {
23
22
  cwd: root,
24
23
  encoding: "utf8",
@@ -50,12 +49,7 @@ it("portable hook restricts HashiCorp release reads to the exact HTTPS host and
50
49
  it("portable hook releases Task authority only for literal ZAIP disable", async () => {
51
50
  const root = await mkdtemp(join(tmpdir(), "zaip-cli-disabled-hook-"));
52
51
  roots.push(root);
53
- const hooks = join(root, ".codex", "hooks");
54
- await mkdir(hooks, { recursive: true });
55
- await writeFile(join(hooks, "zaip-pre-tool-use.mjs"), CODEX_PRE_TOOL_HOOK);
56
- await writeFile(join(hooks, "zaip-hook-activation.mjs"), CODEX_HOOK_ACTIVATION);
57
- await writeFile(join(hooks, "zaip-pre-tool-support.mjs"), CODEX_PRE_TOOL_SUPPORT_HOOK);
58
- await writeFile(join(hooks, "zaip-native-tool-authority.mjs"), CODEX_NATIVE_TOOL_AUTHORITY_HOOK);
52
+ const { hooks } = await installPreToolHook(root);
59
53
  const config = join(root, ".agents", "config", "project.yaml");
60
54
  await mkdir(join(root, ".agents", "config"), { recursive: true });
61
55
  const run = () => spawnSync(process.execPath, [join(hooks, "zaip-pre-tool-use.mjs")], {
@@ -68,10 +62,11 @@ it("portable hook releases Task authority only for literal ZAIP disable", async
68
62
  }),
69
63
  });
70
64
  await writeFile(config, "schema_version: 1\nzaip:\n enabled: false\n");
71
- expect(run().stdout).toBe("");
65
+ expect(run().stdout).toBe("{}\n");
72
66
  await writeFile(config, 'schema_version: 1\nzaip:\n enabled: "false"\n');
73
67
  expect(JSON.parse(run().stdout).hookSpecificOutput.permissionDecision).toBe("deny");
74
68
  await writeFile(config, "schema_version: 1\nzaip:\n enabled: false\n");
69
+ await writeFile(config, "schema_version: 1\nzaip:\n enabled: true\n");
75
70
  await rm(join(hooks, "zaip-hook-activation.mjs"));
76
71
  for (const state of ["missing", "legacy"]) {
77
72
  if (state === "legacy")
@@ -79,17 +74,14 @@ it("portable hook releases Task authority only for literal ZAIP disable", async
79
74
  const result = run();
80
75
  expect(result.status).toBe(0);
81
76
  expect(result.stderr).toBe("");
82
- expect(JSON.parse(result.stdout).hookSpecificOutput.permissionDecision).toBe("deny");
77
+ expect(JSON.parse(result.stdout).hookSpecificOutput?.permissionDecision).toBe("deny");
83
78
  }
84
79
  });
85
80
  it("permits only literal reviewed release commands with controlled external paths", async () => {
86
81
  const root = await mkdtemp(join(tmpdir(), "zaip-cli-release-hook-"));
87
82
  roots.push(root);
88
- const hookPath = join(root, ".codex", "hooks", "zaip-pre-tool-use.mjs");
89
- await mkdir(join(root, ".codex", "hooks"), { recursive: true });
90
- await writeFile(hookPath, CODEX_PRE_TOOL_HOOK);
91
- await writeFile(join(root, ".codex", "hooks", "zaip-pre-tool-support.mjs"), CODEX_PRE_TOOL_SUPPORT_HOOK);
92
- await writeFile(join(root, ".codex", "hooks", "zaip-native-tool-authority.mjs"), CODEX_NATIVE_TOOL_AUTHORITY_HOOK);
83
+ await enableZaip(root);
84
+ const { hookPath } = await installPreToolHook(root);
93
85
  await mkdir(join(root, "scripts"), { recursive: true });
94
86
  await writeFile(join(root, "scripts", "local-release-preparation.mjs"), "export default null;\n");
95
87
  await writeFile(join(root, "scripts", "local-release-deploy.mjs"), "export default null;\n");
@@ -160,11 +152,8 @@ it("permits only literal reviewed release commands with controlled external path
160
152
  it("uses tool_input.cmd, rejects conflicting commands, and keeps read-only git formatting", async () => {
161
153
  const root = await mkdtemp(join(tmpdir(), "zaip-cli-pre-tool-"));
162
154
  roots.push(root);
163
- const hookPath = join(root, ".codex", "hooks", "zaip-pre-tool-use.mjs");
164
- await mkdir(join(root, ".codex", "hooks"), { recursive: true });
165
- await writeFile(hookPath, CODEX_PRE_TOOL_HOOK);
166
- await writeFile(join(root, ".codex", "hooks", "zaip-pre-tool-support.mjs"), CODEX_PRE_TOOL_SUPPORT_HOOK);
167
- await writeFile(join(root, ".codex", "hooks", "zaip-native-tool-authority.mjs"), CODEX_NATIVE_TOOL_AUTHORITY_HOOK);
155
+ await enableZaip(root);
156
+ const { hookPath } = await installPreToolHook(root);
168
157
  const invoke = (tool_input) => spawnSync(process.execPath, [hookPath], {
169
158
  cwd: root,
170
159
  encoding: "utf8",
@@ -306,6 +295,22 @@ it("uses tool_input.cmd, rejects conflicting commands, and keeps read-only git f
306
295
  expect(JSON.parse(result.stdout).hookSpecificOutput.permissionDecision).toBe("deny");
307
296
  expect(JSON.parse(result.stdout).hookSpecificOutput.permissionDecisionReason).toContain(reason);
308
297
  }
298
+ const metadataAndSearch = invoke({
299
+ cmd: "Get-Item package.json | Select-Object FullName; rg -n 'spawn|execPath|node|runCli' packages/cli/src/index.ts | Select-Object -First 22",
300
+ });
301
+ expect(metadataAndSearch.status).toBe(0);
302
+ expect(metadataAndSearch.stdout).toBe("");
303
+ const originalRead = invoke({
304
+ cmd: "Get-Item -LiteralPath node_modules/vitest | Select-Object -ExpandProperty Target; rg -n 'spawn|execPath|node|runCli' packages/cli/src -g 'prd-spec-composite-create*.ts' -g 'cli-test-harness.ts' | Select-Object -First 22",
305
+ });
306
+ expect(originalRead.status).toBe(0);
307
+ expect(originalRead.stdout).toBe("");
308
+ const realInterpreterPipe = invoke({
309
+ cmd: "rg -n 'spawn|execPath|node|runCli' packages/cli/src/index.ts | node",
310
+ });
311
+ expect(JSON.parse(realInterpreterPipe.stdout).hookSpecificOutput.permissionDecisionReason).toContain("Inline or piped interpreter execution");
312
+ expect(JSON.parse(invoke({ cmd: "Get-Item Env:SECRET | Select-Object FullName" }).stdout)
313
+ .hookSpecificOutput.permissionDecision).toBe("deny");
309
314
  const denied = invoke({ cmd: "git status", command: "git reset --hard" });
310
315
  expect(denied.status).toBe(0);
311
316
  expect(JSON.parse(denied.stdout).hookSpecificOutput.permissionDecision).toBe("deny");
@@ -314,57 +319,4 @@ it("uses tool_input.cmd, rejects conflicting commands, and keeps read-only git f
314
319
  const nestedDiskDenied = invoke({ cmd: "cmd /c " + "format" + " C:" });
315
320
  expect(JSON.parse(nestedDiskDenied.stdout).hookSpecificOutput.permissionDecision).toBe("deny");
316
321
  });
317
- it("allows explicit acceptance arguments before Task capture without allowing writes", async () => {
318
- const root = await mkdtemp(join(tmpdir(), "zaip-cli-bootstrap-hook-"));
319
- roots.push(root);
320
- const hooks = join(root, ".codex", "hooks");
321
- await mkdir(hooks, { recursive: true });
322
- await writeFile(join(hooks, "zaip-pre-tool-use.mjs"), CODEX_PRE_TOOL_HOOK);
323
- await writeFile(join(hooks, "zaip-pre-tool-support.mjs"), CODEX_PRE_TOOL_SUPPORT_HOOK);
324
- await writeFile(join(hooks, "zaip-native-tool-authority.mjs"), CODEX_NATIVE_TOOL_AUTHORITY_HOOK);
325
- const invoke = (cmd) => spawnSync(process.execPath, [join(hooks, "zaip-pre-tool-use.mjs")], {
326
- cwd: root,
327
- encoding: "utf8",
328
- windowsHide: true,
329
- env: { ...process.env, CODEX_THREAD_ID: "bootstrap-thread", CODEX_TURN_ID: "bootstrap-turn" },
330
- input: JSON.stringify({
331
- cwd: root,
332
- session_id: "bootstrap-thread",
333
- turn_id: "bootstrap-turn",
334
- tool_use_id: "bootstrap-tool",
335
- tool_name: "exec_command",
336
- tool_input: { cmd },
337
- }),
338
- });
339
- const result = invoke("node .agents/runtime/bin/zaip.mjs conversation start --intent new-work --acceptance AC-WORK-001-09 --json");
340
- expect(result.status, result.stderr).toBe(0);
341
- const output = JSON.parse(result.stdout).hookSpecificOutput;
342
- expect(output.permissionDecision, result.stdout).toBe("allow");
343
- expect(output.updatedInput.cmd).toContain("--codex-hook-id=");
344
- for (const cmd of [
345
- "ac src/a.ts payload",
346
- "AC src/a.ts payload",
347
- "(Set-Content -LiteralPath src/a.ts -Value code)",
348
- "Write-Output (Set-Content -LiteralPath src/a.ts -Value code)",
349
- "env cp src/a.ts src/b.ts",
350
- "exec cp src/a.ts src/b.ts",
351
- "FOO=1 cp src/a.ts src/b.ts",
352
- "unknown-wrapper ac src/a.ts payload",
353
- ".\\echo.cmd ac src/a.ts payload",
354
- ".\\node.cmd .agents/runtime/bin/zaip.mjs conversation start --acceptance AC-WORK-001-09",
355
- "echo.cmd ac src/a.ts payload",
356
- "node.cmd .agents/runtime/bin/zaip.mjs conversation start --acceptance AC-WORK-001-09",
357
- "node scripts/unknown.mjs --acceptance AC-WORK-001-09",
358
- "command ac src/a.ts payload",
359
- ".\\sed.exe -i s/a/b/ src/a.ts",
360
- ".\\sed.exe -f script.sed src/input.ts",
361
- "git apply patch.diff",
362
- "node scripts/read.mjs --acceptance AC-WORK-001-09; ac src/a.ts payload",
363
- "node scripts/read.mjs > src/a.ts",
364
- ]) {
365
- const blocked = invoke(cmd);
366
- expect(blocked.status, blocked.stderr).toBe(0);
367
- expect(JSON.parse(blocked.stdout).hookSpecificOutput.permissionDecision).toBe("deny");
368
- }
369
- });
370
322
  //# sourceMappingURL=codex-pre-tool-hook-template.test.js.map