@gate-forge/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/LICENSE +202 -0
  2. package/dist/baselines/adoption.d.ts +76 -0
  3. package/dist/baselines/adoption.d.ts.map +1 -0
  4. package/dist/baselines/adoption.js +163 -0
  5. package/dist/baselines/adoption.js.map +1 -0
  6. package/dist/baselines/index.d.ts +102 -0
  7. package/dist/baselines/index.d.ts.map +1 -0
  8. package/dist/baselines/index.js +186 -0
  9. package/dist/baselines/index.js.map +1 -0
  10. package/dist/canonical-json.d.ts +51 -0
  11. package/dist/canonical-json.d.ts.map +1 -0
  12. package/dist/canonical-json.js +112 -0
  13. package/dist/canonical-json.js.map +1 -0
  14. package/dist/classifier/bind.d.ts +64 -0
  15. package/dist/classifier/bind.d.ts.map +1 -0
  16. package/dist/classifier/bind.js +188 -0
  17. package/dist/classifier/bind.js.map +1 -0
  18. package/dist/classifier/classify.d.ts +154 -0
  19. package/dist/classifier/classify.d.ts.map +1 -0
  20. package/dist/classifier/classify.js +1207 -0
  21. package/dist/classifier/classify.js.map +1 -0
  22. package/dist/classifier/glob.d.ts +48 -0
  23. package/dist/classifier/glob.d.ts.map +1 -0
  24. package/dist/classifier/glob.js +104 -0
  25. package/dist/classifier/glob.js.map +1 -0
  26. package/dist/classifier/index.d.ts +10 -0
  27. package/dist/classifier/index.d.ts.map +1 -0
  28. package/dist/classifier/index.js +21 -0
  29. package/dist/classifier/index.js.map +1 -0
  30. package/dist/classifier/schema.d.ts +150 -0
  31. package/dist/classifier/schema.d.ts.map +1 -0
  32. package/dist/classifier/schema.js +139 -0
  33. package/dist/classifier/schema.js.map +1 -0
  34. package/dist/config/index.d.ts +259 -0
  35. package/dist/config/index.d.ts.map +1 -0
  36. package/dist/config/index.js +523 -0
  37. package/dist/config/index.js.map +1 -0
  38. package/dist/fingerprints.d.ts +56 -0
  39. package/dist/fingerprints.d.ts.map +1 -0
  40. package/dist/fingerprints.js +59 -0
  41. package/dist/fingerprints.js.map +1 -0
  42. package/dist/graph/build.d.ts +16 -0
  43. package/dist/graph/build.d.ts.map +1 -0
  44. package/dist/graph/build.js +423 -0
  45. package/dist/graph/build.js.map +1 -0
  46. package/dist/graph/index.d.ts +12 -0
  47. package/dist/graph/index.d.ts.map +1 -0
  48. package/dist/graph/index.js +10 -0
  49. package/dist/graph/index.js.map +1 -0
  50. package/dist/graph/schema.d.ts +448 -0
  51. package/dist/graph/schema.d.ts.map +1 -0
  52. package/dist/graph/schema.js +237 -0
  53. package/dist/graph/schema.js.map +1 -0
  54. package/dist/graph/symbols.d.ts +129 -0
  55. package/dist/graph/symbols.d.ts.map +1 -0
  56. package/dist/graph/symbols.js +177 -0
  57. package/dist/graph/symbols.js.map +1 -0
  58. package/dist/graph/util.d.ts +40 -0
  59. package/dist/graph/util.d.ts.map +1 -0
  60. package/dist/graph/util.js +41 -0
  61. package/dist/graph/util.js.map +1 -0
  62. package/dist/index.d.ts +683 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +563 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/mapping/resolve.d.ts +177 -0
  67. package/dist/mapping/resolve.d.ts.map +1 -0
  68. package/dist/mapping/resolve.js +523 -0
  69. package/dist/mapping/resolve.js.map +1 -0
  70. package/dist/policy/coverage.d.ts +79 -0
  71. package/dist/policy/coverage.d.ts.map +1 -0
  72. package/dist/policy/coverage.js +119 -0
  73. package/dist/policy/coverage.js.map +1 -0
  74. package/dist/policy/evaluate.d.ts +275 -0
  75. package/dist/policy/evaluate.d.ts.map +1 -0
  76. package/dist/policy/evaluate.js +382 -0
  77. package/dist/policy/evaluate.js.map +1 -0
  78. package/dist/policy/index.d.ts +15 -0
  79. package/dist/policy/index.d.ts.map +1 -0
  80. package/dist/policy/index.js +14 -0
  81. package/dist/policy/index.js.map +1 -0
  82. package/dist/policy/trusted.d.ts +59 -0
  83. package/dist/policy/trusted.d.ts.map +1 -0
  84. package/dist/policy/trusted.js +96 -0
  85. package/dist/policy/trusted.js.map +1 -0
  86. package/dist/provenance.d.ts +170 -0
  87. package/dist/provenance.d.ts.map +1 -0
  88. package/dist/provenance.js +306 -0
  89. package/dist/provenance.js.map +1 -0
  90. package/dist/receipt/index.d.ts +74 -0
  91. package/dist/receipt/index.d.ts.map +1 -0
  92. package/dist/receipt/index.js +154 -0
  93. package/dist/receipt/index.js.map +1 -0
  94. package/dist/report/index.d.ts +100 -0
  95. package/dist/report/index.d.ts.map +1 -0
  96. package/dist/report/index.js +355 -0
  97. package/dist/report/index.js.map +1 -0
  98. package/dist/schemas/adoption.d.ts +40 -0
  99. package/dist/schemas/adoption.d.ts.map +1 -0
  100. package/dist/schemas/adoption.js +87 -0
  101. package/dist/schemas/adoption.js.map +1 -0
  102. package/dist/schemas/baseline.d.ts +19 -0
  103. package/dist/schemas/baseline.d.ts.map +1 -0
  104. package/dist/schemas/baseline.js +45 -0
  105. package/dist/schemas/baseline.js.map +1 -0
  106. package/dist/schemas/claim.d.ts +32 -0
  107. package/dist/schemas/claim.d.ts.map +1 -0
  108. package/dist/schemas/claim.js +34 -0
  109. package/dist/schemas/claim.js.map +1 -0
  110. package/dist/schemas/classification-policy.d.ts +84 -0
  111. package/dist/schemas/classification-policy.d.ts.map +1 -0
  112. package/dist/schemas/classification-policy.js +130 -0
  113. package/dist/schemas/classification-policy.js.map +1 -0
  114. package/dist/schemas/classification-signal.d.ts +125 -0
  115. package/dist/schemas/classification-signal.d.ts.map +1 -0
  116. package/dist/schemas/classification-signal.js +126 -0
  117. package/dist/schemas/classification-signal.js.map +1 -0
  118. package/dist/schemas/classification.d.ts +120 -0
  119. package/dist/schemas/classification.d.ts.map +1 -0
  120. package/dist/schemas/classification.js +154 -0
  121. package/dist/schemas/classification.js.map +1 -0
  122. package/dist/schemas/common.d.ts +73 -0
  123. package/dist/schemas/common.d.ts.map +1 -0
  124. package/dist/schemas/common.js +60 -0
  125. package/dist/schemas/common.js.map +1 -0
  126. package/dist/schemas/coverage-policy.d.ts +89 -0
  127. package/dist/schemas/coverage-policy.d.ts.map +1 -0
  128. package/dist/schemas/coverage-policy.js +81 -0
  129. package/dist/schemas/coverage-policy.js.map +1 -0
  130. package/dist/schemas/evidence.d.ts +65 -0
  131. package/dist/schemas/evidence.d.ts.map +1 -0
  132. package/dist/schemas/evidence.js +64 -0
  133. package/dist/schemas/evidence.js.map +1 -0
  134. package/dist/schemas/execution-result.d.ts +214 -0
  135. package/dist/schemas/execution-result.d.ts.map +1 -0
  136. package/dist/schemas/execution-result.js +240 -0
  137. package/dist/schemas/execution-result.js.map +1 -0
  138. package/dist/schemas/gate-receipt.d.ts +86 -0
  139. package/dist/schemas/gate-receipt.d.ts.map +1 -0
  140. package/dist/schemas/gate-receipt.js +193 -0
  141. package/dist/schemas/gate-receipt.js.map +1 -0
  142. package/dist/schemas/index.d.ts +22 -0
  143. package/dist/schemas/index.d.ts.map +1 -0
  144. package/dist/schemas/index.js +22 -0
  145. package/dist/schemas/index.js.map +1 -0
  146. package/dist/schemas/obligation.d.ts +37 -0
  147. package/dist/schemas/obligation.d.ts.map +1 -0
  148. package/dist/schemas/obligation.js +46 -0
  149. package/dist/schemas/obligation.js.map +1 -0
  150. package/dist/schemas/plugin.d.ts +19 -0
  151. package/dist/schemas/plugin.d.ts.map +1 -0
  152. package/dist/schemas/plugin.js +20 -0
  153. package/dist/schemas/plugin.js.map +1 -0
  154. package/dist/schemas/policy.d.ts +73 -0
  155. package/dist/schemas/policy.d.ts.map +1 -0
  156. package/dist/schemas/policy.js +46 -0
  157. package/dist/schemas/policy.js.map +1 -0
  158. package/dist/schemas/resource.d.ts +26 -0
  159. package/dist/schemas/resource.d.ts.map +1 -0
  160. package/dist/schemas/resource.js +29 -0
  161. package/dist/schemas/resource.js.map +1 -0
  162. package/dist/schemas/run-manifest.d.ts +72 -0
  163. package/dist/schemas/run-manifest.d.ts.map +1 -0
  164. package/dist/schemas/run-manifest.js +125 -0
  165. package/dist/schemas/run-manifest.js.map +1 -0
  166. package/dist/schemas/runner-adapter.d.ts +169 -0
  167. package/dist/schemas/runner-adapter.d.ts.map +1 -0
  168. package/dist/schemas/runner-adapter.js +2 -0
  169. package/dist/schemas/runner-adapter.js.map +1 -0
  170. package/dist/schemas/test-catalog.d.ts +472 -0
  171. package/dist/schemas/test-catalog.d.ts.map +1 -0
  172. package/dist/schemas/test-catalog.js +311 -0
  173. package/dist/schemas/test-catalog.js.map +1 -0
  174. package/dist/schemas/test-map.d.ts +99 -0
  175. package/dist/schemas/test-map.d.ts.map +1 -0
  176. package/dist/schemas/test-map.js +103 -0
  177. package/dist/schemas/test-map.js.map +1 -0
  178. package/dist/schemas/verdict.d.ts +73 -0
  179. package/dist/schemas/verdict.d.ts.map +1 -0
  180. package/dist/schemas/verdict.js +92 -0
  181. package/dist/schemas/verdict.js.map +1 -0
  182. package/dist/schemas/waiver.d.ts +36 -0
  183. package/dist/schemas/waiver.d.ts.map +1 -0
  184. package/dist/schemas/waiver.js +42 -0
  185. package/dist/schemas/waiver.js.map +1 -0
  186. package/dist/supervision/index.d.ts +170 -0
  187. package/dist/supervision/index.d.ts.map +1 -0
  188. package/dist/supervision/index.js +354 -0
  189. package/dist/supervision/index.js.map +1 -0
  190. package/dist/testing/clock.d.ts +50 -0
  191. package/dist/testing/clock.d.ts.map +1 -0
  192. package/dist/testing/clock.js +75 -0
  193. package/dist/testing/clock.js.map +1 -0
  194. package/dist/testing/env.d.ts +66 -0
  195. package/dist/testing/env.d.ts.map +1 -0
  196. package/dist/testing/env.js +106 -0
  197. package/dist/testing/env.js.map +1 -0
  198. package/dist/testing/gate-runner.d.ts +141 -0
  199. package/dist/testing/gate-runner.d.ts.map +1 -0
  200. package/dist/testing/gate-runner.js +173 -0
  201. package/dist/testing/gate-runner.js.map +1 -0
  202. package/dist/testing/gf19.d.ts +106 -0
  203. package/dist/testing/gf19.d.ts.map +1 -0
  204. package/dist/testing/gf19.js +282 -0
  205. package/dist/testing/gf19.js.map +1 -0
  206. package/dist/testing/index.d.ts +20 -0
  207. package/dist/testing/index.d.ts.map +1 -0
  208. package/dist/testing/index.js +14 -0
  209. package/dist/testing/index.js.map +1 -0
  210. package/dist/testing/red-probe.d.ts +138 -0
  211. package/dist/testing/red-probe.d.ts.map +1 -0
  212. package/dist/testing/red-probe.js +251 -0
  213. package/dist/testing/red-probe.js.map +1 -0
  214. package/dist/testing/temp-repo.d.ts +126 -0
  215. package/dist/testing/temp-repo.d.ts.map +1 -0
  216. package/dist/testing/temp-repo.js +214 -0
  217. package/dist/testing/temp-repo.js.map +1 -0
  218. package/dist/verdict/cause.d.ts +99 -0
  219. package/dist/verdict/cause.d.ts.map +1 -0
  220. package/dist/verdict/cause.js +206 -0
  221. package/dist/verdict/cause.js.map +1 -0
  222. package/dist/verdict/evaluate.d.ts +135 -0
  223. package/dist/verdict/evaluate.d.ts.map +1 -0
  224. package/dist/verdict/evaluate.js +1513 -0
  225. package/dist/verdict/evaluate.js.map +1 -0
  226. package/dist/verdict/index.d.ts +21 -0
  227. package/dist/verdict/index.d.ts.map +1 -0
  228. package/dist/verdict/index.js +20 -0
  229. package/dist/verdict/index.js.map +1 -0
  230. package/dist/verdict/pack-verifiers.d.ts +97 -0
  231. package/dist/verdict/pack-verifiers.d.ts.map +1 -0
  232. package/dist/verdict/pack-verifiers.js +763 -0
  233. package/dist/verdict/pack-verifiers.js.map +1 -0
  234. package/dist/verdict/registry.d.ts +180 -0
  235. package/dist/verdict/registry.d.ts.map +1 -0
  236. package/dist/verdict/registry.js +67 -0
  237. package/dist/verdict/registry.js.map +1 -0
  238. package/dist/waivers/index.d.ts +103 -0
  239. package/dist/waivers/index.d.ts.map +1 -0
  240. package/dist/waivers/index.js +192 -0
  241. package/dist/waivers/index.js.map +1 -0
  242. package/package.json +40 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Policy engine (plan §5.2, G2): evaluates declarative policies
