cedar-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/.github/workflows/release.yml +42 -0
  4. package/.nvmrc +1 -0
  5. package/CHANGELOG.md +241 -0
  6. package/CONTRIBUTING.md +83 -0
  7. package/LICENSE +182 -0
  8. package/README.md +1635 -0
  9. package/SECURITY.md +37 -0
  10. package/dist/http-server.d.ts +61 -0
  11. package/dist/http-server.d.ts.map +1 -0
  12. package/dist/http-server.js +194 -0
  13. package/dist/http-server.js.map +1 -0
  14. package/dist/index.d.ts +32 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +270 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/parser/policy-ast.d.ts +49 -0
  19. package/dist/parser/policy-ast.d.ts.map +1 -0
  20. package/dist/parser/policy-ast.js +311 -0
  21. package/dist/parser/policy-ast.js.map +1 -0
  22. package/dist/prompts/index.d.ts +38 -0
  23. package/dist/prompts/index.d.ts.map +1 -0
  24. package/dist/prompts/index.js +172 -0
  25. package/dist/prompts/index.js.map +1 -0
  26. package/dist/resources/ref-resolver.d.ts +23 -0
  27. package/dist/resources/ref-resolver.d.ts.map +1 -0
  28. package/dist/resources/ref-resolver.js +128 -0
  29. package/dist/resources/ref-resolver.js.map +1 -0
  30. package/dist/resources/store-manager.d.ts +64 -0
  31. package/dist/resources/store-manager.d.ts.map +1 -0
  32. package/dist/resources/store-manager.js +221 -0
  33. package/dist/resources/store-manager.js.map +1 -0
  34. package/dist/server.d.ts +18 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +539 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/tools/advise/avp-rules.d.ts +49 -0
  39. package/dist/tools/advise/avp-rules.d.ts.map +1 -0
  40. package/dist/tools/advise/avp-rules.js +59 -0
  41. package/dist/tools/advise/avp-rules.js.map +1 -0
  42. package/dist/tools/advise/cedar-patterns.d.ts +24 -0
  43. package/dist/tools/advise/cedar-patterns.d.ts.map +1 -0
  44. package/dist/tools/advise/cedar-patterns.js +57 -0
  45. package/dist/tools/advise/cedar-patterns.js.map +1 -0
  46. package/dist/tools/advise/context-builder.d.ts +28 -0
  47. package/dist/tools/advise/context-builder.d.ts.map +1 -0
  48. package/dist/tools/advise/context-builder.js +89 -0
  49. package/dist/tools/advise/context-builder.js.map +1 -0
  50. package/dist/tools/advise/gotchas.d.ts +15 -0
  51. package/dist/tools/advise/gotchas.d.ts.map +1 -0
  52. package/dist/tools/advise/gotchas.js +83 -0
  53. package/dist/tools/advise/gotchas.js.map +1 -0
  54. package/dist/tools/advise.d.ts +96 -0
  55. package/dist/tools/advise.d.ts.map +1 -0
  56. package/dist/tools/advise.js +258 -0
  57. package/dist/tools/advise.js.map +1 -0
  58. package/dist/tools/authorize-batch.d.ts +35 -0
  59. package/dist/tools/authorize-batch.d.ts.map +1 -0
  60. package/dist/tools/authorize-batch.js +262 -0
  61. package/dist/tools/authorize-batch.js.map +1 -0
  62. package/dist/tools/authorize.d.ts +115 -0
  63. package/dist/tools/authorize.d.ts.map +1 -0
  64. package/dist/tools/authorize.js +373 -0
  65. package/dist/tools/authorize.js.map +1 -0
  66. package/dist/tools/check-change.d.ts +19 -0
  67. package/dist/tools/check-change.d.ts.map +1 -0
  68. package/dist/tools/check-change.js +91 -0
  69. package/dist/tools/check-change.js.map +1 -0
  70. package/dist/tools/diff-schema.d.ts +103 -0
  71. package/dist/tools/diff-schema.d.ts.map +1 -0
  72. package/dist/tools/diff-schema.js +379 -0
  73. package/dist/tools/diff-schema.js.map +1 -0
  74. package/dist/tools/diff-stores.d.ts +45 -0
  75. package/dist/tools/diff-stores.d.ts.map +1 -0
  76. package/dist/tools/diff-stores.js +222 -0
  77. package/dist/tools/diff-stores.js.map +1 -0
  78. package/dist/tools/explain.d.ts +80 -0
  79. package/dist/tools/explain.d.ts.map +1 -0
  80. package/dist/tools/explain.js +187 -0
  81. package/dist/tools/explain.js.map +1 -0
  82. package/dist/tools/format.d.ts +11 -0
  83. package/dist/tools/format.d.ts.map +1 -0
  84. package/dist/tools/format.js +20 -0
  85. package/dist/tools/format.js.map +1 -0
  86. package/dist/tools/generate-sample.d.ts +28 -0
  87. package/dist/tools/generate-sample.d.ts.map +1 -0
  88. package/dist/tools/generate-sample.js +568 -0
  89. package/dist/tools/generate-sample.js.map +1 -0
  90. package/dist/tools/link-template.d.ts +17 -0
  91. package/dist/tools/link-template.d.ts.map +1 -0
  92. package/dist/tools/link-template.js +78 -0
  93. package/dist/tools/link-template.js.map +1 -0
  94. package/dist/tools/list-template-links.d.ts +16 -0
  95. package/dist/tools/list-template-links.d.ts.map +1 -0
  96. package/dist/tools/list-template-links.js +22 -0
  97. package/dist/tools/list-template-links.js.map +1 -0
  98. package/dist/tools/list-templates.d.ts +16 -0
  99. package/dist/tools/list-templates.d.ts.map +1 -0
  100. package/dist/tools/list-templates.js +36 -0
  101. package/dist/tools/list-templates.js.map +1 -0
  102. package/dist/tools/translate.d.ts +11 -0
  103. package/dist/tools/translate.d.ts.map +1 -0
  104. package/dist/tools/translate.js +53 -0
  105. package/dist/tools/translate.js.map +1 -0
  106. package/dist/tools/validate-entities.d.ts +19 -0
  107. package/dist/tools/validate-entities.d.ts.map +1 -0
  108. package/dist/tools/validate-entities.js +88 -0
  109. package/dist/tools/validate-entities.js.map +1 -0
  110. package/dist/tools/validate-schema.d.ts +22 -0
  111. package/dist/tools/validate-schema.d.ts.map +1 -0
  112. package/dist/tools/validate-schema.js +89 -0
  113. package/dist/tools/validate-schema.js.map +1 -0
  114. package/dist/tools/validate-template.d.ts +18 -0
  115. package/dist/tools/validate-template.d.ts.map +1 -0
  116. package/dist/tools/validate-template.js +59 -0
  117. package/dist/tools/validate-template.js.map +1 -0
  118. package/dist/tools/validate.d.ts +90 -0
  119. package/dist/tools/validate.d.ts.map +1 -0
  120. package/dist/tools/validate.js +351 -0
  121. package/dist/tools/validate.js.map +1 -0
  122. package/dist/utils/format-detector.d.ts +49 -0
  123. package/dist/utils/format-detector.d.ts.map +1 -0
  124. package/dist/utils/format-detector.js +298 -0
  125. package/dist/utils/format-detector.js.map +1 -0
  126. package/examples/README.md +36 -0
  127. package/examples/abac-multi-tenant/README.md +150 -0
  128. package/examples/abac-multi-tenant/entities/users-and-docs.json +33 -0
  129. package/examples/abac-multi-tenant/policies/member-read-internal.cedar +9 -0
  130. package/examples/abac-multi-tenant/policies/owner-full-access.cedar +9 -0
  131. package/examples/abac-multi-tenant/policies/premium-share-guard.cedar +9 -0
  132. package/examples/abac-multi-tenant/policies/private-doc-guard.cedar +13 -0
  133. package/examples/abac-multi-tenant/run.ts +92 -0
  134. package/examples/abac-multi-tenant/schema.json +60 -0
  135. package/examples/api-gateway-path-routing/README.md +154 -0
  136. package/examples/api-gateway-path-routing/entities/users-and-roles.json +20 -0
  137. package/examples/api-gateway-path-routing/policies/admin-full-access.cedar +6 -0
  138. package/examples/api-gateway-path-routing/policies/developer-projects.cedar +14 -0
  139. package/examples/api-gateway-path-routing/policies/viewer-readonly.cedar +10 -0
  140. package/examples/api-gateway-path-routing/run.ts +108 -0
  141. package/examples/api-gateway-path-routing/schema.json +54 -0
  142. package/examples/rbac-document-management/README.md +167 -0
  143. package/examples/rbac-document-management/entities/users-and-docs.json +43 -0
  144. package/examples/rbac-document-management/policies/admin.cedar +6 -0
  145. package/examples/rbac-document-management/policies/editor.cedar +6 -0
  146. package/examples/rbac-document-management/policies/top-secret-forbid.cedar +13 -0
  147. package/examples/rbac-document-management/policies/viewer.cedar +6 -0
  148. package/examples/rbac-document-management/run.ts +87 -0
  149. package/examples/rbac-document-management/schema.json +57 -0
  150. package/package.json +50 -0
  151. package/src/http-server.ts +239 -0
  152. package/src/index.ts +294 -0
  153. package/src/parser/policy-ast.ts +345 -0
  154. package/src/prompts/README.md +3 -0
  155. package/src/prompts/index.ts +217 -0
  156. package/src/resources/ref-resolver.ts +134 -0
  157. package/src/resources/store-manager.ts +248 -0
  158. package/src/server.ts +711 -0
  159. package/src/tools/advise/avp-rules.ts +70 -0
  160. package/src/tools/advise/cedar-patterns.ts +73 -0
  161. package/src/tools/advise/context-builder.ts +109 -0
  162. package/src/tools/advise/gotchas.ts +92 -0
  163. package/src/tools/advise.ts +366 -0
  164. package/src/tools/authorize-batch.ts +345 -0
  165. package/src/tools/authorize.ts +464 -0
  166. package/src/tools/check-change.ts +119 -0
  167. package/src/tools/diff-schema.ts +510 -0
  168. package/src/tools/diff-stores.ts +298 -0
  169. package/src/tools/explain.ts +278 -0
  170. package/src/tools/format.ts +33 -0
  171. package/src/tools/generate-sample.ts +665 -0
  172. package/src/tools/link-template.ts +109 -0
  173. package/src/tools/list-template-links.ts +41 -0
  174. package/src/tools/list-templates.ts +55 -0
  175. package/src/tools/translate.ts +66 -0
  176. package/src/tools/validate-entities.ts +125 -0
  177. package/src/tools/validate-schema.ts +128 -0
  178. package/src/tools/validate-template.ts +72 -0
  179. package/src/tools/validate.ts +459 -0
  180. package/src/utils/format-detector.ts +356 -0
  181. package/test/fixtures/docmgmt.ts +121 -0
  182. package/test/fixtures/multitenant.ts +163 -0
  183. package/test/index.test.ts +96 -0
  184. package/test/integration/e2e/behavior.test.ts +359 -0
  185. package/test/integration/e2e/edge-cases.test.ts +365 -0
  186. package/test/integration/e2e/failure-modes.test.ts +266 -0
  187. package/test/integration/e2e/protocol.test.ts +252 -0
  188. package/test/integration/http-smoke.test.ts +588 -0
  189. package/test/integration/smoke.test.ts +475 -0
  190. package/test/prompts/prompts.test.ts +173 -0
  191. package/test/property/properties.test.ts +234 -0
  192. package/test/resources/ref-resolver.test.ts +186 -0
  193. package/test/resources/store-manager.test.ts +344 -0
  194. package/test/setup.test.ts +7 -0
  195. package/test/tools/advise/avp-rules.test.ts +76 -0
  196. package/test/tools/advise.test.ts +339 -0
  197. package/test/tools/authorize-batch.test.ts +459 -0
  198. package/test/tools/authorize.test.ts +682 -0
  199. package/test/tools/check-change.test.ts +104 -0
  200. package/test/tools/cross-fixture.test.ts +170 -0
  201. package/test/tools/diff-schema.test.ts +355 -0
  202. package/test/tools/diff-stores.test.ts +291 -0
  203. package/test/tools/explain.test.ts +221 -0
  204. package/test/tools/format.test.ts +33 -0
  205. package/test/tools/generate-sample.test.ts +480 -0
  206. package/test/tools/link-template.test.ts +90 -0
  207. package/test/tools/list-templates.test.ts +151 -0
  208. package/test/tools/translate.test.ts +89 -0
  209. package/test/tools/validate-entities.test.ts +178 -0
  210. package/test/tools/validate-schema.test.ts +86 -0
  211. package/test/tools/validate-template.test.ts +89 -0
  212. package/test/tools/validate.test.ts +331 -0
  213. package/test/utils/format-detector.test.ts +518 -0
  214. package/tsconfig.json +17 -0
  215. package/vitest.config.ts +13 -0
