@ldclabs/kip-lang 0.3.1 → 2.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 (64) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -44
  4. package/dist/ast.d.ts +519 -144
  5. package/dist/ast.d.ts.map +1 -1
  6. package/dist/budget.d.ts +37 -0
  7. package/dist/budget.d.ts.map +1 -0
  8. package/dist/budget.js +105 -0
  9. package/dist/budget.js.map +1 -0
  10. package/dist/diagnostics.d.ts +8 -2
  11. package/dist/diagnostics.d.ts.map +1 -1
  12. package/dist/diagnostics.js +32 -3
  13. package/dist/diagnostics.js.map +1 -1
  14. package/dist/errors.d.ts +29 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +27 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/exec-ast.d.ts +679 -0
  19. package/dist/exec-ast.d.ts.map +1 -0
  20. package/dist/exec-ast.js +24 -0
  21. package/dist/exec-ast.js.map +1 -0
  22. package/dist/formatter.d.ts.map +1 -1
  23. package/dist/formatter.js +870 -479
  24. package/dist/formatter.js.map +1 -1
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +6 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/lexer.d.ts.map +1 -1
  30. package/dist/lexer.js +46 -33
  31. package/dist/lexer.js.map +1 -1
  32. package/dist/lower.d.ts +17 -0
  33. package/dist/lower.d.ts.map +1 -0
  34. package/dist/lower.js +1566 -0
  35. package/dist/lower.js.map +1 -0
  36. package/dist/parser.d.ts.map +1 -1
  37. package/dist/parser.js +2476 -1152
  38. package/dist/parser.js.map +1 -1
  39. package/dist/semantics.d.ts +11 -7
  40. package/dist/semantics.d.ts.map +1 -1
  41. package/dist/semantics.js +295 -180
  42. package/dist/semantics.js.map +1 -1
  43. package/dist/token.d.ts +130 -40
  44. package/dist/token.d.ts.map +1 -1
  45. package/dist/token.js +264 -83
  46. package/dist/token.js.map +1 -1
  47. package/dist/version.d.ts +13 -0
  48. package/dist/version.d.ts.map +1 -0
  49. package/dist/version.js +13 -0
  50. package/dist/version.js.map +1 -0
  51. package/package.json +36 -6
  52. package/src/ast.ts +914 -0
  53. package/src/budget.ts +108 -0
  54. package/src/diagnostics.ts +182 -0
  55. package/src/errors.ts +42 -0
  56. package/src/exec-ast.ts +614 -0
  57. package/src/formatter.ts +1339 -0
  58. package/src/index.ts +226 -0
  59. package/src/lexer.ts +459 -0
  60. package/src/lower.ts +2011 -0
  61. package/src/parser.ts +3506 -0
  62. package/src/semantics.ts +392 -0
  63. package/src/token.ts +408 -0
  64. package/src/version.ts +13 -0
