apex-code 0.0.6 → 0.1.1

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 (234) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +8 -9
  3. package/dist/cli/args.d.ts +0 -12
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +2 -32
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +4 -130
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +1 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/core/agent-session-services.d.ts.map +1 -1
  15. package/dist/core/agent-session-services.js +0 -8
  16. package/dist/core/agent-session-services.js.map +1 -1
  17. package/dist/core/delegation/agents.d.ts.map +1 -1
  18. package/dist/core/delegation/agents.js +2 -1
  19. package/dist/core/delegation/agents.js.map +1 -1
  20. package/dist/core/delegation/runtime.d.ts +6 -14
  21. package/dist/core/delegation/runtime.d.ts.map +1 -1
  22. package/dist/core/delegation/runtime.js +1 -2
  23. package/dist/core/delegation/runtime.js.map +1 -1
  24. package/dist/core/extensions/loader.d.ts.map +1 -1
  25. package/dist/core/extensions/loader.js +3 -4
  26. package/dist/core/extensions/loader.js.map +1 -1
  27. package/dist/core/extensions/source-runtime.d.ts +4 -5
  28. package/dist/core/extensions/source-runtime.d.ts.map +1 -1
  29. package/dist/core/extensions/source-runtime.js +4 -5
  30. package/dist/core/extensions/source-runtime.js.map +1 -1
  31. package/dist/core/formatter-lifecycle.d.ts +2 -1
  32. package/dist/core/formatter-lifecycle.d.ts.map +1 -1
  33. package/dist/core/formatter-lifecycle.js +2 -1
  34. package/dist/core/formatter-lifecycle.js.map +1 -1
  35. package/dist/core/hooks/types.d.ts +2 -3
  36. package/dist/core/hooks/types.d.ts.map +1 -1
  37. package/dist/core/hooks/types.js +2 -3
  38. package/dist/core/hooks/types.js.map +1 -1
  39. package/dist/core/http-idle-timeout.d.ts +3 -5
  40. package/dist/core/http-idle-timeout.d.ts.map +1 -1
  41. package/dist/core/http-idle-timeout.js +3 -5
  42. package/dist/core/http-idle-timeout.js.map +1 -1
  43. package/dist/core/mcp/config.d.ts +18 -1
  44. package/dist/core/mcp/config.d.ts.map +1 -1
  45. package/dist/core/mcp/config.js +24 -7
  46. package/dist/core/mcp/config.js.map +1 -1
  47. package/dist/core/mcp/connector.d.ts +2 -3
  48. package/dist/core/mcp/connector.d.ts.map +1 -1
  49. package/dist/core/mcp/connector.js.map +1 -1
  50. package/dist/core/mcp/oauth/authorize.d.ts +2 -0
  51. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -1
  52. package/dist/core/mcp/oauth/authorize.js +5 -2
  53. package/dist/core/mcp/oauth/authorize.js.map +1 -1
  54. package/dist/core/mcp/oauth/mcp-token.d.ts +4 -6
  55. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -1
  56. package/dist/core/mcp/oauth/mcp-token.js +5 -7
  57. package/dist/core/mcp/oauth/mcp-token.js.map +1 -1
  58. package/dist/core/mcp/runtime.d.ts +1 -0
  59. package/dist/core/mcp/runtime.d.ts.map +1 -1
  60. package/dist/core/mcp/runtime.js +2 -1
  61. package/dist/core/mcp/runtime.js.map +1 -1
  62. package/dist/core/package-manager.d.ts.map +1 -1
  63. package/dist/core/package-manager.js +0 -15
  64. package/dist/core/package-manager.js.map +1 -1
  65. package/dist/core/permissions/store.d.ts +6 -11
  66. package/dist/core/permissions/store.d.ts.map +1 -1
  67. package/dist/core/permissions/store.js +23 -51
  68. package/dist/core/permissions/store.js.map +1 -1
  69. package/dist/core/project-resources.d.ts +62 -0
  70. package/dist/core/project-resources.d.ts.map +1 -0
  71. package/dist/core/project-resources.js +42 -0
  72. package/dist/core/project-resources.js.map +1 -0
  73. package/dist/core/sdk.d.ts +1 -9
  74. package/dist/core/sdk.d.ts.map +1 -1
  75. package/dist/core/sdk.js +10 -35
  76. package/dist/core/sdk.js.map +1 -1
  77. package/dist/core/session-share.d.ts +6 -8
  78. package/dist/core/session-share.d.ts.map +1 -1
  79. package/dist/core/session-share.js +9 -12
  80. package/dist/core/session-share.js.map +1 -1
  81. package/dist/core/settings-manager.d.ts +0 -21
  82. package/dist/core/settings-manager.d.ts.map +1 -1
  83. package/dist/core/settings-manager.js +4 -12
  84. package/dist/core/settings-manager.js.map +1 -1
  85. package/dist/core/tools/bash.d.ts.map +1 -1
  86. package/dist/core/tools/bash.js +0 -47
  87. package/dist/core/tools/bash.js.map +1 -1
  88. package/dist/core/tools/web-fetch.d.ts.map +1 -1
  89. package/dist/core/tools/web-fetch.js +4 -8
  90. package/dist/core/tools/web-fetch.js.map +1 -1
  91. package/dist/core/tools/web-search-exa.d.ts +3 -6
  92. package/dist/core/tools/web-search-exa.d.ts.map +1 -1
  93. package/dist/core/tools/web-search-exa.js +5 -8
  94. package/dist/core/tools/web-search-exa.js.map +1 -1
  95. package/dist/core/trust-manager.d.ts +6 -5
  96. package/dist/core/trust-manager.d.ts.map +1 -1
  97. package/dist/core/trust-manager.js +42 -19
  98. package/dist/core/trust-manager.js.map +1 -1
  99. package/dist/core/web-search-provider.d.ts +0 -10
  100. package/dist/core/web-search-provider.d.ts.map +1 -1
  101. package/dist/core/web-search-provider.js +2 -16
  102. package/dist/core/web-search-provider.js.map +1 -1
  103. package/dist/main.d.ts +0 -1
  104. package/dist/main.d.ts.map +1 -1
  105. package/dist/main.js +2 -3
  106. package/dist/main.js.map +1 -1
  107. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/settings-selector.js +1 -1
  109. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  110. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +0 -29
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  114. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  115. package/dist/modes/rpc/rpc-types.js.map +1 -1
  116. package/dist/utils/tools-manager.d.ts +0 -25
  117. package/dist/utils/tools-manager.d.ts.map +1 -1
  118. package/dist/utils/tools-manager.js +3 -52
  119. package/dist/utils/tools-manager.js.map +1 -1
  120. package/dist/utils/version-check.d.ts +0 -2
  121. package/dist/utils/version-check.d.ts.map +1 -1
  122. package/dist/utils/version-check.js +0 -2
  123. package/dist/utils/version-check.js.map +1 -1
  124. package/docs/containerization.md +8 -4
  125. package/docs/sdk.md +17 -24
  126. package/docs/security.md +7 -5
  127. package/docs/settings.md +2 -7
  128. package/docs/skills.md +4 -7
  129. package/npm-shrinkwrap.json +5 -5
  130. package/package.json +2 -2
  131. package/dist/core/sandbox/bwrap-arguments.d.ts +0 -41
  132. package/dist/core/sandbox/bwrap-arguments.d.ts.map +0 -1
  133. package/dist/core/sandbox/bwrap-arguments.js +0 -136
  134. package/dist/core/sandbox/bwrap-arguments.js.map +0 -1
  135. package/dist/core/sandbox/child-entry.d.ts +0 -2
  136. package/dist/core/sandbox/child-entry.d.ts.map +0 -1
  137. package/dist/core/sandbox/child-entry.js +0 -24
  138. package/dist/core/sandbox/child-entry.js.map +0 -1
  139. package/dist/core/sandbox/cli-launch.d.ts +0 -162
  140. package/dist/core/sandbox/cli-launch.d.ts.map +0 -1
  141. package/dist/core/sandbox/cli-launch.js +0 -366
  142. package/dist/core/sandbox/cli-launch.js.map +0 -1
  143. package/dist/core/sandbox/cli-supervisor.d.ts +0 -31
  144. package/dist/core/sandbox/cli-supervisor.d.ts.map +0 -1
  145. package/dist/core/sandbox/cli-supervisor.js +0 -147
  146. package/dist/core/sandbox/cli-supervisor.js.map +0 -1
  147. package/dist/core/sandbox/default-hosts.d.ts +0 -28
  148. package/dist/core/sandbox/default-hosts.d.ts.map +0 -1
  149. package/dist/core/sandbox/default-hosts.js +0 -69
  150. package/dist/core/sandbox/default-hosts.js.map +0 -1
  151. package/dist/core/sandbox/full-access.d.ts +0 -28
  152. package/dist/core/sandbox/full-access.d.ts.map +0 -1
  153. package/dist/core/sandbox/full-access.js +0 -63
  154. package/dist/core/sandbox/full-access.js.map +0 -1
  155. package/dist/core/sandbox/git-identity.d.ts +0 -57
  156. package/dist/core/sandbox/git-identity.d.ts.map +0 -1
  157. package/dist/core/sandbox/git-identity.js +0 -78
  158. package/dist/core/sandbox/git-identity.js.map +0 -1
  159. package/dist/core/sandbox/host-approval.d.ts +0 -42
  160. package/dist/core/sandbox/host-approval.d.ts.map +0 -1
  161. package/dist/core/sandbox/host-approval.js +0 -99
  162. package/dist/core/sandbox/host-approval.js.map +0 -1
  163. package/dist/core/sandbox/linux-backend.d.ts +0 -48
  164. package/dist/core/sandbox/linux-backend.d.ts.map +0 -1
  165. package/dist/core/sandbox/linux-backend.js +0 -328
  166. package/dist/core/sandbox/linux-backend.js.map +0 -1
  167. package/dist/core/sandbox/macos-backend.d.ts +0 -30
  168. package/dist/core/sandbox/macos-backend.d.ts.map +0 -1
  169. package/dist/core/sandbox/macos-backend.js +0 -385
  170. package/dist/core/sandbox/macos-backend.js.map +0 -1
  171. package/dist/core/sandbox/network-proxy.d.ts +0 -38
  172. package/dist/core/sandbox/network-proxy.d.ts.map +0 -1
  173. package/dist/core/sandbox/network-proxy.js +0 -153
  174. package/dist/core/sandbox/network-proxy.js.map +0 -1
  175. package/dist/core/sandbox/network-refusal.d.ts +0 -24
  176. package/dist/core/sandbox/network-refusal.d.ts.map +0 -1
  177. package/dist/core/sandbox/network-refusal.js +0 -72
  178. package/dist/core/sandbox/network-refusal.js.map +0 -1
  179. package/dist/core/sandbox/policy.d.ts +0 -40
  180. package/dist/core/sandbox/policy.d.ts.map +0 -1
  181. package/dist/core/sandbox/policy.js +0 -55
  182. package/dist/core/sandbox/policy.js.map +0 -1
  183. package/dist/core/sandbox/profiles.d.ts +0 -25
  184. package/dist/core/sandbox/profiles.d.ts.map +0 -1
  185. package/dist/core/sandbox/profiles.js +0 -23
  186. package/dist/core/sandbox/profiles.js.map +0 -1
  187. package/dist/core/sandbox/rpc/command-client.d.ts +0 -28
  188. package/dist/core/sandbox/rpc/command-client.d.ts.map +0 -1
  189. package/dist/core/sandbox/rpc/command-client.js +0 -86
  190. package/dist/core/sandbox/rpc/command-client.js.map +0 -1
  191. package/dist/core/sandbox/rpc/command-proxy.d.ts +0 -48
  192. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +0 -1
  193. package/dist/core/sandbox/rpc/command-proxy.js +0 -120
  194. package/dist/core/sandbox/rpc/command-proxy.js.map +0 -1
  195. package/dist/core/sandbox/rpc/credential-client.d.ts +0 -20
  196. package/dist/core/sandbox/rpc/credential-client.d.ts.map +0 -1
  197. package/dist/core/sandbox/rpc/credential-client.js +0 -170
  198. package/dist/core/sandbox/rpc/credential-client.js.map +0 -1
  199. package/dist/core/sandbox/rpc/credential-proxy.d.ts +0 -44
  200. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +0 -1
  201. package/dist/core/sandbox/rpc/credential-proxy.js +0 -287
  202. package/dist/core/sandbox/rpc/credential-proxy.js.map +0 -1
  203. package/dist/core/sandbox/rpc/framing.d.ts +0 -35
  204. package/dist/core/sandbox/rpc/framing.d.ts.map +0 -1
  205. package/dist/core/sandbox/rpc/framing.js +0 -114
  206. package/dist/core/sandbox/rpc/framing.js.map +0 -1
  207. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +0 -47
  208. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +0 -1
  209. package/dist/core/sandbox/rpc/git-credential-helper.js +0 -222
  210. package/dist/core/sandbox/rpc/git-credential-helper.js.map +0 -1
  211. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +0 -66
  212. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +0 -1
  213. package/dist/core/sandbox/rpc/git-credential-proxy.js +0 -209
  214. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +0 -1
  215. package/dist/core/sandbox/supervisor-temp.d.ts +0 -18
  216. package/dist/core/sandbox/supervisor-temp.d.ts.map +0 -1
  217. package/dist/core/sandbox/supervisor-temp.js +0 -21
  218. package/dist/core/sandbox/supervisor-temp.js.map +0 -1
  219. package/dist/core/sandbox/supervisor.d.ts +0 -62
  220. package/dist/core/sandbox/supervisor.d.ts.map +0 -1
  221. package/dist/core/sandbox/supervisor.js +0 -32
  222. package/dist/core/sandbox/supervisor.js.map +0 -1
  223. package/dist/core/sandbox/terminal-handoff.d.ts +0 -68
  224. package/dist/core/sandbox/terminal-handoff.d.ts.map +0 -1
  225. package/dist/core/sandbox/terminal-handoff.js +0 -221
  226. package/dist/core/sandbox/terminal-handoff.js.map +0 -1
  227. package/dist/core/sandbox/terminal-size.d.ts +0 -53
  228. package/dist/core/sandbox/terminal-size.d.ts.map +0 -1
  229. package/dist/core/sandbox/terminal-size.js +0 -126
  230. package/dist/core/sandbox/terminal-size.js.map +0 -1
  231. package/dist/core/sandbox/violations.d.ts +0 -23
  232. package/dist/core/sandbox/violations.d.ts.map +0 -1
  233. package/dist/core/sandbox/violations.js +0 -30
  234. package/dist/core/sandbox/violations.js.map +0 -1
