@githolon/dsl 0.114.0 → 0.116.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.
- package/assets/operation-contract.wasm +0 -0
- package/dart/CHANGELOG.md +55 -0
- package/dart/lib/src/application_framework_calls.dart +7 -1
- package/dart/lib/src/console_enrolment.dart +7 -0
- package/dart/lib/src/console_http_io.dart +22 -15
- package/dart/lib/src/dev_cloud.dart +62 -0
- package/dart/lib/src/nms_generated_runtime.dart +1 -0
- package/dart/lib/src/operation_receipts.dart +74 -7
- package/dart/lib/src/reception/dart_reception.dart +26 -6
- package/dart/lib/src/result_usda.dart +64 -27
- package/dart/lib/src/runner/node-entry.mjs +4733 -1534
- package/dart/lib/src/runtime_v2_transport.dart +3 -0
- package/dart/lib/src/sync_status_projection.dart +11 -0
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/console_enrolment_test.dart +47 -0
- package/dart/test/kernel_refused_follow_test.dart +36 -0
- package/dart/test/model_adoption_test.dart +49 -0
- package/dart/test/result_usda_test.dart +67 -0
- package/dart/test/runtime_projections_test.dart +41 -0
- package/dart_types/CHANGELOG.md +30 -0
- package/dart_types/lib/src/approved_model.dart +78 -1
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +11 -1
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
- package/dart_types/lib/src/vo_canonical.dart +185 -0
- package/dart_types/pubspec.lock +1 -1
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/vo_canonical_test.dart +91 -0
- package/package.json +4 -2
- package/src/aggregate.ts +95 -0
- package/src/anonymous_value_interning.ts +205 -0
- package/src/baseline.ts +27 -0
- package/src/baseline_export.ts +432 -0
- package/src/baseline_import.ts +312 -0
- package/src/baseline_usda.ts +185 -0
- package/src/birth_key_rule.ts +472 -0
- package/src/births.ts +207 -24
- package/src/build_package.ts +47 -7
- package/src/build_provenance.ts +10 -0
- package/src/business_birth.ts +50 -4
- package/src/check_package_main.ts +50 -6
- package/src/codegen_call_proto.ts +5 -18
- package/src/codegen_call_usda.ts +6 -3
- package/src/codegen_dart.ts +114 -60
- package/src/codegen_dart_client.ts +52 -6
- package/src/codegen_dart_facade.ts +57 -35
- package/src/codegen_dart_framework_calls.ts +20 -1
- package/src/codegen_proof.ts +5 -2
- package/src/codegen_result_proto.ts +8 -6
- package/src/codegen_result_usda.ts +12 -4
- package/src/codegen_ts.ts +109 -10
- package/src/compatibility_graph.ts +41 -4
- package/src/compatibility_usda.ts +11 -1
- package/src/compile_package_main.ts +222 -52
- package/src/composed_wire_identity.ts +24 -0
- package/src/directive.ts +6 -0
- package/src/domain_requirements.ts +75 -0
- package/src/engine_entry.ts +12 -4
- package/src/engine_javascript.ts +12 -6
- package/src/fields.ts +109 -0
- package/src/identity_usda.ts +80 -103
- package/src/index.ts +35 -1
- package/src/intent.ts +33 -3
- package/src/law_check.ts +34 -0
- package/src/law_graph.ts +142 -1
- package/src/law_graph_usda.ts +27 -144
- package/src/law_layer_prims.ts +174 -0
- package/src/law_unit_selection.ts +45 -0
- package/src/law_units.ts +7 -6
- package/src/law_version.ts +108 -0
- package/src/package_config.ts +44 -0
- package/src/package_dependencies.ts +8 -0
- package/src/point_guard.ts +5 -1
- package/src/release_inspection.ts +6 -4
- package/src/release_publication.ts +6 -5
- package/src/usda_text.ts +78 -3
- package/src/value_identity.ts +355 -0
- package/src/value_object.ts +35 -4
- package/src/wire.ts +2 -0
- package/src/wire_paths.ts +17 -0
- package/src/workspace_routing.ts +1 -1
package/src/law_units.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// (kernel/src/law_units.rs) makes from the same package, so both hash identically. The format is
|
|
11
11
|
// specified in architecture/law_units_format.md.
|
|
12
12
|
|
|
13
|
+
import { usdaString, usdaUnquote } from "./usda_text.js";
|
|
13
14
|
import { createHash } from "node:crypto";
|
|
14
15
|
|
|
15
16
|
/** One authenticated file of a law closure, at its package logical path. */
|
|
@@ -136,7 +137,7 @@ export function scanDomainDeclarations(usda: string): DomainDeclarations {
|
|
|
136
137
|
if (frames.includes(null)) continue;
|
|
137
138
|
const at = frames as string[];
|
|
138
139
|
if (at.length !== 5 || at[0] !== "Nomos" || at[1] !== "Domains") continue;
|
|
139
|
-
const value =
|
|
140
|
+
const value = usdaUnquote(property[2]!);
|
|
140
141
|
if (at[3] === "Aggregates") {
|
|
141
142
|
(property[1] === "wireType" ? aggregateWireTypes : aggregateLabels).add(value);
|
|
142
143
|
} else if (at[3] === "Invariants" && property[1] === "label") {
|
|
@@ -213,7 +214,7 @@ export function packageRoutesLayerUsda(routes: readonly LawUnitRoute[]): string
|
|
|
213
214
|
routeOpinions.push(
|
|
214
215
|
` def Scope "r${String(index).padStart(6, "0")}"`,
|
|
215
216
|
" {",
|
|
216
|
-
` custom string nomos:selector = ${
|
|
217
|
+
` custom string nomos:selector = ${usdaString(
|
|
217
218
|
route.selector
|
|
218
219
|
)}`,
|
|
219
220
|
` custom string nomos:plan = "${route.planSha256}"`,
|
|
@@ -268,11 +269,11 @@ export function unitManifestUsda(unit: Omit<LawUnit, "manifest">): string {
|
|
|
268
269
|
"",
|
|
269
270
|
'def NomosLawUnit "__NomosLawUnit_7e9c"',
|
|
270
271
|
"{",
|
|
271
|
-
` custom string nomos:unit = ${
|
|
272
|
+
` custom string nomos:unit = ${usdaString(unit.key)}`,
|
|
272
273
|
` custom token nomos:unitKind = "${unit.kind}"`,
|
|
273
274
|
...(unit.domain === undefined
|
|
274
275
|
? []
|
|
275
|
-
: [` custom string nomos:domain = ${
|
|
276
|
+
: [` custom string nomos:domain = ${usdaString(unit.domain)}`]),
|
|
276
277
|
` custom string nomos:unitHash = "${unit.hash}"`,
|
|
277
278
|
"",
|
|
278
279
|
' def Scope "Assets"',
|
|
@@ -280,7 +281,7 @@ export function unitManifestUsda(unit: Omit<LawUnit, "manifest">): string {
|
|
|
280
281
|
...unit.assets.flatMap((asset) => [
|
|
281
282
|
` def NomosLawAsset "a_${sha256Hex(asset.logicalPath)}"`,
|
|
282
283
|
" {",
|
|
283
|
-
` custom string nomos:logicalPath = ${
|
|
284
|
+
` custom string nomos:logicalPath = ${usdaString(asset.logicalPath)}`,
|
|
284
285
|
` custom string nomos:sha256 = "${asset.sha256}"`,
|
|
285
286
|
" }",
|
|
286
287
|
]),
|
|
@@ -291,7 +292,7 @@ export function unitManifestUsda(unit: Omit<LawUnit, "manifest">): string {
|
|
|
291
292
|
...unit.routes.flatMap((route) => [
|
|
292
293
|
` def NomosLawRoute "s_${sha256Hex(route.selector)}"`,
|
|
293
294
|
" {",
|
|
294
|
-
` custom string nomos:selector = ${
|
|
295
|
+
` custom string nomos:selector = ${usdaString(route.selector)}`,
|
|
295
296
|
` custom string nomos:plan = "${route.planSha256}"`,
|
|
296
297
|
" }",
|
|
297
298
|
]),
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// THE CLAIMED VERSION OF A LAW (CAP-811).
|
|
2
|
+
//
|
|
3
|
+
// A law's identity is its package name (unique per provider, enforced by nomos.cafe). A version's identity is its
|
|
4
|
+
// content hash (the unit set identity). The claimed version orders the versions of one identity: an author declares
|
|
5
|
+
// it in `nomos.project.ts` (`version: "1.4.0"`), the compiler writes it into the package contract
|
|
6
|
+
// (`/Nomos/Package` `nomos:version` in `package.usda`), and so it travels inside the composition unit and is covered
|
|
7
|
+
// by that unit's hash, the unit set identity and the closure identity. Nobody can restate a version without minting
|
|
8
|
+
// a different law.
|
|
9
|
+
//
|
|
10
|
+
// The claim is a claim, not an authority: the provider's release lane (qualify -> publish -> rollout) refuses to
|
|
11
|
+
// publish a claim that does not move its series forward, and a kernel WARNS when an install serves an equal or lower
|
|
12
|
+
// claim for an identity it already serves. Absent claim = no ordering = no warning; a package that declares no
|
|
13
|
+
// version is byte-identical to one compiled before this key.
|
|
14
|
+
//
|
|
15
|
+
// Grammar: semver 2.0 MAJOR.MINOR.PATCH with an optional pre-release, no build metadata (build metadata does not
|
|
16
|
+
// order versions, so it could only make two different laws claim the same place). Dependency-free so the compiler,
|
|
17
|
+
// CLI, console and Peer Office share one comparison; the kernel's twin is `law_version` (kernel/src/law_version.rs).
|
|
18
|
+
import { usdaUnquote } from "./usda_text.js";
|
|
19
|
+
|
|
20
|
+
/** A validated claim, as authored: typed, never a bare string. Only `lawVersionClaim` and the readers here mint one. */
|
|
21
|
+
export type LawVersionClaim = string & { readonly __nomosLawVersionClaim: unique symbol };
|
|
22
|
+
|
|
23
|
+
export interface LawVersion {
|
|
24
|
+
readonly major: number;
|
|
25
|
+
readonly minor: number;
|
|
26
|
+
readonly patch: number;
|
|
27
|
+
/** Dot-separated pre-release identifiers, empty for a release. */
|
|
28
|
+
readonly prerelease: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NUMERIC = "0|[1-9]\\d*";
|
|
32
|
+
const IDENT = "(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)";
|
|
33
|
+
const LAW_VERSION = new RegExp(`^(${NUMERIC})\\.(${NUMERIC})\\.(${NUMERIC})(?:-(${IDENT}(?:\\.${IDENT})*))?$`);
|
|
34
|
+
|
|
35
|
+
/** Parse a claimed version, or explain why it is not one. */
|
|
36
|
+
export function parseLawVersion(text: string): LawVersion {
|
|
37
|
+
if (typeof text !== "string") throw new Error(`a law version must be a string; got ${JSON.stringify(text)}`);
|
|
38
|
+
if (text.includes("+")) {
|
|
39
|
+
throw new Error(`law version '${text}' carries build metadata; a claim must order law, so drop the '+…' suffix`);
|
|
40
|
+
}
|
|
41
|
+
const match = LAW_VERSION.exec(text);
|
|
42
|
+
if (match === null) {
|
|
43
|
+
throw new Error(`law version '${text}' is not semver MAJOR.MINOR.PATCH[-prerelease] (e.g. "1.4.0", "2.0.0-rc.1")`);
|
|
44
|
+
}
|
|
45
|
+
const part = (value: string): number => {
|
|
46
|
+
const number = Number(value);
|
|
47
|
+
if (!Number.isSafeInteger(number)) throw new Error(`law version '${text}' has a component too large to order`);
|
|
48
|
+
return number;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
major: part(match[1]!),
|
|
52
|
+
minor: part(match[2]!),
|
|
53
|
+
patch: part(match[3]!),
|
|
54
|
+
prerelease: match[4] === undefined ? [] : match[4].split("."),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Validate authored text as a claim, or throw why it is not one. */
|
|
59
|
+
export function lawVersionClaim(text: string): LawVersionClaim {
|
|
60
|
+
parseLawVersion(text);
|
|
61
|
+
return text as LawVersionClaim;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isLawVersion(text: unknown): text is LawVersionClaim {
|
|
65
|
+
if (typeof text !== "string") return false;
|
|
66
|
+
try {
|
|
67
|
+
parseLawVersion(text);
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const compareIdentifier = (left: string, right: string): number => {
|
|
75
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
76
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
77
|
+
if (leftNumeric && rightNumeric) return Math.sign(Number(left) - Number(right));
|
|
78
|
+
if (leftNumeric) return -1;
|
|
79
|
+
if (rightNumeric) return 1;
|
|
80
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Semver precedence: negative when `left` orders before `right`, zero when they claim the same place. */
|
|
84
|
+
export function compareLawVersions(left: LawVersionClaim, right: LawVersionClaim): number {
|
|
85
|
+
const a = parseLawVersion(left);
|
|
86
|
+
const b = parseLawVersion(right);
|
|
87
|
+
for (const key of ["major", "minor", "patch"] as const) {
|
|
88
|
+
if (a[key] !== b[key]) return Math.sign(a[key] - b[key]);
|
|
89
|
+
}
|
|
90
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
91
|
+
return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length === 0 ? 1 : -1;
|
|
92
|
+
}
|
|
93
|
+
for (let index = 0; index < Math.min(a.prerelease.length, b.prerelease.length); index += 1) {
|
|
94
|
+
const order = compareIdentifier(a.prerelease[index]!, b.prerelease[index]!);
|
|
95
|
+
if (order !== 0) return order;
|
|
96
|
+
}
|
|
97
|
+
return Math.sign(a.prerelease.length - b.prerelease.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Read the claimed version from a compiled package contract (`package.usda`, the composition unit's asset), or
|
|
102
|
+
* `undefined` when the law claims none. Throws when the contract carries a claim that is not a law version.
|
|
103
|
+
*/
|
|
104
|
+
export function claimedVersionOfPackageContract(packageUsda: string): LawVersionClaim | undefined {
|
|
105
|
+
const match = /^\s*custom uniform string nomos:version = ("(?:[^"\\]|\\.)*")\s*$/mu.exec(packageUsda);
|
|
106
|
+
if (match === null) return undefined;
|
|
107
|
+
return lawVersionClaim(usdaUnquote(match[1]!));
|
|
108
|
+
}
|
package/src/package_config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// NOMOS — one package contract for authoring, checking, compilation and editor diagnostics.
|
|
2
2
|
|
|
3
|
+
import { parseLawVersion } from "./law_version.js";
|
|
3
4
|
import {
|
|
4
5
|
normalizeRuntimePackageDependencies,
|
|
5
6
|
type RuntimePackageDependency,
|
|
@@ -72,6 +73,15 @@ export interface NormalizedNomosPackageConfig extends Omit<NomosPackageConfig, "
|
|
|
72
73
|
*/
|
|
73
74
|
export interface NomosPackageConfig {
|
|
74
75
|
readonly name: string;
|
|
76
|
+
/**
|
|
77
|
+
* THE CLAIMED VERSION of this law (CAP-811): semver `MAJOR.MINOR.PATCH[-prerelease]`, e.g. `"1.4.0"`. The
|
|
78
|
+
* package name is the law's identity, the content hash is the version's identity, and this claim orders the
|
|
79
|
+
* versions of one identity. It is compiled into the package contract, so it is covered by the law's hash. The
|
|
80
|
+
* release lane refuses to publish a claim that does not move its series forward; a kernel warns when an install
|
|
81
|
+
* serves an equal or lower claim. Era rule: a package that declares none is byte-identical to one compiled
|
|
82
|
+
* before this key.
|
|
83
|
+
*/
|
|
84
|
+
readonly version?: string;
|
|
75
85
|
readonly domains: readonly NomosPackageDomainConfig[];
|
|
76
86
|
/** Application-root policy compiled into the private receiving-group law.
|
|
77
87
|
* Required for a published application: Nomos never invents a child-workspace allowance. */
|
|
@@ -91,6 +101,17 @@ export interface NomosPackageConfig {
|
|
|
91
101
|
};
|
|
92
102
|
/** Separately installed law required at runtime; USDA composition arcs do not belong here. */
|
|
93
103
|
readonly dependencies?: readonly RuntimePackageDependency[];
|
|
104
|
+
/**
|
|
105
|
+
* THE RELEASED BASELINE this package's compiles extend (architecture/domain_release_lifecycle.md,
|
|
106
|
+
* step 1): a `nomos.baseline.usda` exported from released custody by `githolon baseline export`,
|
|
107
|
+
* pinned by content address. A publishable compile (`githolon compile --publishable`) refuses
|
|
108
|
+
* without one; every compile that names one verifies the ledger against it and imports the
|
|
109
|
+
* released identities and wire allocations it lacks. `artifact` is relative to this file.
|
|
110
|
+
*/
|
|
111
|
+
readonly baseline?: {
|
|
112
|
+
readonly artifact: string;
|
|
113
|
+
readonly sha256: string;
|
|
114
|
+
};
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
type PackageDomainKey<Config extends NomosPackageConfig> =
|
|
@@ -143,6 +164,13 @@ export function validateNomosPackageConfig(value: unknown): {
|
|
|
143
164
|
if (!Array.isArray(config.domains) || config.domains.length === 0) {
|
|
144
165
|
throw new Error("config needs domains: [{ key, features }, ...]");
|
|
145
166
|
}
|
|
167
|
+
if (config.version !== undefined) {
|
|
168
|
+
try {
|
|
169
|
+
parseLawVersion(config.version);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new Error(`package '${config.name}' version: ${(error as Error).message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
146
174
|
const domainKeys = new Set<string>();
|
|
147
175
|
const normalizedDomains: NormalizedNomosPackageDomainConfig[] = [];
|
|
148
176
|
for (const candidate of config.domains) {
|
|
@@ -219,6 +247,22 @@ export function validateNomosPackageConfig(value: unknown): {
|
|
|
219
247
|
if (config.dependencies !== undefined && !Array.isArray(config.dependencies)) {
|
|
220
248
|
throw new Error("package dependencies must be an array");
|
|
221
249
|
}
|
|
250
|
+
if (config.baseline !== undefined) {
|
|
251
|
+
const baseline = config.baseline as Partial<NonNullable<NomosPackageConfig["baseline"]>> | null;
|
|
252
|
+
if (
|
|
253
|
+
baseline === null
|
|
254
|
+
|| typeof baseline !== "object"
|
|
255
|
+
|| typeof baseline.artifact !== "string"
|
|
256
|
+
|| baseline.artifact.length === 0
|
|
257
|
+
|| typeof baseline.sha256 !== "string"
|
|
258
|
+
|| !/^[0-9a-f]{64}$/u.test(baseline.sha256)
|
|
259
|
+
) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
"baseline must be { artifact: '<path to nomos.baseline.usda>', sha256: '<its 64-hex content address>' }; " +
|
|
262
|
+
"export it with githolon baseline export and pin the sha256 it prints",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
222
266
|
if (config.application !== undefined) {
|
|
223
267
|
const max = config.application.maxChildWorkspacesPerUser;
|
|
224
268
|
if (!Number.isInteger(max) || max < 0) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// NOMOS — runtime package requirements are canonical typed USDA law, not ambient host setup.
|
|
2
2
|
|
|
3
|
+
import { parseLawVersion, type LawVersionClaim } from "./law_version.js";
|
|
3
4
|
import { usdaString, usdaStringArray } from "./usda_text.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -59,9 +60,13 @@ export function normalizeRuntimePackageDependencies(
|
|
|
59
60
|
/** Emit the package contract into the same composed stage the kernel executes. */
|
|
60
61
|
export function packageContractUsda(input: {
|
|
61
62
|
readonly packageName: string;
|
|
63
|
+
/** The law's claimed version (CAP-811, law_version.ts). Absent: no claim, and the contract is byte-identical
|
|
64
|
+
* to one compiled before claims existed. Present: carried in the composition unit, so covered by its hash. */
|
|
65
|
+
readonly claimedVersion?: LawVersionClaim;
|
|
62
66
|
readonly exportedDomains: readonly string[];
|
|
63
67
|
readonly runtimeDependencies: readonly RuntimePackageDependency[];
|
|
64
68
|
}): string {
|
|
69
|
+
if (input.claimedVersion !== undefined) parseLawVersion(input.claimedVersion);
|
|
65
70
|
const dependencies = [...input.runtimeDependencies];
|
|
66
71
|
const dependencyPrimName = (index: number): string => `d_${String(index).padStart(6, "0")}`;
|
|
67
72
|
const targets = dependencies.map(
|
|
@@ -81,6 +86,9 @@ export function packageContractUsda(input: {
|
|
|
81
86
|
' def NomosPackage "Package"',
|
|
82
87
|
" {",
|
|
83
88
|
` custom uniform string nomos:name = ${usdaString(input.packageName)}`,
|
|
89
|
+
...(input.claimedVersion === undefined
|
|
90
|
+
? []
|
|
91
|
+
: [` custom uniform string nomos:version = ${usdaString(input.claimedVersion)}`]),
|
|
84
92
|
' custom uniform token nomos:operationContract = "nomos.protobuf.v1"',
|
|
85
93
|
` custom uniform string[] nomos:domains = ${usdaStringArray([...input.exportedDomains].sort())}`,
|
|
86
94
|
...(targets.length > 0
|
package/src/point_guard.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { AggregateHandle, FieldValue, StoredFields } from "./aggregate.js";
|
|
4
4
|
import type { Field } from "./fields.js";
|
|
5
|
+
import { canonicalJsonValue } from "./value_identity.js";
|
|
5
6
|
|
|
6
7
|
export type PointGuardScalar = string | number | boolean;
|
|
7
8
|
|
|
@@ -185,7 +186,10 @@ export function evaluatePointGuard(
|
|
|
185
186
|
? predicate.right.value
|
|
186
187
|
: valueAt(predicate.right.source === "row" ? row : payload, predicate.right.path);
|
|
187
188
|
if (left === undefined || right === undefined) return predicate.operator === "ne";
|
|
188
|
-
|
|
189
|
+
// Value equality, not text equality: a stored row's members come back in storage order, so
|
|
190
|
+
// `JSON.stringify` would call a value unequal to itself (CAP-853). The kernel twin compares
|
|
191
|
+
// structurally too (`admission-peer` nominal_equals_json / serde_json Value equality).
|
|
192
|
+
const equal = canonicalJsonValue(left) === canonicalJsonValue(right);
|
|
189
193
|
return predicate.operator === "eq" ? equal : !equal;
|
|
190
194
|
}
|
|
191
195
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Read-only evidence, not an evolution/admission judge. No compile, mint or network calls. */
|
|
2
|
+
import { usdaUnquote } from './usda_text.js';
|
|
3
|
+
import { unquoteUsdaList } from './law_layer_prims.js';
|
|
2
4
|
import { createHash } from 'node:crypto';
|
|
3
5
|
import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
4
6
|
import path from 'node:path';
|
|
@@ -12,7 +14,7 @@ const literal = '("(?:[^"\\\\]|\\\\.)*")';
|
|
|
12
14
|
function attribute(source: string, name: string): string {
|
|
13
15
|
const values = [...source.matchAll(new RegExp(`nomos:${name} = ${literal}`, 'g'))];
|
|
14
16
|
if (values.length !== 1) throw new Error(`Expected one nomos:${name}`);
|
|
15
|
-
return
|
|
17
|
+
return usdaUnquote(values[0]![1]!);
|
|
16
18
|
}
|
|
17
19
|
function files(directory: string, root = directory): string[] {
|
|
18
20
|
return readdirSync(directory).sort().flatMap(name => {
|
|
@@ -92,9 +94,9 @@ export function inspectReleaseBaseline(input: { identity: string; units: string;
|
|
|
92
94
|
const label = /nomos:label = ("(?:[^"\\]|\\.)*")/.exec(attrs);
|
|
93
95
|
const wire = /nomos:wireKey = ("(?:[^"\\]|\\.)*")/.exec(attrs);
|
|
94
96
|
const aliases = /nomos:wireAliases = (\[[^\n]*\])/.exec(attrs);
|
|
95
|
-
const item = { ...(label ? { label:
|
|
96
|
-
...(wire ? { wire:
|
|
97
|
-
...(aliases ? { aliases:
|
|
97
|
+
const item = { ...(label ? { label: usdaUnquote(label[1]!) } : {}),
|
|
98
|
+
...(wire ? { wire: usdaUnquote(wire[1]!) } : {}),
|
|
99
|
+
...(aliases ? { aliases: unquoteUsdaList(aliases[1]!) } : {}) };
|
|
98
100
|
for (const key of [match[1]!, ...(wire ? [`u_${match[2]!}`] : [])]) {
|
|
99
101
|
declarations.set(key, [...(declarations.get(key) ?? []), item]);
|
|
100
102
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// hash and canonical manifest follow `law_units.ts` (`lawUnitHash`, `unitManifestUsda`) and the kernel's
|
|
12
12
|
// `DomainLawUnit`; `release_publication.test.ts` proves agreement on compiled units.
|
|
13
13
|
|
|
14
|
+
import { usdaString, usdaUnquote } from "./usda_text.js";
|
|
14
15
|
import { lawUnitSetIdentityPreimage } from "./law_unit_set_identity.ts";
|
|
15
16
|
|
|
16
17
|
export const RELEASE_IDENTITY_TAG = "nomos-domain-release-v1";
|
|
@@ -136,14 +137,14 @@ async function canonicalManifest(unit: ParsedManifest): Promise<string> {
|
|
|
136
137
|
const assetPrims = await Promise.all(unit.assets.map(async (asset) => [
|
|
137
138
|
` def NomosLawAsset "a_${await sha256Hex(asset.logicalPath)}"`,
|
|
138
139
|
" {",
|
|
139
|
-
` custom string nomos:logicalPath = ${
|
|
140
|
+
` custom string nomos:logicalPath = ${usdaString(asset.logicalPath)}`,
|
|
140
141
|
` custom string nomos:sha256 = "${asset.sha256}"`,
|
|
141
142
|
" }",
|
|
142
143
|
]));
|
|
143
144
|
const routePrims = await Promise.all(unit.routes.map(async (route) => [
|
|
144
145
|
` def NomosLawRoute "s_${await sha256Hex(route.selector)}"`,
|
|
145
146
|
" {",
|
|
146
|
-
` custom string nomos:selector = ${
|
|
147
|
+
` custom string nomos:selector = ${usdaString(route.selector)}`,
|
|
147
148
|
` custom string nomos:plan = "${route.planSha256}"`,
|
|
148
149
|
" }",
|
|
149
150
|
]));
|
|
@@ -158,9 +159,9 @@ async function canonicalManifest(unit: ParsedManifest): Promise<string> {
|
|
|
158
159
|
"",
|
|
159
160
|
'def NomosLawUnit "__NomosLawUnit_7e9c"',
|
|
160
161
|
"{",
|
|
161
|
-
` custom string nomos:unit = ${
|
|
162
|
+
` custom string nomos:unit = ${usdaString(unit.key)}`,
|
|
162
163
|
` custom token nomos:unitKind = "${unit.kind}"`,
|
|
163
|
-
...(unit.domain === undefined ? [] : [` custom string nomos:domain = ${
|
|
164
|
+
...(unit.domain === undefined ? [] : [` custom string nomos:domain = ${usdaString(unit.domain)}`]),
|
|
164
165
|
` custom string nomos:unitHash = "${unit.hash}"`,
|
|
165
166
|
"",
|
|
166
167
|
' def Scope "Assets"',
|
|
@@ -181,7 +182,7 @@ const LITERAL = '("(?:[^"\\\\]|\\\\.)*")';
|
|
|
181
182
|
function attribute(source: string, name: string, where: string): string {
|
|
182
183
|
const values = [...source.matchAll(new RegExp(`nomos:${name} = ${LITERAL}`, "g"))];
|
|
183
184
|
if (values.length !== 1) refuse("manifest-invalid", `${where}: expected one nomos:${name}`);
|
|
184
|
-
return
|
|
185
|
+
return usdaUnquote(values[0]![1]!);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/**
|
package/src/usda_text.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// NOMOS — minimal deterministic USDA text literals for build-time emitters.
|
|
2
|
-
// This module deliberately has no parser, generic value tree, flattening model or law
|
|
2
|
+
// This module deliberately has no document parser, generic value tree, flattening model or law
|
|
3
|
+
// vocabulary; it owns only the string-literal spelling every emitter shares.
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The ONE canonical USDA string literal (CAP-862), byte-identical with the kernel's
|
|
7
|
+
* `kernel/src/usda_string.rs` and the generated call encoders (`codegen_call_usda.ts`):
|
|
8
|
+
* `"` `\` LF CR TAB BS FF take `\"` `\\` `\n` `\r` `\t` `\b` `\f`; any other U+0000–U+001F and
|
|
9
|
+
* U+007F take `\xHH` (two lowercase hex digits); C1 and everything else is raw UTF-8. Every
|
|
10
|
+
* escape is one OpenUSD reads. `\uXXXX` (OpenUSD has none) is legacy INPUT only, never written.
|
|
11
|
+
* A lone surrogate is not a Unicode scalar and is refused. Pinned by
|
|
12
|
+
* `dsl/conformance/usda_string.v1.json`.
|
|
13
|
+
*/
|
|
5
14
|
export function usdaString(value: string): string {
|
|
6
15
|
let encoded = '"';
|
|
7
16
|
for (const character of value) {
|
|
@@ -16,7 +25,9 @@ export function usdaString(value: string): string {
|
|
|
16
25
|
default: {
|
|
17
26
|
const codePoint = character.codePointAt(0)!;
|
|
18
27
|
if (codePoint < 0x20 || codePoint === 0x7f) {
|
|
19
|
-
encoded += `\\
|
|
28
|
+
encoded += `\\x${codePoint.toString(16).padStart(2, "0")}`;
|
|
29
|
+
} else if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
|
|
30
|
+
throw new TypeError(`a USDA string cannot carry the lone surrogate U+${codePoint.toString(16).toUpperCase()}`);
|
|
20
31
|
} else {
|
|
21
32
|
encoded += character;
|
|
22
33
|
}
|
|
@@ -26,6 +37,70 @@ export function usdaString(value: string): string {
|
|
|
26
37
|
return `${encoded}"`;
|
|
27
38
|
}
|
|
28
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Expand the escapes of a quoted USDA string's body exactly as OpenUSD (`TfEscapeString`) does —
|
|
42
|
+
* `\a \b \f \n \r \t \v`, `\x` with up to two hex digits, `\NNN` with up to three octal digits,
|
|
43
|
+
* any other escaped character is itself — plus the LEGACY `\uXXXX` the pre-CAP-862 writers spelled
|
|
44
|
+
* controls with, read as the scalar it names (as the kernel normalises it before parsing).
|
|
45
|
+
*/
|
|
46
|
+
export function usdaUnescape(body: string): string {
|
|
47
|
+
let out = "";
|
|
48
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
49
|
+
const character = body[index]!;
|
|
50
|
+
if (character !== "\\") {
|
|
51
|
+
out += character;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
index += 1;
|
|
55
|
+
const escaped = body[index];
|
|
56
|
+
if (escaped === undefined) break;
|
|
57
|
+
switch (escaped) {
|
|
58
|
+
case "a": out += "\x07"; break;
|
|
59
|
+
case "b": out += "\b"; break;
|
|
60
|
+
case "f": out += "\f"; break;
|
|
61
|
+
case "n": out += "\n"; break;
|
|
62
|
+
case "r": out += "\r"; break;
|
|
63
|
+
case "t": out += "\t"; break;
|
|
64
|
+
case "v": out += "\v"; break;
|
|
65
|
+
case "x": {
|
|
66
|
+
const digits = /^[0-9a-fA-F]{0,2}/u.exec(body.slice(index + 1))![0];
|
|
67
|
+
out += String.fromCharCode(digits === "" ? 0 : Number.parseInt(digits, 16));
|
|
68
|
+
index += digits.length;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "u": {
|
|
72
|
+
const digits = body.slice(index + 1, index + 5);
|
|
73
|
+
const codePoint = /^[0-9a-fA-F]{4}$/u.test(digits) ? Number.parseInt(digits, 16) : undefined;
|
|
74
|
+
if (codePoint !== undefined && (codePoint < 0xd800 || codePoint > 0xdfff)) {
|
|
75
|
+
out += String.fromCharCode(codePoint);
|
|
76
|
+
index += 4;
|
|
77
|
+
} else {
|
|
78
|
+
out += "u";
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
if (escaped >= "0" && escaped <= "7") {
|
|
84
|
+
const digits = /^[0-7]{1,3}/u.exec(body.slice(index))![0];
|
|
85
|
+
out += String.fromCharCode(Number.parseInt(digits, 8));
|
|
86
|
+
index += digits.length - 1;
|
|
87
|
+
} else {
|
|
88
|
+
out += escaped;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Decode one double-quoted USDA string literal (see {@link usdaUnescape}). */
|
|
96
|
+
export function usdaUnquote(raw: string): string {
|
|
97
|
+
const value = raw.trim();
|
|
98
|
+
if (value.length < 2 || !value.startsWith('"') || !value.endsWith('"')) {
|
|
99
|
+
throw new Error(`expected a quoted USDA string, got ${value}`);
|
|
100
|
+
}
|
|
101
|
+
return usdaUnescape(value.slice(1, -1));
|
|
102
|
+
}
|
|
103
|
+
|
|
29
104
|
/** Emit a USDA `string[]` literal. */
|
|
30
105
|
export function usdaStringArray(items: readonly string[]): string {
|
|
31
106
|
return `[${items.map(usdaString).join(", ")}]`;
|