@astrale-os/sdk 0.5.0-beta.65 → 0.5.0-beta.66

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0-beta.66](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.65...sdk-v0.5.0-beta.66) (2026-08-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **state:** use machine transition authority ([#301](https://github.com/astrale-os/sdk/issues/301)) ([6424217](https://github.com/astrale-os/sdk/commit/642421784d391863d33e90e85c0a1c11b1a6ed7b))
9
+
3
10
  ## [0.5.0-beta.65](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.64...sdk-v0.5.0-beta.65) (2026-08-27)
4
11
 
5
12
 
@@ -29,8 +29,8 @@ export interface RichMutationBuilder {
29
29
  readonly class: Class;
30
30
  readonly props: RichPropertyDelta<Class>;
31
31
  }): this;
32
- transition<Machine extends StateMachine, Class extends ResolvedClass<'node'>, Property extends LifecyclePropertyName<Class, Machine>>(input: {
33
- readonly lifecycle: Machine;
32
+ transition<Machine extends StateMachine, Class extends ResolvedClass<'node'>, Property extends StateMachinePropertyName<Class, Machine>>(input: {
33
+ readonly machine: Machine;
34
34
  readonly node: ExistingEndpoint;
35
35
  readonly class: Class;
36
36
  readonly property: Property;
@@ -60,8 +60,8 @@ export interface EdgeSelector<Class extends ResolvedClass<'edge'>> {
60
60
  readonly class: Class;
61
61
  }
62
62
  export declare function richMutationBuilder(core: MutationBuilder): RichMutationBuilder;
63
- type LifecyclePropertyName<Class extends ResolvedClass<'node'>, Machine extends StateMachine> = {
64
- [Name in RichPropertyName<Class>]: IsExact<Exclude<RichPropertyInput<Class>[Name], undefined>, StateOf<Machine>> extends true ? Name : never;
63
+ type StateMachinePropertyName<Class extends ResolvedClass<'node'>, Machine extends StateMachine> = {
64
+ [Name in RichPropertyName<Class>]: string extends StateOf<Machine> ? never : IsExact<Exclude<RichPropertyInput<Class>[Name], undefined>, StateOf<Machine>> extends true ? Name : never;
65
65
  }[RichPropertyName<Class>];
66
66
  type AllowedTransitionDecision<Machine extends StateMachine> = Readonly<{
67
67
  kind: 'allowed';
@@ -48,19 +48,30 @@ export function richMutationBuilder(core) {
48
48
  },
49
49
  transition(input) {
50
50
  const submitted = input.decision;
51
- if (submitted.kind !== 'allowed' ||
52
- submitted.transition === undefined ||
53
- submitted.transition === null ||
54
- typeof submitted.transition !== 'object' ||
55
- typeof submitted.transition.from !== 'string' ||
56
- typeof submitted.transition.event !== 'string' ||
57
- typeof submitted.transition.to !== 'string') {
58
- throw new TypeError('Lifecycle transition decision must be allowed.');
51
+ const kind = submitted.kind;
52
+ const rawTransition = submitted.transition;
53
+ if (kind !== 'allowed' ||
54
+ rawTransition === undefined ||
55
+ rawTransition === null ||
56
+ typeof rawTransition !== 'object') {
57
+ throw new TypeError('StateMachine transition decision must be allowed.');
59
58
  }
60
- const transition = submitted.transition;
59
+ const fromState = rawTransition.from;
60
+ const event = rawTransition.event;
61
+ const toState = rawTransition.to;
62
+ if (typeof fromState !== 'string' ||
63
+ typeof event !== 'string' ||
64
+ typeof toState !== 'string') {
65
+ throw new TypeError('StateMachine transition decision must be allowed.');
66
+ }
67
+ const transition = Object.freeze({
68
+ from: fromState,
69
+ event: event,
70
+ to: toState,
71
+ });
61
72
  let decision;
62
73
  try {
63
- decision = input.lifecycle.decide(transition.from, transition.event);
74
+ decision = input.machine.decide(transition.from, transition.event);
64
75
  }
65
76
  catch {
66
77
  invalidTransition(transition);
@@ -71,7 +82,7 @@ export function richMutationBuilder(core) {
71
82
  const selected = resolvedProperty(input.class, input.property);
72
83
  for (const state of [transition.from, transition.to]) {
73
84
  if (selected.validate(state).length > 0) {
74
- throw new TypeError(`Lifecycle property ${String(input.property)} does not admit state ${state}.`);
85
+ throw new TypeError(`StateMachine-backed property ${String(input.property)} does not admit state ${state}.`);
75
86
  }
76
87
  }
77
88
  const from = { [input.property]: transition.from };
@@ -81,7 +92,7 @@ export function richMutationBuilder(core) {
81
92
  Object.hasOwn(suppliedEquals, input.property) &&
82
93
  suppliedEquals[input.property] !== transition.from) ||
83
94
  input.props?.absent?.includes(input.property) === true) {
84
- throw new TypeError(`Lifecycle transition conditions conflict with property ${String(input.property)}.`);
95
+ throw new TypeError(`StateMachine transition conditions conflict with property ${String(input.property)}.`);
85
96
  }
86
97
  core.expect.node({
87
98
  node: existingEndpoint(input.node),
@@ -148,5 +159,5 @@ function classKey(selected) {
148
159
  return ClassKey(selected.key);
149
160
  }
150
161
  function invalidTransition(transition) {
151
- throw new TypeError(`Lifecycle transition witness is not legal: ${transition.from} + ${transition.event} -> ${transition.to}.`);
162
+ throw new TypeError(`StateMachine transition decision is not legal: ${transition.from} + ${transition.event} -> ${transition.to}.`);
152
163
  }
@@ -1,16 +1,16 @@
1
1
  import ts from 'typescript';
2
2
  import { importedSymbol, propertyChain, staticText, unwrap, visit, } from '../../adapters/typescript/index.js';
3
- import { lifecycleIdentity, stateSchemaIdentity } from './lifecycle.js';
4
3
  import { ambiguity, callback, DSL_MODULES, definitionOriginAmbiguity, definitionObjects, domainProjectorEvidence, forbiddenIoImport, hasForbiddenGlobalCall, production, violation, } from './shared.js';
4
+ import { stateMachineIdentity, stateSchemaIdentity } from './state-machine.js';
5
5
  const FORBIDDEN_LAYERS = new Set(['integrations', 'actions', 'providers', 'queries', 'workflows']);
6
6
  const FORBIDDEN_CALLS = new Set(['invoke', 'query', 'retry', 'run', 'submit']);
7
7
  export const mutationRules = [
8
8
  {
9
9
  id: 'MUT-STATE-INITIAL',
10
- ruleRevision: '0a28cb9b7aad0258e6b5b19962f420a81d04e5a7443d37c1e8182e98fde39d60',
10
+ ruleRevision: 'd88f178994e9ad56d14f50ad5ffdff398413e42a690985c5ddc669c0f293d9bb',
11
11
  evaluate(project) {
12
12
  const evidence = [];
13
- const lifecycleProperties = lifecyclePropertiesByClass(project);
13
+ const machineProperties = machinePropertiesByClass(project);
14
14
  for (const file of production(project, 'mutations')) {
15
15
  const builderScopes = mutationBuilderScopes(file);
16
16
  visit(file.source, (node) => {
@@ -20,12 +20,12 @@ export const mutationRules = [
20
20
  }
21
21
  const input = staticObject(node.arguments[0]);
22
22
  if (!input) {
23
- if (hasLifecycleCandidates(lifecycleProperties)) {
24
- evidence.push(ambiguity(file, node, 'Mutation createNode input is dynamic, so lifecycle initialization cannot be proven.'));
23
+ if (hasMachineCandidates(machineProperties)) {
24
+ evidence.push(ambiguity(file, node, 'Mutation createNode input is dynamic, so machine-state initialization cannot be proven.'));
25
25
  }
26
26
  return;
27
27
  }
28
- const selectedClass = propertiesForClass(input, lifecycleProperties);
28
+ const selectedClass = propertiesForClass(input, machineProperties);
29
29
  if (selectedClass.kind === 'ambiguous') {
30
30
  evidence.push(ambiguity(file, assignedValue(input, 'class') ?? input, 'Mutation createNode Class has no statically resolvable Schema owner.'));
31
31
  return;
@@ -37,39 +37,40 @@ export const mutationRules = [
37
37
  const props = staticObject(propsValue);
38
38
  if (!props) {
39
39
  evidence.push(propsValue === undefined
40
- ? violation(file, input, 'Mutation creates a lifecycle-backed node without its initial state.')
41
- : ambiguity(file, propsValue, 'Mutation createNode properties are dynamic, so lifecycle initialization cannot be proven.'));
40
+ ? violation(file, input, 'Mutation creates a StateMachine-backed node without its initial state.')
41
+ : ambiguity(file, propsValue, 'Mutation createNode properties are dynamic, so machine-state initialization cannot be proven.'));
42
42
  return;
43
43
  }
44
- const assignments = lifecycleAssignments(props, selected);
44
+ const assignments = machineAssignments(props, selected);
45
45
  const uncertain = [...selected.keys()].filter((name) => spreadMaySet(props, name));
46
46
  if (uncertain.length > 0) {
47
- evidence.push(ambiguity(file, props, `Mutation createNode properties contain a spread that may determine lifecycle ${uncertain.join(', ')}.`));
47
+ evidence.push(ambiguity(file, props, `Mutation createNode properties contain a spread that may determine machine-backed ${uncertain.join(', ')}.`));
48
48
  }
49
49
  for (const name of selected.keys()) {
50
50
  if (assignments.some((property) => staticPropertyName(property) === name))
51
51
  continue;
52
52
  if (uncertain.includes(name))
53
53
  continue;
54
- evidence.push(violation(file, props, `Mutation creates a lifecycle-backed node without initial property ${name}.`));
54
+ evidence.push(violation(file, props, `Mutation creates a StateMachine-backed node without initial property ${name}.`));
55
55
  }
56
56
  for (const property of assignments) {
57
57
  const name = staticPropertyName(property) ?? '<dynamic>';
58
58
  if (uncertain.includes(name))
59
59
  continue;
60
60
  const expectedIdentity = selected.get(name);
61
- const initial = initialIdentity(project, file, property.initializer);
61
+ const value = assignedPropertyValue(property);
62
+ const initial = initialIdentity(project, file, value);
62
63
  if (initial.kind === 'resolved' && initial.identity === expectedIdentity)
63
64
  continue;
64
65
  if (initial.kind === 'resolved') {
65
- evidence.push(violation(file, property.initializer, `Mutation initializes lifecycle property ${name} from a different StateMachine authority.`));
66
+ evidence.push(violation(file, value, `Mutation initializes machine-backed property ${name} from a different StateMachine authority.`));
66
67
  continue;
67
68
  }
68
- if (staticText(property.initializer) !== undefined) {
69
- evidence.push(violation(file, property.initializer, `Mutation initializes lifecycle property ${name} with a copied state instead of machine.initial.`));
69
+ if (staticText(value) !== undefined) {
70
+ evidence.push(violation(file, value, `Mutation initializes machine-backed property ${name} with a copied state instead of machine.initial.`));
70
71
  }
71
72
  else {
72
- evidence.push(ambiguity(file, property.initializer, `Mutation lifecycle initializer for ${name} has no resolvable machine.initial source.`));
73
+ evidence.push(ambiguity(file, value, `Mutation StateMachine initializer for ${name} has no resolvable machine.initial source.`));
73
74
  }
74
75
  }
75
76
  });
@@ -79,10 +80,10 @@ export const mutationRules = [
79
80
  },
80
81
  {
81
82
  id: 'MUT-STATE-ATOMIC',
82
- ruleRevision: '5cbcc25bbc294f48136dda89ae01d5aff62f98d76c92a8f38ecb51d75621bed1',
83
+ ruleRevision: '0df23be62b6455c45e8cd8a1082c646ab13a786b773326e4276943e017e711fb',
83
84
  evaluate(project) {
84
85
  const evidence = [];
85
- const lifecycleProperties = lifecyclePropertiesByClass(project);
86
+ const machineProperties = machinePropertiesByClass(project);
86
87
  for (const file of production(project, 'mutations')) {
87
88
  const builderScopes = mutationBuilderScopes(file);
88
89
  visit(file.source, (node) => {
@@ -92,14 +93,14 @@ export const mutationRules = [
92
93
  if (member === undefined)
93
94
  return;
94
95
  if (member === 'updateNode') {
95
- if (!hasLifecycleCandidates(lifecycleProperties))
96
+ if (!hasMachineCandidates(machineProperties))
96
97
  return;
97
98
  const input = staticObject(node.arguments[0]);
98
99
  if (!input) {
99
- evidence.push(ambiguity(file, node, 'Mutation updateNode input is dynamic and may bypass a lifecycle transition.'));
100
+ evidence.push(ambiguity(file, node, 'Mutation updateNode input is dynamic and may bypass a StateMachine transition.'));
100
101
  return;
101
102
  }
102
- const selectedClass = propertiesForClass(input, lifecycleProperties);
103
+ const selectedClass = propertiesForClass(input, machineProperties);
103
104
  if (selectedClass.kind === 'ambiguous') {
104
105
  evidence.push(ambiguity(file, assignedValue(input, 'class') ?? input, 'Mutation updateNode Class has no statically resolvable Schema owner.'));
105
106
  return;
@@ -110,84 +111,90 @@ export const mutationRules = [
110
111
  const propsValue = assignedValue(input, 'props');
111
112
  const props = staticObject(propsValue);
112
113
  if (!props) {
113
- evidence.push(ambiguity(file, propsValue ?? input, 'Mutation updateNode properties are dynamic and may bypass a lifecycle transition.'));
114
+ evidence.push(ambiguity(file, propsValue ?? input, 'Mutation updateNode properties are dynamic and may bypass a StateMachine transition.'));
114
115
  return;
115
116
  }
116
117
  if (hasSpread(props)) {
117
- evidence.push(ambiguity(file, props, 'Mutation updateNode properties contain a spread that may bypass a lifecycle transition.'));
118
+ evidence.push(ambiguity(file, props, 'Mutation updateNode properties contain a spread that may bypass a StateMachine transition.'));
118
119
  }
119
120
  const setValue = assignedValue(props, 'set');
120
121
  const set = staticObject(setValue);
121
122
  if (setValue !== undefined && !set) {
122
- evidence.push(ambiguity(file, setValue, 'Mutation updateNode set patch is dynamic and may bypass a lifecycle transition.'));
123
+ evidence.push(ambiguity(file, setValue, 'Mutation updateNode set patch is dynamic and may bypass a StateMachine transition.'));
123
124
  }
124
125
  if (set && hasSpread(set)) {
125
- evidence.push(ambiguity(file, set, 'Mutation updateNode set patch contains a spread that may bypass a lifecycle transition.'));
126
+ evidence.push(ambiguity(file, set, 'Mutation updateNode set patch contains a spread that may bypass a StateMachine transition.'));
126
127
  }
127
- for (const property of set ? lifecycleAssignments(set, selected) : []) {
128
- evidence.push(violation(file, property, `Mutation writes lifecycle property ${String(staticPropertyName(property))} through updateNode; use transition for one atomic compare-and-set.`));
128
+ for (const property of set ? machineAssignments(set, selected) : []) {
129
+ evidence.push(violation(file, property, `Mutation writes machine-backed property ${String(staticPropertyName(property))} through updateNode; use transition for one atomic compare-and-set.`));
129
130
  }
130
131
  const unsetValue = assignedValue(props, 'unset');
131
132
  const unset = unsetValue && staticStringArray(unsetValue);
132
133
  if (unsetValue !== undefined && unset === undefined) {
133
- evidence.push(ambiguity(file, unsetValue, 'Mutation updateNode unset patch is dynamic and may bypass a lifecycle transition.'));
134
+ evidence.push(ambiguity(file, unsetValue, 'Mutation updateNode unset patch is dynamic and may bypass a StateMachine transition.'));
134
135
  }
135
136
  else {
136
137
  for (const property of unset ?? []) {
137
138
  if (!selected.has(property.value))
138
139
  continue;
139
- evidence.push(violation(file, property.node, `Mutation unsets lifecycle property ${property.value} through updateNode; lifecycle changes require transition.`));
140
+ evidence.push(violation(file, property.node, `Mutation unsets machine-backed property ${property.value} through updateNode; StateMachine changes require transition.`));
140
141
  }
141
142
  }
142
143
  }
143
144
  if (member !== 'transition')
144
145
  return;
145
- if (!hasLifecycleCandidates(lifecycleProperties))
146
+ if (!hasMachineCandidates(machineProperties))
146
147
  return;
147
148
  const input = staticObject(node.arguments[0]);
148
149
  if (!input) {
149
- evidence.push(ambiguity(file, node, 'Lifecycle transition input is not one static object.'));
150
+ evidence.push(ambiguity(file, node, 'StateMachine transition input is not one static object.'));
150
151
  return;
151
152
  }
152
153
  if (assignedValue(input, 'to') !== undefined ||
153
154
  assignedValue(input, 'event') !== undefined ||
154
155
  assignedValue(input, 'transition') !== undefined ||
155
156
  assignedValue(input, 'witness') !== undefined) {
156
- evidence.push(violation(file, input, 'Lifecycle transition accepts one allowed decision, not a target, event, transition, or witness field.'));
157
+ evidence.push(violation(file, input, 'StateMachine transition accepts one allowed decision, not a target, event, transition, or witness field.'));
157
158
  }
158
159
  const spread = hasSpread(input);
159
160
  if (spread) {
160
- evidence.push(ambiguity(file, input, 'Lifecycle transition input contains a spread that may override or add governed fields.'));
161
+ evidence.push(ambiguity(file, input, 'StateMachine transition input contains a spread that may override or add governed fields.'));
161
162
  }
162
163
  if (assignedValue(input, 'decision') === undefined) {
163
164
  if (!spread) {
164
- evidence.push(violation(file, input, 'Lifecycle transition has no allowed decision.'));
165
+ evidence.push(violation(file, input, 'StateMachine transition has no allowed decision.'));
165
166
  }
166
167
  }
167
- const selectedClass = propertiesForClass(input, lifecycleProperties);
168
+ const selectedClass = propertiesForClass(input, machineProperties);
168
169
  if (selectedClass.kind === 'ambiguous') {
169
- evidence.push(ambiguity(file, assignedValue(input, 'class') ?? input, 'Lifecycle transition Class has no statically resolvable Schema owner.'));
170
+ evidence.push(ambiguity(file, assignedValue(input, 'class') ?? input, 'StateMachine transition Class has no statically resolvable Schema owner.'));
170
171
  }
171
172
  const property = staticTextValue(assignedValue(input, 'property'));
172
173
  if (property === undefined) {
173
- evidence.push(ambiguity(file, assignedValue(input, 'property') ?? input, 'Lifecycle transition property is not static text.'));
174
+ evidence.push(ambiguity(file, assignedValue(input, 'property') ?? input, 'StateMachine transition property is not static text.'));
174
175
  }
175
176
  else if (selectedClass.kind === 'absent' ||
176
177
  (selectedClass.kind === 'resolved' && !selectedClass.properties.has(property))) {
177
- evidence.push(violation(file, assignedValue(input, 'property') ?? input, `Lifecycle transition property ${property} is not a lifecycle Property of the selected Class.`));
178
+ evidence.push(violation(file, assignedValue(input, 'property') ?? input, `StateMachine transition property ${property} is not a machine-backed Property of the selected Class.`));
178
179
  }
179
- const lifecycle = assignedValue(input, 'lifecycle');
180
- const machine = lifecycle === undefined
180
+ const legacyLifecycle = assignedValue(input, 'lifecycle');
181
+ if (legacyLifecycle !== undefined) {
182
+ evidence.push(violation(file, legacyLifecycle, 'StateMachine transition uses removed lifecycle field; use machine.'));
183
+ }
184
+ const machineInput = assignedValue(input, 'machine');
185
+ const machine = machineInput === undefined
181
186
  ? { kind: 'absent' }
182
- : lifecycleIdentity(project, file, lifecycle);
187
+ : stateMachineIdentity(project, file, machineInput);
183
188
  if (machine.kind !== 'resolved') {
184
- evidence.push(ambiguity(file, lifecycle ?? input, 'Lifecycle transition machine has no resolvable canonical States source.'));
189
+ if (legacyLifecycle === undefined) {
190
+ evidence.push(ambiguity(file, machineInput ?? input, 'StateMachine transition machine has no resolvable canonical States source.'));
191
+ }
185
192
  }
186
193
  else if (property !== undefined &&
187
194
  selectedClass.kind === 'resolved' &&
188
195
  selectedClass.properties.get(property) !== undefined &&
189
196
  selectedClass.properties.get(property) !== machine.identity) {
190
- evidence.push(violation(file, lifecycle ?? input, `Lifecycle transition uses a different StateMachine authority than property ${property}.`));
197
+ evidence.push(violation(file, machineInput ?? input, `StateMachine transition uses a different authority than property ${property}.`));
191
198
  }
192
199
  });
193
200
  }
@@ -339,9 +346,12 @@ export const mutationRules = [
339
346
  },
340
347
  },
341
348
  ];
342
- function lifecycleAssignments(object, lifecycleProperties) {
343
- return object.properties.filter((property) => ts.isPropertyAssignment(property) &&
344
- lifecycleProperties.has(staticPropertyName(property) ?? ''));
349
+ function machineAssignments(object, machineProperties) {
350
+ return object.properties.filter((property) => (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property)) &&
351
+ machineProperties.has(staticPropertyName(property) ?? ''));
352
+ }
353
+ function assignedPropertyValue(property) {
354
+ return ts.isPropertyAssignment(property) ? property.initializer : property.name;
345
355
  }
346
356
  function hasSpread(object) {
347
357
  return object.properties.some((property) => ts.isSpreadAssignment(property));
@@ -372,13 +382,13 @@ function staticStringArray(expression) {
372
382
  function mutationBuilderScopes(file) {
373
383
  const mutable = new Map();
374
384
  const pending = [];
375
- const add = (fn, name) => {
376
- const names = mutable.get(fn) ?? new Set();
377
- if (names.has(name))
385
+ const add = (fn, binding) => {
386
+ const bindings = mutable.get(fn) ?? new Set();
387
+ if (bindings.has(binding))
378
388
  return;
379
- names.add(name);
380
- mutable.set(fn, names);
381
- pending.push([fn, name]);
389
+ bindings.add(binding);
390
+ mutable.set(fn, bindings);
391
+ pending.push([fn, binding]);
382
392
  };
383
393
  for (const definition of definitionObjects(file, 'defineMutation')) {
384
394
  if (definition.origin !== 'resolved')
@@ -386,7 +396,7 @@ function mutationBuilderScopes(file) {
386
396
  const build = definition.object && callback(definition.object, 'build');
387
397
  const builder = build?.parameters[1]?.name;
388
398
  if (build && builder && ts.isIdentifier(builder))
389
- add(build, builder.text);
399
+ add(build, builder);
390
400
  }
391
401
  const richBuilderTypes = new Set();
392
402
  for (const sourceImport of file.imports) {
@@ -408,7 +418,7 @@ function mutationBuilderScopes(file) {
408
418
  ts.isTypeReferenceNode(parameter.type) &&
409
419
  ts.isIdentifier(parameter.type.typeName) &&
410
420
  richBuilderTypes.has(parameter.type.typeName.text)) {
411
- add(node, parameter.name.text);
421
+ add(node, parameter.name);
412
422
  }
413
423
  }
414
424
  });
@@ -416,22 +426,31 @@ function mutationBuilderScopes(file) {
416
426
  for (let index = 0; index < pending.length; index += 1) {
417
427
  const [owner, builder] = pending[index];
418
428
  visitOwnBody(owner, (node) => {
429
+ if (ts.isVariableDeclaration(node) &&
430
+ ts.isIdentifier(node.name) &&
431
+ node.initializer !== undefined &&
432
+ ts.isIdentifier(unwrap(node.initializer)) &&
433
+ identifierDeclaration(unwrap(node.initializer)) === builder &&
434
+ (node.parent.flags & ts.NodeFlags.Const) !== 0) {
435
+ add(owner, node.name);
436
+ }
419
437
  if (!ts.isCallExpression(node))
420
438
  return;
421
439
  const callee = unwrap(node.expression);
422
440
  if (!ts.isIdentifier(callee))
423
441
  return;
424
- const target = localFunctions.get(callee.text);
442
+ const declaration = identifierDeclaration(callee);
443
+ const target = declaration && localFunctions.get(declaration);
425
444
  if (!target)
426
445
  return;
427
446
  for (let argumentIndex = 0; argumentIndex < node.arguments.length; argumentIndex += 1) {
428
447
  const argument = unwrap(node.arguments[argumentIndex]);
429
448
  const parameter = target.parameters[argumentIndex]?.name;
430
449
  if (ts.isIdentifier(argument) &&
431
- argument.text === builder &&
450
+ identifierDeclaration(argument) === builder &&
432
451
  parameter &&
433
452
  ts.isIdentifier(parameter)) {
434
- add(target, parameter.text);
453
+ add(target, parameter);
435
454
  }
436
455
  }
437
456
  });
@@ -446,64 +465,111 @@ function mutationBuilderMember(call, scopes) {
446
465
  const expression = unwrap(call.expression);
447
466
  if (ts.isPropertyAccessExpression(expression)) {
448
467
  const receiver = unwrap(expression.expression);
449
- return ts.isIdentifier(receiver) && builders.has(receiver.text)
468
+ return ts.isIdentifier(receiver) && builders.has(identifierDeclaration(receiver))
450
469
  ? expression.name.text
451
470
  : undefined;
452
471
  }
453
472
  if (ts.isElementAccessExpression(expression)) {
454
473
  const receiver = unwrap(expression.expression);
455
- return ts.isIdentifier(receiver) && builders.has(receiver.text)
474
+ return ts.isIdentifier(receiver) && builders.has(identifierDeclaration(receiver))
456
475
  ? staticText(expression.argumentExpression)
457
476
  : undefined;
458
477
  }
459
478
  if (!ts.isIdentifier(expression))
460
479
  return undefined;
461
- return destructuredBuilderMember(owner, expression, builders);
480
+ return destructuredBuilderMember(expression, builders);
462
481
  }
463
- function destructuredBuilderMember(owner, callee, builders) {
464
- if (!owner.body || !ts.isBlock(owner.body))
482
+ function destructuredBuilderMember(callee, builders) {
483
+ const calleeBinding = identifierDeclaration(callee);
484
+ const element = calleeBinding?.parent;
485
+ if (!element ||
486
+ !ts.isBindingElement(element) ||
487
+ element.dotDotDotToken !== undefined ||
488
+ element.name !== calleeBinding ||
489
+ !ts.isObjectBindingPattern(element.parent)) {
465
490
  return undefined;
466
- for (const statement of owner.body.statements) {
467
- if (statement.getStart() >= callee.getStart())
468
- break;
469
- if (!ts.isVariableStatement(statement))
470
- continue;
471
- for (const declaration of statement.declarationList.declarations) {
472
- const initializer = declaration.initializer && unwrap(declaration.initializer);
473
- if (!initializer || !ts.isIdentifier(initializer) || !builders.has(initializer.text))
474
- continue;
475
- if (!ts.isObjectBindingPattern(declaration.name))
476
- continue;
477
- for (const element of declaration.name.elements) {
478
- if (!ts.isIdentifier(element.name) || element.name.text !== callee.text)
479
- continue;
480
- const member = element.propertyName ?? element.name;
481
- if (ts.isIdentifier(member) || ts.isStringLiteral(member))
482
- return member.text;
491
+ }
492
+ const declaration = element.parent.parent;
493
+ if (!ts.isVariableDeclaration(declaration))
494
+ return undefined;
495
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
496
+ if (!initializer ||
497
+ !ts.isIdentifier(initializer) ||
498
+ !builders.has(identifierDeclaration(initializer))) {
499
+ return undefined;
500
+ }
501
+ const member = element.propertyName ?? element.name;
502
+ return ts.isIdentifier(member) || ts.isStringLiteral(member) ? member.text : undefined;
503
+ }
504
+ function localFunctionBindings(file) {
505
+ const functions = new Map();
506
+ const record = (binding, fn) => {
507
+ functions.set(binding, fn);
508
+ };
509
+ visit(file.source, (node) => {
510
+ if (ts.isFunctionDeclaration(node) && node.name) {
511
+ record(node.name, node);
512
+ return;
513
+ }
514
+ if (!ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name) || !node.initializer)
515
+ return;
516
+ const value = unwrap(node.initializer);
517
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
518
+ record(node.name, value);
519
+ }
520
+ });
521
+ return functions;
522
+ }
523
+ function identifierDeclaration(use) {
524
+ let current = use.parent;
525
+ while (current !== undefined) {
526
+ if (ts.isBlock(current) || ts.isSourceFile(current)) {
527
+ const binding = directBlockBinding(current, use.text);
528
+ if (binding !== undefined)
529
+ return binding;
530
+ }
531
+ if (ts.isFunctionLike(current)) {
532
+ for (const parameter of current.parameters) {
533
+ const binding = bindingIdentifier(parameter.name, use.text);
534
+ if (binding !== undefined)
535
+ return binding;
483
536
  }
484
537
  }
538
+ if (ts.isCatchClause(current) && current.variableDeclaration !== undefined) {
539
+ const binding = bindingIdentifier(current.variableDeclaration.name, use.text);
540
+ if (binding !== undefined)
541
+ return binding;
542
+ }
543
+ current = current.parent;
485
544
  }
486
545
  return undefined;
487
546
  }
488
- function localFunctionBindings(file) {
489
- const functions = new Map();
490
- for (const statement of file.source.statements) {
491
- if (ts.isFunctionDeclaration(statement) && statement.name) {
492
- functions.set(statement.name.text, statement);
493
- continue;
547
+ function directBlockBinding(block, name) {
548
+ for (const statement of block.statements) {
549
+ if (ts.isFunctionDeclaration(statement) && statement.name?.text === name) {
550
+ return statement.name;
494
551
  }
495
552
  if (!ts.isVariableStatement(statement))
496
553
  continue;
497
554
  for (const declaration of statement.declarationList.declarations) {
498
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
499
- continue;
500
- const value = unwrap(declaration.initializer);
501
- if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
502
- functions.set(declaration.name.text, value);
503
- }
555
+ const binding = bindingIdentifier(declaration.name, name);
556
+ if (binding !== undefined)
557
+ return binding;
504
558
  }
505
559
  }
506
- return functions;
560
+ return undefined;
561
+ }
562
+ function bindingIdentifier(binding, name) {
563
+ if (ts.isIdentifier(binding))
564
+ return binding.text === name ? binding : undefined;
565
+ for (const element of binding.elements) {
566
+ if (ts.isOmittedExpression(element))
567
+ continue;
568
+ const identifier = bindingIdentifier(element.name, name);
569
+ if (identifier !== undefined)
570
+ return identifier;
571
+ }
572
+ return undefined;
507
573
  }
508
574
  function containingFunction(node) {
509
575
  let current = node.parent;
@@ -515,7 +581,7 @@ function containingFunction(node) {
515
581
  }
516
582
  return undefined;
517
583
  }
518
- function lifecyclePropertiesByClass(project) {
584
+ function machinePropertiesByClass(project) {
519
585
  const all = new Map();
520
586
  const mutableByClass = new Map();
521
587
  const ambiguousByClass = new Map();
@@ -588,7 +654,7 @@ function lifecyclePropertiesByClass(project) {
588
654
  ambiguousByClass.set(owner, names);
589
655
  mutableByClass.delete(owner);
590
656
  }
591
- inheritLifecycleProperties(project, mutableByClass, ambiguousByClass);
657
+ inheritMachineProperties(project, mutableByClass, ambiguousByClass);
592
658
  for (const file of production(project, 'schema')) {
593
659
  for (const definition of definitionObjects(file, 'defineSchema', undefined, DSL_MODULES, 1)) {
594
660
  if (definition.origin !== 'resolved')
@@ -640,7 +706,7 @@ function lifecyclePropertiesByClass(project) {
640
706
  ambiguousByClass,
641
707
  };
642
708
  }
643
- function inheritLifecycleProperties(project, byClass, ambiguousByClass) {
709
+ function inheritMachineProperties(project, byClass, ambiguousByClass) {
644
710
  const parents = new Map();
645
711
  for (const file of production(project, 'schema')) {
646
712
  visit(file.source, (node) => {
@@ -729,7 +795,7 @@ function propertiesForClass(input, properties) {
729
795
  const selected = properties.byClass.get(className);
730
796
  return selected === undefined ? { kind: 'absent' } : { kind: 'resolved', properties: selected };
731
797
  }
732
- function hasLifecycleCandidates(properties) {
798
+ function hasMachineCandidates(properties) {
733
799
  return properties.all.size > 0 || properties.ambiguousByClass.size > 0;
734
800
  }
735
801
  function isPropertiesMember(node) {
@@ -777,8 +843,13 @@ function staticPropertyName(property) {
777
843
  }
778
844
  function initialIdentity(project, file, expression, seen = new Set()) {
779
845
  const value = unwrap(expression);
780
- if (ts.isPropertyAccessExpression(value) && value.name.text === 'initial') {
781
- return lifecycleIdentity(project, file, value.expression);
846
+ const initialOwner = ts.isPropertyAccessExpression(value) && value.name.text === 'initial'
847
+ ? value.expression
848
+ : ts.isElementAccessExpression(value) && staticText(value.argumentExpression) === 'initial'
849
+ ? value.expression
850
+ : undefined;
851
+ if (initialOwner !== undefined) {
852
+ return stateMachineIdentity(project, file, initialOwner);
782
853
  }
783
854
  if (ts.isIdentifier(value)) {
784
855
  if (seen.has(value.text))
@@ -1,7 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import { importedSymbol, propertyChain, staticText, unwrap, visit, } from '../../adapters/typescript/index.js';
3
- import { lifecycleOrigin, stateMachineConstructorOrigin, stateSchemaIdentity } from './lifecycle.js';
4
3
  import { ambiguity, forbiddenIoImport, production, violation } from './shared.js';
4
+ import { stateMachineConstructorOrigin, stateMachineOrigin, stateSchemaIdentity, } from './state-machine.js';
5
5
  const FORBIDDEN_LAYERS = new Set([
6
6
  'integrations',
7
7
  'actions',
@@ -24,24 +24,24 @@ const EXECUTION_NAMES = new Set([
24
24
  export const schemaRules = [
25
25
  {
26
26
  id: 'SCH-STATE-SOURCE',
27
- ruleRevision: '00c1ad27b0fb32a063d66c98dc68c25cdf2b507c2da118ffa3e6651da8889065',
27
+ ruleRevision: '4f126e23835b6c46dabb94d2c94021ddb992a8e9a295faef4403df74ecfb2a41',
28
28
  evaluate(project) {
29
29
  const evidence = [];
30
- const lifecycleVocabularies = stateVocabularies(project);
30
+ const machineVocabularies = stateVocabularies(project);
31
31
  for (const file of production(project, 'schema')) {
32
- const ownedVocabularies = file.submodule === undefined ? undefined : lifecycleVocabularies.get(file.submodule);
32
+ const ownedVocabularies = file.submodule === undefined ? undefined : machineVocabularies.get(file.submodule);
33
33
  const reportedEnums = new Set();
34
34
  visit(file.source, (node) => {
35
35
  if (ts.isPropertyAssignment(node) && isPropertiesMember(node)) {
36
36
  const codec = stateSchemaIdentity(project, file, node.initializer);
37
37
  if (codec.kind === 'ambiguous') {
38
- evidence.push(ambiguity(file, node.initializer, 'Schema lifecycle Property codec has no exact stateSchema source.'));
38
+ evidence.push(ambiguity(file, node.initializer, 'Schema machine-backed Property codec has no exact stateSchema source.'));
39
39
  }
40
40
  }
41
41
  if (ts.isCallExpression(node)) {
42
42
  const machineOrigin = stateMachineConstructorOrigin(file, node.expression);
43
43
  if (machineOrigin === 'resolved') {
44
- evidence.push(violation(file, node, 'Schema declares a stateMachine; move the lifecycle relation to States.'));
44
+ evidence.push(violation(file, node, 'Schema declares a stateMachine; move the StateMachine relation to States.'));
45
45
  }
46
46
  else if (machineOrigin === 'ambiguous') {
47
47
  evidence.push(ambiguity(file, node, 'Schema call resembles stateMachine through a local facade, but its SDK origin is unresolved.'));
@@ -51,17 +51,17 @@ export const schemaRules = [
51
51
  ownedVocabularies?.has(vocabularyKey(values)) === true &&
52
52
  !reportedEnums.has(node.pos)) {
53
53
  reportedEnums.add(node.pos);
54
- evidence.push(ambiguity(file, node, 'Schema declares an enum equal to its States lifecycle vocabulary, but copied semantic ownership cannot be proven from literals alone.'));
54
+ evidence.push(ambiguity(file, node, 'Schema declares an enum equal to its States machine vocabulary, but copied semantic ownership cannot be proven from literals alone.'));
55
55
  }
56
56
  }
57
- if (ts.isPropertyAccessExpression(node) &&
58
- (node.name.text === 'stateSchema' || node.name.text === 'eventSchema')) {
59
- const origin = lifecycleOrigin(project, file, node.expression);
57
+ const projection = stateMachineCodecProjection(node);
58
+ if (projection !== undefined) {
59
+ const origin = stateMachineOrigin(project, file, projection.owner);
60
60
  if (origin === 'ambiguous') {
61
- evidence.push(ambiguity(file, node, `Schema lifecycle codec ${node.name.text} has an unresolved States export origin.`));
61
+ evidence.push(ambiguity(file, node, `Schema StateMachine codec ${projection.name} has an unresolved States export origin.`));
62
62
  }
63
63
  else if (origin === 'absent') {
64
- evidence.push(violation(file, node, `Schema lifecycle codec ${node.name.text} does not originate from an exported stateMachine.`));
64
+ evidence.push(violation(file, node, `Schema StateMachine codec ${projection.name} does not originate from an exported stateMachine.`));
65
65
  }
66
66
  }
67
67
  });
@@ -171,6 +171,20 @@ function enumValues(file, call) {
171
171
  const values = argument.elements.map((element) => staticText(element));
172
172
  return values.every((value) => value !== undefined) ? values : undefined;
173
173
  }
174
+ function stateMachineCodecProjection(node) {
175
+ if (ts.isPropertyAccessExpression(node)) {
176
+ if (node.name.text === 'stateSchema' || node.name.text === 'eventSchema') {
177
+ return { owner: node.expression, name: node.name.text };
178
+ }
179
+ return undefined;
180
+ }
181
+ if (!ts.isElementAccessExpression(node))
182
+ return undefined;
183
+ const name = staticText(node.argumentExpression);
184
+ return name === 'stateSchema' || name === 'eventSchema'
185
+ ? { owner: node.expression, name }
186
+ : undefined;
187
+ }
174
188
  function isZodCall(file, expression) {
175
189
  const chain = propertyChain(expression);
176
190
  const root = chain?.[0];
@@ -1,7 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import type { SourceFile, SourceProject } from '../../adapters/typescript/index.js';
3
- export type LifecycleOrigin = 'resolved' | 'ambiguous' | 'absent';
4
- export type LifecycleResolution = Readonly<{
3
+ export type StateMachineOrigin = 'resolved' | 'ambiguous' | 'absent';
4
+ export type StateMachineResolution = Readonly<{
5
5
  kind: 'resolved';
6
6
  identity: string;
7
7
  }> | Readonly<{
@@ -9,12 +9,12 @@ export type LifecycleResolution = Readonly<{
9
9
  }>;
10
10
  export type MachineExportStatus = 'exported' | 'private' | 'not-a-declaration';
11
11
  /** Resolve a direct or static local alias of the SDK stateMachine constructor. */
12
- export declare function stateMachineConstructorOrigin(file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): LifecycleOrigin;
12
+ export declare function stateMachineConstructorOrigin(file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): StateMachineOrigin;
13
13
  /** Resolve one local binding to an exported SDK stateMachine authority. */
14
- export declare function lifecycleOrigin(project: SourceProject, file: SourceFile, expression: ts.Expression): LifecycleOrigin;
14
+ export declare function stateMachineOrigin(project: SourceProject, file: SourceFile, expression: ts.Expression): StateMachineOrigin;
15
15
  /** Resolve a consumer expression to the exact exported stateMachine declaration that owns it. */
16
- export declare function lifecycleIdentity(project: SourceProject, file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): LifecycleResolution;
16
+ export declare function stateMachineIdentity(project: SourceProject, file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): StateMachineResolution;
17
17
  /** Resolve a Schema codec expression to the exact machine whose stateSchema it aliases. */
18
- export declare function stateSchemaIdentity(project: SourceProject, file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): LifecycleResolution;
18
+ export declare function stateSchemaIdentity(project: SourceProject, file: SourceFile, expression: ts.Expression, seen?: ReadonlySet<string>): StateMachineResolution;
19
19
  /** Classify whether a stateMachine call is the value of an exported top-level binding. */
20
20
  export declare function machineExportStatus(file: SourceFile, call: ts.CallExpression): MachineExportStatus;
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import { resolveImportedSymbol, resolveProjectImport, unwrap, visit, } from '../../adapters/typescript/index.js';
2
+ import { resolveImportedSymbol, resolveProjectImport, staticText, unwrap, visit, } from '../../adapters/typescript/index.js';
3
3
  const STATE_MODULES = new Set(['@astrale-os/sdk', '@astrale-os/sdk/state']);
4
4
  const SCHEMA_MODULES = new Set([
5
5
  '@astrale-os/sdk',
@@ -29,11 +29,11 @@ export function stateMachineConstructorOrigin(file, expression, seen = new Set()
29
29
  return declaration.constant || origin === 'absent' ? origin : 'ambiguous';
30
30
  }
31
31
  /** Resolve one local binding to an exported SDK stateMachine authority. */
32
- export function lifecycleOrigin(project, file, expression) {
33
- return lifecycleIdentity(project, file, expression).kind;
32
+ export function stateMachineOrigin(project, file, expression) {
33
+ return stateMachineIdentity(project, file, expression).kind;
34
34
  }
35
35
  /** Resolve a consumer expression to the exact exported stateMachine declaration that owns it. */
36
- export function lifecycleIdentity(project, file, expression, seen = new Set()) {
36
+ export function stateMachineIdentity(project, file, expression, seen = new Set()) {
37
37
  const value = unwrap(expression);
38
38
  if (ts.isIdentifier(value)) {
39
39
  const declaration = localVariable(file, value.text);
@@ -43,7 +43,7 @@ export function lifecycleIdentity(project, file, expression, seen = new Set()) {
43
43
  return { kind: 'ambiguous' };
44
44
  const next = new Set(seen);
45
45
  next.add(identity);
46
- const resolution = lifecycleIdentity(project, file, declaration.initializer, next);
46
+ const resolution = stateMachineIdentity(project, file, declaration.initializer, next);
47
47
  return declaration.constant || resolution.kind === 'absent'
48
48
  ? resolution
49
49
  : { kind: 'ambiguous' };
@@ -60,8 +60,9 @@ export function lifecycleIdentity(project, file, expression, seen = new Set()) {
60
60
  /** Resolve a Schema codec expression to the exact machine whose stateSchema it aliases. */
61
61
  export function stateSchemaIdentity(project, file, expression, seen = new Set()) {
62
62
  const value = unwrap(expression);
63
- if (ts.isPropertyAccessExpression(value) && value.name.text === 'stateSchema') {
64
- return lifecycleIdentity(project, file, value.expression);
63
+ const owner = staticMemberReceiver(value, 'stateSchema');
64
+ if (owner !== undefined) {
65
+ return stateMachineIdentity(project, file, owner);
65
66
  }
66
67
  if (ts.isCallExpression(value)) {
67
68
  const symbol = resolveImportedSymbol(file, value.expression);
@@ -87,9 +88,8 @@ export function stateSchemaIdentity(project, file, expression, seen = new Set())
87
88
  }
88
89
  let candidate = false;
89
90
  visit(value, (node) => {
90
- if (ts.isPropertyAccessExpression(node) &&
91
- node.name.text === 'stateSchema' &&
92
- lifecycleIdentity(project, file, node.expression).kind !== 'absent') {
91
+ const owner = ts.isExpression(node) ? staticMemberReceiver(node, 'stateSchema') : undefined;
92
+ if (owner !== undefined && stateMachineIdentity(project, file, owner).kind !== 'absent') {
93
93
  candidate = true;
94
94
  }
95
95
  if (ts.isIdentifier(node) && node !== value) {
@@ -107,6 +107,16 @@ export function stateSchemaIdentity(project, file, expression, seen = new Set())
107
107
  });
108
108
  return candidate ? { kind: 'ambiguous' } : { kind: 'absent' };
109
109
  }
110
+ function staticMemberReceiver(expression, member) {
111
+ const value = unwrap(expression);
112
+ if (ts.isPropertyAccessExpression(value) && value.name.text === member) {
113
+ return value.expression;
114
+ }
115
+ if (ts.isElementAccessExpression(value) && staticText(value.argumentExpression) === member) {
116
+ return value.expression;
117
+ }
118
+ return undefined;
119
+ }
110
120
  function importedBinding(file, expression) {
111
121
  const value = unwrap(expression);
112
122
  if (ts.isIdentifier(value)) {
@@ -1,7 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import { resolveImportedSymbol, resolveProjectImport, staticText, unwrap, visit, } from '../../adapters/typescript/index.js';
3
- import { machineExportStatus, stateMachineConstructorOrigin } from './lifecycle.js';
4
3
  import { ambiguity, forbiddenIoImport, hasForbiddenGlobalCall, production, violation, } from './shared.js';
4
+ import { machineExportStatus, stateMachineConstructorOrigin } from './state-machine.js';
5
5
  const STATE_MODULES = new Set(['@astrale-os/sdk', '@astrale-os/sdk/state']);
6
6
  const FORBIDDEN_LAYERS = new Set([
7
7
  'actions',
@@ -17,7 +17,7 @@ const FORBIDDEN_LAYERS = new Set([
17
17
  export const stateRules = [
18
18
  {
19
19
  id: 'STA-ONE-RELATION',
20
- ruleRevision: 'b80d551bf975f75830902816cc98886f670711af012c7869fa6fa956e208f528',
20
+ ruleRevision: '7b6a334d669288c938b47985dc325c96f6ce277986848858f25e609cdbcc679e',
21
21
  evaluate(project) {
22
22
  const evidence = [];
23
23
  const files = production(project, 'states');
@@ -48,7 +48,7 @@ export const stateRules = [
48
48
  const exportStatus = machineExportStatus(machine.file, machine.call);
49
49
  if (exportStatus !== 'exported') {
50
50
  evidence.push(violation(machine.file, machine.call, exportStatus === 'private'
51
- ? 'State machine relation must be exported as its submodule lifecycle authority.'
51
+ ? 'State machine relation must be exported as its submodule StateMachine authority.'
52
52
  : 'State machine relation must be assigned to one exported top-level binding.'));
53
53
  }
54
54
  evidence.push(...admitStaticRelation(machine.file, machine.call));
@@ -65,7 +65,7 @@ export const stateRules = [
65
65
  if ((symbol.kind === 'resolved' || symbol.kind === 'ambiguous') &&
66
66
  symbol.name === 'defineStateMachine' &&
67
67
  STATE_MODULES.has(symbol.module)) {
68
- evidence.push(violation(file, node, 'State lifecycle uses unsupported defineStateMachine; use stateMachine.'));
68
+ evidence.push(violation(file, node, 'StateMachine uses unsupported defineStateMachine; use stateMachine.'));
69
69
  }
70
70
  });
71
71
  }
@@ -285,7 +285,7 @@ function duplicatedProjection(expression, vocabulary) {
285
285
  const value = unwrap(expression);
286
286
  if (ts.isPropertyAccessExpression(value) &&
287
287
  ['initial', 'states', 'terminalStates', 'transitions'].includes(value.name.text)) {
288
- return `${value.name.text} lifecycle projection`;
288
+ return `${value.name.text} StateMachine projection`;
289
289
  }
290
290
  if (ts.isStringLiteral(value) && vocabulary.states.has(value.text)) {
291
291
  return 'initial-state constant';
@@ -1,7 +1,7 @@
1
1
  /** Generated from the validated Domain knowledge catalog. Regenerate with pnpm sync:linter-policy. */
2
- export const domainPolicyDigest = '0f712cdfaa194dc7970d91334b57188194f4fc5658c7f9991449e0bf1acfa5d6';
2
+ export const domainPolicyDigest = '551b8c93f44fc8b4e4dc3f9820361d69611d7c83f51260b6429cd5a13134dfc0';
3
3
  export const domainPolicySource = Object.freeze({
4
- rules: "id\tscope\tkind\tseverity\tmessage\tverification\texample\nOWN-SEMANTIC\townership\tarchitecture\terror\tEvery semantic fact, rule, representation, transition, and failure meaning has exactly one owner\tInventory declarations by meaning and report each duplicate authority or ownerless concept\tsemantic-owner.md\nMOD-SUBMODULE\tmodules\tstructure\terror\tEvery semantic source file lives in a dedicated semantic submodule or its registered layer facade\tClassify every layer-root source file and report files outside the registered facade or public index\tsemantic-owner.md\nMOD-REQUIRED\tmodules\tstructure\terror\tEvery required layer and root file declared by the injected layout exists\tCompare admitted project entries with required layout entries and report each missing path\trequired-layout.md\nMOD-GOVERNED\tmodules\tstructure\terror\tEvery source file admitted by the Domain compilation scope belongs to a declared layer or supported root\tClassify admitted source paths after declared workspace and tooling delegation and report each unowned path\tgoverned-source.md\nROOT-FACADE\troots\tstructure\terror\tEvery registered layer facade is exposed through a curated public index\tInspect each layer index and facade and report private-owner behavior or misplaced facade declarations\tpackage-facade.md\nROOT-COMPOSE\troots\tstructure\terror\tPackage roots compose only policy-backed Schema, Action handlers, native Routes, Migrations, Provider-backed Integrations, and supported package journeys\tInspect declared composition and package roots and report behavior, provider protocol logic, recursive barrels, or unsupported exports\tcomposition-root.md\nROOT-PACKAGE\troots\tevidence\terror\tEvery public Schema package subpath maps one admitted source entrypoint to its deterministic published declaration and JavaScript targets\tCompare source and publish exports and reject missing duplicate non-Schema locally linked generated or Kernel-leaking package artifacts\tmulti-domain-package.md\nDEP-ALLOWLIST\tdependencies\tdependency\terror\tEvery direct cross-layer import and its runtime or type-only kind appears in the dependency allowlist and dependencies between distinct layers are acyclic\tResolve source imports, reject every absent or kind-mismatched edge, and detect cycles after excluding evidence edges\tsemantic-owner.md\nIMP-FACADE\timports\tdependency\terror\tEvery cross-submodule import uses the semantic owner's facade and every foreign Domain import uses its public package facade\tResolve imports and report private deep paths outside the importing submodule\tcross-domain.md\nIMP-SDK-BOUNDARY\timports\tdependency\terror\tDomain source imports Kernel Core and DSL authoring values only through semantic SDK subpaths\tInspect every static and literal dynamic import and report module specifiers rooted at @astrale-os/kernel-core or @astrale-os/kernel-dsl\tsdk-boundary.md\nIMP-LAYER-ALIAS\timports\tdependency\terror\tEvery local import crossing a layer or semantic-submodule boundary uses the registered # layer alias\tResolve relative imports and report each one whose target leaves the importing semantic submodule\tlayer-aliases.md\nIMP-ALIAS-CFG\timports\tdependency\terror\tPackage and TypeScript resolution exactly implement each active layer-facade and semantic-submodule alias\tCompare active and required layers with the injected alias registry and report missing, undeclared, or drifted mappings\talias-configuration.md\nIMP-STATIC\timports\tdependency\terror\tProduction dependency edges use analyzable static ESM declarations or literal import calls\tReject require calls, import-equals declarations, nonliteral import calls, and unresolved static dependency forms\tstatic-imports.md\nTYP-OWNER\ttypes\tarchitecture\terror\tEvery type and reusable expected error is colocated with the semantic concept that defines its meaning\tTrace each declaration to its semantic authority and report catch-all or remotely owned values\texpected-failure.md\nERR-EXPECTED\terrors\tdataflow\terror\tBoundaries map only declared expected failures and propagate unexpected defects\tInspect catches and result mappings and report broad fallback, swallowed defects, or mappings without a declared source failure\texpected-failure.md\nTST-NO-PROD-IMP\ttests\tdependency\terror\tProduction source imports no root or colocated Test artifact\tResolve production imports and report targets under Root Tests, __tests__, or test, spec, bench, and perf source files\ttest-import-boundary.md\nABS-EARNED\tabstractions\tarchitecture\terror\tAn invariant or real boundary earns an abstraction; reuse alone requires two independent consumers and no duplicated semantic behavior\tIdentify the invariant, boundary, or independent consumers for each abstraction and report reuse wrappers that duplicate an owner's semantics\tsemantic-owner.md\nABS-NO-REPO\tabstractions\tarchitecture\terror\tDomain source places no collection-shaped persistence facade between business code and canonical Query or Mutation definitions\tInspect persistence-facing contracts and report abstractions that replace graph identity, traversal, observation, or atomic change semantics\tsemantic-owner.md\nABS-CANON-GRAPH\tabstractions\tdeclaration\terror\tDomain source declares no alternate Query or Mutation language, AST, compiler, planner, or wire representation\tInspect graph-related declarations and report semantics not identical to canonical Kernel Query or Mutation values\tsemantic-owner.md\nTRUST-ADMIT-ONCE\ttrust\tdataflow\terror\tEvery untrusted value is admitted or translated exactly once at its owning boundary\tTrace values from each external boundary and report missing admission or repeated structural validation\texpected-failure.md\nQLT-EXHAUSTIVE\tquality\tbehavior\terror\tEvery closed alternative is handled exhaustively and no required failure becomes placeholder success\tTrace closed unions and failure paths and report permissive defaults, placeholder success, or catch-and-continue behavior\texpected-failure.md\nQLT-CANON-VALUES\tquality\tdataflow\terror\tNo unchecked cast fabricates a canonical Kernel identity, QueryAST, MutationAST, or admitted schema value\tScan casts to canonical values and require an owning constructor or decoder for each\tsemantic-owner.md\nQLT-DEF-IDS\tquality\tdeclaration\terror\tEvery top-level Query, Mutation, and Migration definition has a stable literal ID unique in its owning namespace\tExtract top-level definition IDs and report missing, dynamic, malformed, or duplicate values\tdefinition-identities.md\nQLT-TYPED-COORD\tquality\tdeclaration\terror\tQueries and Mutations derive graph coordinates from resolved DSL definitions instead of raw string or structural reconstruction\tReject raw PropertyKey calls and structural ClassPath objects; require canonical resolved Classes Properties and keys at graph boundaries\ttyped-coordinates.md\nNODE-INHERITED\tnodes\tdeclaration\terror\tNo Node Class redeclares name, description, createdAt, or updatedAt inherited from Named, Descriptable, and Timestamped\tScan Node Class Properties and report each inherited field redeclaration\tsemantic-owner.md\nDOM-PUBLIC-DEPS\tdomains\tdependency\terror\tEvery directly referenced foreign-Domain graph declaration has one exact Schema dependency\tResolve foreign Schema, Query, and Mutation graph references and report each package without one exact declared Schema dependency\tcross-domain.md\nDOM-CALL-INT\tdomains\tdependency\terror\tEvery cross-Domain callable invocation implements a consumer-owned Integration inside a Provider and uses only the remote Domain's public facade\tTrace foreign callable invocations and report direct Action or Workflow calls, missing Integration contracts, non-Provider call sites, or private remote imports\tremote-domain-call.md\nDOM-ATOMICITY\tdomains\tbehavior\terror\tA cross-Domain change claims atomicity only when it is one MutationAST on one graph; calls combined with other effects are Workflow steps\tTrace atomicity and effect claims and report multiple commits, service boundaries, or foreign calls combined in a Action\tcross-domain.md\nSCH-SUBMODULE\tschema\tdeclaration\terror\tEvery authored declaration and public callable value has exactly one Schema submodule owner\tInventory declarations and callable inputs, results, and failures and report missing, duplicate, or layer-root owners\tschema-facade.md\nSCH-DECL-ONLY\tschema\tdeclaration\terror\tSchema performs no handler execution, Query execution, Mutation submission, Integration call, Workflow step, or Provider call\tResolve Schema imports and call graphs and require zero excluded operations\tpublication-callables.md\nSCH-EXACT-TYPES\tschema\tdeclaration\terror\tSchema authoring values preserve their exact inferred DSL types instead of widening to generic builder contracts\tScan Schema declarations and action returns for explicit generic builder authoring annotations\tschema-facade.md\nSCH-POLICY-AUTH\tschema\tdeclaration\terror\tCallable authority is declared through Policy and Action implementations add no private authorization rule\tTrace callable admission and report authority conditions without one owning Policy\tpolicy.md\nSCH-STATE-SOURCE\tschema\tdeclaration\terror\tEvery lifecycle vocabulary exposed by Schema originates from its canonical States owner rather than a copied literal set\tResolve exact lifecycle origins; report same-owner exact Zod literals as indeterminate and ignore unrelated submodules or DSL method names\tarticle-schema.md\nSTA-ONE-RELATION\tstates\tdeclaration\terror\tEvery lifecycle has one immutable transition relation containing its states, events, initial state, and legal transitions\tIdentify each lifecycle and report missing fields, mutable data, or duplicate transition authorities\tarticle-lifecycle.md\nSTA-PURE\tstates\tdeclaration\terror\tTransition relations contain no executable guard, permission, effect, retry policy, timer, clock, or provider behavior\tInspect transition values and resolved dependencies while treating state and event vocabulary as structural data\tterminal-states.md\nRUL-SYNC\trules\tbehavior\terror\tEvery Rule is synchronous and returns no Promise, continuation, generator, or asynchronous iterator\tInspect Rule signatures and result types and reject asynchronous forms\tnormalization.md\nRUL-PURE\trules\tbehavior\terror\tRules perform no I/O and depend on no graph executor or Domain execution boundary\tResolve imports and effect origins, permitting pure standard value transformations while reporting conclusive I/O and Domain execution dependencies\tpublish-eligibility.md\nRUL-EXPL-FACTS\trules\tbehavior\terror\tEvery clock value, random value, identity, limit, and environmental observation used by a Rule is an explicit input\tTrace every value source and report reads from ambient state, globals, process state, or hidden singletons\ttime-facts.md\nRUL-CLOSED-DEC\trules\tbehavior\terror\tEvery expected business alternative is represented by a closed decision value rather than thrown control flow\tInspect Rule branches and report expected alternatives represented by exceptions or open optional fields\tpublish-eligibility.md\nQRY-CANON\tqueries\tdataflow\terror\tEvery graph request produced by a Query definition is one canonical QueryAST built through Kernel Query APIs\tTrace request construction and reject every non-canonical graph document\tfiltered-query.md\nQRY-CONTRACT\tqueries\tdataflow\terror\tA Query owns its input, observation, optional business projection, pagination, and expected failures\tInspect each Query definition and report a required concern owned elsewhere or omitted\tquery-transforms.md\nQRY-SINGLE\tqueries\tdataflow\terror\tEvery authored single Query definition declares exactly one build callback and at most one project callback\tInspect definition properties and canonical roots; report opaque helper origins as ambiguity rather than assuming cardinality\tsingle-query.md\nQRY-COMPOSE-TYPED\tqueries\tdataflow\terror\tA composite Query constructs one closed QueryPlan containing only typed single-Query leaves, symbolic output references, and explicit result composition\tInspect plan construction, report unresolved call origins as ambiguity, and reject nested composites, raw sessions, graph clients, result-time callbacks, arbitrary promises, or Domain effects\tdependent-query.md\nQRY-COMPOSE-STABLE\tqueries\tdeclaration\terror\tEvery composite Query leaf has a stable semantic literal ID unique within its definition\tInspect declared Query leaves and reject dynamic, malformed, or duplicate leaf identifiers\tdependent-query.md\nQRY-COLL-FANOUT\tqueries\tdataflow\twarning\tThree or more independent same-kind complete-Class collection leaves use one explicit query.union group\tInfer collection kind from canonical definition types and print the direct named replacement plus logical and physical plan counts\tcollection-union.md\nQRY-PROJECT-PURE\tqueries\tdataflow\terror\tEvery Query project callback is synchronous and performs only pure value transformation\tInspect project callback syntax and call graphs and report asynchronous continuation, I/O, mutation, nondeterminism, hidden query execution, or effects\tprojection.md\nQRY-SNAPSHOT\tqueries\tdataflow\terror\tA composite or paginated Query makes no point-in-time graph snapshot claim beyond one Kernel invocation\tReject shared-snapshot claims across leaves unions or continuations and route stronger consistency to an explicit Kernel facility\tdependent-query.md\nMUT-CANON\tmutations\tdataflow\terror\tEvery Mutation definition builds one synchronous change through the canonical builder and constructs no alternate or nested graph document\tInspect every definition build callback and reject missing, asynchronous, alternate, or nested Mutation construction\tatomic-mutation.md\nMUT-CONTRACT\tmutations\tdataflow\terror\tA Mutation owns its input, preconditions, operations, optional business projection, and expected failures\tInspect each definition and report required mutation semantics owned elsewhere or omitted\tcreate-aggregate.md\nMUT-PRECONDS\tmutations\tdataflow\terror\tRules decide eligibility and every live graph condition required for safety is encoded as a precondition in the same MutationAST\tMap decisions to preconditions and report duplicated eligibility or safety enforced only by an earlier observation\tconditional-update.md\nMUT-FRAGMENTS\tmutations\tdataflow\terror\tReusable fragments receive the callback-scoped MutationBuilder and contribute only to the same MutationAST\tResolve fragment signatures and calls and reject escaped builders, opaque documents, or nested MutationAST construction\tmutation-fragments.md\nMUT-PURE\tmutations\tdataflow\terror\tMutation definitions perform no session call, external effect, retry, compensation, or second submission\tScan definition call graphs and require zero excluded operations\tedge-change.md\nMUT-MULTI-WFL\tmutations\tdataflow\terror\tA change requiring several MutationAST values is a Workflow and is never exposed as one atomic Mutation\tTrace multi-commit changes and report definitions or names claiming single-mutation atomicity\tdelete-node.md\nMUT-STATE-INITIAL\tmutations\tdataflow\terror\tEvery lifecycle-backed node is created from its StateMachine initial state\tResolve lifecycle Property initializers and require the canonical machine.initial value rather than a copied state literal\tlifecycle-transition.md\nMUT-STATE-ATOMIC\tmutations\tdataflow\terror\tEvery persisted lifecycle change consumes one allowed StateMachine decision and commits its stale-state precondition and update in the same MutationAST\tInspect lifecycle writes and require transition with the canonical States machine and one allowed decision; reject direct target writes and split precondition updates\tlifecycle-transition.md\nWFL-MULTISTEP\tworkflows\tbehavior\terror\tEvery Workflow definition contains at least two distinct semantic asynchronous operation sites\tInventory operation sites in the definition and report fewer than two; early exit, rejection, and recovery paths may execute fewer\tpublication-workflow.md\nWFL-STEP-EFFECTS\tworkflows\tbehavior\terror\tEvery semantic asynchronous operation maps to one named step.run boundary\tTrace effects and report operations outside step.run\tpublication-workflow.md\nWFL-STEP-IDS\tworkflows\tbehavior\terror\tEvery step identifier is a stable non-empty kebab-case literal unique within its Workflow\tExtract step identifiers and reject dynamic, malformed, empty, or duplicate values\tpublication-workflow.md\nWFL-NO-NEST\tworkflows\tbehavior\terror\tNo step.run callback invokes another step.run directly or through a reachable helper\tInspect callback call graphs and report every nested step.run path\tpublication-workflow.md\nWFL-INT-TYPES\tworkflows\tdeclaration\terror\tWorkflow Integration requirements derive from declared Integration definitions and the SDK projects their clients\tReject Workflow-owned operation interfaces and require its definition generic to reference the declared Integration registry\tcross-domain-workflow.md\nWFL-ONE-OP-STEP\tworkflows\tbehavior\terror\tEvery step owns exactly one semantic asynchronous operation\tTrace each step and report zero or several independently failing operations\tpublication-workflow.md\nWFL-STATE-CODEC\tworkflows\tbehavior\terror\tEvery step result and inter-step value is undefined or portable JSON data\tInspect step values and report actions, clients, symbols, bigint, cycles, or class instances\tpublication-workflow.md\nWFL-RECOVERY\tworkflows\tbehavior\terror\tWhen compensation or unknown-outcome branches exist, they are explicit and rely only on declared operation semantics\tInspect existing recovery branches and report guessed outcomes or compensation without exact evidence\tpublication-workflow.md\nWFL-XDOM-INT\tworkflows\tbehavior\terror\tA Workflow invokes another Domain only through a declared Integration and imports no foreign Domain\tResolve Workflow imports and operations and report foreign Domain imports or calls without Integration ownership\tcross-domain-workflow.md\nMIG-EXACT-REVS\tmigrations\tdataflow\terror\tEvery Migration declares exactly one source revision and one target revision for the same Domain origin\tParse Migration descriptors and reject missing, equal, ambiguous, cross-origin, or dynamically selected revisions\texact-revisions.md\nMIG-APP-DATA\tmigrations\tdataflow\terror\tMigrations transform application-owned facts only and own no schema projection, physical representation, deployment, backup, or external import\tClassify every transformed value and operation and report responsibilities outside application data\trewrite-values.md\nMIG-DEDICATED-CTX\tmigrations\tdataflow\terror\tMigration execution uses only its dedicated source reader, target writer, durable step context, revision-compatible Rules, and generic Utils\tResolve imports and calls and reject ordinary Actions, Workflows, Queries, Mutations, Integrations, Providers, Views, UI, or provider access\texact-revisions.md\nMIG-RESTART-SAFE\tmigrations\tdataflow\terror\tEvery Migration page is idempotent and resumes from a stable checkpoint without reinterpreting accepted target output\tInterrupt before and after every page commit, resume repeatedly, and compare target facts and checkpoints with uninterrupted execution\trestart-safe.md\nMIG-TARGET-PROOF\tmigrations\tdataflow\terror\tA Migration completes only after bounded source coverage and target-revision validation account for every selected source fact\tInject omitted, duplicated, invalid, and over-budget facts and require completion to fail with exact evidence\tverify-target.md\nMIG-DESTRUCTIVE\tmigrations\tdataflow\terror\tEvery destructive or irreversible transform declares and accounts for its intended loss and exposes declared expected failures\tTrace removals and irreversible rewrites and report undeclared or unaccounted loss, open failures, or catch-and-continue behavior\tremove-facts.md\nINT-BOUNDARY\tintegrations\tarchitecture\terror\tEvery Integration represents required behavior across an external, remote-Domain, substitution, process, or trust boundary\tIdentify the boundary for every contract and report behavior that can remain ordinary local Domain code\tpayment-authorization.md\nINT-NEUTRAL\tintegrations\tdeclaration\terror\tEvery Integration owns provider-neutral requests, evidence, correlation, idempotency, trust, and expected failure semantics needed by consumers\tInspect consumers and contracts and report provider fields or missing stable boundary semantics\tid-allocation.md\nINT-PURE\tintegrations\tdependency\terror\tIntegrations remain provider- and execution-boundary-neutral and import no concrete client, credential, configuration, Provider, Action, or Workflow\tResolve runtime and provider imports and report every conclusive concrete or execution-boundary dependency\tdocument-signing.md\nINT-ABILITY-NAME\tintegrations\tstructure\terror\tIntegration submodules are named by required ability and never mirror Schema entities mechanically\tCompare Integration and Schema submodules and report a contract without an independent boundary ability\tobject-storage.md\nACT-ONE-IMPL\tactions\tbehavior\terror\tEvery registered handler binding resolves to exactly one implementation owned by one semantic Actions submodule\tResolve the handler registry and report non-Action owners or duplicate registrations; SDK typing owns callable exhaustiveness\tsynchronous-result.md\nACT-ONE-OP\tactions\tbehavior\terror\tEvery Action performs one terminal semantic operation; only a native binary compatibility Action may precede its Integration call with one exact-receiver Query\tTrace every path and report several terminal effects, writes before effects, non-receiver prerequisite reads, or hidden orchestration\tsingle-operation.md\nACT-OP-KINDS\tactions\tbehavior\terror\tA semantic asynchronous operation is one Query definition, Mutation definition, or Integration call\tClassify every awaited effect and report operations outside the three allowed kinds\tintegration-operation.md\nACT-BOUNDARIES\tactions\tbehavior\terror\tActions use only invocation-bound query and mutate executors, including explicit caller self or union graph partitions, plus declared Integrations; Providers or foreign Domains are never imported\tInspect context use, authority-mode selection, dependencies, imports, and failure mapping; report raw or unbound graph executors, implicit privilege, concrete or foreign dependencies, broad mapping, or swallowed defects\tremote-integration.md\nACT-BINARY-RESULT\tactions\tbehavior\terror\tA binary Action returns detached buffered bytes or streaming bytes with admitted media type status and application headers without collecting or value-framing a provider stream\tExercise buffered and streaming results through their native route; verify exact bytes status headers cancellation and invalid output rejection\tnative-byte-response.md\nRTE-RECIPE\troutes\tdeclaration\terror\tEvery native HTTP Route targets exactly one admitted Action or Workflow recipe\tResolve every route target and report forged missing or several recipes\taction-route.md\nRTE-MAPPING\troutes\tdataflow\terror\tEvery callable input field and instance receiver is mapped exactly once from path query header method or body input\tCompile each route against its resolved callable and reject incomplete ambiguous or unknown mappings\tinstance-route.md\nRTE-CREDENTIAL\troutes\tdataflow\terror\tEvery external credential source is explicit and is never also mapped as callable input\tCompile credential and input mappings and reject duplicate reserved or implicit authority sources\tcredential-route.md\nRTE-ONE-FILE\troutes\tstructure\terror\tEvery Route declaration has one business-intent kebab-case file and Routes composition contains no inline declaration\tInventory route declarations and report generic grouped or inline ownership\tapplication-routes.md\nPRV-INT-IMPL\tproviders\tarchitecture\terror\tEvery Provider implements one or more declared Integrations across a named external or remote-Domain boundary\tIdentify the boundary and implemented contracts and report a Provider without both\tpayment-provider.md\nPRV-BOUNDARY-NAME\tproviders\tstructure\terror\tProvider submodules are named by external system, protocol, or trust boundary rather than Domain entity\tInspect every submodule name and report one justified only by Schema vocabulary\tobject-storage-provider.md\nPRV-ADMIT-RESULT\tproviders\tdataflow\terror\tEvery boundary value is admitted before Integration evidence and recognized boundary failures become stable Integration failures\tTrace input and failure paths and report missing admission, leaked boundary errors, broad mapping, or swallowed defects\twebhook-admission.md\nPRV-NO-DOMAIN\tproviders\tdependency\terror\tProviders perform no local Domain graph operation, business decision, local Action call, or Workflow orchestration\tResolve Provider imports and call graphs and require zero excluded operations\tboundary-composition.md\nPRV-XDOM-TYPED\tproviders\tdependency\terror\tRemote-Domain Providers pass SDK-derived typed callable references to the caller-bound invocation capability without never casts\tInspect Provider execution invoke calls and reject references not constructed through SDK reference, opaque or fabricated references, and never-cast arguments\tremote-domain.md\nPRV-XDOM-REQ\tproviders\tdependency\terror\tEvery statically resolved remote-Domain Provider invocation has one exact Application callable requirement\tMatch each invocation-bound public callable reference to the same foreign facade and callable selector under Application requirements; preserve opaque references or composition as indeterminate\tremote-domain.md\nPRV-XDOM-PUBLIC\tproviders\tdependency\terror\tA remote-Domain Provider uses the remote public facade and invokes at most one public callable per Integration operation\tResolve receiver and facade origins for each remote operation and report private access, missing Integration ownership, unrelated calls, or several public invocations\tremote-domain.md\nVIW-PROJECTION\tviews\tdeclaration\terror\tEvery View converts public Domain values or named Query observations into UI props and connects each action to a named bounded Mutation or public callable contract\tInspect View outputs and actions and report behavior without a Schema, Query, UI, Mutation, Rule, or public callable owner\tdetail-view.md\nVIW-SCHEMA-DECL\tviews\tdeclaration\terror\tSchema owns every DSL View declaration and Views declares no Domain schema construct\tScan View declarations and reject Classes, Properties, Policies, Methods, Actions, or DSL Views\tarticle-list.md\nVIW-DEPS\tviews\tdependency\terror\tViews import only Schema, States, pure Rules, named Queries, bounded named Mutations, UI, shell-react primitives, public callable contracts, and presentation libraries\tInspect resolved imports and report dependencies or raw graph APIs outside the allowlist\tnavigation.md\nVIW-ACTIONS\tviews\tdataflow\terror\tEvery View action executes a named caller-authorized atomic Mutation or invokes a public callable; presentation state is never authorization evidence\tTrace action and authority paths and report inline or raw graph documents, elevated or effectful Mutation use, private Action imports, direct Integration calls, or authority derived from presentation\tform-actions.md\nUI-PRES-DEPS\tui\tdependency\terror\tEvery UI import is a sibling UI module or an external library used only for presentation\tInspect resolved imports and used APIs and report Domain, I/O, persistence, authorization, routing, or provider behavior\tstatus-panel.md\nUI-NO-DOMAIN\tui\tdependency\terror\tUI imports no Domain layer or Domain package facade\tResolve the UI import graph and require zero Domain paths\tresult-list.md\nUI-PURE\tui\tbehavior\terror\tUI performs no I/O, graph access, storage, authorization, business validation, provider call, routing, or Domain result mapping\tInspect components and hooks and report the exact excluded call or branch\tconfirmation-dialog.md\nUI-CALLBACKS\tui\tdataflow\terror\tUI accepts presentation props and forwards intent through callbacks without constructing Domain commands\tInspect props and event handlers and report Domain identities, command construction, or business decisions\tform.md\nUTL-DOM-AGNOSTIC\tutils\tarchitecture\terror\tEvery Utils export is Domain-agnostic and contains no Domain declaration, identity, decision, graph definition, or Workflow step\tInspect signatures and implementations and report the exact Domain concept or business behavior\tresult.md\nUTL-REUSED\tutils\tarchitecture\terror\tEvery Utils extension serves two independent semantic consumers or one package-wide execution boundary\tList direct consumers and report an extension satisfying neither condition\tjson-codec.md\nUTL-PUBLIC-DEPS\tutils\tdependency\terror\tUtils imports only public Kernel, DSL, SDK, standard-library, and sibling Utils modules\tResolve the Utils import graph and reject Domain layers, Providers, or private package paths\tjson-codec.md\nUTL-LIGHTWEIGHT\tutils\tarchitecture\terror\tUtils is private and owns no durable engine, planner, compiler, Repository, service locator, or dependency container\tInspect package exports and declarations and report public exposure or any forbidden framework machinery\tresult.md\nSCR-OPERATOR\tscripts\tbehavior\terror\tEvery root Script implements a supported operator workflow with declared inputs, outcomes, failures, and proof\tInspect each Script contract and report disposable tasks or missing operator semantics\tbackup.md\nSCR-PUBLIC-DEPS\tscripts\tbehavior\terror\tScripts import only dependency-DAG-authorized public facades, standard libraries, and operator libraries and define no Domain behavior\tResolve imports and inspect branches and report private paths, business decisions, graph definitions, or callable behavior\tupgrade.md\nSCR-TEST-DRIVERS\tscripts\tbehavior\terror\tDisposable setup, fixtures, resets, and scenario drivers live under tests/scripts rather than root Scripts\tClassify every Script by supported operator contract and report evidence-only programs at the root\trestore.md\nTST-SYS-OWNER\ttests\tevidence\terror\tRoot Tests owns cross-layer journeys, environments, harnesses, scenarios, seeds, fixtures, and disposable scripts\tInventory evidence support code and report cross-layer artifacts outside Tests or production semantics inside Tests\tmigration-scenario.md\nTST-COLOCATED\ttests\tevidence\terror\tEvery semantic production submodule owns focused evidence in its local __tests__ directory\tJoin production submodules to focused test directories and report missing owners\tfocused-rule.md\n",
4
+ rules: "id\tscope\tkind\tseverity\tmessage\tverification\texample\nOWN-SEMANTIC\townership\tarchitecture\terror\tEvery semantic fact, rule, representation, transition, and failure meaning has exactly one owner\tInventory declarations by meaning and report each duplicate authority or ownerless concept\tsemantic-owner.md\nMOD-SUBMODULE\tmodules\tstructure\terror\tEvery semantic source file lives in a dedicated semantic submodule or its registered layer facade\tClassify every layer-root source file and report files outside the registered facade or public index\tsemantic-owner.md\nMOD-REQUIRED\tmodules\tstructure\terror\tEvery required layer and root file declared by the injected layout exists\tCompare admitted project entries with required layout entries and report each missing path\trequired-layout.md\nMOD-GOVERNED\tmodules\tstructure\terror\tEvery source file admitted by the Domain compilation scope belongs to a declared layer or supported root\tClassify admitted source paths after declared workspace and tooling delegation and report each unowned path\tgoverned-source.md\nROOT-FACADE\troots\tstructure\terror\tEvery registered layer facade is exposed through a curated public index\tInspect each layer index and facade and report private-owner behavior or misplaced facade declarations\tpackage-facade.md\nROOT-COMPOSE\troots\tstructure\terror\tPackage roots compose only policy-backed Schema, Action handlers, native Routes, Migrations, Provider-backed Integrations, and supported package journeys\tInspect declared composition and package roots and report behavior, provider protocol logic, recursive barrels, or unsupported exports\tcomposition-root.md\nROOT-PACKAGE\troots\tevidence\terror\tEvery public Schema package subpath maps one admitted source entrypoint to its deterministic published declaration and JavaScript targets\tCompare source and publish exports and reject missing duplicate non-Schema locally linked generated or Kernel-leaking package artifacts\tmulti-domain-package.md\nDEP-ALLOWLIST\tdependencies\tdependency\terror\tEvery direct cross-layer import and its runtime or type-only kind appears in the dependency allowlist and dependencies between distinct layers are acyclic\tResolve source imports, reject every absent or kind-mismatched edge, and detect cycles after excluding evidence edges\tsemantic-owner.md\nIMP-FACADE\timports\tdependency\terror\tEvery cross-submodule import uses the semantic owner's facade and every foreign Domain import uses its public package facade\tResolve imports and report private deep paths outside the importing submodule\tcross-domain.md\nIMP-SDK-BOUNDARY\timports\tdependency\terror\tDomain source imports Kernel Core and DSL authoring values only through semantic SDK subpaths\tInspect every static and literal dynamic import and report module specifiers rooted at @astrale-os/kernel-core or @astrale-os/kernel-dsl\tsdk-boundary.md\nIMP-LAYER-ALIAS\timports\tdependency\terror\tEvery local import crossing a layer or semantic-submodule boundary uses the registered # layer alias\tResolve relative imports and report each one whose target leaves the importing semantic submodule\tlayer-aliases.md\nIMP-ALIAS-CFG\timports\tdependency\terror\tPackage and TypeScript resolution exactly implement each active layer-facade and semantic-submodule alias\tCompare active and required layers with the injected alias registry and report missing, undeclared, or drifted mappings\talias-configuration.md\nIMP-STATIC\timports\tdependency\terror\tProduction dependency edges use analyzable static ESM declarations or literal import calls\tReject require calls, import-equals declarations, nonliteral import calls, and unresolved static dependency forms\tstatic-imports.md\nTYP-OWNER\ttypes\tarchitecture\terror\tEvery type and reusable expected error is colocated with the semantic concept that defines its meaning\tTrace each declaration to its semantic authority and report catch-all or remotely owned values\texpected-failure.md\nERR-EXPECTED\terrors\tdataflow\terror\tBoundaries map only declared expected failures and propagate unexpected defects\tInspect catches and result mappings and report broad fallback, swallowed defects, or mappings without a declared source failure\texpected-failure.md\nTST-NO-PROD-IMP\ttests\tdependency\terror\tProduction source imports no root or colocated Test artifact\tResolve production imports and report targets under Root Tests, __tests__, or test, spec, bench, and perf source files\ttest-import-boundary.md\nABS-EARNED\tabstractions\tarchitecture\terror\tAn invariant or real boundary earns an abstraction; reuse alone requires two independent consumers and no duplicated semantic behavior\tIdentify the invariant, boundary, or independent consumers for each abstraction and report reuse wrappers that duplicate an owner's semantics\tsemantic-owner.md\nABS-NO-REPO\tabstractions\tarchitecture\terror\tDomain source places no collection-shaped persistence facade between business code and canonical Query or Mutation definitions\tInspect persistence-facing contracts and report abstractions that replace graph identity, traversal, observation, or atomic change semantics\tsemantic-owner.md\nABS-CANON-GRAPH\tabstractions\tdeclaration\terror\tDomain source declares no alternate Query or Mutation language, AST, compiler, planner, or wire representation\tInspect graph-related declarations and report semantics not identical to canonical Kernel Query or Mutation values\tsemantic-owner.md\nTRUST-ADMIT-ONCE\ttrust\tdataflow\terror\tEvery untrusted value is admitted or translated exactly once at its owning boundary\tTrace values from each external boundary and report missing admission or repeated structural validation\texpected-failure.md\nQLT-EXHAUSTIVE\tquality\tbehavior\terror\tEvery closed alternative is handled exhaustively and no required failure becomes placeholder success\tTrace closed unions and failure paths and report permissive defaults, placeholder success, or catch-and-continue behavior\texpected-failure.md\nQLT-CANON-VALUES\tquality\tdataflow\terror\tNo unchecked cast fabricates a canonical Kernel identity, QueryAST, MutationAST, or admitted schema value\tScan casts to canonical values and require an owning constructor or decoder for each\tsemantic-owner.md\nQLT-DEF-IDS\tquality\tdeclaration\terror\tEvery top-level Query, Mutation, and Migration definition has a stable literal ID unique in its owning namespace\tExtract top-level definition IDs and report missing, dynamic, malformed, or duplicate values\tdefinition-identities.md\nQLT-TYPED-COORD\tquality\tdeclaration\terror\tQueries and Mutations derive graph coordinates from resolved DSL definitions instead of raw string or structural reconstruction\tReject raw PropertyKey calls and structural ClassPath objects; require canonical resolved Classes Properties and keys at graph boundaries\ttyped-coordinates.md\nNODE-INHERITED\tnodes\tdeclaration\terror\tNo Node Class redeclares name, description, createdAt, or updatedAt inherited from Named, Descriptable, and Timestamped\tScan Node Class Properties and report each inherited field redeclaration\tsemantic-owner.md\nDOM-PUBLIC-DEPS\tdomains\tdependency\terror\tEvery directly referenced foreign-Domain graph declaration has one exact Schema dependency\tResolve foreign Schema, Query, and Mutation graph references and report each package without one exact declared Schema dependency\tcross-domain.md\nDOM-CALL-INT\tdomains\tdependency\terror\tEvery cross-Domain callable invocation implements a consumer-owned Integration inside a Provider and uses only the remote Domain's public facade\tTrace foreign callable invocations and report direct Action or Workflow calls, missing Integration contracts, non-Provider call sites, or private remote imports\tremote-domain-call.md\nDOM-ATOMICITY\tdomains\tbehavior\terror\tA cross-Domain change claims atomicity only when it is one MutationAST on one graph; calls combined with other effects are Workflow steps\tTrace atomicity and effect claims and report multiple commits, service boundaries, or foreign calls combined in a Action\tcross-domain.md\nSCH-SUBMODULE\tschema\tdeclaration\terror\tEvery authored declaration and public callable value has exactly one Schema submodule owner\tInventory declarations and callable inputs, results, and failures and report missing, duplicate, or layer-root owners\tschema-facade.md\nSCH-DECL-ONLY\tschema\tdeclaration\terror\tSchema performs no handler execution, Query execution, Mutation submission, Integration call, Workflow step, or Provider call\tResolve Schema imports and call graphs and require zero excluded operations\tpublication-callables.md\nSCH-EXACT-TYPES\tschema\tdeclaration\terror\tSchema authoring values preserve their exact inferred DSL types instead of widening to generic builder contracts\tScan Schema declarations and action returns for explicit generic builder authoring annotations\tschema-facade.md\nSCH-POLICY-AUTH\tschema\tdeclaration\terror\tCallable authority is declared through Policy and Action implementations add no private authorization rule\tTrace callable admission and report authority conditions without one owning Policy\tpolicy.md\nSCH-STATE-SOURCE\tschema\tdeclaration\terror\tEvery StateMachine vocabulary exposed by Schema originates from its canonical States owner rather than a copied literal set\tResolve exact StateMachine origins; report same-owner exact Zod literals as indeterminate and ignore unrelated submodules or DSL method names\tarticle-schema.md\nSTA-ONE-RELATION\tstates\tdeclaration\terror\tEvery finite state topology has one immutable StateMachine relation containing its states, events, initial state, and legal transitions\tIdentify each StateMachine and report missing fields, mutable data, or duplicate transition authorities\tarticle-status.md\nSTA-PURE\tstates\tdeclaration\terror\tTransition relations contain no executable guard, permission, effect, retry policy, timer, clock, or provider behavior\tInspect transition values and resolved dependencies while treating state and event vocabulary as structural data\tterminal-states.md\nRUL-SYNC\trules\tbehavior\terror\tEvery Rule is synchronous and returns no Promise, continuation, generator, or asynchronous iterator\tInspect Rule signatures and result types and reject asynchronous forms\tnormalization.md\nRUL-PURE\trules\tbehavior\terror\tRules perform no I/O and depend on no graph executor or Domain execution boundary\tResolve imports and effect origins, permitting pure standard value transformations while reporting conclusive I/O and Domain execution dependencies\tpublish-eligibility.md\nRUL-EXPL-FACTS\trules\tbehavior\terror\tEvery clock value, random value, identity, limit, and environmental observation used by a Rule is an explicit input\tTrace every value source and report reads from ambient state, globals, process state, or hidden singletons\ttime-facts.md\nRUL-CLOSED-DEC\trules\tbehavior\terror\tEvery expected business alternative is represented by a closed decision value rather than thrown control flow\tInspect Rule branches and report expected alternatives represented by exceptions or open optional fields\tpublish-eligibility.md\nQRY-CANON\tqueries\tdataflow\terror\tEvery graph request produced by a Query definition is one canonical QueryAST built through Kernel Query APIs\tTrace request construction and reject every non-canonical graph document\tfiltered-query.md\nQRY-CONTRACT\tqueries\tdataflow\terror\tA Query owns its input, observation, optional business projection, pagination, and expected failures\tInspect each Query definition and report a required concern owned elsewhere or omitted\tquery-transforms.md\nQRY-SINGLE\tqueries\tdataflow\terror\tEvery authored single Query definition declares exactly one build callback and at most one project callback\tInspect definition properties and canonical roots; report opaque helper origins as ambiguity rather than assuming cardinality\tsingle-query.md\nQRY-COMPOSE-TYPED\tqueries\tdataflow\terror\tA composite Query constructs one closed QueryPlan containing only typed single-Query leaves, symbolic output references, and explicit result composition\tInspect plan construction, report unresolved call origins as ambiguity, and reject nested composites, raw sessions, graph clients, result-time callbacks, arbitrary promises, or Domain effects\tdependent-query.md\nQRY-COMPOSE-STABLE\tqueries\tdeclaration\terror\tEvery composite Query leaf has a stable semantic literal ID unique within its definition\tInspect declared Query leaves and reject dynamic, malformed, or duplicate leaf identifiers\tdependent-query.md\nQRY-COLL-FANOUT\tqueries\tdataflow\twarning\tThree or more independent same-kind complete-Class collection leaves use one explicit query.union group\tInfer collection kind from canonical definition types and print the direct named replacement plus logical and physical plan counts\tcollection-union.md\nQRY-PROJECT-PURE\tqueries\tdataflow\terror\tEvery Query project callback is synchronous and performs only pure value transformation\tInspect project callback syntax and call graphs and report asynchronous continuation, I/O, mutation, nondeterminism, hidden query execution, or effects\tprojection.md\nQRY-SNAPSHOT\tqueries\tdataflow\terror\tA composite or paginated Query makes no point-in-time graph snapshot claim beyond one Kernel invocation\tReject shared-snapshot claims across leaves unions or continuations and route stronger consistency to an explicit Kernel facility\tdependent-query.md\nMUT-CANON\tmutations\tdataflow\terror\tEvery Mutation definition builds one synchronous change through the canonical builder and constructs no alternate or nested graph document\tInspect every definition build callback and reject missing, asynchronous, alternate, or nested Mutation construction\tatomic-mutation.md\nMUT-CONTRACT\tmutations\tdataflow\terror\tA Mutation owns its input, preconditions, operations, optional business projection, and expected failures\tInspect each definition and report required mutation semantics owned elsewhere or omitted\tcreate-aggregate.md\nMUT-PRECONDS\tmutations\tdataflow\terror\tRules decide eligibility and every live graph condition required for safety is encoded as a precondition in the same MutationAST\tMap decisions to preconditions and report duplicated eligibility or safety enforced only by an earlier observation\tconditional-update.md\nMUT-FRAGMENTS\tmutations\tdataflow\terror\tReusable fragments receive the callback-scoped MutationBuilder and contribute only to the same MutationAST\tResolve fragment signatures and calls and reject escaped builders, opaque documents, or nested MutationAST construction\tmutation-fragments.md\nMUT-PURE\tmutations\tdataflow\terror\tMutation definitions perform no session call, external effect, retry, compensation, or second submission\tScan definition call graphs and require zero excluded operations\tedge-change.md\nMUT-MULTI-WFL\tmutations\tdataflow\terror\tA change requiring several MutationAST values is a Workflow and is never exposed as one atomic Mutation\tTrace multi-commit changes and report definitions or names claiming single-mutation atomicity\tdelete-node.md\nMUT-STATE-INITIAL\tmutations\tdataflow\terror\tEvery StateMachine-backed node is created from its machine's initial state\tResolve machine-backed Property initializers and require the canonical machine.initial value rather than a copied state literal\tstate-transition.md\nMUT-STATE-ATOMIC\tmutations\tdataflow\terror\tEvery persisted machine-state change consumes one allowed decision and commits its stale-state precondition and update in the same MutationAST\tInspect machine-backed writes and require transition with the canonical States machine and one allowed decision; reject direct target writes and split precondition updates\tstate-transition.md\nWFL-MULTISTEP\tworkflows\tbehavior\terror\tEvery Workflow definition contains at least two distinct semantic asynchronous operation sites\tInventory operation sites in the definition and report fewer than two; early exit, rejection, and recovery paths may execute fewer\tpublication-workflow.md\nWFL-STEP-EFFECTS\tworkflows\tbehavior\terror\tEvery semantic asynchronous operation maps to one named step.run boundary\tTrace effects and report operations outside step.run\tpublication-workflow.md\nWFL-STEP-IDS\tworkflows\tbehavior\terror\tEvery step identifier is a stable non-empty kebab-case literal unique within its Workflow\tExtract step identifiers and reject dynamic, malformed, empty, or duplicate values\tpublication-workflow.md\nWFL-NO-NEST\tworkflows\tbehavior\terror\tNo step.run callback invokes another step.run directly or through a reachable helper\tInspect callback call graphs and report every nested step.run path\tpublication-workflow.md\nWFL-INT-TYPES\tworkflows\tdeclaration\terror\tWorkflow Integration requirements derive from declared Integration definitions and the SDK projects their clients\tReject Workflow-owned operation interfaces and require its definition generic to reference the declared Integration registry\tcross-domain-workflow.md\nWFL-ONE-OP-STEP\tworkflows\tbehavior\terror\tEvery step owns exactly one semantic asynchronous operation\tTrace each step and report zero or several independently failing operations\tpublication-workflow.md\nWFL-STATE-CODEC\tworkflows\tbehavior\terror\tEvery step result and inter-step value is undefined or portable JSON data\tInspect step values and report actions, clients, symbols, bigint, cycles, or class instances\tpublication-workflow.md\nWFL-RECOVERY\tworkflows\tbehavior\terror\tWhen compensation or unknown-outcome branches exist, they are explicit and rely only on declared operation semantics\tInspect existing recovery branches and report guessed outcomes or compensation without exact evidence\tpublication-workflow.md\nWFL-XDOM-INT\tworkflows\tbehavior\terror\tA Workflow invokes another Domain only through a declared Integration and imports no foreign Domain\tResolve Workflow imports and operations and report foreign Domain imports or calls without Integration ownership\tcross-domain-workflow.md\nMIG-EXACT-REVS\tmigrations\tdataflow\terror\tEvery Migration declares exactly one source revision and one target revision for the same Domain origin\tParse Migration descriptors and reject missing, equal, ambiguous, cross-origin, or dynamically selected revisions\texact-revisions.md\nMIG-APP-DATA\tmigrations\tdataflow\terror\tMigrations transform application-owned facts only and own no schema projection, physical representation, deployment, backup, or external import\tClassify every transformed value and operation and report responsibilities outside application data\trewrite-values.md\nMIG-DEDICATED-CTX\tmigrations\tdataflow\terror\tMigration execution uses only its dedicated source reader, target writer, durable step context, revision-compatible Rules, and generic Utils\tResolve imports and calls and reject ordinary Actions, Workflows, Queries, Mutations, Integrations, Providers, Views, UI, or provider access\texact-revisions.md\nMIG-RESTART-SAFE\tmigrations\tdataflow\terror\tEvery Migration page is idempotent and resumes from a stable checkpoint without reinterpreting accepted target output\tInterrupt before and after every page commit, resume repeatedly, and compare target facts and checkpoints with uninterrupted execution\trestart-safe.md\nMIG-TARGET-PROOF\tmigrations\tdataflow\terror\tA Migration completes only after bounded source coverage and target-revision validation account for every selected source fact\tInject omitted, duplicated, invalid, and over-budget facts and require completion to fail with exact evidence\tverify-target.md\nMIG-DESTRUCTIVE\tmigrations\tdataflow\terror\tEvery destructive or irreversible transform declares and accounts for its intended loss and exposes declared expected failures\tTrace removals and irreversible rewrites and report undeclared or unaccounted loss, open failures, or catch-and-continue behavior\tremove-facts.md\nINT-BOUNDARY\tintegrations\tarchitecture\terror\tEvery Integration represents required behavior across an external, remote-Domain, substitution, process, or trust boundary\tIdentify the boundary for every contract and report behavior that can remain ordinary local Domain code\tpayment-authorization.md\nINT-NEUTRAL\tintegrations\tdeclaration\terror\tEvery Integration owns provider-neutral requests, evidence, correlation, idempotency, trust, and expected failure semantics needed by consumers\tInspect consumers and contracts and report provider fields or missing stable boundary semantics\tid-allocation.md\nINT-PURE\tintegrations\tdependency\terror\tIntegrations remain provider- and execution-boundary-neutral and import no concrete client, credential, configuration, Provider, Action, or Workflow\tResolve runtime and provider imports and report every conclusive concrete or execution-boundary dependency\tdocument-signing.md\nINT-ABILITY-NAME\tintegrations\tstructure\terror\tIntegration submodules are named by required ability and never mirror Schema entities mechanically\tCompare Integration and Schema submodules and report a contract without an independent boundary ability\tobject-storage.md\nACT-ONE-IMPL\tactions\tbehavior\terror\tEvery registered handler binding resolves to exactly one implementation owned by one semantic Actions submodule\tResolve the handler registry and report non-Action owners or duplicate registrations; SDK typing owns callable exhaustiveness\tsynchronous-result.md\nACT-ONE-OP\tactions\tbehavior\terror\tEvery Action performs one terminal semantic operation; only a native binary compatibility Action may precede its Integration call with one exact-receiver Query\tTrace every path and report several terminal effects, writes before effects, non-receiver prerequisite reads, or hidden orchestration\tsingle-operation.md\nACT-OP-KINDS\tactions\tbehavior\terror\tA semantic asynchronous operation is one Query definition, Mutation definition, or Integration call\tClassify every awaited effect and report operations outside the three allowed kinds\tintegration-operation.md\nACT-BOUNDARIES\tactions\tbehavior\terror\tActions use only invocation-bound query and mutate executors, including explicit caller self or union graph partitions, plus declared Integrations; Providers or foreign Domains are never imported\tInspect context use, authority-mode selection, dependencies, imports, and failure mapping; report raw or unbound graph executors, implicit privilege, concrete or foreign dependencies, broad mapping, or swallowed defects\tremote-integration.md\nACT-BINARY-RESULT\tactions\tbehavior\terror\tA binary Action returns detached buffered bytes or streaming bytes with admitted media type status and application headers without collecting or value-framing a provider stream\tExercise buffered and streaming results through their native route; verify exact bytes status headers cancellation and invalid output rejection\tnative-byte-response.md\nRTE-RECIPE\troutes\tdeclaration\terror\tEvery native HTTP Route targets exactly one admitted Action or Workflow recipe\tResolve every route target and report forged missing or several recipes\taction-route.md\nRTE-MAPPING\troutes\tdataflow\terror\tEvery callable input field and instance receiver is mapped exactly once from path query header method or body input\tCompile each route against its resolved callable and reject incomplete ambiguous or unknown mappings\tinstance-route.md\nRTE-CREDENTIAL\troutes\tdataflow\terror\tEvery external credential source is explicit and is never also mapped as callable input\tCompile credential and input mappings and reject duplicate reserved or implicit authority sources\tcredential-route.md\nRTE-ONE-FILE\troutes\tstructure\terror\tEvery Route declaration has one business-intent kebab-case file and Routes composition contains no inline declaration\tInventory route declarations and report generic grouped or inline ownership\tapplication-routes.md\nPRV-INT-IMPL\tproviders\tarchitecture\terror\tEvery Provider implements one or more declared Integrations across a named external or remote-Domain boundary\tIdentify the boundary and implemented contracts and report a Provider without both\tpayment-provider.md\nPRV-BOUNDARY-NAME\tproviders\tstructure\terror\tProvider submodules are named by external system, protocol, or trust boundary rather than Domain entity\tInspect every submodule name and report one justified only by Schema vocabulary\tobject-storage-provider.md\nPRV-ADMIT-RESULT\tproviders\tdataflow\terror\tEvery boundary value is admitted before Integration evidence and recognized boundary failures become stable Integration failures\tTrace input and failure paths and report missing admission, leaked boundary errors, broad mapping, or swallowed defects\twebhook-admission.md\nPRV-NO-DOMAIN\tproviders\tdependency\terror\tProviders perform no local Domain graph operation, business decision, local Action call, or Workflow orchestration\tResolve Provider imports and call graphs and require zero excluded operations\tboundary-composition.md\nPRV-XDOM-TYPED\tproviders\tdependency\terror\tRemote-Domain Providers pass SDK-derived typed callable references to the caller-bound invocation capability without never casts\tInspect Provider execution invoke calls and reject references not constructed through SDK reference, opaque or fabricated references, and never-cast arguments\tremote-domain.md\nPRV-XDOM-REQ\tproviders\tdependency\terror\tEvery statically resolved remote-Domain Provider invocation has one exact Application callable requirement\tMatch each invocation-bound public callable reference to the same foreign facade and callable selector under Application requirements; preserve opaque references or composition as indeterminate\tremote-domain.md\nPRV-XDOM-PUBLIC\tproviders\tdependency\terror\tA remote-Domain Provider uses the remote public facade and invokes at most one public callable per Integration operation\tResolve receiver and facade origins for each remote operation and report private access, missing Integration ownership, unrelated calls, or several public invocations\tremote-domain.md\nVIW-PROJECTION\tviews\tdeclaration\terror\tEvery View converts public Domain values or named Query observations into UI props and connects each action to a named bounded Mutation or public callable contract\tInspect View outputs and actions and report behavior without a Schema, Query, UI, Mutation, Rule, or public callable owner\tdetail-view.md\nVIW-SCHEMA-DECL\tviews\tdeclaration\terror\tSchema owns every DSL View declaration and Views declares no Domain schema construct\tScan View declarations and reject Classes, Properties, Policies, Methods, Actions, or DSL Views\tarticle-list.md\nVIW-DEPS\tviews\tdependency\terror\tViews import only Schema, States, pure Rules, named Queries, bounded named Mutations, UI, shell-react primitives, public callable contracts, and presentation libraries\tInspect resolved imports and report dependencies or raw graph APIs outside the allowlist\tnavigation.md\nVIW-ACTIONS\tviews\tdataflow\terror\tEvery View action executes a named caller-authorized atomic Mutation or invokes a public callable; presentation state is never authorization evidence\tTrace action and authority paths and report inline or raw graph documents, elevated or effectful Mutation use, private Action imports, direct Integration calls, or authority derived from presentation\tform-actions.md\nUI-PRES-DEPS\tui\tdependency\terror\tEvery UI import is a sibling UI module or an external library used only for presentation\tInspect resolved imports and used APIs and report Domain, I/O, persistence, authorization, routing, or provider behavior\tstatus-panel.md\nUI-NO-DOMAIN\tui\tdependency\terror\tUI imports no Domain layer or Domain package facade\tResolve the UI import graph and require zero Domain paths\tresult-list.md\nUI-PURE\tui\tbehavior\terror\tUI performs no I/O, graph access, storage, authorization, business validation, provider call, routing, or Domain result mapping\tInspect components and hooks and report the exact excluded call or branch\tconfirmation-dialog.md\nUI-CALLBACKS\tui\tdataflow\terror\tUI accepts presentation props and forwards intent through callbacks without constructing Domain commands\tInspect props and event handlers and report Domain identities, command construction, or business decisions\tform.md\nUTL-DOM-AGNOSTIC\tutils\tarchitecture\terror\tEvery Utils export is Domain-agnostic and contains no Domain declaration, identity, decision, graph definition, or Workflow step\tInspect signatures and implementations and report the exact Domain concept or business behavior\tresult.md\nUTL-REUSED\tutils\tarchitecture\terror\tEvery Utils extension serves two independent semantic consumers or one package-wide execution boundary\tList direct consumers and report an extension satisfying neither condition\tjson-codec.md\nUTL-PUBLIC-DEPS\tutils\tdependency\terror\tUtils imports only public Kernel, DSL, SDK, standard-library, and sibling Utils modules\tResolve the Utils import graph and reject Domain layers, Providers, or private package paths\tjson-codec.md\nUTL-LIGHTWEIGHT\tutils\tarchitecture\terror\tUtils is private and owns no durable engine, planner, compiler, Repository, service locator, or dependency container\tInspect package exports and declarations and report public exposure or any forbidden framework machinery\tresult.md\nSCR-OPERATOR\tscripts\tbehavior\terror\tEvery root Script implements a supported operator workflow with declared inputs, outcomes, failures, and proof\tInspect each Script contract and report disposable tasks or missing operator semantics\tbackup.md\nSCR-PUBLIC-DEPS\tscripts\tbehavior\terror\tScripts import only dependency-DAG-authorized public facades, standard libraries, and operator libraries and define no Domain behavior\tResolve imports and inspect branches and report private paths, business decisions, graph definitions, or callable behavior\tupgrade.md\nSCR-TEST-DRIVERS\tscripts\tbehavior\terror\tDisposable setup, fixtures, resets, and scenario drivers live under tests/scripts rather than root Scripts\tClassify every Script by supported operator contract and report evidence-only programs at the root\trestore.md\nTST-SYS-OWNER\ttests\tevidence\terror\tRoot Tests owns cross-layer journeys, environments, harnesses, scenarios, seeds, fixtures, and disposable scripts\tInventory evidence support code and report cross-layer artifacts outside Tests or production semantics inside Tests\tmigration-scenario.md\nTST-COLOCATED\ttests\tevidence\terror\tEvery semantic production submodule owns focused evidence in its local __tests__ directory\tJoin production submodules to focused test directories and report missing owners\tfocused-rule.md\n",
5
5
  layers: frozenRows([
6
6
  {
7
7
  id: 'schema',
@@ -211,7 +211,7 @@ export const domainPolicySource = Object.freeze({
211
211
  source: 'migrations',
212
212
  target: 'states',
213
213
  kind: 'type-only',
214
- condition: 'Migration transforms preserve lifecycle vocabulary',
214
+ condition: 'Migration transforms preserve machine-state vocabulary',
215
215
  },
216
216
  {
217
217
  source: 'migrations',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.5.0-beta.65",
3
+ "version": "0.5.0-beta.66",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",