@@ -57,19 +57,17 @@ export interface PermissionRuleStore {
57
57
  snapshot(): Promise<PermissionStoreSnapshot>;
58
58
  apply(update: PermissionUpdate): Promise<void>;
59
59
  }
60
+ /**
61
+ * Whether a permission file's contents grant anything. The trust classifier asks this
62
+ * so a `{}` file does not raise a prompt that carries no decision. Unparseable content
63
+ * counts as granting: a malformed or hostile file must prompt rather than pass.
64
+ */
65
+ export declare function permissionContentConfersAuthority(content: string | undefined): boolean;
60
66
  export interface CreateFilePermissionRuleStoreOptions {
61
67
  cwd: string;
62
68
  agentDir?: string;
63
69
  /** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */
64
70
  policyPath?: string;
65
- /**
66
- * The supervisor's captured host `policy` and `user` scopes. Defaults to
67
- * $APEX_CODE_POLICY_SNAPSHOT_PATH, which only a sandbox supervisor sets. When present
68
- * it replaces both `policyPath` and the `user` file for reading; `apply()` still
69
- * writes a `user` update to this process's own agent directory, which is how an
70
- * in-session approval persists without the child's copy becoming an authority source.
71
- */
72
- policySnapshotPath?: string;
73
71
  /** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */
74
72
  backends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;
75
73
  /** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */
@@ -81,19 +79,16 @@ export interface CreateFilePermissionRuleStoreOptions {
81
79
  export declare class FilePermissionRuleStore implements PermissionRuleStore {
82
80
  private readonly backends;
83
81
  private readonly policyPath;
84
- private readonly policySnapshotPath?;
85
82
  private readonly initialRules;
86
83
  private readonly runtimeRules;
87
84
  private readonly runtimeModes;
88
85
  private readonly projectTrusted;
89
86
  private readonly fileScopes;
90
- private snapshotScope?;
91
87
  private readonly fileScopeUpdates;
92
88
  constructor(options: CreateFilePermissionRuleStoreOptions);
93
89
  private capturedFileBackedScope;
94
90
  private readFileBackedScope;
95
91
  private readPolicy;
96
- private readPolicySnapshot;
97
92
  private capturedPolicyAndUser;
98
93
  snapshot(): Promise<PermissionStoreSnapshot>;
99
94
  apply(update: PermissionUpdate): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport { POLICY_SNAPSHOT_PATH_VARIABLE } from \"../sandbox/cli-launch.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The supervisor's captured host `policy` and `user` scopes (PS.3).\n *\n * Inside a sandboxed child neither source is reachable at its own path: the child's\n * environment allowlist removes `APEX_CODE_POLICY_PATH`, and `APEX_CODE_CODING_AGENT_DIR`\n * points at a directory inside the workspace that the child itself may write. Reading both\n * from one supervisor-written file outside every writable root is what makes the child's\n * effective authority equal the supervisor's, rather than equal to whatever the child last\n * wrote. Named only by `core/sandbox/cli-launch.ts`, which sets the variable on the\n * launch's own environment and keeps it out of the child-environment allowlist.\n */\n/** Raw file text per source, so the child validates exactly what the host would have. */\ninterface PolicySnapshotFile {\n\tversion: number;\n\tpolicy: string | null;\n\tuser: string | null;\n}\n\nfunction parseSnapshotFile(content: string): PolicySnapshotFile {\n\tconst parsed = JSON.parse(content);\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tparsed.version !== 1 ||\n\t\t(parsed.policy !== null && typeof parsed.policy !== \"string\") ||\n\t\t(parsed.user !== null && typeof parsed.user !== \"string\")\n\t) {\n\t\tthrow new Error(\"Invalid permission snapshot file: unexpected shape\");\n\t}\n\treturn parsed as PolicySnapshotFile;\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/**\n\t * The supervisor's captured host `policy` and `user` scopes. Defaults to\n\t * $APEX_CODE_POLICY_SNAPSHOT_PATH, which only a sandbox supervisor sets. When present\n\t * it replaces both `policyPath` and the `user` file for reading; `apply()` still\n\t * writes a `user` update to this process's own agent directory, which is how an\n\t * in-session approval persists without the child's copy becoming an authority source.\n\t */\n\tpolicySnapshotPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly policySnapshotPath?: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate snapshotScope?: Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}>;\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicySnapshot(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\ttry {\n\t\t\tconst snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath as string, \"utf-8\"));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: parseScope(snapshot.policy ?? undefined) },\n\t\t\t\tuser: { scope: parseScope(snapshot.user ?? undefined) },\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: emptyScope(), error: failure },\n\t\t\t\tuser: { scope: emptyScope() },\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\tif (!this.policySnapshotPath) {\n\t\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\t\tpolicy,\n\t\t\t\tuser,\n\t\t\t}));\n\t\t}\n\t\tthis.snapshotScope ??= this.readPolicySnapshot();\n\t\treturn this.snapshotScope;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current =\n\t\t\t\tdestination === \"user\" && this.policySnapshotPath\n\t\t\t\t\t? (await this.capturedPolicyAndUser()).user\n\t\t\t\t\t: await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t\tif (destination === \"user\" && this.policySnapshotPath) {\n\t\t\t\tthis.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({\n\t\t\t\t\t...snapshot,\n\t\t\t\t\tuser: { scope: next },\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport {\n\tPROJECT_LOCAL_PERMISSIONS_FILE,\n\tPROJECT_PERMISSIONS_FILE,\n\tprojectResourcePathByName,\n} from \"../project-resources.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\n/**\n * Whether a permission file's contents grant anything. The trust classifier asks this\n * so a `{}` file does not raise a prompt that carries no decision. Unparseable content\n * counts as granting: a malformed or hostile file must prompt rather than pass.\n */\nexport function permissionContentConfersAuthority(content: string | undefined): boolean {\n\tlet scope: StoredPermissionScope;\n\ttry {\n\t\tscope = parseScope(content);\n\t} catch {\n\t\treturn true;\n\t}\n\treturn scope.rules.length > 0 || scope.mode !== undefined;\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_PERMISSIONS_FILE)),\n\t\t\tlocal: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_LOCAL_PERMISSIONS_FILE)),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\tpolicy,\n\t\t\tuser,\n\t\t}));\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current = await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
@@ -15,7 +15,7 @@ import { join } from "node:path";
15
15
  import { getAgentDir } from "../../config.js";
16
16
  import { resolvePath } from "../../utils/paths.js";
17
17
  import { FileAuthStorageBackend } from "../auth-storage.js";
18
- import { POLICY_SNAPSHOT_PATH_VARIABLE } from "../sandbox/cli-launch.js";
18
+ import { PROJECT_LOCAL_PERMISSIONS_FILE, PROJECT_PERMISSIONS_FILE, projectResourcePathByName, } from "../project-resources.js";
19
19
  export const PERMISSION_MODES = ["default", "plan", "acceptEdits", "bypassPermissions", "dontAsk"];
20
20
  /** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,
21
21
  * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not
@@ -27,6 +27,21 @@ const STORE_VERSION = 1;
27
27
  function emptyScope() {
28
28
  return { version: STORE_VERSION, rules: [] };
29
29
  }
30
+ /**
31
+ * Whether a permission file's contents grant anything. The trust classifier asks this
32
+ * so a `{}` file does not raise a prompt that carries no decision. Unparseable content
33
+ * counts as granting: a malformed or hostile file must prompt rather than pass.
34
+ */
35
+ export function permissionContentConfersAuthority(content) {
36
+ let scope;
37
+ try {
38
+ scope = parseScope(content);
39
+ }
40
+ catch {
41
+ return true;
42
+ }
43
+ return scope.rules.length > 0 || scope.mode !== undefined;
44
+ }
30
45
  function isPermissionBehavior(value) {
31
46
  return value === "allow" || value === "deny" || value === "ask";
32
47
  }
@@ -79,41 +94,26 @@ function defaultPolicyPath() {
79
94
  ? join(process.env.ProgramData ?? "C:\\ProgramData", "apex-code", "policy.json")
80
95
  : "/etc/apex-code/policy.json";
81
96
  }
82
- function parseSnapshotFile(content) {
83
- const parsed = JSON.parse(content);
84
- if (typeof parsed !== "object" ||
85
- parsed === null ||
86
- Array.isArray(parsed) ||
87
- parsed.version !== 1 ||
88
- (parsed.policy !== null && typeof parsed.policy !== "string") ||
89
- (parsed.user !== null && typeof parsed.user !== "string")) {
90
- throw new Error("Invalid permission snapshot file: unexpected shape");
91
- }
92
- return parsed;
93
- }
94
97
  /** File-backed store for policy/local/project/user, in-memory for command/session. */
95
98
  export class FilePermissionRuleStore {
96
99
  backends;
97
100
  policyPath;
98
- policySnapshotPath;
99
101
  initialRules;
100
102
  runtimeRules = { command: [], session: [] };
101
103
  runtimeModes = {};
102
104
  projectTrusted;
103
105
  fileScopes = {};
104
- snapshotScope;
105
106
  fileScopeUpdates = {};
106
107
  constructor(options) {
107
108
  const agentDir = options.agentDir ?? getAgentDir();
108
109
  const cwd = resolvePath(options.cwd);
109
110
  this.backends = {
110
111
  user: new FileAuthStorageBackend(join(agentDir, "permissions.json")),
111
- project: new FileAuthStorageBackend(join(cwd, ".apex-code", "permissions.json")),
112
- local: new FileAuthStorageBackend(join(cwd, ".apex-code", "permissions.local.json")),
112
+ project: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_PERMISSIONS_FILE)),
113
+ local: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_LOCAL_PERMISSIONS_FILE)),
113
114
  ...options.backends,
114
115
  };
115
116
  this.policyPath = options.policyPath ?? defaultPolicyPath();
116
- this.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];
117
117
  this.initialRules = options.initialRules ?? [];