3
+ * (`PolicyFile`) against the classified resources of a built
4
+ * {@link ResourceGraph} and produces obligations plus the blocking
5
+ * verdict entries the gate must stay visible for (invariants 1, 8).
6
+ *
7
+ * Semantics (plan §5.2 + ADR 0001):
8
+ * - `crud:create|read|update|delete` obligations are LIFECYCLE-GATED:
9
+ * a contract is emitted only when the classification's lifecycle
10
+ * flag for that operation is `true` (user-facing + `lifecycle.create`
11
+ * ⇒ `crud:create`, etc.). Non-uniform lifecycles fall out naturally:
12
+ * immutable/read-only resources generate only `crud:read`, append-only
13
+ * resources `crud:create` + `crud:read`, archive-not-delete resources
14
+ * `crud:delete` with `deleteSemantics: 'archive'` in the fingerprint.
15
+ * - Non-`crud:` contracts pass through ungated (the policy author
16
+ * explicitly required them).
17
+ * - Internal resources generate NO CRUD obligations — their claims are
18
+ * invalid (ADR 0001). Non-CRUD contracts still apply.
19
+ * - Unclassified and unresolved resources produce blocking entries and
20
+ * no obligations until classified/resolved, but stay gate-visible.
21
+ * So do detector/graph `findings` (a partial discovery must never
22
+ * yield a green gate) and `stale` references (invariant 9).
23
+ * - Policies are evaluated in file order; the first policy generating
24
+ * a given `<resourceId>:<contract>` owns it (obligation ids are
25
+ * unique identities — pin #1's fingerprint keys on them).
26
+ */
27
+ import { z } from 'zod';
28
+ import { LocationSchema, SchemaVersionField } from '../schemas/common.js';
29
+ import { CauseCodeSchema } from '../schemas/verdict.js';
30
+ import { ObligationSchema } from '../schemas/obligation.js';
31
+ import { PolicyFileSchema } from '../schemas/policy.js';
32
+ import { ClaimSchema } from '../schemas/claim.js';
33
+ import { HTTP_ENDPOINT_RESOURCE_KIND } from '../graph/schema.js';
34
+ import { compareStrings } from '../graph/util.js';
35
+ /** The CRUD contract namespace gated by lifecycle flags. */
36
+ export const CRUD_CONTRACT_PREFIX = 'crud:';
37
+ /**
38
+ * The persistence-level CRUD contract namespace (audit round 5):
39
+ * lifecycle-gated exactly like `crud:`, but graded on the witness's own
40
+ * engine-side observations (absence/presence, before/after deltas,
41
+ * owner-declared archive state). UI-semantic `crud:` contracts stay
42
+ * fail-closed in the verdict engine until a witness-controlled UI
43
+ * observation channel exists.
44
+ */
45
+ export const PERSISTENCE_CONTRACT_PREFIX = 'persistence:';
46
+ /** The four lifecycle-gated operations, in canonical order. */
47
+ const CRUD_OPERATIONS = ['create', 'read', 'update', 'delete'];
48
+ /**
49
+ * Fail-closed policy-engine error: thrown for policy documents that
50
+ * are structurally invalid (zod) or use the `crud:` namespace with an
51
+ * unknown operation — a config/usage problem, never a silent skip.
52
+ */
53
+ export class PolicyEvaluationError extends Error {
54
+ constructor(message) {
55
+ super(message);
56
+ this.name = 'PolicyEvaluationError';
57
+ }
58
+ }
59
+ /** Why a resource currently generates no obligations. */
60
+ export const BlockingEntrySchema = z
61
+ .strictObject({
62
+ /**
63
+ * `unclassified` = resolved but lacking any effective classification
64
+ * (the classifier blocked it definitionally); `classification` = a
65
+ * typed classifier block (contradiction, incomplete proof scope,
66
+ * stale/invalid signal, missing adapter) — machine-actionable, the
67
+ * conservative decision stands where one exists; `unresolved`
68
+ * = identity not statically resolvable; `finding` = a detector/graph
69
+ * finding (e.g. PARSE_ERROR) — discovery only partly succeeded, so
70
+ * the gate cannot claim full knowledge; `stale-reference` = an
71
+ * artifact still pointing at a removed resource (invariant 9:
72
+ * nothing silently disappears).
73
+ */
74
+ kind: z.enum([
75
+ 'unclassified',
76
+ 'classification',
77
+ 'unresolved',
78
+ 'finding',
79
+ 'stale-reference',
80
+ ]),
81
+ /** Plane-qualified id when one could be derived, else `null`. */
82
+ resourceId: z.string().min(1).nullable(),
83
+ /** Bare resource name when known, else `null`. */
84
+ name: z.string().min(1).nullable(),
85
+ /** Single-cause human explanation (invariant 8: explain every block). */
86
+ detail: z.string().min(1),
87
+ /** Source location when known, else `null`. */
88
+ location: LocationSchema.nullable(),
89
+ /**
90
+ * Stable plan §5.4 cause code when the entry maps to one (coverage
91
+ * findings → CRUD_COVERAGE_MISSING, strict-capability preflight →
92
+ * VERIFIER_UNSUPPORTED, strict-mode waivers → ENFORCEMENT_UNTRUSTED).
93
+ * Absent/null for entries without a Phase 0 mapping.
94
+ */
95
+ cause: CauseCodeSchema.nullish(),
96
+ /** Human next action accompanying `cause`; absent/null when unmapped. */
97
+ nextAction: z.string().min(1).nullish(),
98
+ });
99
+ /**
100
+ * The adopted identity of a classification-blocked resource (two-layer
101
+ * adoption): the ONE canonical string both `gateforge adopt` captures
102
+ * into the receipt's `classificationBlocked` set and the check matches
103
+ * against when waiving — so the two sides can never disagree.
104
+ *
105
+ * - The plane-qualified `resourceId` when the entry carries one (blocks
106
+ * on otherwise-classified resources, e.g. delete-semantics).
107
+ * - Else the bare resource name under a `name:` namespace — a resource
108
+ * with NO derivable plane (PLANE_UNRESOLVED) has no plane-qualified id
109
+ * by definition, and its bare name is the stable, merge-surviving
110
+ * identity. The `name:` prefix keeps the fallback out of the
111
+ * plane-qualified id space (`tenant.users` et al.).
112
+ * - Both the `classification` entry (the typed block) AND the
113
+ * `unclassified` shadow entry (the definitional "no effective
114
+ * classification" block the policy engine emits for the SAME resource)
115
+ * map to the same identity: waiving the resource waives its whole
116
+ * adopted block, or the gate could never return to green.
117
+ *
118
+ * Returns null for entries that are never resource-scoped (document-level
119
+ * classifier blocks — stale targets, invalid signals — and
120
+ * unresolved/finding/stale-reference kinds): those can never be adopted
121
+ * by this layer.
122
+ */
123
+ export function classificationBlockedIdentity(entry) {
124
+ if (entry.kind !== 'classification' && entry.kind !== 'unclassified')
125
+ return null;
126
+ if (entry.resourceId !== null)
127
+ return entry.resourceId;
128
+ if (entry.name !== null)
129
+ return `name:${entry.name}`;
130
+ return null;
131
+ }
132
+ /** Assessment of one watched claim against the generated obligations. */
133
+ export const ClaimAssessmentSchema = z
134
+ .strictObject({
135
+ claim: ClaimSchema,
136
+ status: z.enum(['valid', 'invalid']),
137
+ /** Single-cause reason for `invalid`; `null` when valid. */
138
+ reason: z.string().min(1).nullable(),
139
+ });
140
+ /** The complete policy-engine result. Deterministic; fully sorted. */
141
+ export const PolicyEvaluationResultSchema = z
142
+ .strictObject({
143
+ schemaVersion: SchemaVersionField,
144
+ /** Generated obligations, sorted by id (unique — first policy wins). */
145
+ obligations: z.array(ObligationSchema),
146
+ /**
147
+ * Entries the gate must stay visible for, sorted deterministically:
148
+ * unclassified + unresolved resources, detector/graph findings, and
149
+ * stale references (fail closed — a discovery pass that partly
150
+ * failed or left orphans can never yield a green run).
151
+ */
152
+ blocking: z.array(BlockingEntrySchema),
153
+ /** Claim assessments, sorted by obligation id then test id. */
154
+ claims: z.array(ClaimAssessmentSchema),
155
+ });
156
+ /**
157
+ * Whether a contract is allowed by a lifecycle. `crud:` and
158
+ * `persistence:` contracts map to their lifecycle flag (`crud:create` ⇔
159
+ * `lifecycle.create === true`, etc. — unknown operations in either
160
+ * namespace are a policy-authoring error); every other contract passes
161
+ * through ungated.
162
+ *
163
+ * Args:
164
+ * contract: the required contract name, e.g. `crud:update`.
165
+ * lifecycle: the classification's lifecycle flags.
166
+ *
167
+ * Returns:
168
+ * boolean: true when an obligation for this contract must be generated.
169
+ * @throws PolicyEvaluationError for `crud:`/`persistence:` contracts
170
+ * outside the four lifecycle operations.
171
+ */
172
+ export function lifecycleAllowsContract(contract, lifecycle) {
173
+ for (const prefix of [CRUD_CONTRACT_PREFIX, PERSISTENCE_CONTRACT_PREFIX]) {
174
+ if (!contract.startsWith(prefix))
175
+ continue;
176
+ const operation = contract.slice(prefix.length);
177
+ if (!CRUD_OPERATIONS.includes(operation)) {
178
+ throw new PolicyEvaluationError(`policy requires '${contract}' but the namespace is limited to: ` +
179
+ `${CRUD_OPERATIONS.map((op) => `${prefix}${op}`).join(', ')}`);
180
+ }
181
+ return lifecycle[operation] === true;
182
+ }
183
+ return true;
184
+ }
185
+ /**
186
+ * Evaluates the policy document against a built resource graph.
187
+ *
188
+ * Args:
189
+ * input: graph + policies (+ claims to assess). The policy document
190
+ * is validated fail-closed; resources are consumed in the graph's
191
+ * deterministic order.
192
+ *
193
+ * Returns:
194
+ * PolicyEvaluationResult: obligations, blocking entries, and claim
195
+ * assessments — all sorted, byte-for-byte deterministic.
196
+ * @throws z.ZodError when the policy document is invalid (config error).
197
+ * @throws PolicyEvaluationError for `crud:` contracts outside the four
198
+ * lifecycle operations.
199
+ */
200
+ export function evaluatePolicies(input) {
201
+ const policyFile = PolicyFileSchema.parse(input.policies);
202
+ const obligations = [];
203
+ const obligationIds = new Set();
204
+ const blocking = [];
205
+ for (const resource of input.graph.resources) {
206
+ if (resource.classification === null) {
207
+ blocking.push({
208
+ kind: 'unclassified',
209
+ resourceId: resource.id,
210
+ name: resource.name,
211
+ detail: `resource '${resource.id ?? resource.name}' has no effective classification ` +
212
+ '(the classifier blocked it definitionally); run ' +
213
+ `'gateforge explain ${resource.id ?? resource.name}' for the typed ` +
214
+ 'reason and its in-code resolution',
215
+ location: resource.location,
216
+ });
217
+ continue;
218
+ }
219
+ generateObligations(resource, policyFile.policies, obligations, obligationIds);
220
+ }
221
+ for (const entry of input.graph.unresolved) {
222
+ blocking.push({
223
+ kind: 'unresolved',
224
+ resourceId: null,
225
+ name: null,
226
+ detail: `${entry.reason.code}: ${entry.reason.detail}`,
227
+ location: entry.reason.location,
228
+ });
229
+ }
230
+ // Fail closed on partial discovery: findings (PARSE_ERROR and friends)
231
+ // mean the graph was built from incomplete knowledge — a parse failure
232
+ // can erase resources and obligations — and stale references mean an
233
+ // artifact still points at something removed (invariant 9). Both block
234
+ // the gate; `discover` remains the non-gating introspection surface.
235
+ for (const finding of input.graph.findings) {
236
+ blocking.push({
237
+ kind: 'finding',
238
+ resourceId: null,
239
+ name: null,
240
+ detail: `${finding.code}: ${finding.detail} (detector ${finding.detectorId})`,
241
+ location: finding.locations[0] ?? null,
242
+ });
243
+ }
244
+ for (const stale of input.graph.stale) {
245
+ blocking.push({
246
+ kind: 'stale-reference',
247
+ resourceId: null,
248
+ name: null,
249
+ detail: `stale ${stale.kind} reference '${stale.reference}': ${stale.detail}`,
250
+ location: null,
251
+ });
252
+ }
253
+ blocking.push(...(input.extraBlocking ?? []));
254
+ const claims = assessClaims(input.claims ?? [], obligationIds, input.graph.resources);
255
+ return {
256
+ schemaVersion: 1,
257
+ obligations: sortObligations(obligations),
258
+ blocking: sortBlocking(blocking),
259
+ claims: sortClaims(claims),
260
+ };
261
+ }
262
+ /**
263
+ * Generates obligations for one classified resource: every matching
264
+ * policy's required contract that the lifecycle allows, deduplicated
265
+ * by obligation id (first matching policy owns the id).
266
+ */
267
+ function generateObligations(resource, policies, obligations, obligationIds) {
268
+ const classification = resource.classification;
269
+ if (classification === null || resource.id === null)
270
+ return;
271
+ const exposure = classification.exposure;
272
+ for (const policy of policies) {
273
+ if (!policyMatches(policy, resource, exposure))
274
+ continue;
275
+ for (const contract of policy.require) {
276
+ if (contract.startsWith(CRUD_CONTRACT_PREFIX) ||
277
+ contract.startsWith(PERSISTENCE_CONTRACT_PREFIX)) {
278
+ if (exposure === 'internal')
279
+ continue; // ADR 0001: internal ⇒ no CRUD obligations
280
+ // ADR 0004 D8: endpoints are routes, not tables. CRUD state
281
+ // obligations flow through the LINKED business resource's own
282
+ // identity; generating persistence contracts against a route
283
+ // would conflate the two planes of meaning.
284
+ if (resource.kind === HTTP_ENDPOINT_RESOURCE_KIND)
285
+ continue;
286
+ if (!lifecycleAllowsContract(contract, classification.lifecycle))
287
+ continue;
288
+ }
289
+ const id = `${resource.id}:${contract}`;
290
+ if (obligationIds.has(id))
291
+ continue;
292
+ obligationIds.add(id);
293
+ obligations.push({
294
+ schemaVersion: 1,
295
+ id,
296
+ resourceId: resource.id,
297
+ contract,
298
+ policyId: policy.id,
299
+ lifecycle: classification.lifecycle,
300
+ });
301
+ }
302
+ }
303
+ }
304
+ function policyMatches(policy, resource, exposure) {
305
+ const when = policy.when;
306
+ if (when.kind !== undefined && when.kind !== resource.kind)
307
+ return false;
308
+ if (when.exposure !== undefined && when.exposure !== exposure)
309
+ return false;
310
+ if (when.plane !== undefined && when.plane !== resource.plane)
311
+ return false;
312
+ if (when.capability !== undefined) {
313
+ // Endpoint capability match: the resource must carry a `capabilities`
314
+ // ARRAY containing the exact string. Anything else — absent
315
+ // attribute, non-array value, near-miss strings — never matches
316
+ // (fail closed).
317
+ const capabilities = resource.attributes['capabilities'];
318
+ if (!Array.isArray(capabilities) ||
319
+ !capabilities.some((capability) => capability === when.capability)) {
320
+ return false;
321
+ }
322
+ }
323
+ if (when.consumed !== undefined) {
324
+ // Endpoint consumption match: `true` requires the attribute to be
325
+ // EXACTLY true; `false` matches everything else (including absent —
326
+ // non-endpoint resources are "not consumed", never match-excluded).
327
+ if (when.consumed === true && resource.attributes['frontendConsumed'] !== true) {
328
+ return false;
329
+ }
330
+ if (when.consumed === false && resource.attributes['frontendConsumed'] === true) {
331
+ return false;
332
+ }
333
+ }
334
+ return true;
335
+ }
336
+ /**
337
+ * Assesses claims against the generated obligation set. Claims on
338
+ * internal resources are invalid outright (ADR 0001 — internal
339
+ * resources carry no CRUD obligations and never accept claims);
340
+ * claims whose obligation id was not generated are invalid as unknown;
341
+ * the rest are valid linkages (validity ≠ satisfaction — pin #9).
342
+ */
343
+ function assessClaims(claims, obligationIds, resources) {
344
+ const internalIds = new Set();
345
+ for (const resource of resources) {
346
+ if (resource.id !== null && resource.classification?.exposure === 'internal') {
347
+ internalIds.add(resource.id);
348
+ }
349
+ }
350
+ return claims.map((claim) => {
351
+ const resourceId = claim.obligationId.slice(0, claim.obligationId.indexOf(':'));
352
+ if (internalIds.has(resourceId)) {
353
+ return {
354
+ claim,
355
+ status: 'invalid',
356
+ reason: `resource '${resourceId}' is internal: internal resources carry no CRUD obligations and their claims are invalid (ADR 0001)`,
357
+ };
358
+ }
359
+ if (!obligationIds.has(claim.obligationId)) {
360
+ return {
361
+ claim,
362
+ status: 'invalid',
363
+ reason: `claim references '${claim.obligationId}' which no policy generates`,
364
+ };
365
+ }
366
+ return { claim, status: 'valid', reason: null };
367
+ });
368
+ }
369
+ function sortObligations(obligations) {
370
+ return [...obligations].sort((a, b) => compareStrings(a.id, b.id));
371
+ }
372
+ function sortBlocking(blocking) {
373
+ return [...blocking].sort((a, b) => compareStrings(a.kind, b.kind) ||
374
+ compareStrings(a.resourceId ?? '', b.resourceId ?? '') ||
375
+ compareStrings(a.name ?? '', b.name ?? '') ||
376
+ compareStrings(a.detail, b.detail));
377
+ }
378
+ function sortClaims(claims) {
379
+ return [...claims].sort((a, b) => compareStrings(a.claim.obligationId, b.claim.obligationId) ||
380
+ compareStrings(a.claim.testId, b.claim.testId));
381
+ }
382
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/policy/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Policy engine (G2): declarative policies → obligations, lifecycle
3
+ * gating (plan §5.2), internal-resource claim invalidation (ADR 0001),
4
+ * and gate-visible blocking entries for unclassified/unresolved
5
+ * resources (invariants 1, 8).
6
+ *
7
+ * Also (plan 2026-09-13 Phase 0): the closed-world CRUD coverage
8
+ * evaluator (§3.6) and the protected-policy-ownership foundation
9
+ * (trusted policy digest + weakening check, ADR 0005 D5/D6).
10
+ */
11
+ export { CRUD_CONTRACT_PREFIX, PERSISTENCE_CONTRACT_PREFIX, BlockingEntrySchema, ClaimAssessmentSchema, PolicyEvaluationError, PolicyEvaluationResultSchema, classificationBlockedIdentity, evaluatePolicies, lifecycleAllowsContract, } from './evaluate.js';
12
+ export type { BlockingEntry, ClaimAssessment, PolicyEvaluationInput, PolicyEvaluationResult, } from './evaluate.js';
13
+ export { evaluateCoveragePolicy, type CoverageInventoryTable, type MappedCoverage, type CoverageConfigError, type CoverageBlockingFinding, type CoveragePolicyResult, } from './coverage.js';
14
+ export { TRUSTED_POLICY_DOMAIN, trustedPolicyDigest, policyWeakenedCandidate, type TrustedPolicyInput, type PolicyWeakeningCheck, } from './trusted.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Policy engine (G2): declarative policies → obligations, lifecycle
3
+ * gating (plan §5.2), internal-resource claim invalidation (ADR 0001),
4
+ * and gate-visible blocking entries for unclassified/unresolved
5
+ * resources (invariants 1, 8).
6
+ *
7
+ * Also (plan 2026-09-13 Phase 0): the closed-world CRUD coverage
8
+ * evaluator (§3.6) and the protected-policy-ownership foundation
9
+ * (trusted policy digest + weakening check, ADR 0005 D5/D6).
10
+ */
11
+ export { CRUD_CONTRACT_PREFIX, PERSISTENCE_CONTRACT_PREFIX, BlockingEntrySchema, ClaimAssessmentSchema, PolicyEvaluationError, PolicyEvaluationResultSchema, classificationBlockedIdentity, evaluatePolicies, lifecycleAllowsContract, } from './evaluate.js';
12
+ export { evaluateCoveragePolicy, } from './coverage.js';
13
+ export { TRUSTED_POLICY_DOMAIN, trustedPolicyDigest, policyWeakenedCandidate, } from './trusted.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAOvB,OAAO,EACL,sBAAsB,GAMvB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,GAGxB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,59 @@
1
+ /** Domain tag separating trusted-policy digests from every other hash. */
2
+ export declare const TRUSTED_POLICY_DOMAIN = "gateforge.trusted-policy.v1";
3
+ /** One trusted policy/config document's effective bytes. */
4
+ export interface TrustedPolicyInput {
5
+ /** Stable repo-root-relative name, e.g. `.gateforge/policies.yml`. */
6
+ name: string;
7
+ /** The exact effective bytes of the document. */
8
+ bytes: string;
9
+ }
10
+ /**
11
+ * Computes the trusted policy digest: a domain-separated canonical hash
12
+ * over the named document bytes (each byte-hashed, entries sorted by
13
+ * name). Deterministic — identical policy revisions produce identical
14
+ * digests, so a receipt can bind "evaluated under this policy revision"
15
+ * and any later byte change breaks the binding.
16
+ *
17
+ * Args:
18
+ * inputs: the effective policy/config documents (config, policies,
19
+ * classification policy, adapters, baselines, waivers, runner
20
+ * definitions — the trusted-revision-owned set).
21
+ *
22
+ * Returns:
23
+ * string: 64-char lowercase hex digest.
24
+ *
25
+ * Throws:
26
+ * TypeError: when the same name appears twice (an ambiguous trusted
27
+ * revision is a caller bug, never silently deduplicated).
28
+ */
29
+ export declare function trustedPolicyDigest(inputs: readonly TrustedPolicyInput[]): string;
30
+ /** The typed outcome of comparing a candidate revision to the trusted one. */
31
+ export type PolicyWeakeningCheck = {
32
+ weakened: false;
33
+ trustedDigest: string;
34
+ candidateDigest: string;
35
+ } | {
36
+ /** True when the candidate is not byte-identical to the trusted revision. */
37
+ weakened: true;
38
+ /** Single-cause fail-closed explanation. */
39
+ reason: string;
40
+ trustedDigest: string;
41
+ candidateDigest: string;
42
+ };
43
+ /**
44
+ * The pure weakening check (ADR 0005 D6): a candidate whose policy digest
45
+ * differs from the trusted revision is a WEAKENING CANDIDATE — it may
46
+ * only proceed after a separate trusted update is accepted, never by
47
+ * self-approval. Fail-closed: a missing or malformed digest cannot be
48
+ * verified, so it counts as weakened rather than trusted.
49
+ *
50
+ * Args:
51
+ * trustedDigest: the digest of the trusted policy revision.
52
+ * candidateDigest: the digest of the candidate's effective policy.
53
+ *
54
+ * Returns:
55
+ * PolicyWeakeningCheck: `{weakened: false}` only for an exact, well-formed
56
+ * match; otherwise `{weakened: true, reason}` naming the failure.
57
+ */
58
+ export declare function policyWeakenedCandidate(trustedDigest: string, candidateDigest: string): PolicyWeakeningCheck;
59
+ //# sourceMappingURL=trusted.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted.d.ts","sourceRoot":"","sources":["../../src/policy/trusted.ts"],"names":[],"mappings":"AAiBA,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AAEnE,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,CAYjF;AAED,8EAA8E;AAC9E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACnE;IACE,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC;IACf,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAIN;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,oBAAoB,CA6BtB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Protected policy ownership foundation (plan 2026-09-13 Phase 0 item 5,
3
+ * ADR 0005 D6). Classifiers, exclusions, mappings that suppress scope,
4
+ * adapters, runner definitions, gate binaries, CI jobs, baselines, and
5
+ * waivers are TRUSTED-REVISION-OWNED: a candidate change that edits them
6
+ * cannot authorize its own weaker checks. The engine therefore computes a
7
+ * domain-separated digest over the effective policy/config bytes and
8
+ * evaluates candidates against it.
9
+ *
10
+ * Phase 0 delivers the typed foundation only: the digest computation and
11
+ * the pure weakening check, both unit-testable. Receipt binding and real
12
+ * enforcement land in Phases 4-5 (plan §5.1 gate receipt: "trusted
13
+ * policy/verifier digest").
14
+ */
15
+ import { sha256Canonical, sha256Hex } from '../canonical-json.js';
16
+ import { compareStrings } from '../graph/util.js';
17
+ /** Domain tag separating trusted-policy digests from every other hash. */
18
+ export const TRUSTED_POLICY_DOMAIN = 'gateforge.trusted-policy.v1';
19
+ /**
20
+ * Computes the trusted policy digest: a domain-separated canonical hash
21
+ * over the named document bytes (each byte-hashed, entries sorted by
22
+ * name). Deterministic — identical policy revisions produce identical
23
+ * digests, so a receipt can bind "evaluated under this policy revision"
24
+ * and any later byte change breaks the binding.
25
+ *
26
+ * Args:
27
+ * inputs: the effective policy/config documents (config, policies,
28
+ * classification policy, adapters, baselines, waivers, runner
29
+ * definitions — the trusted-revision-owned set).
30
+ *
31
+ * Returns:
32
+ * string: 64-char lowercase hex digest.
33
+ *
34
+ * Throws:
35
+ * TypeError: when the same name appears twice (an ambiguous trusted
36
+ * revision is a caller bug, never silently deduplicated).
37
+ */
38
+ export function trustedPolicyDigest(inputs) {
39
+ const seen = new Set();
40
+ for (const input of inputs) {
41
+ if (seen.has(input.name)) {
42
+ throw new TypeError(`trustedPolicyDigest: duplicate policy input name '${input.name}'`);
43
+ }
44
+ seen.add(input.name);
45
+ }
46
+ const files = [...inputs]
47
+ .sort((a, b) => compareStrings(a.name, b.name))
48
+ .map((input) => ({ name: input.name, sha256: sha256Hex(input.bytes) }));
49
+ return sha256Canonical({ domain: TRUSTED_POLICY_DOMAIN, files });
50
+ }
51
+ const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
52
+ /**
53
+ * The pure weakening check (ADR 0005 D6): a candidate whose policy digest
54
+ * differs from the trusted revision is a WEAKENING CANDIDATE — it may
55
+ * only proceed after a separate trusted update is accepted, never by
56
+ * self-approval. Fail-closed: a missing or malformed digest cannot be
57
+ * verified, so it counts as weakened rather than trusted.
58
+ *
59
+ * Args:
60
+ * trustedDigest: the digest of the trusted policy revision.
61
+ * candidateDigest: the digest of the candidate's effective policy.
62
+ *
63
+ * Returns:
64
+ * PolicyWeakeningCheck: `{weakened: false}` only for an exact, well-formed
65
+ * match; otherwise `{weakened: true, reason}` naming the failure.
66
+ */
67
+ export function policyWeakenedCandidate(trustedDigest, candidateDigest) {
68
+ if (!DIGEST_PATTERN.test(trustedDigest)) {
69
+ return {
70
+ weakened: true,
71
+ reason: `trusted policy digest is missing or malformed ('${trustedDigest}'); a candidate cannot be trusted against an unverifiable revision (fail closed)`,
72
+ trustedDigest,
73
+ candidateDigest,
74
+ };
75
+ }
76
+ if (!DIGEST_PATTERN.test(candidateDigest)) {
77
+ return {
78
+ weakened: true,
79
+ reason: `candidate policy digest is missing or malformed ('${candidateDigest}'); the candidate's effective policy cannot be verified (fail closed)`,
80
+ trustedDigest,
81
+ candidateDigest,
82
+ };
83
+ }
84
+ if (trustedDigest !== candidateDigest) {
85
+ return {
86
+ weakened: true,
87
+ reason: 'candidate policy digest differs from the trusted policy revision: candidate edits to ' +
88
+ 'classifiers, exclusions, scope-suppressing mappings, adapters, runner definitions, ' +
89
+ 'baselines, or waivers cannot authorize their own weaker checks (a trusted update is required)',
90
+ trustedDigest,
91
+ candidateDigest,
92
+ };
93
+ }
94
+ return { weakened: false, trustedDigest, candidateDigest };
95
+ }
96
+ //# sourceMappingURL=trusted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted.js","sourceRoot":"","sources":["../../src/policy/trusted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,qBAAqB,GAAG,6BAA6B,CAAC;AAUnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAqC;IACvE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,SAAS,CAAC,qDAAqD,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;QAC1F,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC;SACtB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;SAC9C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,OAAO,eAAe,CAAC,EAAE,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,CAAC,CAAC;AACnE,CAAC;AAcD,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CACrC,aAAqB,EACrB,eAAuB;IAEvB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,mDAAmD,aAAa,kFAAkF;YAC1J,aAAa;YACb,eAAe;SAChB,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QAC1C,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,qDAAqD,eAAe,uEAAuE;YACnJ,aAAa;YACb,eAAe;SAChB,CAAC;IACJ,CAAC;IACD,IAAI,aAAa,KAAK,eAAe,EAAE,CAAC;QACtC,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,MAAM,EACJ,uFAAuF;gBACvF,qFAAqF;gBACrF,+FAA+F;YACjG,aAAa;YACb,eAAe;SAChB,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC;AAC7D,CAAC"}