@astrale-os/sdk 0.6.0-beta.27 → 0.6.0-beta.29

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/README.md CHANGED
@@ -95,6 +95,66 @@ valid; the linter emits a non-failing `SCH-ICON-NEUTRAL` warning. Bare names suc
95
95
  non-canonical empty SVGs, external references, and executable SVG content are rejected during
96
96
  Schema authoring.
97
97
 
98
+ ## Read rules and caller rules
99
+
100
+ Every concrete Class authored through the SDK declares who may read it, and every executable
101
+ `authorized` callable declares who may call it. Omission is rejected by TypeScript and at the
102
+ runtime authoring boundary. A rule is a predicate on the caller: a Policy, or its constant value
103
+ `true` or `false`.
104
+
105
+ ```typescript
106
+ import { classIcon, func, nodeClass } from '@astrale-os/sdk/schema'
107
+ import { FileText, Lock } from '@astrale-os/sdk/schema/icons'
108
+ import { z } from 'zod'
109
+
110
+ export const Article = nodeClass({
111
+ icon: classIcon.lucide(FileText),
112
+ policies: { read: true },
113
+ })
114
+
115
+ export const Secret = nodeClass({
116
+ icon: classIcon.lucide(Lock),
117
+ policies: { read: false, traverse: false },
118
+ })
119
+
120
+ export const purge = func({
121
+ auth: 'authorized',
122
+ policy: false,
123
+ input: z.strictObject({}),
124
+ output: z.boolean(),
125
+ })
126
+ ```
127
+
128
+ A Class rule judges the caller, which passes by owning the Class's installation or by the rule,
129
+ never by a capability it holds. When a Domain acts for a caller, the Domain must also hold its own
130
+ right on the Class (the capability its requirements declare, or ownership), whatever the rule; an
131
+ Identity acting for itself delegates nothing and meets no such ceiling. A callable reads the same
132
+ way, except that its principal always holds its right (`can_use` or ownership). `true` admits
133
+ every Identity of the instance, never an anonymous request.
134
+
135
+ | Declared | Caller is admitted when |
136
+ | --- | --- |
137
+ | Class `read: true` | always |
138
+ | Class `read: false` | it is the owning installation |
139
+ | Class `read: aPolicy` | ownership or the Policy |
140
+ | Callable `policy: false` | it is Root or the owning installation |
141
+ | Callable `policy: true` | always |
142
+ | Callable `policy: checks` | Root, ownership, or the checks |
143
+
144
+ - `read: true` and `policy: false` are what an absent rule already meant. Writing them changes
145
+ neither the compiled Schema nor its revision.
146
+ - An excluded caller stays denied: `true` holds only for a caller whose live composition is not
147
+ denied.
148
+ - `read` governs values, `traverse` governs reachability: a Node is reachable, and its reference
149
+ selectable, when either rule admits the caller. A Class whose `read` is not `true` must
150
+ therefore declare `traverse` as well: `traverse: false` hides that its Nodes exist,
151
+ `traverse: true` keeps them reachable for every caller. Beside `read: true`, `traverse` stays
152
+ optional.
153
+ - An abstract Class declares no rule. An abstract Method declares its caller rule only where a
154
+ concrete Class executes it.
155
+ - `read: false`, `traverse: false` and `policy: true` need a Kernel that knows the two constants.
156
+ Earlier Kernels refuse such a Schema at installation.
157
+
98
158
  ## Business-error declarations
99
159
 
100
160
  Declare reusable business errors in `schema/modules/<module>/errors/` through the Schema facade:
@@ -263,15 +323,13 @@ evaluated against the caller. Kernel callables, including graph reads and writes
263
323
  credential.
264
324
 
265
325
  ```ts
266
- import { connect, createSessionCredentialProvider } from '@astrale-os/sdk/client/session'
326
+ import { connect, credentials } from '@astrale-os/sdk/client/session'
267
327
 
268
328
  const session = connect({
269
329
  url: 'https://instance.example/api',
270
- auth: createSessionCredentialProvider({
271
- ttlSeconds: 60,
272
- // Return { credential, expiresAt } with expiresAt in Unix milliseconds.
273
- mint: (signal) => mintSourceCredential(signal),
274
- }),
330
+ // A fixed `astrale token`; pass a mint instead, `credentials((signal) => fetchToken(signal))`,
331
+ // for a script that outlives it.
332
+ auth: credentials(token),
275
333
  exchange: { mode: 'auto' },
276
334
  })
277
335
 
@@ -279,16 +337,19 @@ await session.call({ target: '/:orders.example:class.Order:create', input }) //
279
337
  await session.graph.get('@order-id') // Kernel callable: the source credential
280
338
  ```
281
339
 
282
- - **Hold the source in a provider.** Domain credentials are cached per source credential, so a
283
- `resolve` that returns a new token on every call also exchanges on every call. The provider also
284
- lets the Session renew a source that is too close to expiry to feed a Domain credential.
340
+ - **Hold the source in `credentials`.** It takes a fixed credential (a compact JWT whose `exp` is
341
+ its expiry), a mint returning a JWT or `{ credential, expiresAt }` in Unix milliseconds, or options
342
+ (`ttlSeconds`, default 60; `mint`; `initial`; `onChange`). Domain credentials are cached per source
343
+ credential, so a `resolve` that returns a new token on every call also exchanges on every call.
344
+ With a mint, `credentials` also lets the Session renew a source that is too close to expiry to
345
+ feed a Domain credential; a fixed credential fails once it no longer covers a call.
285
346
  - **Lifetimes.** In `exchange: { mode: 'auto', ttlSeconds }`, `ttlSeconds` (default 300) must exceed
286
- the provider's `ttlSeconds` plus a 5-second settlement skew. A Domain credential never outlives its
347
+ the `ttlSeconds` of `credentials` plus a 5-second settlement skew. A Domain credential never outlives its
287
348
  source. `exchange: { mode: 'source' }` keeps the source credential for every Call and takes no