118
118
  this.projectTrusted = options.projectTrusted ?? true;
119
119
  if (!this.projectTrusted) {
@@ -153,31 +153,11 @@ export class FilePermissionRuleStore {
153
153
  return { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };
154
154
  }
155
155
  }
156
- async readPolicySnapshot() {
157
- try {
158
- const snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath, "utf-8"));
159
- return {
160
- policy: { scope: parseScope(snapshot.policy ?? undefined) },
161
- user: { scope: parseScope(snapshot.user ?? undefined) },
162
- };
163
- }
164
- catch (error) {
165
- const failure = error instanceof Error ? error : new Error(String(error));
166
- return {
167
- policy: { scope: emptyScope(), error: failure },
168
- user: { scope: emptyScope() },
169
- };
170
- }
171
- }
172
156
  capturedPolicyAndUser() {
173
- if (!this.policySnapshotPath) {
174
- return Promise.all([this.readPolicy(), this.capturedFileBackedScope("user")]).then(([policy, user]) => ({
175
- policy,
176
- user,
177
- }));
178
- }
179
- this.snapshotScope ??= this.readPolicySnapshot();
180
- return this.snapshotScope;
157
+ return Promise.all([this.readPolicy(), this.capturedFileBackedScope("user")]).then(([policy, user]) => ({
158
+ policy,
159
+ user,
160
+ }));
181
161
  }