@@ -0,0 +1,366 @@
1
+ /**
2
+ * cedar_advise — Cedar policy change planning context preparator.
3
+ *
4
+ * This tool returns deterministic, structured context for the calling LLM to
5
+ * produce a Cedar policy change plan natively. No MCP sampling, no client LLM
6
+ * round-trip. The bundle encodes Cedar/AVP knowledge that does not live in the
7
+ * policy files themselves (pattern classification, AVP UpdatePolicy mutability
8
+ * rules, intent-selected gotchas, sequencing rules) and is what makes the
9
+ * server load-bearing rather than substitutable by Read.
10
+ *
11
+ * Design v2, 2026-05-21: pivoted from sampling-based planner. See
12
+ * projects/cedar-mcp-server/02-technical-design.md "design v2".
13
+ */
14
+
15
+ import { checkParseSchema, schemaToJsonWithResolvedTypes } from "@cedar-policy/cedar-wasm/nodejs";
16
+ import type { Schema } from "@cedar-policy/cedar-wasm/nodejs";
17
+ import { resolveStoreRef, buildStoreContext } from "./advise/context-builder.js";
18
+ import type { PolicyInventoryEntry } from "./advise/context-builder.js";
19
+ import { selectGotchas, type Gotcha } from "./advise/gotchas.js";
20
+ import { CEDAR_PATTERNS_SUMMARY } from "./advise/cedar-patterns.js";
21
+ import { AVP_RULES_SUMMARY, AVP_VALIDATION_ERRORS } from "./advise/avp-rules.js";
22
+ import { storeManager, StoreManager } from "../resources/store-manager.js";
23
+
24
+ export interface AdviseInput {
25
+ intent: string;
26
+ store_ref?: string;
27
+ }
28
+
29
+ export interface AdviseGotcha {
30
+ id: string;
31
+ severity: "high" | "medium" | "info";
32
+ description: string;
33
+ avp_error_category?: string;
34
+ }
35
+
36
+ export interface SchemaSummary {
37
+ valid: boolean;
38
+ format: "json" | "cedarschema";
39
+ namespaces: string[];
40
+ entity_type_count: number;
41
+ action_count: number;
42
+ raw_text: string;
43
+ errors?: string[];
44
+ }
45
+
46
+ export interface PatternDetected {
47
+ pattern: string;
48
+ count: number;
49
+ }
50
+
51
+ export interface CedarPatternDescription {
52
+ name: string;
53
+ description: string;
54
+ example: string;
55
+ }
56
+
57
+ export interface AvpUpdatePolicyRules {
58
+ summary: string;
59
+ in_place_via_update_policy: string[];
60
+ requires_delete_recreate: string[];
61
+ new_via_create_policy: string[];
62
+ notes: string[];
63
+ }
64
+
65
+ export interface AdviseContextBundle {
66
+ tool: "cedar_advise";
67
+ bundle_version: "v2";
68
+ intent: string;
69
+ store_name?: string;
70
+ store_status: "loaded" | "not_provided" | "not_found" | "ambiguous";
71
+ /**
72
+ * Names of every currently loaded store, populated when the resolver could
73
+ * not commit to a single store: `not_found` (caller named an unknown store)
74
+ * and `ambiguous` (no `store_ref` passed but multiple stores are loaded).
75
+ * Lets the calling LLM recover by retrying with an explicit `store_ref`
76
+ * without making another tool call just to learn the candidate names.
77
+ */
78
+ available_stores?: string[];
79
+ /**
80
+ * Populated when the resolver inferred the store rather than honoring an
81
+ * explicit `store_ref`. Currently the only resolution path is
82
+ * `single_loaded_store` (no `store_ref`, exactly one store loaded).
83
+ */
84
+ auto_discovered?: { store_from: "single_loaded_store" };
85
+ schema_summary?: SchemaSummary;
86
+ policy_inventory: PolicyInventoryEntry[];
87
+ patterns_detected_in_store: PatternDetected[];
88
+ applicable_gotchas: AdviseGotcha[];
89
+ avp_update_policy_rules: AvpUpdatePolicyRules;
90
+ avp_validation_error_catalog: { id: string; description: string }[];
91
+ cedar_patterns_reference: {
92
+ summary: string;
93
+ patterns: CedarPatternDescription[];
94
+ };
95
+ sequencing_guidance: string[];
96
+ next_steps_for_llm: string;
97
+ }
98
+
99
+ const SEQUENCING_GUIDANCE: string[] = [
100
+ "Schema changes that add new entity types, attributes, or actions MUST be deployed BEFORE policies that reference them. AVP validates each policy against the current schema at CreatePolicy/UpdatePolicy time.",
101
+ "Removing an entity type, action, or required attribute is BREAKING for any policy that references it. Update or delete dependent policies first, then remove the schema element.",
102
+ "Changing a required attribute to optional is safe; changing an optional attribute to required can silently skip policies that previously matched (Cedar drops policies that touch a missing required attribute via UnsafeOptionalAttributeAccess).",
103
+ "For renames (entity type, action id, attribute name), there is no atomic rename in AVP. Plan as: add the new name + dual-write policies, migrate consumers, then remove the old name.",
104
+ ];
105
+
106
+ const AVP_UPDATE_POLICY_RULES: AvpUpdatePolicyRules = {
107
+ summary: AVP_RULES_SUMMARY,
108
+ in_place_via_update_policy: [
109
+ "Action scope (action == ... or action in [...])",
110
+ "When/unless condition clauses",
111
+ "Policy name / description metadata",
112
+ ],
113
+ requires_delete_recreate: [
114
+ "Effect (permit ↔ forbid)",
115
+ "Principal scope (head clause)",
116
+ "Resource scope (head clause)",
117
+ "Conversion between static and template-linked policy",
118
+ ],
119
+ new_via_create_policy: [
120
+ "Wholly new policy added to the store (no in-place path applies)",
121
+ ],
122
+ notes: [
123
+ "UpdatePolicy only updates STATIC policies. Template-linked policies use UpdatePolicyTemplate (or relink).",
124
+ "AVP error code for an attempted illegal change is ConflictException / ResourceNotFoundException depending on whether the policy still resolves under the new shape.",
125
+ ],
126
+ };
127
+
128
+ const CEDAR_PATTERNS_DETAIL: CedarPatternDescription[] = [
129
+ {
130
+ name: "Membership (RBAC)",
131
+ description: "Principal head clause uses `in Role::\"…\"` or similar group/role parent. Permissions follow group membership.",
132
+ example: 'permit (principal in App::Role::"editor", action in [App::Action::"read"], resource);',
133
+ },
134
+ {
135
+ name: "Relationship (ReBAC)",
136
+ description: "Conditions reference principal's relationship to the resource (often via an attribute on resource like `owners`, `viewers`, `team`).",
137
+ example: 'permit (principal is App::User, action in [App::Action::"read"], resource is App::Doc) when { principal in resource.owners };',
138
+ },
139
+ {
140
+ name: "Discretionary",
141
+ description: "Principal head clause uses `==` to grant access to a specific named entity (no group, no relationship — ad hoc).",
142
+ example: 'permit (principal == App::Service::"ingest", action == App::Action::"write", resource == App::Bucket::"raw");',
143
+ },
144
+ {
145
+ name: "Hybrid",
146
+ description: "Policy combines membership in the head clause with a relationship or ABAC predicate in the condition body. Common in production stores.",
147
+ example: 'permit (principal in App::Role::"editor", action in [App::Action::"write"], resource) when { principal in resource.team };',
148
+ },
149
+ ];
150
+
151
+ const NEXT_STEPS_FOR_LLM = `Use this context to produce a Cedar policy change plan. Do not skip these steps:
152
+
153
+ 1. Identify the entity types, attributes, and actions in schema_summary that the user's intent touches. If schema_summary is absent (store_status != "loaded"), state the assumption you are making about the schema and ask the user to confirm. If store_status is "ambiguous", do NOT pick a store silently; ask the user which one from available_stores and re-invoke cedar_advise with an explicit store_ref. If store_status is "not_found" and available_stores is populated, the caller probably mistyped the store name; re-invoke with the corrected name from available_stores. If policy_inventory is empty even though the store loaded, state that the store has no policies yet and confirm with the user before proposing anything other than initial-state policies.
154
+ 2. Pick the most appropriate cedar_patterns_reference pattern for the change. If the store already uses a dominant pattern (see patterns_detected_in_store), prefer it for consistency unless the intent requires otherwise.
155
+ 3. Sequence steps per sequencing_guidance. Schema changes that add a referenced attribute MUST precede policy changes that read it.
156
+ 4. For each step that modifies an existing policy, classify it against avp_update_policy_rules. Effect / principal / resource changes require delete-and-recreate; action and when/unless changes are in-place.
157
+ 5. Address every applicable_gotcha in the plan (either by structuring the snippet to avoid it, or by surfacing it as a warning to the user).
158
+ 6. After drafting Cedar snippets, call cedar_validate on each to confirm syntax + schema-typing before recommending them. Reading a snippet does not tell you whether the Cedar parser accepts it.
159
+ 7. For each modification to an existing policy, call cedar_check_policy_change with the old and new text to confirm the AVP UpdatePolicy classification you assigned.
160
+ 8. If the change spans two stores (current vs. proposed), call cedar_diff_policy_stores to check for behavioral drift before recommending deployment.`;
161
+
162
+ /**
163
+ * Build the cedar_advise context bundle.
164
+ *
165
+ * Pure function — no sampler, no LLM round-trip. The calling MCP client's
166
+ * conversation LLM is expected to interpret this bundle and produce the plan.
167
+ */
168
+ export function handleAdvise(
169
+ input: AdviseInput,
170
+ manager: StoreManager = storeManager
171
+ ): AdviseContextBundle {
172
+ const gotchas = selectGotchas(input.intent).map(gotchaToAdvise);
173
+ const storeView = resolveStoreView(input.store_ref, manager);
174
+
175
+ const bundle: AdviseContextBundle = {
176
+ tool: "cedar_advise",
177
+ bundle_version: "v2",
178
+ intent: input.intent,
179
+ store_name: storeView.store_name,
180
+ store_status: storeView.store_status,
181
+ schema_summary: storeView.schema_summary,
182
+ policy_inventory: storeView.policy_inventory,
183
+ patterns_detected_in_store: storeView.patterns_detected,
184
+ applicable_gotchas: gotchas,
185
+ avp_update_policy_rules: AVP_UPDATE_POLICY_RULES,
186
+ avp_validation_error_catalog: AVP_VALIDATION_ERRORS.map(e => ({ id: e.id, description: e.description })),
187
+ cedar_patterns_reference: {
188
+ summary: CEDAR_PATTERNS_SUMMARY,
189
+ patterns: CEDAR_PATTERNS_DETAIL,
190
+ },
191
+ sequencing_guidance: SEQUENCING_GUIDANCE,
192
+ next_steps_for_llm: NEXT_STEPS_FOR_LLM,
193
+ };
194
+ if (storeView.available_stores) bundle.available_stores = storeView.available_stores;
195
+ if (storeView.auto_discovered) bundle.auto_discovered = storeView.auto_discovered;
196
+ return bundle;
197
+ }
198
+
199
+ interface StoreView {
200
+ store_name?: string;
201
+ store_status: "loaded" | "not_provided" | "not_found" | "ambiguous";
202
+ schema_summary?: SchemaSummary;
203
+ policy_inventory: PolicyInventoryEntry[];
204
+ patterns_detected: PatternDetected[];
205
+ available_stores?: string[];
206
+ auto_discovered?: { store_from: "single_loaded_store" };
207
+ }
208
+
209
+ function resolveStoreView(storeRef: string | undefined, manager: StoreManager): StoreView {
210
+ if (!storeRef) {
211
+ // Round 4 dogfood (Scenario A): with no store_ref, the calling LLM has no
212
+ // way to learn what stores are loaded short of making a separate tool call
213
+ // and reading `auto_discovered` off the response. Auto-resolve when the
214
+ // resolution is unambiguous; surface candidate names otherwise so the
215
+ // caller can retry with an explicit `store_ref`.
216
+ const names = manager.listStoreNames();
217
+ if (names.length === 0) {
218
+ return { store_status: "not_provided", policy_inventory: [], patterns_detected: [] };
219
+ }
220
+ if (names.length === 1) {
221
+ const onlyStoreName = names[0]!;
222
+ const ctx = buildStoreContext(onlyStoreName, manager);
223
+ if (!ctx) {
224
+ // 11d audit finding: the single loaded store exists in StoreManager but
225
+ // cannot be grounded (most often: workspace has policies/ but no
226
+ // schema.cedarschema or schema.json, so `buildStoreContext` throws
227
+ // inside readSchema and returns null). Returning `not_found` with the
228
+ // candidate name is self-referential and triggers the next_steps_for_llm
229
+ // "re-invoke with corrected name from available_stores" advice, which
230
+ // loops. Degrade to `not_provided` instead: the bundle still carries
231
+ // the universal Cedar/AVP context, and the LLM treats it as
232
+ // store-less rather than as a typo'd ref.
233
+ return { store_status: "not_provided", policy_inventory: [], patterns_detected: [] };
234
+ }
235
+ return {
236
+ store_name: ctx.store_name,
237
+ store_status: "loaded",
238
+ schema_summary: summarizeSchema(ctx.schema_text),
239
+ policy_inventory: ctx.policy_inventory,
240
+ patterns_detected: countPatterns(ctx.policy_inventory),
241
+ auto_discovered: { store_from: "single_loaded_store" },
242
+ };
243
+ }
244
+ return {
245
+ store_status: "ambiguous",
246
+ policy_inventory: [],
247
+ patterns_detected: [],
248
+ available_stores: names,
249
+ };
250
+ }
251
+ const storeName = resolveStoreRef(storeRef);
252
+ const ctx = buildStoreContext(storeName, manager);
253
+ if (!ctx) {
254
+ const available = manager.listStoreNames();
255
+ const view: StoreView = {
256
+ store_name: storeName,
257
+ store_status: "not_found",
258
+ policy_inventory: [],
259
+ patterns_detected: [],
260
+ };
261
+ if (available.length > 0) view.available_stores = available;
262
+ return view;
263
+ }
264
+ return {
265
+ store_name: ctx.store_name,
266
+ store_status: "loaded",
267
+ schema_summary: summarizeSchema(ctx.schema_text),
268
+ policy_inventory: ctx.policy_inventory,
269
+ patterns_detected: countPatterns(ctx.policy_inventory),
270
+ };
271
+ }
272
+
273
+ function summarizeSchema(schemaText: string): SchemaSummary {
274
+ const parsed = parseSchemaInput(schemaText);
275
+ const answer = checkParseSchema(parsed.schema);
276
+ if (answer.type === "failure") {
277
+ return {
278
+ valid: false,
279
+ format: parsed.format,
280
+ namespaces: [],
281
+ entity_type_count: 0,
282
+ action_count: 0,
283
+ raw_text: schemaText,
284
+ errors: answer.errors.map(e => e.message),
285
+ };
286
+ }
287
+
288
+ if (parsed.format === "json") {
289
+ const counts = summarizeJsonSchema(parsed.schema);
290
+ return { valid: true, format: parsed.format, raw_text: schemaText, ...counts };
291
+ }
292
+
293
+ // For cedarschema text, translate to JSON form to derive structural counts.
294
+ try {
295
+ const jsonAnswer = schemaToJsonWithResolvedTypes(schemaText);
296
+ if (jsonAnswer.type === "success") {
297
+ const counts = summarizeJsonSchema(jsonAnswer.json);
298
+ return { valid: true, format: parsed.format, raw_text: schemaText, ...counts };
299
+ }
300
+ } catch {
301
+ // fall through to summary-less success
302
+ }
303
+
304
+ return {
305
+ valid: true,
306
+ format: parsed.format,
307
+ namespaces: [],
308
+ entity_type_count: 0,
309
+ action_count: 0,
310
+ raw_text: schemaText,
311
+ };
312
+ }
313
+
314
+ function parseSchemaInput(schemaStr: string): { schema: Schema; format: "json" | "cedarschema" } {
315
+ try {
316
+ return { schema: JSON.parse(schemaStr), format: "json" };
317
+ } catch {
318
+ return { schema: schemaStr, format: "cedarschema" };
319
+ }
320
+ }
321
+
322
+ interface JsonSchemaShape {
323
+ [namespace: string]: {
324
+ entityTypes?: Record<string, unknown>;
325
+ actions?: Record<string, unknown>;
326
+ };
327
+ }
328
+
329
+ function summarizeJsonSchema(json: unknown): {
330
+ namespaces: string[];
331
+ entity_type_count: number;
332
+ action_count: number;
333
+ } {
334
+ const empty = { namespaces: [], entity_type_count: 0, action_count: 0 };
335
+ if (!json || typeof json !== "object") return empty;
336
+ const shape = json as JsonSchemaShape;
337
+ const namespaces = Object.keys(shape);
338
+ let entity_type_count = 0;
339
+ let action_count = 0;
340
+ for (const ns of namespaces) {
341
+ const block = shape[ns];
342
+ if (block.entityTypes) entity_type_count += Object.keys(block.entityTypes).length;
343
+ if (block.actions) action_count += Object.keys(block.actions).length;
344
+ }
345
+ return { namespaces, entity_type_count, action_count };
346
+ }
347
+
348
+ function countPatterns(inventory: PolicyInventoryEntry[]): PatternDetected[] {
349
+ const counts = new Map<string, number>();
350
+ for (const entry of inventory) {
351
+ counts.set(entry.pattern, (counts.get(entry.pattern) ?? 0) + 1);
352
+ }
353
+ return Array.from(counts.entries()).map(([pattern, count]) => ({ pattern, count }));
354
+ }
355
+
356
+ function gotchaToAdvise(g: Gotcha): AdviseGotcha {
357
+ const advise: AdviseGotcha = {
358
+ id: g.id,
359
+ severity: g.severity,
360
+ description: g.description,
361
+ };
362
+ if (g.avp_error_category) {
363
+ advise.avp_error_category = g.avp_error_category;
364
+ }
365
+ return advise;
366
+ }