@prisma-next/contract 0.12.0 → 0.13.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/apply-specifier-default-control-policy.d.mts +7 -0
  2. package/dist/apply-specifier-default-control-policy.d.mts.map +1 -0
  3. package/dist/apply-specifier-default-control-policy.mjs +12 -0
  4. package/dist/apply-specifier-default-control-policy.mjs.map +1 -0
  5. package/dist/{canonicalization-DFE0HJkI.d.mts → canonicalization-ChdV0ueh.d.mts} +2 -2
  6. package/dist/canonicalization-ChdV0ueh.d.mts.map +1 -0
  7. package/dist/{canonicalization-path-match-b2jFuEso.mjs → canonicalization-path-match-CNgHuwM_.mjs} +1 -1
  8. package/dist/{canonicalization-path-match-b2jFuEso.mjs.map → canonicalization-path-match-CNgHuwM_.mjs.map} +1 -1
  9. package/dist/{contract-types-xgwKtd7y.d.mts → contract-types-CBbD-VV1.d.mts} +47 -9
  10. package/dist/contract-types-CBbD-VV1.d.mts.map +1 -0
  11. package/dist/{contract-validation-error-ClZaKqMW.mjs → contract-validation-error-BFA66rwU.mjs} +1 -1
  12. package/dist/{contract-validation-error-ClZaKqMW.mjs.map → contract-validation-error-BFA66rwU.mjs.map} +1 -1
  13. package/dist/{contract-validation-error-T5LH4DW-.d.mts → contract-validation-error-D7g0kmcc.d.mts} +1 -1
  14. package/dist/{contract-validation-error-T5LH4DW-.d.mts.map → contract-validation-error-D7g0kmcc.d.mts.map} +1 -1
  15. package/dist/contract-validation-error.d.mts +1 -1
  16. package/dist/contract-validation-error.mjs +1 -1
  17. package/dist/default-namespace-D5X_k6hJ.d.mts +25 -0
  18. package/dist/default-namespace-D5X_k6hJ.d.mts.map +1 -0
  19. package/dist/default-namespace-rpdJeUMq.mjs +33 -0
  20. package/dist/default-namespace-rpdJeUMq.mjs.map +1 -0
  21. package/dist/default-namespace.d.mts +2 -0
  22. package/dist/default-namespace.mjs +2 -0
  23. package/dist/{domain-envelope-4hyFtJ4_.d.mts → domain-envelope-OkWsysCY.d.mts} +68 -15
  24. package/dist/domain-envelope-OkWsysCY.d.mts.map +1 -0
  25. package/dist/hashing-utils.d.mts +1 -1
  26. package/dist/hashing-utils.d.mts.map +1 -1
  27. package/dist/hashing-utils.mjs +5 -4
  28. package/dist/hashing-utils.mjs.map +1 -1
  29. package/dist/hashing.d.mts +2 -2
  30. package/dist/hashing.d.mts.map +1 -1
  31. package/dist/hashing.mjs +38 -11
  32. package/dist/hashing.mjs.map +1 -1
  33. package/dist/{namespace-id-CVpkSFUK.mjs → namespace-id-CUxYd4KL.mjs} +1 -1
  34. package/dist/{namespace-id-CVpkSFUK.mjs.map → namespace-id-CUxYd4KL.mjs.map} +1 -1
  35. package/dist/resolve-domain-model-BhAr8VRJ.d.mts +17 -0
  36. package/dist/resolve-domain-model-BhAr8VRJ.d.mts.map +1 -0
  37. package/dist/resolve-domain-model-BovPAsW2.mjs +20 -0
  38. package/dist/resolve-domain-model-BovPAsW2.mjs.map +1 -0
  39. package/dist/resolve-domain-model.d.mts +2 -0
  40. package/dist/resolve-domain-model.mjs +2 -0
  41. package/dist/types.d.mts +21 -4
  42. package/dist/types.d.mts.map +1 -0
  43. package/dist/types.mjs +40 -30
  44. package/dist/types.mjs.map +1 -1
  45. package/dist/validate-domain.d.mts +1 -1
  46. package/dist/validate-domain.mjs +6 -3
  47. package/dist/validate-domain.mjs.map +1 -1
  48. package/package.json +9 -6
  49. package/src/apply-specifier-default-control-policy.ts +12 -0
  50. package/src/canonicalization-storage-sort.ts +8 -1
  51. package/src/canonicalization.ts +13 -4
  52. package/src/contract-types.ts +9 -5
  53. package/src/control-policy.ts +25 -0
  54. package/src/cross-reference.ts +15 -3
  55. package/src/default-namespace.ts +36 -0
  56. package/src/domain-envelope.ts +3 -61
  57. package/src/domain-namespace-access.ts +32 -0
  58. package/src/domain-types.ts +31 -1
  59. package/src/exports/apply-specifier-default-control-policy.ts +1 -0
  60. package/src/exports/default-namespace.ts +1 -0
  61. package/src/exports/resolve-domain-model.ts +1 -0
  62. package/src/exports/types.ts +16 -7
  63. package/src/hashing.ts +42 -13
  64. package/src/resolve-domain-model.ts +27 -0
  65. package/src/types.ts +18 -2
  66. package/src/validate-domain.ts +6 -0
  67. package/src/value-set-ref.ts +26 -0
  68. package/dist/canonicalization-DFE0HJkI.d.mts.map +0 -1
  69. package/dist/contract-types-xgwKtd7y.d.mts.map +0 -1
  70. package/dist/domain-envelope-4hyFtJ4_.d.mts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import type { ControlPolicy } from './control-policy';