182
162
  async snapshot() {
183
163
  // File-backed authorization is captured once. Direct filesystem writes,
@@ -228,21 +208,13 @@ export class FilePermissionRuleStore {
228
208
  const destination = update.destination;
229
209
  const previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();
230
210
  const nextUpdate = previousUpdate.then(async () => {
231
- const current = destination === "user" && this.policySnapshotPath
232
- ? (await this.capturedPolicyAndUser()).user
233
- : await this.capturedFileBackedScope(destination);
211
+ const current = await this.capturedFileBackedScope(destination);
234
212
  const next = applyToScope(current.scope, update);
235
213
  await this.backends[destination].withLockAsync(async () => ({
236
214
  result: undefined,
237
215
  next: JSON.stringify(next, null, 2),
238
216
  }));
239
217
  this.fileScopes[destination] = Promise.resolve({ scope: next });
240
- if (destination === "user" && this.policySnapshotPath) {
241
- this.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({
242
- ...snapshot,
243
- user: { scope: next },
244
- }));
245
- }
246
218
  });
247
219
  this.fileScopeUpdates[destination] = nextUpdate;
248
220
  await nextUpdate;
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport { POLICY_SNAPSHOT_PATH_VARIABLE } from \"../sandbox/cli-launch.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The supervisor's captured host `policy` and `user` scopes (PS.3).\n *\n * Inside a sandboxed child neither source is reachable at its own path: the child's\n * environment allowlist removes `APEX_CODE_POLICY_PATH`, and `APEX_CODE_CODING_AGENT_DIR`\n * points at a directory inside the workspace that the child itself may write. Reading both\n * from one supervisor-written file outside every writable root is what makes the child's\n * effective authority equal the supervisor's, rather than equal to whatever the child last\n * wrote. Named only by `core/sandbox/cli-launch.ts`, which sets the variable on the\n * launch's own environment and keeps it out of the child-environment allowlist.\n */\n/** Raw file text per source, so the child validates exactly what the host would have. */\ninterface PolicySnapshotFile {\n\tversion: number;\n\tpolicy: string | null;\n\tuser: string | null;\n}\n\nfunction parseSnapshotFile(content: string): PolicySnapshotFile {\n\tconst parsed = JSON.parse(content);\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tparsed.version !== 1 ||\n\t\t(parsed.policy !== null && typeof parsed.policy !== \"string\") ||\n\t\t(parsed.user !== null && typeof parsed.user !== \"string\")\n\t) {\n\t\tthrow new Error(\"Invalid permission snapshot file: unexpected shape\");\n\t}\n\treturn parsed as PolicySnapshotFile;\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/**\n\t * The supervisor's captured host `policy` and `user` scopes. Defaults to\n\t * $APEX_CODE_POLICY_SNAPSHOT_PATH, which only a sandbox supervisor sets. When present\n\t * it replaces both `policyPath` and the `user` file for reading; `apply()` still\n\t * writes a `user` update to this process's own agent directory, which is how an\n\t * in-session approval persists without the child's copy becoming an authority source.\n\t */\n\tpolicySnapshotPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly policySnapshotPath?: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate snapshotScope?: Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}>;\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicySnapshot(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\ttry {\n\t\t\tconst snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath as string, \"utf-8\"));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: parseScope(snapshot.policy ?? undefined) },\n\t\t\t\tuser: { scope: parseScope(snapshot.user ?? undefined) },\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: emptyScope(), error: failure },\n\t\t\t\tuser: { scope: emptyScope() },\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\tif (!this.policySnapshotPath) {\n\t\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\t\tpolicy,\n\t\t\t\tuser,\n\t\t\t}));\n\t\t}\n\t\tthis.snapshotScope ??= this.readPolicySnapshot();\n\t\treturn this.snapshotScope;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current =\n\t\t\t\tdestination === \"user\" && this.policySnapshotPath\n\t\t\t\t\t? (await this.capturedPolicyAndUser()).user\n\t\t\t\t\t: await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t\tif (destination === \"user\" && this.policySnapshotPath) {\n\t\t\t\tthis.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({\n\t\t\t\t\t...snapshot,\n\t\t\t\t\tuser: { scope: next },\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport {\n\tPROJECT_LOCAL_PERMISSIONS_FILE,\n\tPROJECT_PERMISSIONS_FILE,\n\tprojectResourcePathByName,\n} from \"../project-resources.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\n/**\n * Whether a permission file's contents grant anything. The trust classifier asks this\n * so a `{}` file does not raise a prompt that carries no decision. Unparseable content\n * counts as granting: a malformed or hostile file must prompt rather than pass.\n */\nexport function permissionContentConfersAuthority(content: string | undefined): boolean {\n\tlet scope: StoredPermissionScope;\n\ttry {\n\t\tscope = parseScope(content);\n\t} catch {\n\t\treturn true;\n\t}\n\treturn scope.rules.length > 0 || scope.mode !== undefined;\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_PERMISSIONS_FILE)),\n\t\t\tlocal: new FileAuthStorageBackend(projectResourcePathByName(cwd, PROJECT_LOCAL_PERMISSIONS_FILE)),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\tpolicy,\n\t\t\tuser,\n\t\t}));\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current = await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The single list of project-controlled resources a repository can supply, and the only
3
+ * place their paths are built.
4
+ *
5
+ * Both halves of the trust boundary resolve through here: the loaders ask for a path,
6
+ * and `hasTrustRequiringProjectResources` asks the same question of the same entries.
7
+ * Before this existed the two were independent, and four resources were read without
8
+ * ever reaching the classifier (`permissions.json`, `permissions.local.json`, `agents/`,
9
+ * and `.mcp.json`), so a cloned repository supplying one of them was classified trusted
10
+ * and the guard that drops untrusted scopes was satisfied rather than triggered.
11
+ *
12
+ * Relocating the list is not enough on its own. A loader that builds `join(cwd,
13
+ * ".apex-code", …)` by hand diverges the moment `CONFIG_DIR_NAME` is overridden, which
14
+ * reintroduces the same bypass with no test failing, so the path builders below are the
15
+ * supported way to reach a project resource. See
16
+ * `docs/specs/2026-09-11-trust-classification-and-proof-integrity.md`.
17
+ */
18
+ /**
19
+ * Where an entry's `name` is resolved from.
20
+ *
21
+ * `config-dir` resolves under `<cwd>/<CONFIG_DIR_NAME>`. `root` resolves at `<cwd>`,
22
+ * which is what lets `.mcp.json` reach the classifier at all.
23
+ *
24
+ * The ancestor `.agents/skills` walk is deliberately not a scope here. It has no fixed
25
+ * depth and one path under the user's home is exempt, so it lives in the classifier
26
+ * rather than as an entry a caller could resolve to a single path. Adding an `ancestor`
27
+ * member without implementing the walk would let an entry silently resolve at `cwd`
28
+ * only, which is the narrowing this registry exists to prevent.
29
+ */
30
+ export type ProjectResourceScope = "config-dir" | "root";
31
+ /**
32
+ * How presence maps to authority.
33
+ *
34
+ * `presence` means existing is enough. `permission-scope` means the file is parsed and
35
+ * only grants authority when it carries a rule or a mode, because a `{}` permissions
36
+ * file confers nothing and this repository ships one. A prompt carrying no decision
37
+ * teaches people to dismiss prompts.
38
+ */
39
+ export type ProjectResourceAuthority = "presence" | "permission-scope";
40
+ export interface ProjectResource {
41
+ /** Path relative to the entry's scope root. A single path segment. */
42
+ readonly name: string;
43
+ readonly scope: ProjectResourceScope;
44
+ readonly authority: ProjectResourceAuthority;
45
+ }
46
+ export declare const PROJECT_PERMISSIONS_FILE = "permissions.json";
47
+ export declare const PROJECT_LOCAL_PERMISSIONS_FILE = "permissions.local.json";
48
+ export declare const PROJECT_AGENTS_DIR = "agents";
49
+ export declare const PROJECT_MCP_CONFIG_FILE = ".mcp.json";
50
+ export declare const PROJECT_RESOURCES: readonly ProjectResource[];
51
+ /** The project config directory for `cwd`. Honors a `piConfig.configDir` override. */
52
+ export declare function projectConfigDir(cwd: string): string;
53
+ /**
54
+ * Absolute path of one project resource. Loaders call this instead of composing the
55
+ * config directory themselves, so a loader cannot read a path the classifier does not
56
+ * check.
57
+ */
58
+ export declare function projectResourcePath(cwd: string, resource: ProjectResource): string;
59
+ export declare function projectResource(name: string): ProjectResource;
60
+ /** Convenience for the common case: a resource addressed by name. */
61
+ export declare function projectResourcePathByName(cwd: string, name: string): string;
62
+ //# sourceMappingURL=project-resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-resources.d.ts","sourceRoot":"","sources":["../../src/core/project-resources.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,MAAM,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,wBAAwB,GAAG,UAAU,GAAG,kBAAkB,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,wBAAwB,CAAC;CAC7C;AAED,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAC3D,eAAO,MAAM,8BAA8B,2BAA2B,CAAC;AACvE,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAEnD,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAY9C,CAAC;AAEX,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,MAAM,CAElF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAI7D;AAED,qEAAqE;AACrE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E","sourcesContent":["import { join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\n\n/**\n * The single list of project-controlled resources a repository can supply, and the only\n * place their paths are built.\n *\n * Both halves of the trust boundary resolve through here: the loaders ask for a path,\n * and `hasTrustRequiringProjectResources` asks the same question of the same entries.\n * Before this existed the two were independent, and four resources were read without\n * ever reaching the classifier (`permissions.json`, `permissions.local.json`, `agents/`,\n * and `.mcp.json`), so a cloned repository supplying one of them was classified trusted\n * and the guard that drops untrusted scopes was satisfied rather than triggered.\n *\n * Relocating the list is not enough on its own. A loader that builds `join(cwd,\n * \".apex-code\", …)` by hand diverges the moment `CONFIG_DIR_NAME` is overridden, which\n * reintroduces the same bypass with no test failing, so the path builders below are the\n * supported way to reach a project resource. See\n * `docs/specs/2026-09-11-trust-classification-and-proof-integrity.md`.\n */\n\n/**\n * Where an entry's `name` is resolved from.\n *\n * `config-dir` resolves under `<cwd>/<CONFIG_DIR_NAME>`. `root` resolves at `<cwd>`,\n * which is what lets `.mcp.json` reach the classifier at all.\n *\n * The ancestor `.agents/skills` walk is deliberately not a scope here. It has no fixed\n * depth and one path under the user's home is exempt, so it lives in the classifier\n * rather than as an entry a caller could resolve to a single path. Adding an `ancestor`\n * member without implementing the walk would let an entry silently resolve at `cwd`\n * only, which is the narrowing this registry exists to prevent.\n */\nexport type ProjectResourceScope = \"config-dir\" | \"root\";\n\n/**\n * How presence maps to authority.\n *\n * `presence` means existing is enough. `permission-scope` means the file is parsed and\n * only grants authority when it carries a rule or a mode, because a `{}` permissions\n * file confers nothing and this repository ships one. A prompt carrying no decision\n * teaches people to dismiss prompts.\n */\nexport type ProjectResourceAuthority = \"presence\" | \"permission-scope\";\n\nexport interface ProjectResource {\n\t/** Path relative to the entry's scope root. A single path segment. */\n\treadonly name: string;\n\treadonly scope: ProjectResourceScope;\n\treadonly authority: ProjectResourceAuthority;\n}\n\nexport const PROJECT_PERMISSIONS_FILE = \"permissions.json\";\nexport const PROJECT_LOCAL_PERMISSIONS_FILE = \"permissions.local.json\";\nexport const PROJECT_AGENTS_DIR = \"agents\";\nexport const PROJECT_MCP_CONFIG_FILE = \".mcp.json\";\n\nexport const PROJECT_RESOURCES: readonly ProjectResource[] = [\n\t{ name: \"settings.json\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"extensions\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"skills\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"prompts\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"themes\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"SYSTEM.md\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"APPEND_SYSTEM.md\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: PROJECT_PERMISSIONS_FILE, scope: \"config-dir\", authority: \"permission-scope\" },\n\t{ name: PROJECT_LOCAL_PERMISSIONS_FILE, scope: \"config-dir\", authority: \"permission-scope\" },\n\t{ name: PROJECT_AGENTS_DIR, scope: \"config-dir\", authority: \"presence\" },\n\t{ name: PROJECT_MCP_CONFIG_FILE, scope: \"root\", authority: \"presence\" },\n] as const;\n\n/** The project config directory for `cwd`. Honors a `piConfig.configDir` override. */\nexport function projectConfigDir(cwd: string): string {\n\treturn join(cwd, CONFIG_DIR_NAME);\n}\n\n/**\n * Absolute path of one project resource. Loaders call this instead of composing the\n * config directory themselves, so a loader cannot read a path the classifier does not\n * check.\n */\nexport function projectResourcePath(cwd: string, resource: ProjectResource): string {\n\treturn join(resource.scope === \"config-dir\" ? projectConfigDir(cwd) : cwd, resource.name);\n}\n\nexport function projectResource(name: string): ProjectResource {\n\tconst resource = PROJECT_RESOURCES.find((candidate) => candidate.name === name);\n\tif (!resource) throw new Error(`Unknown project resource: ${name}`);\n\treturn resource;\n}\n\n/** Convenience for the common case: a resource addressed by name. */\nexport function projectResourcePathByName(cwd: string, name: string): string {\n\treturn projectResourcePath(cwd, projectResource(name));\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import { join } from "node:path";
2
+ import { CONFIG_DIR_NAME } from "../config.js";
3
+ export const PROJECT_PERMISSIONS_FILE = "permissions.json";
4
+ export const PROJECT_LOCAL_PERMISSIONS_FILE = "permissions.local.json";
5
+ export const PROJECT_AGENTS_DIR = "agents";
6
+ export const PROJECT_MCP_CONFIG_FILE = ".mcp.json";
7
+ export const PROJECT_RESOURCES = [
8
+ { name: "settings.json", scope: "config-dir", authority: "presence" },
9
+ { name: "extensions", scope: "config-dir", authority: "presence" },
10
+ { name: "skills", scope: "config-dir", authority: "presence" },
11
+ { name: "prompts", scope: "config-dir", authority: "presence" },
12
+ { name: "themes", scope: "config-dir", authority: "presence" },
13
+ { name: "SYSTEM.md", scope: "config-dir", authority: "presence" },
14
+ { name: "APPEND_SYSTEM.md", scope: "config-dir", authority: "presence" },
15
+ { name: PROJECT_PERMISSIONS_FILE, scope: "config-dir", authority: "permission-scope" },
16
+ { name: PROJECT_LOCAL_PERMISSIONS_FILE, scope: "config-dir", authority: "permission-scope" },
17
+ { name: PROJECT_AGENTS_DIR, scope: "config-dir", authority: "presence" },
18
+ { name: PROJECT_MCP_CONFIG_FILE, scope: "root", authority: "presence" },
19
+ ];
20
+ /** The project config directory for `cwd`. Honors a `piConfig.configDir` override. */
21
+ export function projectConfigDir(cwd) {
22
+ return join(cwd, CONFIG_DIR_NAME);
23
+ }
24
+ /**
25
+ * Absolute path of one project resource. Loaders call this instead of composing the
26
+ * config directory themselves, so a loader cannot read a path the classifier does not
27
+ * check.
28
+ */
29
+ export function projectResourcePath(cwd, resource) {
30
+ return join(resource.scope === "config-dir" ? projectConfigDir(cwd) : cwd, resource.name);
31
+ }
32
+ export function projectResource(name) {
33
+ const resource = PROJECT_RESOURCES.find((candidate) => candidate.name === name);
34
+ if (!resource)
35
+ throw new Error(`Unknown project resource: ${name}`);
36
+ return resource;
37
+ }
38
+ /** Convenience for the common case: a resource addressed by name. */
39
+ export function projectResourcePathByName(cwd, name) {
40
+ return projectResourcePath(cwd, projectResource(name));
41
+ }
42
+ //# sourceMappingURL=project-resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-resources.js","sourceRoot":"","sources":["../../src/core/project-resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAmD/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAC;AAC3D,MAAM,CAAC,MAAM,8BAA8B,GAAG,wBAAwB,CAAC;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAC3C,MAAM,CAAC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAEnD,MAAM,CAAC,MAAM,iBAAiB,GAA+B;IAC5D,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IACrE,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IAClE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IAC9D,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IAC/D,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IAC9D,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IACjE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IACxE,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE;IACtF,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE;IAC5F,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE;IACxE,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE;CAC9D,CAAC;AAEX,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAU;IACrD,OAAO,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AAAA,CAClC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,QAAyB,EAAU;IACnF,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;AAAA,CAC1F;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAAmB;IAC9D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAChF,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,qEAAqE;AACrE,MAAM,UAAU,yBAAyB,CAAC,GAAW,EAAE,IAAY,EAAU;IAC5E,OAAO,mBAAmB,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,CACvD","sourcesContent":["import { join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\n\n/**\n * The single list of project-controlled resources a repository can supply, and the only\n * place their paths are built.\n *\n * Both halves of the trust boundary resolve through here: the loaders ask for a path,\n * and `hasTrustRequiringProjectResources` asks the same question of the same entries.\n * Before this existed the two were independent, and four resources were read without\n * ever reaching the classifier (`permissions.json`, `permissions.local.json`, `agents/`,\n * and `.mcp.json`), so a cloned repository supplying one of them was classified trusted\n * and the guard that drops untrusted scopes was satisfied rather than triggered.\n *\n * Relocating the list is not enough on its own. A loader that builds `join(cwd,\n * \".apex-code\", …)` by hand diverges the moment `CONFIG_DIR_NAME` is overridden, which\n * reintroduces the same bypass with no test failing, so the path builders below are the\n * supported way to reach a project resource. See\n * `docs/specs/2026-09-11-trust-classification-and-proof-integrity.md`.\n */\n\n/**\n * Where an entry's `name` is resolved from.\n *\n * `config-dir` resolves under `<cwd>/<CONFIG_DIR_NAME>`. `root` resolves at `<cwd>`,\n * which is what lets `.mcp.json` reach the classifier at all.\n *\n * The ancestor `.agents/skills` walk is deliberately not a scope here. It has no fixed\n * depth and one path under the user's home is exempt, so it lives in the classifier\n * rather than as an entry a caller could resolve to a single path. Adding an `ancestor`\n * member without implementing the walk would let an entry silently resolve at `cwd`\n * only, which is the narrowing this registry exists to prevent.\n */\nexport type ProjectResourceScope = \"config-dir\" | \"root\";\n\n/**\n * How presence maps to authority.\n *\n * `presence` means existing is enough. `permission-scope` means the file is parsed and\n * only grants authority when it carries a rule or a mode, because a `{}` permissions\n * file confers nothing and this repository ships one. A prompt carrying no decision\n * teaches people to dismiss prompts.\n */\nexport type ProjectResourceAuthority = \"presence\" | \"permission-scope\";\n\nexport interface ProjectResource {\n\t/** Path relative to the entry's scope root. A single path segment. */\n\treadonly name: string;\n\treadonly scope: ProjectResourceScope;\n\treadonly authority: ProjectResourceAuthority;\n}\n\nexport const PROJECT_PERMISSIONS_FILE = \"permissions.json\";\nexport const PROJECT_LOCAL_PERMISSIONS_FILE = \"permissions.local.json\";\nexport const PROJECT_AGENTS_DIR = \"agents\";\nexport const PROJECT_MCP_CONFIG_FILE = \".mcp.json\";\n\nexport const PROJECT_RESOURCES: readonly ProjectResource[] = [\n\t{ name: \"settings.json\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"extensions\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"skills\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"prompts\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"themes\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"SYSTEM.md\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: \"APPEND_SYSTEM.md\", scope: \"config-dir\", authority: \"presence\" },\n\t{ name: PROJECT_PERMISSIONS_FILE, scope: \"config-dir\", authority: \"permission-scope\" },\n\t{ name: PROJECT_LOCAL_PERMISSIONS_FILE, scope: \"config-dir\", authority: \"permission-scope\" },\n\t{ name: PROJECT_AGENTS_DIR, scope: \"config-dir\", authority: \"presence\" },\n\t{ name: PROJECT_MCP_CONFIG_FILE, scope: \"root\", authority: \"presence\" },\n] as const;\n\n/** The project config directory for `cwd`. Honors a `piConfig.configDir` override. */\nexport function projectConfigDir(cwd: string): string {\n\treturn join(cwd, CONFIG_DIR_NAME);\n}\n\n/**\n * Absolute path of one project resource. Loaders call this instead of composing the\n * config directory themselves, so a loader cannot read a path the classifier does not\n * check.\n */\nexport function projectResourcePath(cwd: string, resource: ProjectResource): string {\n\treturn join(resource.scope === \"config-dir\" ? projectConfigDir(cwd) : cwd, resource.name);\n}\n\nexport function projectResource(name: string): ProjectResource {\n\tconst resource = PROJECT_RESOURCES.find((candidate) => candidate.name === name);\n\tif (!resource) throw new Error(`Unknown project resource: ${name}`);\n\treturn resource;\n}\n\n/** Convenience for the common case: a resource addressed by name. */\nexport function projectResourcePathByName(cwd: string, name: string): string {\n\treturn projectResourcePath(cwd, projectResource(name));\n}\n"]}
@@ -139,14 +139,10 @@ export interface CreateAgentSessionOptions {
139
139
  };
140
140
  /**
141
141
  * Credential store the MCP OAuth connector uses (spec 2026-09-01-mcp-oauth).
142
- * Default: the sandbox channel store in a sandboxed child, else the host
143
- * AuthStorage — the same fallback chain the model runtime applies.
142
+ * Default: the host AuthStorage, the same fallback the model runtime applies.
144
143
  */
145
144
  mcpCredentials?: CredentialStore;
146
- /** The caller's assertion about OS sandbox containment. */
147
- sandbox?: SdkSandboxContract;
148
145
  }
149
- export type SdkSandboxContract = "required" | "external" | "none";
150
146
  /** Result from createAgentSession */
151
147
  export interface CreateAgentSessionResult {
152
148
  /** The created session */
@@ -162,11 +158,7 @@ export interface CreateAgentSessionResult {
162
158
  delegationRuntime?: DelegationRuntimeOptions;
163
159
  /** Warning if session was restored with a different model than saved */
164
160
  modelFallbackMessage?: string;
165
- sandboxContract: SdkSandboxContract;
166
- sandboxDiagnostic?: string;
167
161
  }
168
- export declare const SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE = "APEX_CODE_SANDBOX_ENFORCED";
169
- export declare const SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE = "1";
170
162
  export * from "./agent-session-runtime.ts";
171
163
  export type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionFactory, InlineExtension, SlashCommandInfo, SlashCommandSource, ToolDefinition, } from "./extensions/index.ts";
172
164
  export type { PromptTemplate } from "./prompt-templates.ts";