package/dist/lower.js ADDED
@@ -0,0 +1,1566 @@
1
+ import { invalidSyntax } from './errors.js';
2
+ const AGGREGATIONS = new Map([
3
+ ['COUNT', 'Count'],
4
+ ['SUM', 'Sum'],
5
+ ['AVG', 'Avg'],
6
+ ['MIN', 'Min'],
7
+ ['MAX', 'Max']
8
+ ]);
9
+ const FILTER_FUNCTIONS = new Map([
10
+ ['CONTAINS', 'Contains'],
11
+ ['STARTS_WITH', 'StartsWith'],
12
+ ['ENDS_WITH', 'EndsWith'],
13
+ ['REGEX', 'Regex'],
14
+ ['IN', 'In'],
15
+ ['IS_NULL', 'IsNull'],
16
+ ['IS_NOT_NULL', 'IsNotNull'],
17
+ ['IS_LITERAL', 'IsLiteral'],
18
+ ['IS_ELEMENT', 'IsElement'],
19
+ ['IS_KIND', 'IsKind'],
20
+ ['LITERAL_TYPE', 'LiteralType']
21
+ ]);
22
+ const UPDATE_FUNCTIONS = new Map([
23
+ ['ADD', 'Add'],
24
+ ['MUL', 'Mul'],
25
+ ['CLAMP', 'Clamp'],
26
+ ['COALESCE', 'Coalesce']
27
+ ]);
28
+ const UPDATE_ARITY = {
29
+ Add: 2,
30
+ Mul: 2,
31
+ Coalesce: 2,
32
+ Clamp: 3
33
+ };
34
+ const COMPARISONS = new Map([
35
+ ['==', 'Equal'],
36
+ ['!=', 'NotEqual'],
37
+ ['<', 'LessThan'],
38
+ ['>', 'GreaterThan'],
39
+ ['<=', 'LessEqual'],
40
+ ['>=', 'GreaterEqual']
41
+ ]);
42
+ /**
43
+ * Engine-owned state no cognitive mutation may write (Spec §6.3, §2.11).
44
+ *
45
+ * These are checked by name on every mutation, not just on UPDATE: author
46
+ * content that could rewrite engine truth or its own authority is exactly
47
+ * what "external cognition cannot self-escalate authority" forbids.
48
+ */
49
+ const PROTECTED_FIELDS = new Set([
50
+ '_system',
51
+ 'governance',
52
+ 'space_id',
53
+ 'space_seq'
54
+ ]);
55
+ /**
56
+ * Assertion payload that is immutable after creation (Spec §13.7).
57
+ *
58
+ * Changing epistemic commitment means a new Assertion plus supersession, so
59
+ * an UPDATE naming one of these is the `EpistemicRevisionRequired` mistake
60
+ * caught statically wherever the WHERE block says the target is an Assertion.
61
+ */
62
+ const ASSERTION_IMMUTABLE = new Set([
63
+ 'proposition_id',
64
+ 'proposition',
65
+ 'asserted_by',
66
+ 'stance',
67
+ 'mode',
68
+ 'confidence',
69
+ 'asserted_at',
70
+ 'valid_time',
71
+ 'evidence_refs'
72
+ ]);
73
+ /** Evidence payload and observation identity are immutable (Spec §15.5). */
74
+ const EVIDENCE_IMMUTABLE = new Set([
75
+ 'evidence_class',
76
+ 'payload',
77
+ 'content_digest',
78
+ 'media_type',
79
+ 'observed_at'
80
+ ]);
81
+ /** A Proposition tuple is immutable after creation (Spec §12.5). */
82
+ const PROPOSITION_IMMUTABLE = new Set(['subject', 'predicate', 'object']);
83
+ /**
84
+ * Lowers a parsed program carrying exactly one command.
85
+ *
86
+ * KIP's request envelope binds one command to one result, so a source text
87
+ * that holds two statements is not a command — it is a batch, and silently
88
+ * running the first would answer a question the caller did not ask. Use
89
+ * {@link lowerAll} for multi-statement text such as a schema capsule.
90
+ *
91
+ * @throws {KipSyntaxError} on anything that is not one executable command.
92
+ */
93
+ export function lower(program) {
94
+ const [first, second] = program.statements;
95
+ if (!first) {
96
+ throw invalidSyntax('expected a KIP command, found none');
97
+ }
98
+ if (second) {
99
+ throw invalidSyntax('expected one KIP command, found several: wrap consecutive mutations in ' +
100
+ 'MUTATE { ... } to make them one transaction, or use lowerAll for a batch', second.range);
101
+ }
102
+ return lowerStatement(first);
103
+ }
104
+ /** Lowers every statement in a multi-command source text. */
105
+ export function lowerAll(program) {
106
+ if (program.statements.length === 0) {
107
+ throw invalidSyntax('expected at least one KIP command, found none');
108
+ }
109
+ return program.statements.map(lowerStatement);
110
+ }
111
+ export function lowerStatement(stmt) {
112
+ switch (stmt.kind) {
113
+ case 'FindStatement':
114
+ return { Kql: lowerFind(stmt) };
115
+ case 'MutateStatement':
116
+ return { Kml: lowerMutate(stmt) };
117
+ case 'CreateConceptStatement':
118
+ case 'UpsertConceptStatement':
119
+ case 'EnsurePropositionStatement':
120
+ case 'AssertStatement':
121
+ case 'CreateEvidenceStatement':
122
+ case 'CreateAssertionStatement':
123
+ case 'CreateActivityStatement':
124
+ case 'UpdateStatement':
125
+ case 'RetractAssertionStatement':
126
+ case 'SupersedeAssertionStatement':
127
+ case 'CorrectEvidenceStatement':
128
+ case 'TransitionActivityStatement':
129
+ case 'SetRetentionStatement':
130
+ case 'ArchiveStatement':
131
+ case 'TombstoneStatement':
132
+ case 'PurgeStatement':
133
+ case 'MergeConceptStatement':
134
+ const clauses = lowerMutationClause(stmt, 0);
135
+ assertUniqueHandles(clauses, stmt.range);
136
+ assertResolvedHandles(clauses, stmt.range);
137
+ return {
138
+ Kml: {
139
+ explicit_transaction: false,
140
+ clauses
141
+ }
142
+ };
143
+ default:
144
+ return { Meta: lowerMeta(stmt) };
145
+ }
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // KQL
149
+ // ---------------------------------------------------------------------------
150
+ function lowerFind(stmt) {
151
+ if (stmt.projections.length === 0) {
152
+ throw invalidSyntax('FIND requires at least one projection', stmt.range);
153
+ }
154
+ return {
155
+ find_clause: {
156
+ expressions: stmt.projections.map((p) => lowerFindExpression(p))
157
+ },
158
+ where_clauses: lowerWhere(stmt.where),
159
+ as_of: stmt.asOf ? lowerAsOf(stmt.asOf) : null,
160
+ for_time: stmt.forTime ? lowerScalar(stmt.forTime.value) : null,
161
+ epistemic: stmt.epistemic ? lowerBoundObject(stmt.epistemic.options) : null,
162
+ order_by: stmt.orderBy ? stmt.orderBy.items.map(lowerOrderItem) : null,
163
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
164
+ cursor: stmt.cursor ? lowerScalar(stmt.cursor.value) : null
165
+ };
166
+ }
167
+ function lowerAsOf(clause) {
168
+ const value = lowerScalar(clause.value);
169
+ switch (clause.basis) {
170
+ case 'SEQ':
171
+ return { Seq: value };
172
+ case 'TX':
173
+ return { Tx: value };
174
+ case 'TIME':
175
+ return { Time: value };
176
+ }
177
+ }
178
+ function lowerFindExpression(expr) {
179
+ if (expr.kind === 'AggregateExpr') {
180
+ const func = AGGREGATIONS.get(expr.name.toUpperCase());
181
+ if (!func) {
182
+ throw invalidSyntax(`unknown aggregate ${expr.name}`, expr.range);
183
+ }
184
+ return {
185
+ Aggregation: {
186
+ func,
187
+ var: lowerDotPath(expr.argument),
188
+ distinct: expr.distinct
189
+ }
190
+ };
191
+ }
192
+ return { Variable: lowerDotPath(expr) };
193
+ }
194
+ function lowerOrderItem(item) {
195
+ const direction = item.direction === 'DESC' ? 'Desc' : 'Asc';
196
+ if (item.expression.kind === 'AggregateExpr') {
197
+ const func = AGGREGATIONS.get(item.expression.name.toUpperCase());
198
+ if (!func) {
199
+ throw invalidSyntax(`unknown aggregate ${item.expression.name}`, item.expression.range);
200
+ }
201
+ return {
202
+ variable: lowerDotPath(item.expression.argument),
203
+ direction,
204
+ aggregation: func
205
+ };
206
+ }
207
+ return {
208
+ variable: lowerDotPath(item.expression),
209
+ direction,
210
+ aggregation: null
211
+ };
212
+ }
213
+ /** A projection or sort key must resolve to one variable plus a path. */
214
+ function lowerDotPath(expr) {
215
+ if (expr.kind === 'VariableRef') {
216
+ return { var: varName(expr.name, expr.range), path: [] };
217
+ }
218
+ if (expr.kind === 'FieldAccess') {
219
+ const path = expr.steps.map((step) => step.kind === 'DotStep'
220
+ ? { Field: step.name }
221
+ : { Key: step.key.parsed });
222
+ return { var: varName(expr.base.name, expr.base.range), path };
223
+ }
224
+ throw invalidSyntax(`expected a variable or a dot path, found ${describeExpression(expr)}`, expr.range);
225
+ }
226
+ function lowerWhere(clause) {
227
+ return clause.patterns.map(lowerWherePattern);
228
+ }
229
+ function lowerWherePattern(pattern) {
230
+ switch (pattern.kind) {
231
+ case 'ConceptPattern':
232
+ return {
233
+ Concept: {
234
+ variable: varName(pattern.variable.name, pattern.variable.range),
235
+ matcher: lowerObjectMatcher(pattern.matcher)
236
+ }
237
+ };
238
+ case 'PropositionPattern':
239
+ return {
240
+ Proposition: {
241
+ variable: pattern.variable
242
+ ? varName(pattern.variable.name, pattern.variable.range)
243
+ : null,
244
+ matcher: lowerPropositionMatcher(pattern.tuple)
245
+ }
246
+ };
247
+ case 'AssertionPattern':
248
+ return {
249
+ Assertion: {
250
+ variable: varName(pattern.variable.name, pattern.variable.range),
251
+ matcher: lowerObjectMatcher(pattern.matcher)
252
+ }
253
+ };
254
+ case 'EvidencePattern':
255
+ return {
256
+ Evidence: {
257
+ variable: varName(pattern.variable.name, pattern.variable.range),
258
+ matcher: lowerObjectMatcher(pattern.matcher)
259
+ }
260
+ };
261
+ case 'ActivityPattern':
262
+ return {
263
+ Activity: {
264
+ variable: varName(pattern.variable.name, pattern.variable.range),
265
+ matcher: lowerObjectMatcher(pattern.matcher)
266
+ }
267
+ };
268
+ case 'StructuralPattern':
269
+ return {
270
+ Structural: {
271
+ variable: pattern.variable
272
+ ? varName(pattern.variable.name, pattern.variable.range)
273
+ : null,
274
+ subject: lowerTerm(pattern.subject),
275
+ field: lowerSymbol(pattern.field),
276
+ object: lowerTerm(pattern.object)
277
+ }
278
+ };
279
+ case 'BeliefPattern': {
280
+ let target;
281
+ if (pattern.proposition) {
282
+ target = {
283
+ Proposition: varName(pattern.proposition.name, pattern.proposition.range)
284
+ };
285
+ }
286
+ else if (pattern.propositionId) {
287
+ // Same slot, same reference form as `?p PROPOSITION (id: ...)`.
288
+ target = { Id: lowerScalar(pattern.propositionId) };
289
+ }
290
+ else {
291
+ if (!pattern.subject || !pattern.predicate || !pattern.object) {
292
+ throw invalidSyntax('BELIEF requires one bound Proposition, an (id: ...) reference, or a full (subject, predicate, object) tuple', pattern.range);
293
+ }
294
+ target = {
295
+ Tuple: {
296
+ subject: lowerPropositionSubject(pattern.subject),
297
+ predicate: { Atom: lowerPredAtom(pattern.predicate) },
298
+ object: lowerTerm(pattern.object)
299
+ }
300
+ };
301
+ }
302
+ return {
303
+ Belief: {
304
+ variable: varName(pattern.variable.name, pattern.variable.range),
305
+ target
306
+ }
307
+ };
308
+ }
309
+ case 'BeliefSlotPattern':
310
+ return {
311
+ BeliefSlot: {
312
+ variable: varName(pattern.variable.name, pattern.variable.range),
313
+ subject: lowerPropositionSubject(pattern.subject),
314
+ predicate: lowerPredAtom(pattern.predicate)
315
+ }
316
+ };
317
+ case 'FilterClause':
318
+ return { Filter: { expression: lowerFilter(pattern.expression) } };
319
+ case 'NotClause':
320
+ return { Not: pattern.patterns.map(lowerWherePattern) };
321
+ case 'OptionalClause':
322
+ return { Optional: pattern.patterns.map(lowerWherePattern) };
323
+ case 'UnionClause':
324
+ return { Union: pattern.patterns.map(lowerWherePattern) };
325
+ }
326
+ }
327
+ function lowerPropositionMatcher(tuple) {
328
+ if (tuple.id)
329
+ return { Id: lowerScalar(tuple.id) };
330
+ if (!tuple.subject || !tuple.predicate || !tuple.object) {
331
+ throw invalidSyntax('a Proposition expression is either (subject, predicate, object) or (id: ...)', tuple.range);
332
+ }
333
+ return {
334
+ Tuple: {
335
+ subject: lowerPropositionSubject(tuple.subject),
336
+ predicate: lowerPredicate(tuple.predicate),
337
+ object: lowerTerm(tuple.object)
338
+ }
339
+ };
340
+ }
341
+ /**
342
+ * Resolves the tuple a resolve-or-create statement needs.
343
+ *
344
+ * `(id: ...)` is match-only: it names a Proposition that must already exist,
345
+ * so it cannot drive ENSURE PROPOSITION — or the ASSERT sugar that desugars
346
+ * through it — whose job is to create the tuple when it is absent.
347
+ */
348
+ function requireStructuralTuple(tuple, statement) {
349
+ if (tuple.id) {
350
+ throw invalidSyntax(`${statement} needs a (subject, predicate, object) tuple: (id: ...) only matches an ` +
351
+ 'existing Proposition, and no structure can be created from an id', tuple.range);
352
+ }
353
+ const predicateExpression = tuple.predicate;
354
+ const predicate = lowerPredicate(predicateExpression);
355
+ if (!('Atom' in predicate)) {
356
+ throw invalidSyntax(`${statement} needs one exact predicate; alternation and hop quantifiers are KQL traversal forms`, tuple.predicate.range);
357
+ }
358
+ if ('Variable' in predicate.Atom) {
359
+ throw invalidSyntax(`${statement} needs an exact quoted predicate or :parameter; ?variables are KQL read-pattern syntax`, predicateExpression.range);
360
+ }
361
+ return {
362
+ subject: lowerPropositionSubject(tuple.subject),
363
+ predicate: predicate.Atom,
364
+ object: lowerTerm(tuple.object)
365
+ };
366
+ }
367
+ function lowerPredicate(expr) {
368
+ const [only] = expr.atoms;
369
+ if (expr.atoms.length === 1 && only && !only.quantifier) {
370
+ return { Atom: lowerPredAtom(only.atom) };
371
+ }
372
+ const path = expr.atoms.map((atom) => ({
373
+ predicate: lowerPredAtom(atom.atom),
374
+ hops: atom.quantifier
375
+ ? { min: atom.quantifier.min, max: atom.quantifier.max ?? null }
376
+ : null
377
+ }));
378
+ return { Path: path };
379
+ }
380
+ function lowerPredAtom(atom) {
381
+ switch (atom.kind) {
382
+ case 'StringLiteral':
383
+ return { Literal: atom.parsed };
384
+ case 'ParameterRef':
385
+ return { Param: paramName(atom.name) };
386
+ case 'VariableRef':
387
+ return { Variable: varName(atom.name, atom.range) };
388
+ }
389
+ }
390
+ function lowerTerm(term) {
391
+ switch (term.kind) {
392
+ case 'VariableRef':
393
+ return { Variable: varName(term.name, term.range) };
394
+ case 'ParameterRef':
395
+ return { Param: paramName(term.name) };
396
+ case 'ObjectPattern':
397
+ return { Match: lowerObjectMatcher(term) };
398
+ case 'PropositionTuple':
399
+ return { Proposition: lowerPropositionMatcher(term) };
400
+ default:
401
+ return { Literal: lowerKipValue(term) };
402
+ }
403
+ }
404
+ /** A Proposition subject is always an Element reference, never a Literal. */
405
+ function lowerPropositionSubject(term) {
406
+ switch (term.kind) {
407
+ case 'StringLiteral':
408
+ case 'NumberLiteral':
409
+ case 'BooleanLiteral':
410
+ case 'NullLiteral':
411
+ throw invalidSyntax('a Proposition subject must be a local Element reference, never a Literal', term.range);
412
+ default:
413
+ return lowerTerm(term);
414
+ }
415
+ }
416
+ function lowerObjectMatcher(pattern) {
417
+ const matcher = {};
418
+ for (const member of pattern.members) {
419
+ if (Object.prototype.hasOwnProperty.call(matcher, member.key)) {
420
+ throw invalidSyntax(`duplicate match field ${member.key}`, member.range);
421
+ }
422
+ matcher[member.key] = lowerMatchValue(member.value);
423
+ }
424
+ return matcher;
425
+ }
426
+ function lowerMatchValue(expr) {
427
+ switch (expr.kind) {
428
+ case 'VariableRef':
429
+ return { Variable: varName(expr.name, expr.range) };
430
+ case 'ParameterRef':
431
+ return { Param: paramName(expr.name) };
432
+ case 'ArrayLiteral':
433
+ return { Array: expr.elements.map(lowerMatchValue) };
434
+ case 'ObjectPattern':
435
+ return { Match: lowerObjectMatcher(expr) };
436
+ case 'PropositionTuple':
437
+ return { Proposition: lowerPropositionMatcher(expr) };
438
+ default:
439
+ return { Literal: lowerKipValue(expr) };
440
+ }
441
+ }
442
+ // ---------------------------------------------------------------------------
443
+ // Filters
444
+ // ---------------------------------------------------------------------------
445
+ function lowerFilter(expr) {
446
+ switch (expr.kind) {
447
+ case 'BinaryExpression': {
448
+ if (expr.operator === '&&' || expr.operator === '||') {
449
+ return {
450
+ Logical: {
451
+ left: lowerFilter(expr.left),
452
+ operator: expr.operator === '&&' ? 'And' : 'Or',
453
+ right: lowerFilter(expr.right)
454
+ }
455
+ };
456
+ }
457
+ const operator = COMPARISONS.get(expr.operator);
458
+ if (!operator) {
459
+ throw invalidSyntax(`unknown comparison operator ${expr.operator}`, expr.range);
460
+ }
461
+ return {
462
+ Comparison: {
463
+ left: lowerFilterOperand(expr.left),
464
+ operator,
465
+ right: lowerFilterOperand(expr.right)
466
+ }
467
+ };
468
+ }
469
+ case 'UnaryExpression':
470
+ if (expr.operator === '!') {
471
+ return { Not: lowerFilter(expr.operand) };
472
+ }
473
+ throw invalidSyntax('a filter must be a comparison, a logical combination, a negation or a function call', expr.range);
474
+ case 'FunctionCallExpr': {
475
+ const func = FILTER_FUNCTIONS.get(expr.name.toUpperCase());
476
+ if (!func) {
477
+ throw invalidSyntax(`${expr.name} is not a KIP filter function`, expr.range);
478
+ }
479
+ return {
480
+ Function: { func, args: expr.args.map(lowerFilterOperand) }
481
+ };
482
+ }
483
+ case 'AggregateExpr':
484
+ // An aggregate summarizes a solution set; a filter runs per candidate
485
+ // row, so there is no set for it to summarize yet.
486
+ throw invalidSyntax(`${expr.name} is an aggregate and cannot appear inside FILTER`, expr.range);
487
+ default:
488
+ throw invalidSyntax(`a filter must be a comparison, a logical combination, a negation or a function call, found ${describeExpression(expr)}`, expr.range);
489
+ }
490
+ }
491
+ function lowerFilterOperand(expr) {
492
+ switch (expr.kind) {
493
+ case 'VariableRef':
494
+ case 'FieldAccess':
495
+ return { Variable: lowerDotPath(expr) };
496
+ case 'ParameterRef':
497
+ return { Param: paramName(expr.name) };
498
+ case 'ArrayLiteral':
499
+ if (expr.trailingComma) {
500
+ throw invalidSyntax('a filter list does not allow a trailing comma', expr.range);
501
+ }
502
+ return { List: expr.elements.map(lowerFilterOperand) };
503
+ case 'UnaryExpression':
504
+ if (expr.operator === '-') {
505
+ return { Negate: lowerFilterOperand(expr.operand) };
506
+ }
507
+ throw invalidSyntax(`expected a filter operand, found ${describeExpression(expr)}`, expr.range);
508
+ case 'AggregateExpr':
509
+ // An aggregate summarizes a solution set; a filter runs per candidate
510
+ // row, so there is no set for it to summarize yet.
511
+ throw invalidSyntax(`${expr.name} is an aggregate and cannot appear inside FILTER`, expr.range);
512
+ case 'FunctionCallExpr':
513
+ case 'BinaryExpression':
514
+ throw invalidSyntax(`expected a filter operand, found ${describeExpression(expr)}`, expr.range);
515
+ default:
516
+ return { Literal: lowerKipValue(expr) };
517
+ }
518
+ }
519
+ // ---------------------------------------------------------------------------
520
+ // KML
521
+ // ---------------------------------------------------------------------------
522
+ function lowerMutate(stmt) {
523
+ if (stmt.clauses.length === 0) {
524
+ throw invalidSyntax('MUTATE requires at least one mutation', stmt.range);
525
+ }
526
+ const clauses = stmt.clauses.flatMap((clause, i) => lowerMutationClause(clause, i));
527
+ assertUniqueHandles(clauses, stmt.range);
528
+ assertResolvedHandles(clauses, stmt.range);
529
+ return { explicit_transaction: true, clauses };
530
+ }
531
+ /**
532
+ * Handles are block-local names. Two clauses claiming the same handle make
533
+ * every forward reference to it ambiguous, so the whole plan is rejected
534
+ * rather than resolved by position.
535
+ */
536
+ function assertUniqueHandles(clauses, range) {
537
+ const seen = new Set();
538
+ for (const clause of clauses) {
539
+ const handle = handleOf(clause);
540
+ if (handle === null)
541
+ continue;
542
+ if (seen.has(handle)) {
543
+ throw invalidSyntax(`duplicate local handle ?${handle} in one mutation plan`, range);
544
+ }
545
+ seen.add(handle);
546
+ }
547
+ }
548
+ function handleOf(clause) {
549
+ if ('CreateConcept' in clause)
550
+ return clause.CreateConcept.handle;
551
+ if ('UpsertConcept' in clause)
552
+ return clause.UpsertConcept.handle;
553
+ if ('CreateEvidence' in clause)
554
+ return clause.CreateEvidence.handle;
555
+ if ('CreateAssertion' in clause)
556
+ return clause.CreateAssertion.handle;
557
+ if ('CreateActivity' in clause)
558
+ return clause.CreateActivity.handle;
559
+ if ('EnsureProposition' in clause)
560
+ return clause.EnsureProposition.handle;
561
+ return null;
562
+ }
563
+ /**
564
+ * Every executable `Handle` must be created by this mutation plan or bound by
565
+ * that clause's WHERE. Parameters remain runtime bindings and are unaffected.
566
+ */
567
+ function assertResolvedHandles(clauses, range) {
568
+ const planHandles = new Set();
569
+ for (const clause of clauses) {
570
+ const handle = handleOf(clause);
571
+ if (handle !== null)
572
+ planHandles.add(handle);
573
+ }
574
+ for (const clause of clauses) {
575
+ const allowed = new Set(planHandles);
576
+ const body = Object.values(clause)[0];
577
+ collectWhereVariables(body.where_clauses, allowed);
578
+ const referenced = new Set();
579
+ collectTaggedHandles(body, referenced);
580
+ for (const handle of referenced) {
581
+ if (!allowed.has(handle)) {
582
+ throw invalidSyntax(`?${handle} is not bound by this command's mutation outputs or WHERE clause`, range);
583
+ }
584
+ }
585
+ }
586
+ }
587
+ function collectWhereVariables(value, out) {
588
+ if (Array.isArray(value)) {
589
+ for (const item of value)
590
+ collectWhereVariables(item, out);
591
+ return;
592
+ }
593
+ if (!value || typeof value !== 'object')
594
+ return;
595
+ const record = value;
596
+ if (typeof record.variable === 'string')
597
+ out.add(record.variable);
598
+ // Pattern terms and predicate atoms use the tagged `{ Variable: name }`
599
+ // shape. Filter operands also use `Variable`, but carry a DotPathVar object
600
+ // rather than a string, so they cannot accidentally introduce a binding.
601
+ if (typeof record.Variable === 'string')
602
+ out.add(record.Variable);
603
+ for (const child of Object.values(record))
604
+ collectWhereVariables(child, out);
605
+ }
606
+ function collectTaggedHandles(value, out) {
607
+ if (Array.isArray(value)) {
608
+ for (const item of value)
609
+ collectTaggedHandles(item, out);
610
+ return;
611
+ }
612
+ if (!value || typeof value !== 'object')
613
+ return;
614
+ const record = value;
615
+ if (typeof record.Handle === 'string')
616
+ out.add(record.Handle);
617
+ for (const child of Object.values(record))
618
+ collectTaggedHandles(child, out);
619
+ }
620
+ /**
621
+ * One source statement may lower to several clauses; `ASSERT` is the case.
622
+ *
623
+ * `seq` is the clause's position in its plan, used only to keep synthetic
624
+ * handles distinct between two handle-less ASSERTs in the same transaction.
625
+ */
626
+ function lowerMutationClause(stmt, seq) {
627
+ switch (stmt.kind) {
628
+ case 'CreateConceptStatement':
629
+ return [{ CreateConcept: lowerCreateConcept(stmt) }];
630
+ case 'UpsertConceptStatement':
631
+ return [{ UpsertConcept: lowerUpsertConcept(stmt) }];
632
+ case 'EnsurePropositionStatement':
633
+ return [{ EnsureProposition: lowerEnsureProposition(stmt) }];
634
+ case 'AssertStatement':
635
+ return lowerAssertSugar(stmt, seq);
636
+ case 'CreateEvidenceStatement':
637
+ return [{ CreateEvidence: lowerRecordCreate(stmt) }];
638
+ case 'CreateAssertionStatement':
639
+ return [{ CreateAssertion: lowerRecordCreate(stmt) }];
640
+ case 'CreateActivityStatement':
641
+ return [{ CreateActivity: lowerRecordCreate(stmt) }];
642
+ case 'UpdateStatement':
643
+ return [{ Update: lowerUpdate(stmt) }];
644
+ case 'RetractAssertionStatement':
645
+ return [
646
+ {
647
+ RetractAssertion: {
648
+ target: lowerElementRef(stmt.target),
649
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
650
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
651
+ expect_state: stmt.expectState
652
+ ? lowerScalar(stmt.expectState.value)
653
+ : null
654
+ }
655
+ }
656
+ ];
657
+ case 'SupersedeAssertionStatement':
658
+ return [
659
+ {
660
+ SupersedeAssertion: {
661
+ target: lowerElementRef(stmt.target),
662
+ by: lowerElementRef(stmt.by),
663
+ expect_state: stmt.expectState
664
+ ? lowerScalar(stmt.expectState.value)
665
+ : null
666
+ }
667
+ }
668
+ ];
669
+ case 'CorrectEvidenceStatement':
670
+ return [
671
+ {
672
+ CorrectEvidence: {
673
+ target: lowerElementRef(stmt.target),
674
+ by: lowerElementRef(stmt.by),
675
+ expect_state: stmt.expectState
676
+ ? lowerScalar(stmt.expectState.value)
677
+ : null
678
+ }
679
+ }
680
+ ];
681
+ case 'TransitionActivityStatement':
682
+ return [{ TransitionActivity: lowerTransition(stmt) }];
683
+ case 'SetRetentionStatement':
684
+ return [
685
+ {
686
+ SetRetention: {
687
+ target: lowerElementRef(stmt.target),
688
+ values: lowerAssignments(stmt.assignments, null),
689
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
690
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
691
+ expect_version: stmt.expectVersion
692
+ ? lowerScalar(stmt.expectVersion.value)
693
+ : null
694
+ }
695
+ }
696
+ ];
697
+ case 'ArchiveStatement':
698
+ return [{ Archive: lowerRemoval(stmt) }];
699
+ case 'TombstoneStatement':
700
+ return [{ Tombstone: lowerRemoval(stmt) }];
701
+ case 'PurgeStatement':
702
+ return [{ Purge: lowerPurge(stmt) }];
703
+ case 'MergeConceptStatement':
704
+ return [
705
+ {
706
+ MergeConcept: {
707
+ source: lowerElementRef(stmt.source),
708
+ into: lowerElementRef(stmt.into),
709
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
710
+ expect_version: stmt.expectVersion
711
+ ? lowerScalar(stmt.expectVersion.value)
712
+ : null
713
+ }
714
+ }
715
+ ];
716
+ }
717
+ }
718
+ function lowerCreateConcept(stmt) {
719
+ return {
720
+ handle: varName(stmt.handle.name, stmt.handle.range),
721
+ type: stmt.type ? lowerSymbol(stmt.type.value) : null,
722
+ client_key: stmt.clientKey ? lowerScalar(stmt.clientKey.value) : null,
723
+ name: stmt.name ? lowerScalar(stmt.name.value) : null,
724
+ set_fields: stmt.setFields
725
+ ? lowerAssignments(stmt.setFields.assignments, null)
726
+ : null,
727
+ set_attributes: stmt.setAttributes
728
+ ? lowerAssignments(stmt.setAttributes.assignments, null)
729
+ : null,
730
+ set_facets: stmt.setFacets.map((f) => lowerFacet(f, null)),
731
+ set_structural: stmt.setStructural
732
+ ? lowerStructural(stmt.setStructural, null)
733
+ : null
734
+ };
735
+ }
736
+ function lowerUpsertConcept(stmt) {
737
+ const match = stmt.match ? lowerObjectMatcher(stmt.match.pattern) : null;
738
+ // Identity for an upsert is `id` or `key`; a name-only match is forbidden
739
+ // because names are mutable grounding state with duplicates allowed, so
740
+ // "the Concept named X" can silently address a different node over time.
741
+ if (match) {
742
+ const fields = Object.keys(match);
743
+ const hasIdentity = fields.includes('id') || fields.includes('key');
744
+ if (!hasIdentity) {
745
+ throw invalidSyntax('UPSERT CONCEPT must match on a stable identity: add {id: ...} or {key: ...} — ' +
746
+ 'name is mutable grounding state and never identifies a Concept', stmt.match.range);
747
+ }
748
+ }
749
+ return {
750
+ handle: varName(stmt.handle.name, stmt.handle.range),
751
+ match,
752
+ expect_version: stmt.expectVersion
753
+ ? lowerScalar(stmt.expectVersion.value)
754
+ : null,
755
+ set_fields: stmt.setFields
756
+ ? lowerAssignments(stmt.setFields.assignments, null)
757
+ : null,
758
+ set_attributes: stmt.setAttributes
759
+ ? lowerAssignments(stmt.setAttributes.assignments, null)
760
+ : null,
761
+ set_facets: stmt.setFacets.map((f) => lowerFacet(f, null)),
762
+ unset_attributes: stmt.unsetAttributes
763
+ ? lowerUnsetFields(stmt.unsetAttributes.fields)
764
+ : null,
765
+ unset_facets: stmt.unsetFacets.map((f) => ({
766
+ facet: lowerSymbol(f.facet),
767
+ fields: lowerUnsetFields(f.fields)
768
+ })),
769
+ set_structural: stmt.setStructural
770
+ ? lowerStructural(stmt.setStructural, null)
771
+ : null,
772
+ unset_structural: stmt.unsetStructural
773
+ ? lowerStructuralRemovals(stmt.unsetStructural, null)
774
+ : null
775
+ };
776
+ }
777
+ function lowerEnsureProposition(stmt) {
778
+ const triple = requireStructuralTuple(stmt.tuple, 'ENSURE PROPOSITION');
779
+ return {
780
+ handle: stmt.handle ? varName(stmt.handle.name, stmt.handle.range) : null,
781
+ ...triple,
782
+ expect_version: stmt.expectVersion
783
+ ? lowerScalar(stmt.expectVersion.value)
784
+ : null
785
+ };
786
+ }
787
+ function lowerRecordCreate(stmt) {
788
+ const fields = stmt.setFields
789
+ ? lowerAssignments(stmt.setFields.assignments, null)
790
+ : null;
791
+ return {
792
+ handle: varName(stmt.handle.name, stmt.handle.range),
793
+ client_key: stmt.clientKey ? lowerScalar(stmt.clientKey.value) : null,
794
+ set_fields: fields,
795
+ set_facets: stmt.setFacets.map((f) => lowerFacet(f, null)),
796
+ set_structural: stmt.setStructural
797
+ ? lowerStructural(stmt.setStructural, null)
798
+ : null
799
+ };
800
+ }
801
+ /**
802
+ * Desugars `ASSERT` into exactly what the Spec defines it as (§55.1):
803
+ * `ENSURE PROPOSITION` + `CREATE ASSERTION`, plus `SUPERSEDE` when written.
804
+ *
805
+ * Nothing else is fabricated. The sugar exists because recording an
806
+ * attributed claim is the hot path, not because it means anything new.
807
+ */
808
+ function lowerAssertSugar(stmt, seq) {
809
+ const members = new Map();
810
+ for (const entry of stmt.assignments.entries) {
811
+ if (members.has(entry.key)) {
812
+ throw invalidSyntax(`duplicate ASSERT member ${entry.key}`, entry.range);
813
+ }
814
+ members.set(entry.key, entry.value);
815
+ }
816
+ const known = new Set([
817
+ 'by',
818
+ 'mode',
819
+ 'stance',
820
+ 'confidence',
821
+ 'at',
822
+ 'valid',
823
+ 'evidence',
824
+ 'key'
825
+ ]);
826
+ for (const [key, value] of members) {
827
+ if (!known.has(key)) {
828
+ throw invalidSyntax(`${key} is not an ASSERT member; expected one of ${[...known].join(', ')}`, value.range);
829
+ }
830
+ }
831
+ // `by` names whose stance this is, and `mode` says how it was arrived at.
832
+ // Neither has a safe default: guessing the actor would forge attribution,
833
+ // and guessing the mode would turn hearsay into observation.
834
+ const by = members.get('by');
835
+ if (!by) {
836
+ throw invalidSyntax('ASSERT requires by: <semantic actor> — an Assertion without an assertor has no epistemic owner', stmt.assignments.range);
837
+ }
838
+ const mode = members.get('mode');
839
+ if (!mode) {
840
+ throw invalidSyntax('ASSERT requires mode: one of observed, stated, inferred, predicted, hypothetical, imported', stmt.assignments.range);
841
+ }
842
+ // The Proposition handle is synthesized, so it must collide with neither a
843
+ // user handle nor another ASSERT in the same plan. `#` cannot occur in a KIP
844
+ // identifier, which rules out the first; `seq` is the clause position, which
845
+ // rules out the second — two handle-less ASSERTs in one MUTATE are ordinary
846
+ // input, not a name clash.
847
+ const assertionHandle = stmt.handle
848
+ ? varName(stmt.handle.name, stmt.handle.range)
849
+ : `#assert${seq}`;
850
+ const propositionHandle = `${assertionHandle}#proposition`;
851
+ const triple = requireStructuralTuple(stmt.tuple, 'ASSERT');
852
+ const clauses = [
853
+ {
854
+ EnsureProposition: {
855
+ handle: propositionHandle,
856
+ ...triple,
857
+ expect_version: null
858
+ }
859
+ }
860
+ ];
861
+ const fields = [
862
+ ['proposition', { Handle: propositionHandle }],
863
+ ['asserted_by', lowerMutationValue(by, null)],
864
+ ['mode', lowerMutationValue(mode, null)],
865
+ // The normative expansion carries a stance even when the source omitted
866
+ // one, so the default is materialized here rather than left for the
867
+ // engine to re-derive.
868
+ [
869
+ 'stance',
870
+ members.has('stance')
871
+ ? lowerMutationValue(members.get('stance'), null)
872
+ : { Value: { String: 'support' } }
873
+ ]
874
+ ];
875
+ const optional = [
876
+ ['confidence', 'confidence'],
877
+ ['at', 'asserted_at'],
878
+ ['valid', 'valid_time']
879
+ ];
880
+ for (const [member, field] of optional) {
881
+ const value = members.get(member);
882
+ if (value)
883
+ fields.push([field, lowerMutationValue(value, null)]);
884
+ }
885
+ // `evidence` is a reserved Core *structural* field, not a plain one: the
886
+ // normative desugaring emits `("evidence", ref) {role: "support"}`. An array
887
+ // cites several artifacts, so it becomes one role-qualified edge each.
888
+ const evidenceExpr = members.get('evidence');
889
+ const evidenceEdges = evidenceExpr === undefined
890
+ ? []
891
+ : (evidenceExpr.kind === 'ArrayLiteral'
892
+ ? evidenceExpr.elements
893
+ : [evidenceExpr]).map((ref) => ({
894
+ field: { Name: 'evidence' },
895
+ value: lowerMutationValue(ref, null),
896
+ options: { role: { Value: { String: 'support' } } }
897
+ }));
898
+ const clientKeyExpr = members.get('key');
899
+ clauses.push({
900
+ CreateAssertion: {
901
+ handle: assertionHandle,
902
+ client_key: clientKeyExpr ? lowerScalarExpression(clientKeyExpr) : null,
903
+ set_fields: fields,
904
+ set_facets: [],
905
+ set_structural: evidenceEdges.length > 0 ? evidenceEdges : null
906
+ }
907
+ });
908
+ if (stmt.superseding) {
909
+ clauses.push({
910
+ SupersedeAssertion: {
911
+ target: lowerElementRef(stmt.superseding),
912
+ by: { Handle: assertionHandle },
913
+ expect_state: null
914
+ }
915
+ });
916
+ }
917
+ return clauses;
918
+ }
919
+ function lowerTransition(stmt) {
920
+ let setFields = null;
921
+ let setStructural = null;
922
+ for (const clause of stmt.finalize) {
923
+ if (clause.kind === 'SetFieldsClause') {
924
+ if (setFields) {
925
+ throw invalidSyntax('duplicate SET FIELDS clause', clause.range);
926
+ }
927
+ setFields = lowerAssignments(clause.assignments, null);
928
+ }
929
+ else {
930
+ if (setStructural) {
931
+ throw invalidSyntax('duplicate SET STRUCTURAL clause', clause.range);
932
+ }
933
+ setStructural = lowerStructural(clause, null);
934
+ }
935
+ }
936
+ return {
937
+ target: lowerElementRef(stmt.target),
938
+ to: lowerScalar(stmt.to),
939
+ set_fields: setFields,
940
+ set_structural: setStructural,
941
+ expect_state: stmt.expectState ? lowerScalar(stmt.expectState.value) : null
942
+ };
943
+ }
944
+ function lowerRemoval(stmt) {
945
+ return {
946
+ target: lowerElementRef(stmt.target),
947
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
948
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
949
+ expect_state: stmt.expectState ? lowerScalar(stmt.expectState.value) : null
950
+ };
951
+ }
952
+ function lowerPurge(stmt) {
953
+ if (stmt.confirm.parsed !== 'PURGE') {
954
+ throw invalidSyntax('PURGE must be confirmed with the exact literal "PURGE"', stmt.confirm.range);
955
+ }
956
+ return {
957
+ target: lowerElementRef(stmt.target),
958
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
959
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
960
+ reference_policy: stmt.referencePolicy
961
+ ? lowerScalar(stmt.referencePolicy)
962
+ : null,
963
+ confirm: 'PURGE'
964
+ };
965
+ }
966
+ // ---------------------------------------------------------------------------
967
+ // UPDATE
968
+ // ---------------------------------------------------------------------------
969
+ function lowerUpdate(stmt) {
970
+ if (stmt.actions.length === 0) {
971
+ throw invalidSyntax('UPDATE requires at least one SET or UNSET action', stmt.range);
972
+ }
973
+ const target = lowerElementRef(stmt.target);
974
+ const targetVar = 'Handle' in target ? target.Handle : null;
975
+ const kind = targetVar && stmt.where ? boundKindOf(targetVar, stmt.where.patterns) : null;
976
+ const actions = stmt.actions.map((action) => lowerUpdateAction(action, targetVar, kind));
977
+ return {
978
+ target,
979
+ expect_version: stmt.expectVersion
980
+ ? lowerScalar(stmt.expectVersion.value)
981
+ : null,
982
+ actions,
983
+ where_clauses: stmt.where ? lowerWhere(stmt.where) : null,
984
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null
985
+ };
986
+ }
987
+ function boundKindOf(variable, patterns) {
988
+ for (const pattern of patterns) {
989
+ switch (pattern.kind) {
990
+ case 'AssertionPattern':
991
+ if (varName(pattern.variable.name, pattern.variable.range) === variable) {
992
+ return 'assertion';
993
+ }
994
+ break;
995
+ case 'EvidencePattern':
996
+ if (varName(pattern.variable.name, pattern.variable.range) === variable) {
997
+ return 'evidence';
998
+ }
999
+ break;
1000
+ case 'ActivityPattern':
1001
+ if (varName(pattern.variable.name, pattern.variable.range) === variable) {
1002
+ return 'activity';
1003
+ }
1004
+ break;
1005
+ case 'ConceptPattern':
1006
+ if (varName(pattern.variable.name, pattern.variable.range) === variable) {
1007
+ return 'concept';
1008
+ }
1009
+ break;
1010
+ case 'PropositionPattern':
1011
+ if (pattern.variable &&
1012
+ varName(pattern.variable.name, pattern.variable.range) === variable) {
1013
+ return 'proposition';
1014
+ }
1015
+ break;
1016
+ case 'NotClause':
1017
+ case 'OptionalClause':
1018
+ case 'UnionClause': {
1019
+ const nested = boundKindOf(variable, pattern.patterns);
1020
+ if (nested)
1021
+ return nested;
1022
+ break;
1023
+ }
1024
+ }
1025
+ }
1026
+ return null;
1027
+ }
1028
+ function lowerUpdateAction(action, targetVar, kind) {
1029
+ switch (action.kind) {
1030
+ case 'SetFieldsClause': {
1031
+ const assignments = lowerAssignments(action.assignments, targetVar);
1032
+ for (const entry of action.assignments.entries) {
1033
+ guardImmutableField(entry.key, kind, entry.range);
1034
+ }
1035
+ return { SetFields: assignments };
1036
+ }
1037
+ case 'SetAttributesClause': {
1038
+ const assignments = lowerAssignments(action.assignments, targetVar);
1039
+ for (const entry of action.assignments.entries) {
1040
+ guardProtectedField(entry.key, entry.range);
1041
+ }
1042
+ return { SetAttributes: assignments };
1043
+ }
1044
+ case 'SetFacetClause':
1045
+ return { SetFacet: lowerFacet(action, targetVar) };
1046
+ case 'UnsetAttributesClause':
1047
+ return { UnsetAttributes: lowerUnsetFields(action.fields) };
1048
+ case 'UnsetFacetClause':
1049
+ return {
1050
+ UnsetFacet: {
1051
+ facet: lowerSymbol(action.facet),
1052
+ fields: lowerUnsetFields(action.fields)
1053
+ }
1054
+ };
1055
+ case 'SetStructuralClause':
1056
+ guardStructuralMutation('SET STRUCTURAL', kind, action.range);
1057
+ return { SetStructural: lowerStructural(action, targetVar) };
1058
+ case 'UnsetStructuralClause':
1059
+ guardStructuralMutation('UNSET STRUCTURAL', kind, action.range);
1060
+ return { UnsetStructural: lowerStructuralRemovals(action, targetVar) };
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Structural mutation reaches mutable Concept topology only (Spec §17.5).
1065
+ * Record kinds keep their topology: an Assertion's citations and an
1066
+ * Evidence's lineage are immutable payload, a Proposition has no structural
1067
+ * fields, and a pending Activity finalizes through TRANSITION ACTIVITY.
1068
+ */
1069
+ function guardStructuralMutation(verb, kind, range) {
1070
+ switch (kind) {
1071
+ case 'assertion':
1072
+ throw invalidSyntax(`${verb} cannot change an Assertion's citations: they are immutable payload — record a new Assertion with SUPERSEDING`, range);
1073
+ case 'evidence':
1074
+ throw invalidSyntax(`${verb} cannot change Evidence topology: correct it with CORRECT EVIDENCE :old BY :new`, range);
1075
+ case 'proposition':
1076
+ throw invalidSyntax(`${verb} has no target on a Proposition: a Proposition is its tuple and carries no structural fields`, range);
1077
+ case 'activity':
1078
+ throw invalidSyntax(`${verb} cannot change Activity topology: finalize a pending Activity with TRANSITION ACTIVITY ... SET STRUCTURAL; a terminal Activity is immutable`, range);
1079
+ default:
1080
+ return;
1081
+ }
1082
+ }
1083
+ /** Engine-owned state is never author-writable, whatever the element kind. */
1084
+ function guardProtectedField(field, range) {
1085
+ if (PROTECTED_FIELDS.has(field)) {
1086
+ throw invalidSyntax(`${field} is engine-maintained state and cannot be written by a mutation`, range);
1087
+ }
1088
+ }
1089
+ function guardImmutableField(field, kind, range) {
1090
+ guardProtectedField(field, range);
1091
+ if (kind === 'assertion' && ASSERTION_IMMUTABLE.has(field)) {
1092
+ throw invalidSyntax(`${field} is immutable Assertion payload: record the change as a new Assertion with SUPERSEDING, ` +
1093
+ 'never by rewriting the old one', range);
1094
+ }
1095
+ if (kind === 'evidence' && EVIDENCE_IMMUTABLE.has(field)) {
1096
+ throw invalidSyntax(`${field} is immutable Evidence payload: correct it with CORRECT EVIDENCE :old BY :new`, range);
1097
+ }
1098
+ if (kind === 'proposition' && PROPOSITION_IMMUTABLE.has(field)) {
1099
+ throw invalidSyntax(`${field} is part of the immutable Proposition tuple: a different tuple is a different Proposition`, range);
1100
+ }
1101
+ }
1102
+ // ---------------------------------------------------------------------------
1103
+ // Assignments, facets, structural edges
1104
+ // ---------------------------------------------------------------------------
1105
+ function lowerAssignments(object, targetVar) {
1106
+ const seen = new Set();
1107
+ const out = [];
1108
+ for (const entry of object.entries) {
1109
+ guardProtectedField(entry.key, entry.range);
1110
+ if (seen.has(entry.key)) {
1111
+ throw invalidSyntax(`duplicate assignment for ${entry.key}`, entry.range);
1112
+ }
1113
+ seen.add(entry.key);
1114
+ out.push([entry.key, lowerMutationValue(entry.value, targetVar)]);
1115
+ }
1116
+ return out;
1117
+ }
1118
+ function lowerFacet(clause, targetVar) {
1119
+ return {
1120
+ facet: lowerSymbol(clause.facet),
1121
+ values: lowerAssignments(clause.assignments, targetVar)
1122
+ };
1123
+ }
1124
+ function lowerStructural(clause, targetVar) {
1125
+ return clause.assignments.map((assignment) => ({
1126
+ field: lowerSymbol(assignment.field),
1127
+ value: lowerMutationValue(assignment.value, targetVar),
1128
+ options: assignment.options ? lowerBoundObject(assignment.options) : null
1129
+ }));
1130
+ }
1131
+ function lowerStructuralRemovals(clause, targetVar) {
1132
+ if (clause.removals.length === 0) {
1133
+ throw invalidSyntax('UNSET STRUCTURAL removes named references; list at least one (field, target)', clause.range);
1134
+ }
1135
+ return clause.removals.map((removal) => ({
1136
+ field: lowerSymbol(removal.field),
1137
+ value: lowerMutationValue(removal.value, targetVar)
1138
+ }));
1139
+ }
1140
+ function lowerUnsetFields(fields) {
1141
+ const seen = new Set();
1142
+ for (const field of fields) {
1143
+ if (seen.has(field.name)) {
1144
+ throw invalidSyntax(`duplicate field ${field.name}`, field.range);
1145
+ }
1146
+ guardProtectedField(field.name, field.range);
1147
+ seen.add(field.name);
1148
+ }
1149
+ return [...seen];
1150
+ }
1151
+ function lowerMutationValue(expr, targetVar) {
1152
+ if (expr.kind === 'FunctionCallExpr') {
1153
+ return { Expr: lowerUpdateExpr(expr, targetVar) };
1154
+ }
1155
+ if (expr.kind === 'AggregateExpr') {
1156
+ throw invalidSyntax(`${expr.name} is an aggregate and cannot appear in an assignment`, expr.range);
1157
+ }
1158
+ return lowerBoundValue(expr, targetVar);
1159
+ }
1160
+ /**
1161
+ * Lowers a `data_value`, keeping structure only where something still needs
1162
+ * binding. A wholly literal subtree collapses to one `Value`, so an engine
1163
+ * that has nothing to substitute never walks a binding tree.
1164
+ */
1165
+ function lowerBoundValue(expr, targetVar) {
1166
+ switch (expr.kind) {
1167
+ case 'ParameterRef':
1168
+ return { Param: paramName(expr.name) };
1169
+ case 'VariableRef':
1170
+ return { Handle: varName(expr.name, expr.range) };
1171
+ case 'FieldAccess': {
1172
+ const path = lowerDotPath(expr);
1173
+ guardOwnField(path, targetVar, expr.range);
1174
+ return { Variable: path };
1175
+ }
1176
+ case 'ArrayLiteral':
1177
+ return isFullyLiteral(expr)
1178
+ ? { Value: lowerKipValue(expr) }
1179
+ : { Array: expr.elements.map((e) => lowerBoundValue(e, targetVar)) };
1180
+ case 'ObjectLiteral':
1181
+ return isFullyLiteral(expr)
1182
+ ? { Value: lowerKipValue(expr) }
1183
+ : {
1184
+ Object: expr.entries.map((e) => [e.key, lowerBoundValue(e.value, targetVar)])
1185
+ };
1186
+ default:
1187
+ return { Value: lowerKipValue(expr) };
1188
+ }
1189
+ }
1190
+ /** True when nothing in the subtree needs binding at execution time. */
1191
+ function isFullyLiteral(expr) {
1192
+ switch (expr.kind) {
1193
+ case 'StringLiteral':
1194
+ case 'NumberLiteral':
1195
+ case 'BooleanLiteral':
1196
+ case 'NullLiteral':
1197
+ return true;
1198
+ case 'ArrayLiteral':
1199
+ return expr.elements.every(isFullyLiteral);
1200
+ case 'ObjectLiteral':
1201
+ return expr.entries.every((e) => isFullyLiteral(e.value));
1202
+ case 'UnaryExpression':
1203
+ return expr.operator === '-' && isFullyLiteral(expr.operand);
1204
+ default:
1205
+ return false;
1206
+ }
1207
+ }
1208
+ function lowerUpdateExpr(expr, targetVar) {
1209
+ switch (expr.kind) {
1210
+ case 'FunctionCallExpr': {
1211
+ const func = UPDATE_FUNCTIONS.get(expr.name.toUpperCase());
1212
+ if (!func) {
1213
+ throw invalidSyntax(`${expr.name} is not a KIP update function; expected ADD, MUL, CLAMP or COALESCE`, expr.range);
1214
+ }
1215
+ const arity = UPDATE_ARITY[func];
1216
+ if (expr.args.length !== arity) {
1217
+ throw invalidSyntax(`${expr.name} takes ${arity} arguments, found ${expr.args.length}`, expr.range);
1218
+ }
1219
+ return {
1220
+ Function: {
1221
+ func,
1222
+ args: expr.args.map((arg) => lowerUpdateExpr(arg, targetVar))
1223
+ }
1224
+ };
1225
+ }
1226
+ case 'ParameterRef':
1227
+ return { Param: paramName(expr.name) };
1228
+ case 'NumberLiteral':
1229
+ return { Number: expr.value };
1230
+ case 'UnaryExpression':
1231
+ if (expr.operator === '-' && expr.operand.kind === 'NumberLiteral') {
1232
+ return { Number: -expr.operand.value };
1233
+ }
1234
+ throw invalidSyntax(`expected a number, a parameter, the target's own field or a registered function, found ${describeExpression(expr)}`, expr.range);
1235
+ case 'VariableRef':
1236
+ case 'FieldAccess': {
1237
+ const path = lowerDotPath(expr);
1238
+ guardOwnField(path, targetVar, expr.range);
1239
+ return { Variable: path };
1240
+ }
1241
+ default:
1242
+ throw invalidSyntax(`expected a number, a parameter, the target's own field or a registered function, found ${describeExpression(expr)}`, expr.range);
1243
+ }
1244
+ }
1245
+ /**
1246
+ * An update expression may read only the element being updated.
1247
+ *
1248
+ * Reading another variable would make the result depend on a join the
1249
+ * statement never declared, so each matched element must be computable from
1250
+ * its own row.
1251
+ */
1252
+ function guardOwnField(path, targetVar, range) {
1253
+ if (targetVar !== null && path.var !== targetVar) {
1254
+ throw invalidSyntax(`an update expression may read only the target ?${targetVar}, found ?${path.var}`, range);
1255
+ }
1256
+ }
1257
+ // ---------------------------------------------------------------------------
1258
+ // META
1259
+ // ---------------------------------------------------------------------------
1260
+ function lowerMeta(stmt) {
1261
+ switch (stmt.kind) {
1262
+ case 'DescribeStatement':
1263
+ return { Describe: lowerDescribe(stmt) };
1264
+ case 'ListStatement':
1265
+ return { List: lowerList(stmt) };
1266
+ case 'SearchStatement':
1267
+ return { Search: lowerSearch(stmt) };
1268
+ case 'VerifyStatement':
1269
+ return {
1270
+ Verify: {
1271
+ target: VERIFY_TARGETS[stmt.target],
1272
+ value: lowerScalar(stmt.value)
1273
+ }
1274
+ };
1275
+ case 'ValidateStatement':
1276
+ return {
1277
+ Validate: {
1278
+ target: VALIDATE_TARGETS[stmt.target],
1279
+ value: lowerScalar(stmt.value),
1280
+ options: stmt.options ? lowerBoundObject(stmt.options) : null
1281
+ }
1282
+ };
1283
+ case 'PreviewStatement':
1284
+ return {
1285
+ Preview: stmt.target === 'KML'
1286
+ ? { Kml: lowerScalar(stmt.value) }
1287
+ : {
1288
+ ImportCapsule: {
1289
+ capsule: lowerScalar(stmt.value),
1290
+ into: lowerScalar(stmt.into)
1291
+ }
1292
+ }
1293
+ };
1294
+ case 'HistoryStatement':
1295
+ return { History: lowerHistory(stmt) };
1296
+ case 'ChangesStatement':
1297
+ return {
1298
+ Changes: stmt.mode === 'SINCE'
1299
+ ? {
1300
+ Since: {
1301
+ cursor: lowerScalar(stmt.value),
1302
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null
1303
+ }
1304
+ }
1305
+ : {
1306
+ AfterSeq: {
1307
+ seq: lowerScalar(stmt.value),
1308
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null
1309
+ }
1310
+ }
1311
+ };
1312
+ case 'SnapshotStatement':
1313
+ return { Snapshot: { as_of: stmt.asOf ? lowerAsOf(stmt.asOf) : null } };
1314
+ case 'ExportCapsuleStatement':
1315
+ return { ExportCapsule: lowerExport(stmt) };
1316
+ default:
1317
+ throw invalidSyntax(`${stmt.kind} is not an executable KIP command`, stmt.range);
1318
+ }
1319
+ }
1320
+ const VERIFY_TARGETS = {
1321
+ CAPSULE: 'Capsule',
1322
+ SCHEMA_PACKAGE: 'SchemaPackage',
1323
+ RECEIPT: 'Receipt',
1324
+ BLOB: 'Blob',
1325
+ CHECKPOINT: 'Checkpoint'
1326
+ };
1327
+ const VALIDATE_TARGETS = {
1328
+ KQL: 'Kql',
1329
+ KML: 'Kml',
1330
+ CAPSULE: 'Capsule',
1331
+ SCHEMA_PACKAGE: 'SchemaPackage',
1332
+ IMPORT_PLAN: 'ImportPlan'
1333
+ };
1334
+ const LIST_TARGETS = {
1335
+ SPACES: 'Spaces',
1336
+ SCHEMA_PACKAGES: 'SchemaPackages',
1337
+ TYPES: 'Types',
1338
+ PREDICATES: 'Predicates',
1339
+ FACETS: 'Facets',
1340
+ STRUCTURAL_FIELDS: 'StructuralFields',
1341
+ EPISTEMIC_POLICIES: 'EpistemicPolicies'
1342
+ };
1343
+ const SEARCH_TARGETS = {
1344
+ CONCEPT: 'Concept',
1345
+ PROPOSITION: 'Proposition',
1346
+ ASSERTION: 'Assertion',
1347
+ EVIDENCE: 'Evidence',
1348
+ ACTIVITY: 'Activity',
1349
+ COGNITION: 'Cognition'
1350
+ };
1351
+ function lowerDescribe(stmt) {
1352
+ const value = () => {
1353
+ if (!stmt.value) {
1354
+ throw invalidSyntax(`DESCRIBE ${stmt.target} requires an operand`, stmt.range);
1355
+ }
1356
+ return lowerScalar(stmt.value);
1357
+ };
1358
+ switch (stmt.target) {
1359
+ case 'PRIMER':
1360
+ return { Primer: { mode: stmt.mode ? lowerScalar(stmt.mode) : null } };
1361
+ case 'PROTOCOL':
1362
+ return 'Protocol';
1363
+ case 'EXECUTION_CONTEXT':
1364
+ return 'ExecutionContext';
1365
+ case 'CAPABILITIES':
1366
+ return 'Capabilities';
1367
+ case 'SPACE':
1368
+ return { Space: { value: stmt.value ? lowerScalar(stmt.value) : null } };
1369
+ case 'SCHEMA_ENVIRONMENT':
1370
+ return {
1371
+ SchemaEnvironment: { as_of: stmt.asOf ? lowerAsOf(stmt.asOf) : null }
1372
+ };
1373
+ case 'PACKAGE':
1374
+ return { Package: value() };
1375
+ case 'TYPE':
1376
+ return { Type: value() };
1377
+ case 'PREDICATE':
1378
+ return { Predicate: value() };
1379
+ case 'FACET':
1380
+ return { Facet: value() };
1381
+ case 'STRUCTURAL_FIELD':
1382
+ return { StructuralField: value() };
1383
+ case 'COMPATIBILITY':
1384
+ if (!stmt.from || !stmt.to) {
1385
+ throw invalidSyntax('DESCRIBE COMPATIBILITY requires FROM and TO', stmt.range);
1386
+ }
1387
+ return {
1388
+ Compatibility: { from: lowerScalar(stmt.from), to: lowerScalar(stmt.to) }
1389
+ };
1390
+ case 'ERROR':
1391
+ return { Error: value() };
1392
+ case 'TRANSACTION':
1393
+ return { Transaction: value() };
1394
+ case 'TRANSACTION_BY_IDEMPOTENCY_KEY':
1395
+ return { TransactionByIdempotencyKey: value() };
1396
+ case 'SNAPSHOT':
1397
+ return { Snapshot: { as_of: stmt.asOf ? lowerAsOf(stmt.asOf) : null } };
1398
+ case 'CAPSULE':
1399
+ return { Capsule: value() };
1400
+ case 'EPISTEMIC_POLICY':
1401
+ return {
1402
+ EpistemicPolicy: { value: stmt.value ? lowerScalar(stmt.value) : null }
1403
+ };
1404
+ case 'PROJECTION_CAPABILITY':
1405
+ return 'ProjectionCapability';
1406
+ case 'TRUST':
1407
+ return { Trust: { value: stmt.value ? lowerScalar(stmt.value) : null } };
1408
+ case 'ACCESS':
1409
+ return {
1410
+ Access: { with: stmt.with ? lowerBoundObject(stmt.with) : null }
1411
+ };
1412
+ }
1413
+ }
1414
+ function lowerList(stmt) {
1415
+ return {
1416
+ target: LIST_TARGETS[stmt.target],
1417
+ status: stmt.status ? lowerScalar(stmt.status) : null,
1418
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
1419
+ cursor: stmt.cursor ? lowerScalar(stmt.cursor.value) : null
1420
+ };
1421
+ }
1422
+ function lowerSearch(stmt) {
1423
+ return {
1424
+ target: SEARCH_TARGETS[stmt.searchKind],
1425
+ term: lowerScalar(stmt.term),
1426
+ with_type: stmt.withType ? lowerScalar(stmt.withType) : null,
1427
+ with_predicate: stmt.withPredicate ? lowerScalar(stmt.withPredicate) : null,
1428
+ mode: stmt.mode ? lowerScalar(stmt.mode) : null,
1429
+ threshold: stmt.threshold ? lowerScalar(stmt.threshold) : null,
1430
+ as_of_seq: stmt.asOfSeq ? lowerScalar(stmt.asOfSeq) : null,
1431
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
1432
+ cursor: stmt.cursor ? lowerScalar(stmt.cursor.value) : null
1433
+ };
1434
+ }
1435
+ function lowerHistory(stmt) {
1436
+ const paging = {
1437
+ from_seq: stmt.fromSeq ? lowerScalar(stmt.fromSeq) : null,
1438
+ to_seq: stmt.toSeq ? lowerScalar(stmt.toSeq) : null,
1439
+ limit: stmt.limit ? lowerScalar(stmt.limit.value) : null,
1440
+ cursor: stmt.cursor ? lowerScalar(stmt.cursor.value) : null
1441
+ };
1442
+ if (stmt.target === 'SPACE') {
1443
+ return { Space: paging };
1444
+ }
1445
+ if (!stmt.value) {
1446
+ throw invalidSyntax('HISTORY ELEMENT requires an element id', stmt.range);
1447
+ }
1448
+ return { Element: { value: lowerScalar(stmt.value), ...paging } };
1449
+ }
1450
+ function lowerExport(stmt) {
1451
+ return {
1452
+ target: lowerElementRef(stmt.target),
1453
+ where_clauses: lowerWhere(stmt.where),
1454
+ options: stmt.options ? lowerBoundObject(stmt.options) : null,
1455
+ as_of: stmt.asOf ? lowerAsOf(stmt.asOf) : null
1456
+ };
1457
+ }
1458
+ // ---------------------------------------------------------------------------
1459
+ // Leaf conversions
1460
+ // ---------------------------------------------------------------------------
1461
+ function lowerScalar(value) {
1462
+ if (value.kind === 'ParameterRef') {
1463
+ return { Param: paramName(value.name) };
1464
+ }
1465
+ return { Literal: lowerKipValue(value) };
1466
+ }
1467
+ /** An ASSERT member used where the grammar needs a scalar, e.g. `key:`. */
1468
+ function lowerScalarExpression(expr) {
1469
+ if (expr.kind === 'ParameterRef') {
1470
+ return { Param: paramName(expr.name) };
1471
+ }
1472
+ if (expr.kind === 'StringLiteral' ||
1473
+ expr.kind === 'NumberLiteral' ||
1474
+ expr.kind === 'BooleanLiteral' ||
1475
+ expr.kind === 'NullLiteral') {
1476
+ return { Literal: lowerKipValue(expr) };
1477
+ }
1478
+ throw invalidSyntax(`expected a literal or :parameter, found ${describeExpression(expr)}`, expr.range);
1479
+ }
1480
+ function lowerSymbol(symbol) {
1481
+ return symbol.kind === 'ParameterRef'
1482
+ ? { Param: paramName(symbol.name) }
1483
+ : { Name: symbol.parsed };
1484
+ }
1485
+ function lowerElementRef(ref) {
1486
+ switch (ref.kind) {
1487
+ case 'VariableRef':
1488
+ return { Handle: varName(ref.name, ref.range) };
1489
+ case 'ParameterRef':
1490
+ return { Param: paramName(ref.name) };
1491
+ case 'StringLiteral':
1492
+ return { Id: ref.parsed };
1493
+ }
1494
+ }
1495
+ function lowerKipValue(expr) {
1496
+ switch (expr.kind) {
1497
+ case 'StringLiteral':
1498
+ return { String: expr.parsed };
1499
+ case 'NumberLiteral':
1500
+ if (!Number.isFinite(expr.value)) {
1501
+ throw invalidSyntax(`only finite numbers are valid KIP literals, found ${expr.raw}`, expr.range);
1502
+ }
1503
+ return { Number: expr.value };
1504
+ case 'BooleanLiteral':
1505
+ return { Bool: expr.value };
1506
+ case 'NullLiteral':
1507
+ return 'Null';
1508
+ case 'ArrayLiteral':
1509
+ return { Array: expr.elements.map(lowerKipValue) };
1510
+ case 'ObjectLiteral':
1511
+ case 'ObjectPattern': {
1512
+ const entries = expr.kind === 'ObjectLiteral' ? expr.entries : expr.members;
1513
+ const out = {};
1514
+ for (const entry of entries) {
1515
+ out[entry.key] = lowerKipValue(entry.value);
1516
+ }
1517
+ return { Object: out };
1518
+ }
1519
+ case 'UnaryExpression':
1520
+ if (expr.operator === '-' && expr.operand.kind === 'NumberLiteral') {
1521
+ return { Number: -expr.operand.value };
1522
+ }
1523
+ throw invalidSyntax(`expected a value, found ${describeExpression(expr)}`, expr.range);
1524
+ default:
1525
+ throw invalidSyntax(`expected a value, found ${describeExpression(expr)}`, expr.range);
1526
+ }
1527
+ }
1528
+ /**
1529
+ * Option and epistemic blocks are `data_value`s, not plain JSON: the grammar
1530
+ * lets a parameter stand anywhere inside them.
1531
+ */
1532
+ function lowerBoundObject(object) {
1533
+ const out = {};
1534
+ for (const entry of object.entries) {
1535
+ out[entry.key] = lowerBoundValue(entry.value, null);
1536
+ }
1537
+ return out;
1538
+ }
1539
+ /** Strips the `?` sigil; the executable form carries bare names. */
1540
+ function varName(name, range) {
1541
+ if (!name.startsWith('?')) {
1542
+ throw invalidSyntax(`expected a variable, found ${name}`, range);
1543
+ }
1544
+ return name.slice(1);
1545
+ }
1546
+ /** Strips the `:` sigil; the executable form carries bare names. */
1547
+ function paramName(name) {
1548
+ return name.startsWith(':') ? name.slice(1) : name;
1549
+ }
1550
+ function describeExpression(expr) {
1551
+ switch (expr.kind) {
1552
+ case 'ParameterRef':
1553
+ return `the parameter ${expr.name}`;
1554
+ case 'VariableRef':
1555
+ return `the variable ${expr.name}`;
1556
+ case 'FunctionCallExpr':
1557
+ return `a call to ${expr.name}`;
1558
+ case 'AggregateExpr':
1559
+ return `the aggregate ${expr.name}`;
1560
+ case 'BinaryExpression':
1561
+ return `the operator ${expr.operator}`;
1562
+ default:
1563
+ return expr.kind;
1564
+ }
1565
+ }
1566
+ //# sourceMappingURL=lower.js.map