1
2
  import type { CrossReference } from './cross-reference';
2
3
  import type { ApplicationDomain } from './domain-envelope';
3
4
  import type { ContractModelBase, ContractValueObject } from './domain-types';
@@ -57,12 +58,15 @@ export interface Contract<
57
58
  readonly execution?: ContractExecutionSection;
58
59
  readonly profileHash: ProfileHashBase<string>;
59
60
  readonly meta: Record<string, unknown>;
61
+ readonly defaultControlPolicy?: ControlPolicy;
60
62
  }
61
63
 
62
- export type ContractModelsMap<TContract extends Contract> =
64
+ /** Model definitions union carried on a {@link Contract}'s `TModels` type parameter. */
65
+ export type ContractModelDefinitions<TContract extends Contract> =
63
66
  TContract extends Contract<StorageBase, infer TModels> ? TModels : never;
64
67
 
65
- type ExactlyOneKey<T extends Record<string, unknown>> = keyof T extends infer Only extends keyof T
68
+ type ExactlyOneNamespace<T extends Record<string, unknown>> = keyof T extends infer Only extends
69
+ keyof T
66
70
  ? [keyof T] extends [Only]
67
71
  ? Only
68
72
  : never
@@ -76,10 +80,10 @@ type NamespaceValueObjectsOf<TNamespace> = TNamespace extends {
76
80
  : Record<never, never>
77
81
  : Record<never, never>;
78
82
 
79
- /** Value-object map for the contract's sole domain namespace (type-level single-namespace projection). */
80
- export type ContractValueObjectsMap<TContract extends Contract> =
83
+ /** Value-object map when the contract declares exactly one domain namespace. */
84
+ export type ContractValueObjectDefinitions<TContract extends Contract> =
81
85
  NamespaceValueObjectsOf<
82
- TContract['domain']['namespaces'][ExactlyOneKey<TContract['domain']['namespaces']>]
86
+ TContract['domain']['namespaces'][ExactlyOneNamespace<TContract['domain']['namespaces']>]
83
87
  > extends infer Projected
84
88
  ? Projected extends Record<string, ContractValueObject>
85
89
  ? Projected
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Governance posture for a storage-plane node or for the contract as a whole.
3
+ *
4
+ * - `managed` — Prisma Next owns the full lifecycle (DDL, migrations, verification).
5
+ * - `tolerated` — node was found in the database but is not schema-managed; Prisma Next
6
+ * leaves it untouched while tracking its existence.
7
+ * - `external` — node is owned by an external system; Prisma Next never emits DDL for it.
8
+ * - `observed` — read-only access; Prisma Next does not write to or migrate the node.
9
+ */
10
+ export type ControlPolicy = 'managed' | 'tolerated' | 'external' | 'observed';
11
+
12
+ /**
13
+ * Resolves the effective control policy for a storage-plane node.
14
+ *
15
+ * Precedence: node-level value → contract default → `'managed'`.
16
+ *
17
+ * Both parameters are optional raw values so this function stays node-type-agnostic
18
+ * and can be called by any consumer (verifier, planner, etc.) without importing IR classes.
19
+ */
20
+ export function effectiveControlPolicy(
21
+ nodeControl: ControlPolicy | undefined,
22
+ defaultControlPolicy: ControlPolicy | undefined,
23
+ ): ControlPolicy {
24
+ return nodeControl ?? defaultControlPolicy ?? 'managed';
25
+ }
@@ -5,16 +5,23 @@ import { asNamespaceId, type NamespaceId } from './namespace-id';
5
5
  export interface CrossReference {
6
6
  readonly namespace: NamespaceId;
7
7
  readonly model: string;
8
+ /**
9
+ * Contract-space identity for cross-space relations. When present, the
10
+ * referenced model lives in a different contract space. Absent for local
11
+ * (same-space) relations.
12
+ */
13
+ readonly space?: string;
8
14
  }
9
15
 
10
- export const CrossReferenceSchema = blindCast<
16
+ export const CrossReferenceSchema = /* @__PURE__ */ blindCast<
11
17
  Type<CrossReference>,
12
18
  'namespace is validated as string at runtime and branded to NamespaceId by asNamespaceId in crossRef(); the schema accepts plain strings but the public type reflects the branded shape'
13
19
  >(
14
- type({
20
+ /* @__PURE__ */ type({
15
21
  '+': 'reject',
16
22
  namespace: 'string',
17
23
  model: 'string',
24
+ 'space?': 'string',
18
25
  }),
19
26
  );
20
27
 
@@ -23,6 +30,11 @@ const DEFAULT_CROSS_REF_NAMESPACE = '__unbound__';
23
30
  export function crossRef(
24
31
  model: string,
25
32
  namespace: string = DEFAULT_CROSS_REF_NAMESPACE,
33
+ space?: string,
26
34
  ): CrossReference {
27
- return { namespace: asNamespaceId(namespace), model };
35
+ return {
36
+ namespace: asNamespaceId(namespace),
37
+ model,
38
+ ...(space !== undefined ? { space } : {}),
39
+ };
28
40
  }
@@ -0,0 +1,36 @@
1
+ import { DomainNamespaceResolutionError } from './contract-validation-error';
2
+
3
+ /**
4
+ * Reserved sentinel domain namespace id for the late-bound application-domain
5
+ * slot — the namespace a model lands in when it is authored without an explicit
6
+ * namespace. This is target-agnostic: targets that allow un-namespaced
7
+ * authoring (e.g. Mongo, SQLite) declare this id as their default on the target
8
+ * descriptor; the framework names the sentinel, never a target. Mirrors
9
+ * storage's `UNBOUND_NAMESPACE_ID` on the domain plane.
10
+ */
11
+ export const UNBOUND_DOMAIN_NAMESPACE_ID = '__unbound__' as const;
12
+
13
+ /**
14
+ * Resolve the single domain namespace of a single-namespace contract.
15
+ *
16
+ * Bare-name access (`db.User`) reads "the contract's one namespace". Every
17
+ * contract in scope today declares exactly one domain namespace, so this is
18
+ * exact — there is nothing to infer. A contract that declares more than one
19
+ * namespace is ambiguous for a bare name, so rather than silently pick one this
20
+ * throws; cross-namespace selection is made explicit (TML-2550).
21
+ */
22
+ export function soleDomainNamespaceId(domain: {
23
+ readonly namespaces: Readonly<Record<string, unknown>>;
24
+ }): string {
25
+ const [soleNamespaceId, ...rest] = Object.keys(domain.namespaces);
26
+ if (soleNamespaceId === undefined) {
27
+ throw new DomainNamespaceResolutionError('domain has no namespaces');
28
+ }
29
+ if (rest.length > 0) {
30
+ const all = [soleNamespaceId, ...rest];
31
+ throw new DomainNamespaceResolutionError(
32
+ `bare-name resolution requires exactly one domain namespace, found ${all.length} (${all.join(', ')}); select a namespace explicitly`,
33
+ );
34
+ }
35
+ return soleNamespaceId;
36
+ }
@@ -1,7 +1,6 @@
1
- import { DomainNamespaceResolutionError } from './contract-validation-error';
2
- import type { ContractModelBase, ContractValueObject } from './domain-types';
1
+ import type { ContractEnum, ContractModelBase, ContractValueObject } from './domain-types';
3
2
 
4
- export const UNBOUND_DOMAIN_NAMESPACE_ID = '__unbound__' as const;
3
+ export { UNBOUND_DOMAIN_NAMESPACE_ID } from './default-namespace';
5
4
 
6
5
  /**
7
6
  * One namespace's application-domain entities — models and optional value
@@ -12,6 +11,7 @@ export interface ApplicationDomainNamespace<
12
11
  > {
13
12
  readonly models: TModels;
14
13
  readonly valueObjects?: Record<string, ContractValueObject>;
14
+ readonly enum?: Record<string, ContractEnum>;
15
15
  }
16
16
 
17
17
  /**
@@ -27,61 +27,3 @@ export interface ApplicationDomain<
27
27
  export type ContractWithDomain = {
28
28
  readonly domain: ApplicationDomain;
29
29
  };
30
-
31
- export function resolveSingleDomainNamespaceId(
32
- domain: ApplicationDomain,
33
- namespaceId?: string,
34
- ): string {
35
- if (namespaceId !== undefined) {
36
- if (!Object.hasOwn(domain.namespaces, namespaceId)) {
37
- throw new DomainNamespaceResolutionError(
38
- `domain namespace "${namespaceId}" is not present on the contract`,
39
- );
40
- }
41
- return namespaceId;
42
- }
43
-
44
- const namespaceIds = Object.keys(domain.namespaces);
45
- if (namespaceIds.length === 0) {
46
- throw new DomainNamespaceResolutionError('domain has no namespaces');
47
- }
48
- if (namespaceIds.length > 1) {
49
- throw new DomainNamespaceResolutionError(
50
- `expected exactly one domain namespace, found ${namespaceIds.length} (${namespaceIds.join(', ')})`,
51
- );
52
- }
53
- const [soleNamespaceId] = namespaceIds;
54
- if (soleNamespaceId === undefined) {
55
- throw new DomainNamespaceResolutionError('domain has no namespaces');
56
- }
57
- return soleNamespaceId;
58
- }
59
-
60
- // Transitional single-namespace projection; pending runtime-qualification slice.
61
- export function contractModels<TModels extends Record<string, ContractModelBase>>(
62
- contract: { readonly domain: ApplicationDomain<TModels> },
63
- namespaceId?: string,
64
- ): TModels {
65
- const resolved = resolveSingleDomainNamespaceId(contract.domain, namespaceId);
66
- const domainNamespace = contract.domain.namespaces[resolved];
67
- if (domainNamespace === undefined) {
68
- throw new DomainNamespaceResolutionError(
69
- `domain namespace "${resolved}" is not present on the contract`,
70
- );
71
- }
72
- return domainNamespace.models;
73
- }
74
-
75
- export function contractValueObjects<TModels extends Record<string, ContractModelBase>>(
76
- contract: { readonly domain: ApplicationDomain<TModels> },
77
- namespaceId?: string,
78
- ): Record<string, ContractValueObject> | undefined {
79
- const resolved = resolveSingleDomainNamespaceId(contract.domain, namespaceId);
80
- const domainNamespace = contract.domain.namespaces[resolved];
81
- if (domainNamespace === undefined) {
82
- throw new DomainNamespaceResolutionError(
83
- `domain namespace "${resolved}" is not present on the contract`,
84
- );
85
- }
86
- return domainNamespace.valueObjects;
87
- }
@@ -0,0 +1,32 @@
1
+ import { DomainNamespaceResolutionError } from './contract-validation-error';
2
+ import { soleDomainNamespaceId } from './default-namespace';
3
+ import type { ApplicationDomain } from './domain-envelope';
4
+ import type { ContractModelBase, ContractValueObject } from './domain-types';
5
+
6
+ /**
7
+ * Models map for the contract's single domain namespace. Throws when the
8
+ * contract does not declare exactly one namespace — bare-name access is
9
+ * ambiguous across namespaces and must be qualified explicitly (TML-2550).
10
+ */
11
+ export function domainModelsAtDefaultNamespace<TModels extends Record<string, ContractModelBase>>(
12
+ domain: ApplicationDomain<TModels>,
13
+ ): TModels {
14
+ const namespaceId = soleDomainNamespaceId(domain);
15
+ const domainNamespace = domain.namespaces[namespaceId];
16
+ if (domainNamespace === undefined) {
17
+ throw new DomainNamespaceResolutionError(
18
+ `domain namespace "${namespaceId}" is not present on the contract`,
19
+ );
20
+ }
21
+ return domainNamespace.models;
22
+ }
23
+
24
+ /**
25
+ * Value objects for the contract's single domain namespace, when present.
26
+ * Throws when the contract does not declare exactly one namespace.
27
+ */
28
+ export function domainValueObjectsAtDefaultNamespace<
29
+ TModels extends Record<string, ContractModelBase>,
30
+ >(domain: ApplicationDomain<TModels>): Record<string, ContractValueObject> | undefined {
31
+ return domain.namespaces[soleDomainNamespaceId(domain)]?.valueObjects;
32
+ }
@@ -1,4 +1,5 @@
1
1
  import type { CrossReference } from './cross-reference';
2
+ import type { ValueSetRef } from './value-set-ref';
2
3
 
3
4
  export type ScalarFieldType = {
4
5
  readonly kind: 'scalar';
@@ -23,6 +24,17 @@ export type ContractField = {
23
24
  readonly type: ContractFieldType;
24
25
  readonly many?: true;
25
26
  readonly dict?: true;
27
+ readonly valueSet?: ValueSetRef;
28
+ };
29
+
30
+ /**
31
+ * A domain enum: an ordered set of named members, each with a codec-encoded
32
+ * value. The `codecId` identifies the codec used to encode member values in
33
+ * storage. The `members` array is ordered (declaration order is preserved).
34
+ */
35
+ export type ContractEnum = {
36
+ readonly codecId: string;
37
+ readonly members: readonly { readonly name: string; readonly value: string }[];
26
38
  };
27
39
 
28
40
  export type ContractRelationOn = {
@@ -30,12 +42,30 @@ export type ContractRelationOn = {
30
42
  readonly targetFields: readonly string[];
31
43
  };
32
44
 
33
- export type ContractReferenceRelation = {
45
+ export type ContractRelationThrough = {
46
+ readonly table: string;
47
+ readonly namespaceId: string;
48
+ readonly parentColumns: readonly string[];
49
+ readonly childColumns: readonly string[];
50
+ readonly targetColumns: readonly string[];
51
+ };
52
+
53
+ export type ContractManyToManyRelation = {
54
+ readonly to: CrossReference;
55
+ readonly cardinality: 'N:M';
56
+ readonly on: ContractRelationOn;
57
+ readonly through: ContractRelationThrough;
58
+ };
59
+
60
+ export type ContractNonJunctionRelation = {
34
61
  readonly to: CrossReference;
35
62
  readonly cardinality: '1:1' | '1:N' | 'N:1';
36
63
  readonly on: ContractRelationOn;
64
+ readonly through?: never;
37
65
  };
38
66
 
67
+ export type ContractReferenceRelation = ContractManyToManyRelation | ContractNonJunctionRelation;
68
+
39
69
  export type ContractEmbedRelation = {
40
70
  readonly to: CrossReference;
41
71
  readonly cardinality: '1:1' | '1:N';
@@ -0,0 +1 @@
1
+ export { applySpecifierDefaultControlPolicy } from '../apply-specifier-default-control-policy';
@@ -0,0 +1 @@
1
+ export { soleDomainNamespaceId, UNBOUND_DOMAIN_NAMESPACE_ID } from '../default-namespace';
@@ -0,0 +1 @@
1
+ export { type ResolvedDomainModel, resolveDomainModel } from '../resolve-domain-model';
@@ -1,33 +1,39 @@
1
1
  export type {
2
2
  Contract,
3
3
  ContractExecutionSection,
4
- ContractModelsMap,
5
- ContractValueObjectsMap,
4
+ ContractModelDefinitions,
5
+ ContractValueObjectDefinitions,
6
6
  } from '../contract-types';
7
7
  export { DomainNamespaceResolutionError } from '../contract-validation-error';
8
+ export type { ControlPolicy } from '../control-policy';
9
+ export { effectiveControlPolicy } from '../control-policy';
8
10
  export type { CrossReference } from '../cross-reference';
9
11
  export { CrossReferenceSchema, crossRef } from '../cross-reference';
12
+ export { soleDomainNamespaceId } from '../default-namespace';
10
13
  export type {
11
14
  ApplicationDomain,
12
15
  ApplicationDomainNamespace,
13
16
  ContractWithDomain,
14
17
  } from '../domain-envelope';
18
+ export { UNBOUND_DOMAIN_NAMESPACE_ID } from '../domain-envelope';
15
19
  export {
16
- contractModels,
17
- contractValueObjects,
18
- resolveSingleDomainNamespaceId,
19
- UNBOUND_DOMAIN_NAMESPACE_ID,
20
- } from '../domain-envelope';
20
+ domainModelsAtDefaultNamespace,
21
+ domainValueObjectsAtDefaultNamespace,
22
+ } from '../domain-namespace-access';
21
23
  export type {
22
24
  ContractDiscriminator,
23
25
  ContractEmbedRelation,
26
+ ContractEnum,
24
27
  ContractField,
25
28
  ContractFieldType,
29
+ ContractManyToManyRelation,
26
30
  ContractModel,
27
31
  ContractModelBase,
32
+ ContractNonJunctionRelation,
28
33
  ContractReferenceRelation,
29
34
  ContractRelation,
30
35
  ContractRelationOn,
36
+ ContractRelationThrough,
31
37
  ContractValueObject,
32
38
  ContractVariantEntry,
33
39
  EmbedRelationKeys,
@@ -39,6 +45,7 @@ export type {
39
45
  } from '../domain-types';
40
46
  export type { NamespaceId } from '../namespace-id';
41
47
  export { asNamespaceId } from '../namespace-id';
48
+ export { type ResolvedDomainModel, resolveDomainModel } from '../resolve-domain-model';
42
49
  export type {
43
50
  $,
44
51
  Brand,
@@ -58,6 +65,7 @@ export type {
58
65
  GeneratedValueSpec,
59
66
  JsonPrimitive,
60
67
  JsonValue,
68
+ LedgerEntryRecord,
61
69
  PlanMeta,
62
70
  ProfileHashBase,
63
71
  Source,
@@ -74,3 +82,4 @@ export {
74
82
  isExecutionMutationDefaultValue,
75
83
  profileHash,
76
84
  } from '../types';
85
+ export type { ValueSetRef } from '../value-set-ref';
package/src/hashing.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { blindCast, castAs } from '@prisma-next/utils/casts';
2
3
  import { ifDefined } from '@prisma-next/utils/defined';
3
4
  import type { JsonObject } from '@prisma-next/utils/json';
4
5
  import {
@@ -11,6 +12,33 @@ import type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './type
11
12
 
12
13
  const SCHEMA_VERSION = '1';
13
14
 
15
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
16
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
17
+ }
18
+
19
+ // Storage hashes fingerprint table/column layout, not which target pack emitted a
20
+ // namespace. Persisted contract.json carries namespace `kind` discriminators;
21
+ // authoring-time hashes never included them (IR `kind` is non-enumerable).
22
+ function omitNamespaceKindsForHash(storage: unknown): unknown {
23
+ if (!isPlainRecord(storage)) {
24
+ return storage;
25
+ }
26
+ const namespaces = storage['namespaces'];
27
+ if (!isPlainRecord(namespaces)) {
28
+ return storage;
29
+ }
30
+ const stripped: Record<string, unknown> = {};
31
+ for (const [nsId, ns] of Object.entries(namespaces)) {
32
+ if (isPlainRecord(ns)) {
33
+ const { kind: _kind, ...rest } = ns;
34
+ stripped[nsId] = rest;
35
+ } else {
36
+ stripped[nsId] = ns;
37
+ }
38
+ }
39
+ return { ...storage, namespaces: stripped };
40
+ }
41
+
14
42
  function sha256(content: string): string {
15
43
  const hash = createHash('sha256');
16
44
  hash.update(content);
@@ -24,28 +52,23 @@ type HashContractSection = Record<string, unknown> & {
24
52
 
25
53
  function hashContract(section: HashContractSection): string {
26
54
  const { shouldPreserveEmpty, sortStorage, ...sectionData } = section;
27
- // Blind cast: the synthesised object is a hash-only stand-in
28
- // never returned to callers, never executed as a Contract.
29
- // `canonicalizeContract` only walks the storage / execution /
30
- // capabilities slices, all of which are populated above, so the
31
- // missing precise Contract typing on the other slots is
32
- // immaterial for the hash result.
33
- const contract = {
55
+ const storageForHash = omitNamespaceKindsForHash(sectionData['storage'] ?? {});
56
+ const contract = blindCast<Contract, 'hash-only partial contract for canonicalizeContract'>({
34
57
  targetFamily: sectionData['targetFamily'],
35
58
  target: sectionData['target'],
36
59
  roots: {},
37
60
  domain: { namespaces: {} },
38
- storage: sectionData['storage'] ?? {},
39
61
  execution: sectionData['execution'],
40
62
  extensionPacks: {},
41
63
  capabilities: sectionData['capabilities'] ?? {},
42
64
  meta: {},
43
65
  profileHash: '',
44
66
  ...sectionData,
45
- } as unknown as Contract;
67
+ storage: storageForHash,
68
+ });
46
69
  return canonicalizeContract(contract, {
47
70
  schemaVersion: SCHEMA_VERSION,
48
- serializeContract: (c) => JSON.parse(JSON.stringify(c)) as JsonObject,
71
+ serializeContract: (c) => castAs<JsonObject>(JSON.parse(JSON.stringify(c))),
49
72
  ...ifDefined('shouldPreserveEmpty', shouldPreserveEmpty),
50
73
  ...ifDefined('sortStorage', sortStorage),
51
74
  });
@@ -60,7 +83,9 @@ export type ComputeStorageHashArgs = {
60
83
  };
61
84
 
62
85
  export function computeStorageHash(args: ComputeStorageHashArgs): StorageHashBase<string> {
63
- return sha256(hashContract(args)) as StorageHashBase<string>;
86
+ return blindCast<StorageHashBase<string>, 'sha256 digest of canonicalized storage'>(
87
+ sha256(hashContract(args)),
88
+ );
64
89
  }
65
90
 
66
91
  export function computeExecutionHash(args: {
@@ -68,7 +93,9 @@ export function computeExecutionHash(args: {
68
93
  targetFamily: string;
69
94
  execution: Record<string, unknown>;
70
95
  }): ExecutionHashBase<string> {
71
- return sha256(hashContract(args)) as ExecutionHashBase<string>;
96
+ return blindCast<ExecutionHashBase<string>, 'sha256 digest of canonicalized execution'>(
97
+ sha256(hashContract(args)),
98
+ );
72
99
  }
73
100
 
74
101
  export function computeProfileHash(args: {
@@ -76,5 +103,7 @@ export function computeProfileHash(args: {
76
103
  targetFamily: string;
77
104
  capabilities: Record<string, Record<string, boolean>>;
78
105
  }): ProfileHashBase<string> {
79
- return sha256(hashContract(args)) as ProfileHashBase<string>;
106
+ return blindCast<ProfileHashBase<string>, 'sha256 digest of canonicalized profile'>(
107
+ sha256(hashContract(args)),
108
+ );
80
109
  }
@@ -0,0 +1,27 @@
1
+ import type { ApplicationDomain } from './domain-envelope';
2
+ import type { ContractModelBase } from './domain-types';
3
+
4
+ export interface ResolvedDomainModel {
5
+ readonly namespaceId: string;
6
+ readonly model: ContractModelBase;
7
+ }
8
+
9
+ /**
10
+ * Resolve a bare domain model name to its namespace coordinate and model IR by
11
+ * scanning the contract's namespaces. For the single-namespace contracts in
12
+ * scope the scan is exact; cross-namespace bare-name collisions are selected
13
+ * explicitly (TML-2550).
14
+ */
15
+ export function resolveDomainModel(
16
+ domain: ApplicationDomain,
17
+ modelName: string,
18
+ ): ResolvedDomainModel | undefined {
19
+ for (const namespaceId of Object.keys(domain.namespaces)) {
20
+ const model = domain.namespaces[namespaceId]?.models[modelName];
21
+ if (model !== undefined) {
22
+ return { namespaceId, model };
23
+ }
24
+ }
25
+
26
+ return undefined;
27
+ }
package/src/types.ts CHANGED
@@ -57,11 +57,13 @@ export type StorageEntitySlot = Readonly<Record<string, unknown>>;
57
57
 
58
58
  /**
59
59
  * Plain-data namespace entry in a storage block. Every hydrated contract
60
- * carries at least `id` plus zero or more entity-kind slot maps (`tables`,
61
- * `collections`, …). Foundation declares only this shape — no IR machinery.
60
+ * carries at least `id` plus entity-kind slot maps under `entries`
61
+ * (`table`, `collection`, …). Foundation declares only this shape — no IR
62
+ * machinery.
62
63
  */
63
64
  export interface StorageNamespace {
64
65
  readonly id: string;
66
+ readonly entries: Readonly<Record<string, StorageEntitySlot>>;
65
67
  }
66
68
 
67
69
  /**
@@ -243,3 +245,17 @@ export interface ContractMarkerRecord {
243
245
  readonly meta: Record<string, unknown>;
244
246
  readonly invariants: readonly string[];
245
247
  }
248
+
249
+ /**
250
+ * One applied migration edge from the per-space ledger journal.
251
+ * Returned by `readLedger` in append (apply) order.
252
+ */
253
+ export interface LedgerEntryRecord {
254
+ readonly space: string;
255
+ readonly migrationName: string;
256
+ readonly migrationHash: string;
257
+ readonly from: string | null;
258
+ readonly to: string;
259
+ readonly appliedAt: Date;
260
+ readonly operationCount: number;
261
+ }
@@ -140,6 +140,12 @@ function validateVariantsAndBases(modelIndex: ModelIndex, errors: string[]): voi
140
140
  function validateRelationTargets(modelIndex: ModelIndex, errors: string[]): void {
141
141
  for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) {
142
142
  for (const [relName, relation] of Object.entries(model.relations ?? {})) {
143
+ // Cross-space relations (relation.to.space is defined) target a model in a
144
+ // different contract space. The local domain index only contains this
145
+ // contract's own models, so the target is intentionally absent here.
146
+ // Existence of the target is verified by the aggregate verifier at
147
+ // deploy time (after the spaces are composed), not at parse time.
148
+ if (relation.to.space !== undefined) continue;
143
149
  if (!lookupModel(modelIndex, relation.to)) {
144
150
  errors.push(
145
151
  `Relation "${relName}" on model "${namespaceId}:${modelName}" targets "${relation.to.namespace}:${relation.to.model}" which does not exist in domain.namespaces`,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Space-aware reference coordinate for a domain enum or storage value-set.
3
+ *
4
+ * `plane` names the contract plane the referenced entity lives in:
5
+ * - `'domain'` — the entity lives in the domain plane's `enum` slot.
6
+ * - `'storage'` — the entity lives in the storage plane's `valueSet` slot.
7
+ *
8
+ * `entityKind` names the source entity-kind:
9
+ * - `'enum'` — the referenced entity is a domain enum.
10
+ * - `'value-set'` — the referenced entity is a storage value-set.
11
+ *
12
+ * `namespaceId` admits the `UNBOUND_NAMESPACE_ID` (`__unbound__`) sentinel for
13
+ * single-namespace (unbound) references.
14
+ *
15
+ * Cross-space discrimination is presence-based: when `spaceId` is absent the
16
+ * reference is local (same contract-space); when `spaceId` is present the
17
+ * reference is cross-space. This mirrors the `ForeignKeyReference` carrier
18
+ * convention — no separate tag field — so local refs are JSON-minimal.
19
+ */
20
+ export interface ValueSetRef {
21
+ readonly plane: 'domain' | 'storage';
22
+ readonly namespaceId: string;
23
+ readonly entityKind: 'enum' | 'value-set';
24
+ readonly name: string;
25
+ readonly spaceId?: string;
26
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"canonicalization-DFE0HJkI.d.mts","names":[],"sources":["../src/canonicalization.ts"],"mappings":";;;;;;AAcA;;;;;;KAAY,iBAAA,IAAqB,QAAA,EAAU,QAAA,KAAa,UAAU;;AAAA;AAUlE;;;;AAA6D;AAS7D;KATY,sBAAA,IAA0B,IAAuB;;;AASlB;AA6K3C;;;;KA7KY,WAAA,IAAe,OAAgB;AAAA,UA6K1B,2BAAA;EAAA,SACN,aAAA;EAAA;;;;;;;;AA0ByB;AAQpC;EAlCW,SAWA,iBAAA,EAAmB,iBAAA;;;;;;;WAOnB,mBAAA,GAAsB,sBAAA;EAiB/B;;;;;AAEO;AA0BT;EA5BE,SATS,WAAA,GAAc,WAAA;AAAA;;;;;;iBAQT,4BAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAAA,GACR,MAAA;AAAA,iBA0Ba,oBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,EAAS,2BAA2B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-types-xgwKtd7y.d.mts","names":[],"sources":["../src/types.ts","../src/contract-types.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"domain-envelope-4hyFtJ4_.d.mts","names":[],"sources":["../src/namespace-id.ts","../src/cross-reference.ts","../src/domain-types.ts","../src/domain-envelope.ts"],"mappings":"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"}