288
349
  `ttlSeconds`; an empty `{}` or a bare `{ ttlSeconds }` is rejected.
289
350
  - **Reading as a Domain.** To call Kernel callables as a Domain, for example to read Shell-owned
290
351
  Classes, feed a second Session from an explicit exchange:
291
- `createSessionCredentialProvider({ ttlSeconds: 60, mint: (signal) => session.exchange(shellIssuer, { signal }) })`.
352
+ `credentials((signal) => session.exchange(shellIssuer, { signal }))`.
292
353
  `session.exchange(issuer)` caches nothing.
293
354
  - **Current identity.** Use `session.auth.whoami()`; `@self` is a CLI shorthand.
294
355
  - **Failures.** A failed exchange raises `ExchangeError` from `@astrale-os/sdk/client`. Its
@@ -16,6 +16,11 @@ export interface VerificationBasisResolver {
16
16
  /**
17
17
  * Retain only recently admitted public issuer material for one execution Identity.
18
18
  * An unseen key ID may refresh once per cooldown; failures never replace a usable basis.
19
+ *
20
+ * Callers share admitted bases, never a discovery in flight: each refresh runs on its own caller's
21
+ * fetch and signal. A Worker binds a subrequest to the request that started it and, once that
22
+ * request is cancelled, never settles it, so a shared refresh would hold every later caller of the
23
+ * issuer until its own deadline, for as long as the isolate lives.
19
24
  */
20
25
  export declare function createVerificationBasisResolver(fetch: Fetch, policy: IdentityNetworkPolicy, now?: () => number): VerificationBasisResolver;
21
26
  export declare function verifyCredential(input: CredentialInput, audience: IssuerId, bases: VerificationBasisResolver, signal: AbortSignal, policy: IdentityNetworkPolicy): Promise<{
@@ -22,10 +22,23 @@ const TRANSIENT_TRANSPORT_CODES = new Set([
22
22
  /**
23
23
  * Retain only recently admitted public issuer material for one execution Identity.
24
24
  * An unseen key ID may refresh once per cooldown; failures never replace a usable basis.
25
+ *
26
+ * Callers share admitted bases, never a discovery in flight: each refresh runs on its own caller's
27
+ * fetch and signal. A Worker binds a subrequest to the request that started it and, once that
28
+ * request is cancelled, never settles it, so a shared refresh would hold every later caller of the
29
+ * issuer until its own deadline, for as long as the isolate lives.
25
30
  */
26
31
  export function createVerificationBasisResolver(fetch, policy, now = Date.now) {
27
32
  const bases = new Map();
28
- const pending = new Map();
33
+ const retain = (issuer, entry) => {
34
+ if (bases.size >= MAXIMUM_VERIFICATION_BASES && !bases.has(issuer)) {
35
+ const oldest = bases.keys().next().value;
36
+ if (oldest !== undefined)
37
+ bases.delete(oldest);
38
+ }
39
+ bases.delete(issuer);
40
+ bases.set(issuer, Object.freeze(entry));
41
+ };
29
42
  return Object.freeze({
30
43
  async resolve(issuer, keyId, signal) {
31
44
  signal.throwIfAborted();
@@ -38,66 +51,33 @@ export function createVerificationBasisResolver(fetch, policy, now = Date.now) {
38
51
  const unknownKeyCooldown = cached?.unknownKeyRefreshedAt !== undefined &&
39
52
  observedAt - cached.unknownKeyRefreshedAt >= 0 &&
40
53
  observedAt - cached.unknownKeyRefreshedAt < UNKNOWN_KEY_REFRESH_COOLDOWN_MS;
41
- if (fresh && knownKey) {
42
- bases.delete(issuer);
43
- bases.set(issuer, cached);
54
+ if (fresh && (knownKey || unknownKeyCooldown)) {
55
+ retain(issuer, cached);
44
56
  return cached.basis;
45
57
  }
46
58
  const unknownKeyRefresh = cached !== undefined && !knownKey;
47
- let refresh = pending.get(issuer);
48
- if (refresh === undefined && fresh && unknownKeyCooldown) {
49
- bases.delete(issuer);
50
- bases.set(issuer, cached);
51
- return cached.basis;
52
- }
53
- if (refresh === undefined && unknownKeyRefresh && cached !== undefined) {
54
- bases.set(issuer, Object.freeze({ ...cached, unknownKeyRefreshedAt: observedAt }));
59
+ let basis;
60
+ try {
61
+ basis = await discover(issuer, fetch, signal, policy);
55
62
  }
56
- if (refresh === undefined) {
57
- const controller = new AbortController();
58
- refresh = {
59
- controller,
60
- operation: undefined,
61
- subscribers: 0,
62
- settled: false,
63
- unknownKeyRefresh,
64
- requestedKeyIds: new Set(keyId === undefined ? [] : [keyId]),
65
- };
66
- const owned = refresh;
67
- owned.operation = discover(issuer, fetch, controller.signal, policy)
68
- .then((basis) => {
69
- controller.signal.throwIfAborted();
70
- const refreshedAt = now();
71
- if (bases.size >= MAXIMUM_VERIFICATION_BASES && !bases.has(issuer)) {
72
- const oldest = bases.keys().next().value;
73
- if (oldest !== undefined)
74
- bases.delete(oldest);
75
- }
76
- bases.delete(issuer);
77
- bases.set(issuer, Object.freeze({
78
- basis,
79
- refreshedAt,
80
- ...(owned.unknownKeyRefresh ||
81
- [...owned.requestedKeyIds].some((requested) => !basis.keys.some((candidate) => candidate.kid === requested))
82
- ? { unknownKeyRefreshedAt: refreshedAt }
83
- : {}),
84
- }));
85
- return basis;
86
- })
87
- .finally(() => {
88
- owned.settled = true;
89
- if (pending.get(issuer) === owned)
90
- pending.delete(issuer);
91
- });
92
- pending.set(issuer, owned);
93
- }
94
- else if (unknownKeyRefresh) {
95
- refresh.unknownKeyRefresh = true;
63
+ catch (cause) {
64
+ // A failed unknown-key refresh spends the cooldown and keeps the usable basis.
65
+ const current = bases.get(issuer);
66
+ if (unknownKeyRefresh && !signal.aborted && current !== undefined) {
67
+ retain(issuer, { ...current, unknownKeyRefreshedAt: observedAt });
68
+ }
69
+ throw cause;
96
70
  }
97
- if (keyId !== undefined)
98
- refresh.requestedKeyIds.add(keyId);
99
- const basis = await subscribe(refresh, issuer, pending, signal);
100
71
  signal.throwIfAborted();
72
+ const refreshedAt = now();
73
+ retain(issuer, {
74
+ basis,
75
+ refreshedAt,
76
+ ...(unknownKeyRefresh ||
77
+ (keyId !== undefined && !basis.keys.some((candidate) => candidate.kid === keyId))
78
+ ? { unknownKeyRefreshedAt: refreshedAt }
79
+ : {}),
80
+ });
101
81
  return basis;
102
82
  },
103
83
  });
@@ -127,41 +107,6 @@ export async function verifyCredential(input, audience, bases, signal, policy) {
127
107
  admitEndpoint(`${admittedIssuer.replace(/\/+$/u, '')}/invoke`, policy);
128
108
  return Object.freeze({ raw: input, verified: verified.verified, basis });
129
109
  }
130
- function subscribe(refresh, issuer, pending, signal) {
131
- signal.throwIfAborted();
132
- refresh.subscribers += 1;
133
- return new Promise((resolve, reject) => {
134
- let complete = false;
135
- const finish = () => {
136
- if (complete)
137
- return false;
138
- complete = true;
139
- signal.removeEventListener('abort', cancel);
140
- refresh.subscribers -= 1;
141
- if (!refresh.settled && refresh.subscribers === 0) {
142
- if (pending.get(issuer) === refresh)
143
- pending.delete(issuer);
144
- refresh.controller.abort(new DOMException('Issuer discovery was cancelled.', 'AbortError'));
145
- }
146
- return true;
147
- };
148
- const cancel = () => {
149
- if (!finish())
150
- return;
151
- reject(signal.reason);
152
- };
153
- signal.addEventListener('abort', cancel, { once: true });
154
- refresh.operation.then((basis) => {
155
- if (!finish())
156
- return;
157
- resolve(basis);
158
- }, (cause) => {
159
- if (!finish())
160
- return;
161
- reject(cause);
162
- });
163
- });
164
- }
165
110
  export async function verifyCredentialAgainst(input, audience, basis, preinspected) {
166
111
  let inspected;
167
112
  try {
@@ -246,7 +191,7 @@ async function request(fetch, url, signal) {
246
191
  throw signal.reason;
247
192
  if (cause instanceof IssuerUnavailable)
248
193
  throw cause;
249
- // One retry belongs to the shared issuer load, never to each observer or business call.
194
+ // One retry belongs to the caller's issuer load, never to its business call.
250
195
  if (attempt !== 0 || !transientTransport(cause)) {
251
196
  throw new IssuerUnavailable('Issuer discovery request failed.', cause);
252
197
  }
@@ -0,0 +1,21 @@
1
+ import { func as kernelFunc, method as kernelMethod, type CallablePolicyBuilder, type FunctionInput, type Method, type MethodInput } from '@astrale-os/kernel-dsl/v1/builder';
2
+ /** Who may call an authorized callable: Policy checks, or the constant `true` or `false`. */
3
+ export type CallerRule = CallablePolicyBuilder | boolean;
4
+ /**
5
+ * An executable `authorized` callable declares its caller rule. An abstract Method is a contract
6
+ * on an abstract Class and executable on a concrete one, so its rule is settled by the Class that
7
+ * declares it.
8
+ */
9
+ export type DeclaredCallerRule<Config> = Config extends {
10
+ readonly auth: 'authorized';
11
+ } ? Config extends {
12
+ readonly abstract: true;
13
+ } ? unknown : {
14
+ readonly policy: CallerRule;
15
+ } : unknown;
16
+ /** Author a Function. An `authorized` Function declares its caller rule. */
17
+ export declare function func<const Config extends FunctionInput>(config: Config & DeclaredCallerRule<Config>): ReturnType<typeof kernelFunc<Config>>;
18
+ /** Author a Method. An executable `authorized` Method declares its caller rule. */
19
+ export declare function method<const Config extends MethodInput>(config: Config & DeclaredCallerRule<Config>): ReturnType<typeof kernelMethod<Config>>;
20
+ /** A concrete Class executes every Method it declares, abstract contracts included. */
21
+ export declare function assertNoUndeclaredCallerRule(methods: Readonly<Record<string, Method>> | undefined): void;
@@ -0,0 +1,27 @@
1
+ import { func as kernelFunc, method as kernelMethod, } from '@astrale-os/kernel-dsl/v1/builder';
2
+ /** Authorized abstract Methods authored without a caller rule, until a Class declares them. */
3
+ const undeclared = new WeakSet();
4
+ export function func(config) {
5
+ if (config.auth === 'authorized' && config.policy === undefined)
6
+ undeclaredCallerRule('Function');
7
+ return kernelFunc(config);
8
+ }
9
+ export function method(config) {
10
+ const value = kernelMethod(config);
11
+ if (config.auth === 'authorized' && config.policy === undefined) {
12
+ if (config.abstract !== true)
13
+ undeclaredCallerRule('Method');
14
+ undeclared.add(value);
15
+ }
16
+ return value;
17
+ }
18
+ /** A concrete Class executes every Method it declares, abstract contracts included. */
19
+ export function assertNoUndeclaredCallerRule(methods) {
20
+ for (const [name, value] of Object.entries(methods ?? {})) {
21
+ if (undeclared.has(value))
22
+ undeclaredCallerRule(`Method ${name}`);
23
+ }
24
+ }
25
+ function undeclaredCallerRule(subject) {
26
+ throw new TypeError(`${subject} is authorized and executable: declare its policy with Policy checks, true or false.`);
27
+ }
@@ -1,36 +1,86 @@
1
- import { nodeClass as kernelNodeClass, type DirectedEdgeClass, type DirectedEdgeInput, type NodeClass, type NodeClassInput as KernelNodeClassInput, type UndirectedEdgeClass, type UndirectedEdgeInput } from '@astrale-os/kernel-dsl/v1/builder';
1
+ import { edgeClass as kernelEdgeClass, nodeClass as kernelNodeClass, type DirectedEdgeClass, type DirectedEdgeInput, type Lazy, type NodeClass, type NodeClassInput as KernelNodeClassInput, type NodeClassPolicyInput, type Policy, type UndirectedEdgeClass, type UndirectedEdgeInput } from '@astrale-os/kernel-dsl/v1/builder';
2
2
  import { type ClassIcon } from './class-icon.js';
3
- /** SDK Node Class input: every author must make an explicit, admitted icon choice. */
3
+ /**
4
+ * SDK Node Class input: every author must make an explicit, admitted icon choice, and every
5
+ * concrete Class must declare its read rule.
6
+ */
4
7
  export type NodeClassInput = Omit<KernelNodeClassInput, 'icon'> & {
5
8
  readonly icon: ClassIcon;
6
9
  };
10
+ /** Who may read or traverse an Edge Class: a named Policy, or the constant `true` or `false`. */
11
+ export type ClassReadRule = Lazy<Policy> | boolean;
12
+ /**
13
+ * Who may read or traverse a Node Class: a named Policy, the constant `true` or `false`, or Policy
14
+ * checks over the observed Node (`self`) and fixed members (`ref(...)`), composed as the checks of
15
+ * an authorized callable.
16
+ */
17
+ export type NodeClassReadRule = NodeClassPolicyInput;
18
+ /**
19
+ * The observation rules of a concrete Class. A Node stays traversable through either rule, so a
20
+ * read rule other than `true` leaves every caller able to reach it, and to select its reference,
21
+ * until the traverse rule is declared too.
22
+ */
23
+ export type DeclaredObservation<Rule = ClassReadRule> = {
24
+ readonly read: true;
25
+ readonly traverse?: Rule;
26
+ } | {
27
+ readonly read: Exclude<Rule, true>;
28
+ readonly traverse: Rule;
29
+ };
30
+ /**
31
+ * A concrete Class declares its read rule, and its traverse rule whenever the read rule is not
32
+ * `true`. An abstract Class is never observed directly, so it declares neither.
33
+ */
34
+ export type DeclaredReadRule<Config, Rule = ClassReadRule> = Config extends {
35
+ readonly abstract: true;
36
+ } ? unknown : {
37
+ readonly policies: DeclaredObservation<Rule>;
38
+ };
7
39
  /** Preserve authored input instead of serializing expanded inheritance projections in consumers. */
8
40
  export interface AuthoredNodeClass<Config extends NodeClassInput> extends ReturnType<typeof kernelNodeClass<Config>> {
9
41
  }
42
+ type DirectedOptions = Omit<DirectedEdgeInput, 'orientation'>;
43
+ type UndirectedOptions = Omit<UndirectedEdgeInput, 'orientation'>;
44
+ /** The two states `defineClass` admits: abstract, or concrete with declared observation rules. */
45
+ type Declared<Input, Rule = ClassReadRule> = (Input & {
46
+ readonly abstract: true;
47
+ }) | (Input & {
48
+ readonly abstract: false;
49
+ } & {
50
+ readonly policies: DeclaredObservation<Rule>;
51
+ });
10
52
  type NodeInput = {
11
53
  readonly kind: 'node';
12
- readonly abstract: boolean;
13
- } & NodeClassInput;
54
+ } & Declared<NodeClassInput, NodeClassReadRule>;
14
55
  type DirectedInput = {
15
56
  readonly kind: 'edge';
16
57
  readonly orientation: 'directed';
17
- readonly abstract: boolean;
18
- } & Omit<DirectedEdgeInput, 'orientation'>;
58
+ } & Declared<DirectedOptions>;
19
59
  type UndirectedInput = {
20
60
  readonly kind: 'edge';
21
61
  readonly orientation: 'undirected';
22
- readonly abstract: boolean;
23
- } & Omit<UndirectedEdgeInput, 'orientation'>;
62
+ } & Declared<UndirectedOptions>;
24
63
  /**
25
64
  * Author a Node Class whose materialized configuration always carries an explicitly selected,
26
- * admitted SVG icon. The mapped input retains inference while expanding package-private
27
- * Config interface names into portable structure for downstream declaration emit.
65
+ * admitted SVG icon and, when concrete, a declared read rule. The mapped input retains inference
66
+ * while expanding package-private Config interface names into portable structure for downstream
67
+ * declaration emit.
28
68
  */
29
- export declare function nodeClass<const Config extends NodeClassInput>(config: Config & Record<Exclude<keyof Config, keyof NodeClassInput>, never>): AuthoredNodeClass<{
69
+ export declare function nodeClass<const Config extends NodeClassInput>(config: Config & Record<Exclude<keyof Config, keyof NodeClassInput>, never> & DeclaredReadRule<Config, NodeClassReadRule>): AuthoredNodeClass<{
30
70
  [Key in keyof Config]: Key extends 'properties' | 'methods' | 'policies' ? {
31
71
  [Member in keyof NonNullable<Config[Key]>]: NonNullable<Config[Key]>[Member];
32
72
  } | Extract<Config[Key], undefined> : Key extends 'extends' ? Config[Key] extends infer Parents extends NodeClassInput['extends'] ? Parents extends readonly unknown[] ? readonly [...Parents] : Parents : never : Config[Key];
33
73
  }>;
74
+ declare function directed<const Input extends DirectedOptions>(input: Input & Parameters<typeof kernelEdgeClass.directed<Input>>[0] & DeclaredReadRule<Input>): ReturnType<typeof kernelEdgeClass.directed<Input>>;
75
+ declare function undirected<const Input extends UndirectedOptions>(input: Input & Parameters<typeof kernelEdgeClass.undirected<Input>>[0] & DeclaredReadRule<Input>): ReturnType<typeof kernelEdgeClass.undirected<Input>>;
76
+ /**
77
+ * Author an Edge Class. A concrete Edge Class declares its read rule exactly as a Node Class
78
+ * does; the handle is the canonical DSL handle.
79
+ */
80
+ export declare const edgeClass: Readonly<{
81
+ directed: typeof directed;
82
+ undirected: typeof undirected;
83
+ }>;
34
84
  export declare function defineClass(config: NodeInput): NodeClass;
35
85
  export declare function defineClass(config: DirectedInput): DirectedEdgeClass;
36
86
  export declare function defineClass(config: UndirectedInput): UndirectedEdgeClass;
@@ -1,20 +1,53 @@
1
- import { defineClass as defineKernelClass, nodeClass as kernelNodeClass, } from '@astrale-os/kernel-dsl/v1/builder';
1
+ import { edgeClass as kernelEdgeClass, nodeClass as kernelNodeClass, } from '@astrale-os/kernel-dsl/v1/builder';
2
+ import { assertNoUndeclaredCallerRule } from './callable-authoring.js';
2
3
  import { classIcon } from './class-icon.js';
3
4
  export function nodeClass(config) {
4
5
  if (config === undefined || config.icon === undefined) {
5
6
  throw new TypeError('Node Class icon is required; use classIcon.neutral to make an explicit neutral choice.');
6
7
  }
8
+ assertDeclaredReadRule('Node', config);
9
+ if (config.abstract !== true)
10
+ assertNoUndeclaredCallerRule(config.methods);
7
11
  return kernelNodeClass({
8
12
  ...config,
9
13
  icon: classIcon.svg(config.icon),
10
14
  });
11
15
  }
16
+ function directed(input) {
17
+ assertDeclaredReadRule('Edge', input);
18
+ return kernelEdgeClass.directed(input);
19
+ }
20
+ function undirected(input) {
21
+ assertDeclaredReadRule('Edge', input);
22
+ return kernelEdgeClass.undirected(input);
23
+ }
24
+ /**
25
+ * Author an Edge Class. A concrete Edge Class declares its read rule exactly as a Node Class
26
+ * does; the handle is the canonical DSL handle.
27
+ */
28
+ export const edgeClass = Object.freeze({ directed, undirected });
12
29
  export function defineClass(config) {
13
30
  if (config.kind === 'node') {
14
31
  const { kind: _, ...input } = config;
15
32
  return nodeClass(input);
16
33
  }
17
- if (config.orientation === 'directed')
18
- return defineKernelClass(config);
19
- return defineKernelClass(config);
34
+ if (config.orientation === 'directed') {
35
+ const { kind: _, orientation: __, ...input } = config;
36
+ return edgeClass.directed(input);
37
+ }
38
+ const { kind: _, orientation: __, ...input } = config;
39
+ return edgeClass.undirected(input);
40
+ }
41
+ /** The authoring boundary restates the type contract for untyped and widened configurations. */
42
+ function assertDeclaredReadRule(kind, config) {
43
+ if (config.abstract === true)
44
+ return;
45
+ const rule = kind === 'Node' ? 'a Policy, Policy checks, true or false' : 'a Policy, true or false';
46
+ const read = config.policies?.read;
47
+ if (read === undefined) {
48
+ throw new TypeError(`${kind} Class read rule is required; declare policies.read with ${rule}.`);
49
+ }
50
+ if (read !== true && config.policies?.traverse === undefined) {
51
+ throw new TypeError(`${kind} Class traverse rule is required when its read rule is not true; declare policies.traverse with ${rule}.`);
52
+ }
20
53
  }
@@ -7,14 +7,16 @@ export type { StateProperty } from './state-property.js';
7
7
  export { property } from './property.js';
8
8
  export type { AuthoredProperty, PropertyOptions } from './property.js';
9
9
  export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
10
- export { defineClass, nodeClass } from './class-authoring.js';
11
- export type { AuthoredNodeClass, NodeClassInput } from './class-authoring.js';
10
+ export { defineClass, edgeClass, nodeClass } from './class-authoring.js';
11
+ export type { AuthoredNodeClass, ClassReadRule, DeclaredObservation, DeclaredReadRule, NodeClassReadRule, NodeClassInput, } from './class-authoring.js';
12
+ export { func, method } from './callable-authoring.js';
13
+ export type { CallerRule, DeclaredCallerRule } from './callable-authoring.js';
12
14
  export { error } from './error.js';
13
15
  export type { DeclaredErrorDefinition, DeclaredErrorFamily } from './error.js';
14
16
  export { classIcon } from './class-icon.js';
15
17
  export type { ClassIcon } from './class-icon.js';
16
18
  export type { StoredPropertyInputOf } from './property-input.js';
17
- export type { CallablePolicyBuilder, CallablePolicyContext, CallableProjection, AuthoredMethodProjection, AuthoredPropertyProjection, ClassIdentityProjection, ClassOwnerProjection, CoreEdge, CoreEndpoint, CoreNode, DefinedSchema, DirectedEdgeClass, ClassReferenceProjection, ClassProjection, FunctionProjection, EdgeClass, Function, Method, MethodProjection, Lazy, NodeClass, Policy, PolicyExistsContext, PolicyNodeBinder, PolicyPatternContext, Property, PropertyProjection, ResolvedDomainOfInput, View, } from '@astrale-os/kernel-dsl/v1/builder';
19
+ export type { CallablePolicyBuilder, CallablePolicyContext, ClassRuleBuilder, ClassRuleContext, PolicyCheckContext, CallableProjection, AuthoredMethodProjection, AuthoredPropertyProjection, ClassIdentityProjection, ClassOwnerProjection, CoreEdge, CoreEndpoint, CoreNode, DefinedSchema, DirectedEdgeClass, ClassReferenceProjection, ClassProjection, FunctionProjection, EdgeClass, Function, Method, MethodProjection, Lazy, NodeClass, Policy, PolicyExistsContext, PolicyNodeBinder, PolicyPatternContext, Property, PropertyProjection, ResolvedDomainOfInput, View, } from '@astrale-os/kernel-dsl/v1/builder';
18
20
  export type { Array, Object, Value } from '@astrale-os/kernel-dsl/value';
19
21
  export type { CallableExecution, ClassKind, DirectedEdgeContract, EdgeClassPolicies, NodeClassDefinition, PolicyCheckObject, PolicyObjectRef, PropertyFacet, PropertyImmutability, StateTopology, UndirectedEdgeContract, } from '@astrale-os/kernel-dsl/v1/language';
20
22
  export type { DomainSchema } from '@astrale-os/kernel-dsl/v1/schema';
@@ -4,7 +4,8 @@ export { defineSchema } from './schema-authoring.js';
4
4
  export { stateProperty } from './state-property.js';
5
5
  export { property } from './property.js';
6
6
  export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
7
- export { defineClass, nodeClass } from './class-authoring.js';
7
+ export { defineClass, edgeClass, nodeClass } from './class-authoring.js';
8
+ export { func, method } from './callable-authoring.js';
8
9
  export { error } from './error.js';
9
10
  export { classIcon } from './class-icon.js';
10
11
  /** Kernel-owned Schema values used by authored product Schemas and graph operations. */
@@ -1,7 +1,7 @@
1
1
  /** Generated from the validated Domain knowledge catalog. Regenerate with pnpm sync:linter-policy. */
2
- export const domainPolicyDigest = '9b8051c5ae8863dad24dad2fc593df158250d7078a5f63970a84aab6e063e8ac';
2
+ export const domainPolicyDigest = '6f95caf8358a49390417603f8646222b76dab2cdee648384929271ac337dabd8';
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, Runtime, frontend deployment, native Routes, 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 and Mutation 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-DECLARED\tdomains\tdependency\terror\tCross-Domain calls use exact declared public Schema dependencies and typed invocation; Integrations are optional consumer-owned boundaries\tTrace remote invocations and reject undeclared targets, private imports, forged references, and missing exact Domain Function requirements\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\tstructure\terror\tEvery Schema source path belongs to the registered facade, one canonical shared declaration kind, or one business-module facade or canonical declaration kind\tClassify admitted Schema source paths including shared and module-owned errors directories and report flat files or source-bearing directories outside the canonical Schema vocabulary\tschema-facade.md\nSCH-ONE-DECL\tschema\tdeclaration\terror\tEvery Schema declaration leaf file exports exactly one locally authored declaration of its directory kind and every declaration-kind index is a curated facade\tInspect runtime exports in Classes, Functions, Policies, States, and Views; resolve canonical SDK constructor origins and report zero, several, re-exported, aggregated, wrong-kind, or index-owned values\tdeclaration-file.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-ICON-REQUIRED\tschema\tdeclaration\terror\tEvery Node Class must explicitly declare one admitted SVG icon\tInspect each SDK nodeClass and Node-kind defineClass declaration and report statically proven missing icon values while preserving opaque configurations as indeterminate\tclass-icon.md\nSCH-ICON-NEUTRAL\tschema\tdeclaration\twarning\tEvery neutral Node Class icon should be replaced with a meaningful icon\tInspect each SDK nodeClass and Node-kind defineClass declaration and report explicit classIcon.neutral or exact neutral SVG values\tclass-icon.md\nSCH-POLICY-AUTH\tschema\tdeclaration\terror\tCallable authority and existential Node scope are declared explicitly through Policy and Action implementations add no private authorization rule\tTrace callable admission and existential Node binders; report hidden authority or an ambiguous Node extent\tpolicy.md\nSCH-STATE-SOURCE\tschema\tdeclaration\terror\tEvery persisted StateMachine vocabulary originates from an exported authority in the same Schema business module rather than a copied literal set\tResolve exact StateMachine and stateProperty origins; report same-owner copied literals and reject cross-module association\tarticle-schema.md\nSCH-STATE-RELATION\tschema\tdeclaration\terror\tEvery StateMachine is one exported static immutable finite relation without parallel vocabulary authorities\tInspect Schema module machines and exported sibling values for split or copied topology\tarticle-status.md\nSCH-STATE-PURE\tschema\tdeclaration\terror\tStateMachine topology contains no behavior, effects, guards, clocks, retry, recovery, or orchestration\tInspect files that declare StateMachine authorities for behavioral exports, boundary imports, and effectful calls\tstate-pure.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-PLAN-REQ\tmutations\tdependency\terror\tEvery identity operation and foreign Mutation plan declares the exact Kernel Function and foreign Class authority it uses\tInspect SDK MutationAST builders and Kernel Provision or Register calls and match every foreign Class operation and identity syscall invocation to Domain requirements\tprovision-plan.md\nMUT-LOCAL-ALIAS\tmutations\tdeclaration\terror\tEvery literal Mutation createNode alias is a canonical local binding accepted by the Kernel\tInspect statically resolved createNode as values and reject invalid local binding literals before runtime\tatomic-mutation.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 Schema module machine and one allowed decision; reject direct target writes and split precondition updates\tstate-transition.md\nFNC-ONE-IMPL\tfunctions\tbehavior\terror\tEvery registered handler binding resolves to exactly one implementation owned by one semantic Functions submodule\tResolve the handler registry and report non-Action owners or duplicate registrations; SDK typing owns callable exhaustiveness\tsynchronous-result.md\nFNC-ONE-OP\tfunctions\tbehavior\terror\tEvery Action performs one terminal semantic operation; only a native binary compatibility Action may precede its Integration call with one exact-instance Query\tTrace every path and report several terminal effects, writes before effects, non-instance prerequisite reads, or hidden orchestration\tsingle-operation.md\nFNC-OP-KINDS\tfunctions\tbehavior\terror\tA semantic asynchronous operation is one Query, Mutation, Integration call, dependency invocation, or canonical Kernel operation\tClassify every awaited effect including dependency and Kernel calls; report undeclared effect kinds and hidden multiple operations\tintegration-operation.md\nFNC-BOUNDARIES\tfunctions\tbehavior\terror\tHandlers use self-default kernel graph query and mutate capabilities, explicit-authority direct dependencies, and optional declared Integrations; explicit Kernel and graph authority modes are available\tInspect context use, authority selection, dependencies, imports and failure mapping; reject raw unbound graph executors, foreign handler imports, implicit authority changes and swallowed defects\tremote-integration.md\nFNC-BINARY-RESULT\tfunctions\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\nFNC-MULTISTEP\tfunctions\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\nFNC-STEP-EFFECTS\tfunctions\tbehavior\terror\tEvery semantic asynchronous operation maps to one named step.run boundary\tTrace effects and report operations outside step.run\tpublication-workflow.md\nFNC-STEP-IDS\tfunctions\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\nFNC-NO-NEST\tfunctions\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\nFNC-INT-TYPES\tfunctions\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\nFNC-ONE-OP-STEP\tfunctions\tbehavior\terror\tEvery step owns exactly one semantic asynchronous operation\tTrace each step and report zero or several independently failing operations\tpublication-workflow.md\nFNC-STATE-CODEC\tfunctions\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\nFNC-RECOVERY\tfunctions\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\nFNC-XDOM-DECLARED\tfunctions\tbehavior\terror\tActions and Workflows invoke direct declared Domain dependencies through explicit-authority dependency invokers or canonical typed kernel.invoke references and import no foreign handler\tResolve handler imports, direct dependency aliases, explicit caller/self authority, selectors, and typed references; reject foreign imports, implicit or chained authority, undeclared aliases and forged references, and report opaque bindings as indeterminate\tcross-domain-workflow.md\nFNC-XDOM-REQ\tfunctions\tbehavior\terror\tEvery selected remote callable has one exact Domain Function requirement including its defining Class and static or instance kind\tCompare each statically selected remote callable with exact Domain Function requirements; report missing requirements and mark opaque selectors or requirement lists indeterminate\tcross-domain-workflow.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\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 Domain Function requirement\tMatch each invocation-bound public callable reference to the same foreign facade and Function selector under Domain 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-NO-COMPOSE\tviews\tdeclaration\terror\tThe Domain definition owns frontend deployment composition and Views never calls defineFrontend\tResolve direct imported defineFrontend calls in Views and report SDK constructor calls while ignoring unrelated local functions\tfrontend-composition.md\nVIW-DEPS\tviews\tdependency\terror\tViews import only Schema and its StateMachines, 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\tinterruption-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, Runtime, frontend deployment, native Routes, 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 and Mutation 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-DECLARED\tdomains\tdependency\terror\tCross-Domain calls use exact declared public Schema dependencies and typed invocation; Integrations are optional consumer-owned boundaries\tTrace remote invocations and reject undeclared targets, private imports, forged references, and missing exact Domain Function requirements\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\tstructure\terror\tEvery Schema source path belongs to the registered facade, one canonical shared declaration kind, or one business-module facade or canonical declaration kind\tClassify admitted Schema source paths including shared and module-owned errors directories and report flat files or source-bearing directories outside the canonical Schema vocabulary\tschema-facade.md\nSCH-ONE-DECL\tschema\tdeclaration\terror\tEvery Schema declaration leaf file exports exactly one locally authored declaration of its directory kind and every declaration-kind index is a curated facade\tInspect runtime exports in Classes, Functions, Policies, States, and Views; resolve canonical SDK constructor origins and report zero, several, re-exported, aggregated, wrong-kind, or index-owned values\tdeclaration-file.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-ICON-REQUIRED\tschema\tdeclaration\terror\tEvery Node Class must explicitly declare one admitted SVG icon\tInspect each SDK nodeClass and Node-kind defineClass declaration and report statically proven missing icon values while preserving opaque configurations as indeterminate\tclass-icon.md\nSCH-ICON-NEUTRAL\tschema\tdeclaration\twarning\tEvery neutral Node Class icon should be replaced with a meaningful icon\tInspect each SDK nodeClass and Node-kind defineClass declaration and report explicit classIcon.neutral or exact neutral SVG values\tclass-icon.md\nSCH-POLICY-AUTH\tschema\tdeclaration\terror\tCallable authority and existential Node scope are declared explicitly through Policy and Action implementations add no private authorization rule\tTrace callable admission and existential Node binders; report hidden authority or an ambiguous Node extent\tpolicy.md\nSCH-POLICY-NAME\tschema\tdeclaration\twarning\tEvery Policy is named as the sentence its subject completes, in the Domain's words: each business hop to the object in order, the object last\tRead each Policy name against its match and report a permission prefix such as can or may, a graph mechanics word such as source, target, sub, super, subject, object, or caller, or a business hop of the match the name omits\tpolicy.md\nSCH-STATE-SOURCE\tschema\tdeclaration\terror\tEvery persisted StateMachine vocabulary originates from an exported authority in the same Schema business module rather than a copied literal set\tResolve exact StateMachine and stateProperty origins; report same-owner copied literals and reject cross-module association\tarticle-schema.md\nSCH-STATE-RELATION\tschema\tdeclaration\terror\tEvery StateMachine is one exported static immutable finite relation without parallel vocabulary authorities\tInspect Schema module machines and exported sibling values for split or copied topology\tarticle-status.md\nSCH-STATE-PURE\tschema\tdeclaration\terror\tStateMachine topology contains no behavior, effects, guards, clocks, retry, recovery, or orchestration\tInspect files that declare StateMachine authorities for behavioral exports, boundary imports, and effectful calls\tstate-pure.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-PLAN-REQ\tmutations\tdependency\terror\tEvery identity operation and foreign Mutation plan declares the exact Kernel Function and foreign Class authority it uses\tInspect SDK MutationAST builders and Kernel Provision or Register calls and match every foreign Class operation and identity syscall invocation to Domain requirements\tprovision-plan.md\nMUT-LOCAL-ALIAS\tmutations\tdeclaration\terror\tEvery literal Mutation createNode alias is a canonical local binding accepted by the Kernel\tInspect statically resolved createNode as values and reject invalid local binding literals before runtime\tatomic-mutation.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 Schema module machine and one allowed decision; reject direct target writes and split precondition updates\tstate-transition.md\nFNC-ONE-IMPL\tfunctions\tbehavior\terror\tEvery registered handler binding resolves to exactly one implementation owned by one semantic Functions submodule\tResolve the handler registry and report non-Action owners or duplicate registrations; SDK typing owns callable exhaustiveness\tsynchronous-result.md\nFNC-ONE-OP\tfunctions\tbehavior\terror\tEvery Action performs one terminal semantic operation; only a native binary compatibility Action may precede its Integration call with one exact-instance Query\tTrace every path and report several terminal effects, writes before effects, non-instance prerequisite reads, or hidden orchestration\tsingle-operation.md\nFNC-OP-KINDS\tfunctions\tbehavior\terror\tA semantic asynchronous operation is one Query, Mutation, Integration call, dependency invocation, or canonical Kernel operation\tClassify every awaited effect including dependency and Kernel calls; report undeclared effect kinds and hidden multiple operations\tintegration-operation.md\nFNC-BOUNDARIES\tfunctions\tbehavior\terror\tHandlers use self-default kernel graph query and mutate capabilities, explicit-authority direct dependencies, and optional declared Integrations; explicit Kernel and graph authority modes are available\tInspect context use, authority selection, dependencies, imports and failure mapping; reject raw unbound graph executors, foreign handler imports, implicit authority changes and swallowed defects\tremote-integration.md\nFNC-BINARY-RESULT\tfunctions\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\nFNC-MULTISTEP\tfunctions\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\nFNC-STEP-EFFECTS\tfunctions\tbehavior\terror\tEvery semantic asynchronous operation maps to one named step.run boundary\tTrace effects and report operations outside step.run\tpublication-workflow.md\nFNC-STEP-IDS\tfunctions\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\nFNC-NO-NEST\tfunctions\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\nFNC-INT-TYPES\tfunctions\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\nFNC-ONE-OP-STEP\tfunctions\tbehavior\terror\tEvery step owns exactly one semantic asynchronous operation\tTrace each step and report zero or several independently failing operations\tpublication-workflow.md\nFNC-STATE-CODEC\tfunctions\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\nFNC-RECOVERY\tfunctions\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\nFNC-XDOM-DECLARED\tfunctions\tbehavior\terror\tActions and Workflows invoke direct declared Domain dependencies through explicit-authority dependency invokers or canonical typed kernel.invoke references and import no foreign handler\tResolve handler imports, direct dependency aliases, explicit caller/self authority, selectors, and typed references; reject foreign imports, implicit or chained authority, undeclared aliases and forged references, and report opaque bindings as indeterminate\tcross-domain-workflow.md\nFNC-XDOM-REQ\tfunctions\tbehavior\terror\tEvery selected remote callable has one exact Domain Function requirement including its defining Class and static or instance kind\tCompare each statically selected remote callable with exact Domain Function requirements; report missing requirements and mark opaque selectors or requirement lists indeterminate\tcross-domain-workflow.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\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 Domain Function requirement\tMatch each invocation-bound public callable reference to the same foreign facade and Function selector under Domain 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-NO-COMPOSE\tviews\tdeclaration\terror\tThe Domain definition owns frontend deployment composition and Views never calls defineFrontend\tResolve direct imported defineFrontend calls in Views and report SDK constructor calls while ignoring unrelated local functions\tfrontend-composition.md\nVIW-DEPS\tviews\tdependency\terror\tViews import only Schema and its StateMachines, 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\tinterruption-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',
@@ -88,6 +88,7 @@ export const domainRuleRequirements = Object.freeze([
88
88
  row('SCH-ICON-REQUIRED', DECLARATION),
89
89
  row('SCH-ICON-NEUTRAL', DECLARATION),
90
90
  row('SCH-POLICY-AUTH', DECLARATION_FLOW),
91
+ row('SCH-POLICY-NAME', DECLARATION),
91
92
  row('SCH-STATE-SOURCE', DECLARATION),
92
93
  row('SCH-STATE-RELATION', DECLARATION),
93
94
  row('SCH-STATE-PURE', DECLARATION_FLOW),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.6.0-beta.27",
3
+ "version": "0.6.0-beta.29",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",
@@ -257,11 +257,11 @@
257
257
  "registry": "https://registry.npmjs.org/"
258
258
  },
259
259
  "dependencies": {
260
- "@astrale-os/kernel-client": "0.6.0-beta.88",
261
- "@astrale-os/kernel-core": "0.9.0-beta.67",
262
- "@astrale-os/kernel-dsl": "0.2.0-beta.49",
263
- "@astrale-os/kernel-protocol": "0.5.0-beta.73",
264
- "@astrale-os/kernel-server": "0.5.0-beta.75",
260
+ "@astrale-os/kernel-client": "1.0.0-beta.2",
261
+ "@astrale-os/kernel-core": "1.0.0-beta.2",
262
+ "@astrale-os/kernel-dsl": "1.0.0-beta.2",
263
+ "@astrale-os/kernel-protocol": "1.0.0-beta.2",
264
+ "@astrale-os/kernel-server": "1.0.0-beta.2",
265
265
  "@lucide/icons": "1.34.0",
266
266
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
267
267
  "cross-spawn": "7.0.6",