@githolon/dsl 0.105.7 → 0.105.9
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/dart/CHANGELOG.md +5 -0
- package/dart/lib/src/runner/node-entry.mjs +3 -0
- package/dart/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +3 -0
- package/src/codegen_dart.ts +1 -1
- package/src/codegen_proof.ts +1 -1
- package/src/codegen_ts.ts +21 -8
- package/src/compat_manifest.ts +1 -1
- package/src/compile_package_main.ts +81 -13
- package/src/framework/home.ts +10 -11
- package/src/framework/workspaces.ts +1 -1
- package/src/index.ts +1 -1
- package/src/law_check.ts +1 -1
- package/src/relation_schema.ts +1 -1
- package/src/relation_sugar.ts +1 -1
- package/src/shape_compat.ts +1 -1
- package/src/value_object.ts +2 -2
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 0.64.25
|
|
2
|
+
|
|
3
|
+
- Rebuild the vendored Dart runner from `@githolon/client` 0.105.8. Web-client source is an input to
|
|
4
|
+
this immutable pub.dev archive, so the Dart package now advances whenever that source changes.
|
|
5
|
+
|
|
1
6
|
## 0.64.24
|
|
2
7
|
|
|
3
8
|
- Restaged with the current kernel candidate: regenerated vendored assets (kernel release identity is baked into them). Published pub.dev archives are immutable, so regenerated bytes ride their own version.
|
package/dart/pubspec.yaml
CHANGED
|
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
|
|
|
7
7
|
description: >-
|
|
8
8
|
Nomos 2 Dart client — generated domain values, intent payloads and application
|
|
9
9
|
lifecycle types over the typed Nomos kernel protocol and USDA custody model.
|
|
10
|
-
version: 0.64.
|
|
10
|
+
version: 0.64.25
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
package/package.json
CHANGED
|
@@ -7530,6 +7530,9 @@ function createSerializedScheduler() {
|
|
|
7530
7530
|
return scheduler;
|
|
7531
7531
|
}
|
|
7532
7532
|
|
|
7533
|
+
// ../cloud/web-client/src/intent_offer_codec.ts
|
|
7534
|
+
var encoder = new TextEncoder();
|
|
7535
|
+
|
|
7533
7536
|
// ../cloud/web-client/src/index.ts
|
|
7534
7537
|
var enc4 = new TextEncoder();
|
|
7535
7538
|
var dec4 = new TextDecoder();
|
package/src/codegen_dart.ts
CHANGED
|
@@ -575,7 +575,7 @@ function introspectField(
|
|
|
575
575
|
case "ZodArray": {
|
|
576
576
|
const elem = zodArrayElement(zt);
|
|
577
577
|
// A `string[]` is a typed `List<String>`. A list of DECLARED VALUE OBJECTS is typed
|
|
578
|
-
// with the generated VO class (
|
|
578
|
+
// with the generated VO class (vo_first_class.md). ANY other element
|
|
579
579
|
// type is arbitrary JSON DATA to the engine, modelled as `List<Object?>`.
|
|
580
580
|
if (zodTypeName(elem) === "ZodString") {
|
|
581
581
|
return { name, dartType: "List<String>", toJson: "strList" };
|
package/src/codegen_proof.ts
CHANGED
|
@@ -239,7 +239,7 @@ function sampleOfZod(
|
|
|
239
239
|
next.add(zt);
|
|
240
240
|
return sampleOfZod(zodUnwrapLazy(zt), path, where, voIncludeOptional, next);
|
|
241
241
|
}
|
|
242
|
-
// VALUE OBJECTS FIRST (
|
|
242
|
+
// VALUE OBJECTS FIRST (vo_first_class.md): a declared VO carries its own lawful
|
|
243
243
|
// sample (the author's validated `example`, the blessed geometry sample, or per-field descriptor
|
|
244
244
|
// synthesis, recursing through nested VOs / arrays-of-VOs / unions). The raw-zod walk below cannot
|
|
245
245
|
// see interior constraints (geo's min-2 coordinates, a `.min()` on a subfield), so after building the
|
package/src/codegen_ts.ts
CHANGED
|
@@ -333,7 +333,7 @@ function tsTypeOfZod(
|
|
|
333
333
|
const target = idOfTargetKindOf(zt) === "contract" ? contractTypeName(aggregateType) : aggregateType;
|
|
334
334
|
return `NomosRef<${target}>`;
|
|
335
335
|
}
|
|
336
|
-
// A DECLARED VALUE OBJECT keeps its NAME (
|
|
336
|
+
// A DECLARED VALUE OBJECT keeps its NAME (vo_first_class.md): the payload
|
|
337
337
|
// references the same exported interface the read model uses — one name, both directions —
|
|
338
338
|
// instead of an anonymous inline literal type. Checked on the BARE object only: optional/
|
|
339
339
|
// default wrappers must keep flowing through the switch so `| undefined` survives.
|
|
@@ -761,6 +761,9 @@ export interface TsClientOptions {
|
|
|
761
761
|
/** Explicit application home for direct/synthetic generation. The package compiler derives this from
|
|
762
762
|
* business birth topology and passes it here; ambiguous multi-domain packages fail rather than flatten. */
|
|
763
763
|
readonly homeDomain?: string;
|
|
764
|
+
/** Emit the structural holon factory for the compiler-owned headless artifact. The ordinary tenant
|
|
765
|
+
* client leaves this false so framework transport types never enter its public declaration graph. */
|
|
766
|
+
readonly exportHolonFactory?: boolean;
|
|
764
767
|
}
|
|
765
768
|
|
|
766
769
|
/** TS string literal (JSON.stringify covers escaping). */
|
|
@@ -1890,9 +1893,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1890
1893
|
` /** THE ACTIVITY FEED (declared \`.activity(...)\`): subscribe to per-committed-intent RAW ROWS`,
|
|
1891
1894
|
` * (the runtime folds the intent log). \`cb\` fires with the current rows + on every new intent;`,
|
|
1892
1895
|
` * returns an unsubscribe fn. The generated client maps these through \`renderActivity\`. */`,
|
|
1893
|
-
` watchActivity(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
|
|
1896
|
+
` watchActivity?(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
|
|
1894
1897
|
` /** One-shot: the current committed activity log as RAW ROWS (folded to typed rows by the client). */`,
|
|
1895
|
-
` activityLog(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
|
|
1898
|
+
` activityLog?(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
|
|
1896
1899
|
]
|
|
1897
1900
|
: []),
|
|
1898
1901
|
` /** THE COMPAT SUPERPOWER (#72b) — OPTIONAL: a holon from @githolon/client's connect() attaches it.`,
|
|
@@ -3658,15 +3661,15 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3658
3661
|
` * Every public directive has a baseline; \`.activity(...)\` enriches it. Framework/control work`,
|
|
3659
3662
|
` * outside this generated package is dropped. Returns stop(). */`,
|
|
3660
3663
|
` watchActivity: (cb: (events: ActivityEvent[]) => void, opts?: { base?: string }) =>`,
|
|
3661
|
-
`
|
|
3664
|
+
` _nomosWatchActivity((rows) => cb(rows.map(renderActivity).filter((e): e is ActivityEvent => e !== null)), opts),`,
|
|
3662
3665
|
` /** One-shot: the current committed activity log, folded to typed [ActivityEvent]s. */`,
|
|
3663
3666
|
` activityLog: async (opts?: { base?: string; limit?: number }): Promise<ActivityEvent[]> =>`,
|
|
3664
|
-
` (await
|
|
3667
|
+
` (await _nomosActivityLog(opts)).map(renderActivity).filter((e): e is ActivityEvent => e !== null),`,
|
|
3665
3668
|
` /** THE SELF-DESCRIPTION: root links to exactly six grounded pages — model, current state, rules,`,
|
|
3666
3669
|
` * history, decisions and evidence. The old \`events\` / \`transcript\` fields remain, so this is a`,
|
|
3667
3670
|
` * strict enrichment of the activity narrative. \`section\` selects one page. */`,
|
|
3668
3671
|
` describe: async (opts: { section?: NomosDescribeSection; since?: string; performedBy?: string; category?: string; limit?: number } = {}): Promise<NomosDescription> => {`,
|
|
3669
|
-
` const raw = await
|
|
3672
|
+
` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
|
|
3670
3673
|
` let events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null);`,
|
|
3671
3674
|
` if (opts.performedBy !== undefined) events = events.filter((e) => e.performedBy === opts.performedBy);`,
|
|
3672
3675
|
` if (opts.category !== undefined) events = events.filter((e) => e.category === opts.category);`,
|
|
@@ -3778,7 +3781,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3778
3781
|
` * point at it (only directives declaring a ref to the aggregate appear in its story) + a transcript. */`,
|
|
3779
3782
|
` describeHistory: async <TAggregate>(record: NomosRef<TAggregate>, opts: { limit?: number } = {}): Promise<ActivityNarrative> => {`,
|
|
3780
3783
|
` const aggregateId = _nmsReferenceId(record, "", holon.workspace, "record");`,
|
|
3781
|
-
` const raw = await
|
|
3784
|
+
` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
|
|
3782
3785
|
` const events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null)`,
|
|
3783
3786
|
` .filter((e) => Object.values(e.refs).some((r) => _nmsReferenceData(r).id === aggregateId));`,
|
|
3784
3787
|
` return { events, transcript: _activityTranscript(events) };`,
|
|
@@ -3789,9 +3792,19 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3789
3792
|
out.push(
|
|
3790
3793
|
`/** Framework binding for the generated application. Law identity, author identity and key custody`,
|
|
3791
3794
|
` * are captured from the connected stateful client; callers cannot override them. */`,
|
|
3792
|
-
|
|
3795
|
+
`/** THE HOLON-LEVEL FACTORY — the headless/code-mode binding (headless-operation.md). Binds`,
|
|
3796
|
+
` * structurally to any NomosHolon: a full \`connect()\` holon, or the thin remote holon`,
|
|
3797
|
+
` * (\`connectRemote\`) for isolates that live beside the edge. The application surface`,
|
|
3798
|
+
` * (\`open<Name>Application\`) remains the batteries-included entry for devices. */`,
|
|
3799
|
+
`${opts.exportHolonFactory === true ? "export " : ""}function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
|
|
3793
3800
|
` const domainHash = ${hashConst};`,
|
|
3794
3801
|
` const actor = holon.principal;`,
|
|
3802
|
+
...(activityPresent
|
|
3803
|
+
? [
|
|
3804
|
+
` const _nomosActivityLog = (opts?: { base?: string; limit?: number }) => { if (!holon.activityLog) throw new Error("activity reads need a custody-capable @githolon/client binding"); return holon.activityLog(opts); };`,
|
|
3805
|
+
` const _nomosWatchActivity = (cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }) => { if (!holon.watchActivity) throw new Error("activity watches need a custody-capable @githolon/client binding"); return holon.watchActivity(cb, opts); };`,
|
|
3806
|
+
]
|
|
3807
|
+
: []),
|
|
3795
3808
|
` holon.prepareDomain?.(${JSON.stringify(domain)}, domainHash);`,
|
|
3796
3809
|
...(encPresent ? [` const keyStore: NomosKeyStore | undefined = ${domScopes.length > 0 ? `{ scopeKey: async (scope) => { if (!holon.__nomosScopeKey) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKey({ scope }); }, readKeys: async (scopes) => { if (!holon.__nomosScopeKeys) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKeys({ scopes }); } }` : "undefined"};`] : []),
|
|
3797
3810
|
...(ownedScope !== undefined && Object.keys(ownedActions).length > 0
|
package/src/compat_manifest.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The CLIENT/LAW COMPATIBILITY MANIFEST (
|
|
1
|
+
// The CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
|
|
2
2
|
//
|
|
3
3
|
// A DIAGNOSIS surface, NEVER a gate. Clients fit workspaces by STRUCTURAL INTERFACE
|
|
4
4
|
// (stable-id subtyping, #72), not by law-hash pin — the hash pin was a bug we removed.
|
|
@@ -1660,8 +1660,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
|
|
|
1660
1660
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1661
1661
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1662
1662
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1663
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1664
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1663
|
+
const NOMOS_CLIENT_DEP = "^0.64.25";
|
|
1664
|
+
const NOMOS_FLUTTER_DEP = "^0.63.47";
|
|
1665
1665
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1666
1666
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1667
1667
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -5080,7 +5080,7 @@ async function main(): Promise<void> {
|
|
|
5080
5080
|
identityHashes[module.domain ?? module.name] = hash;
|
|
5081
5081
|
}
|
|
5082
5082
|
|
|
5083
|
-
// ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (
|
|
5083
|
+
// ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
|
|
5084
5084
|
// A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
|
|
5085
5085
|
// fit workspaces by STRUCTURAL INTERFACE (stable-id subtyping), not by law-hash pin. This
|
|
5086
5086
|
// block is baked into both generated clients (TS + Dart) and mirrored into the deploy JSON
|
|
@@ -5253,20 +5253,29 @@ async function main(): Promise<void> {
|
|
|
5253
5253
|
// deployed law's content hash baked in (emitted AFTER the package is hashed) ──
|
|
5254
5254
|
const clientPath = path.join(outDir, `${cfg.name}.client.mjs`);
|
|
5255
5255
|
const clientDeclarationPath = path.join(outDir, `${cfg.name}.client.d.mts`);
|
|
5256
|
-
const
|
|
5256
|
+
const headlessClientPath = path.join(outDir, `${cfg.name}.headless.mjs`);
|
|
5257
|
+
const headlessClientDeclarationPath = path.join(outDir, `${cfg.name}.headless.d.mts`);
|
|
5258
|
+
const clientOptions = {
|
|
5257
5259
|
packageName: cfg.name,
|
|
5258
5260
|
domainHash,
|
|
5259
5261
|
interface: packageInterface,
|
|
5260
5262
|
compat: nomosCompat,
|
|
5261
5263
|
homeDomain: resolveApplicationTopology(presentationDomainModules).homeDomain,
|
|
5264
|
+
} as const;
|
|
5265
|
+
const clientSource = generateTsClient(presentationDomainModules, clientOptions);
|
|
5266
|
+
const headlessClientSource = generateTsClient(presentationDomainModules, {
|
|
5267
|
+
...clientOptions,
|
|
5268
|
+
exportHolonFactory: true,
|
|
5262
5269
|
});
|
|
5263
5270
|
const runtimePath = path.join(DSL_DIR, "runtime", "generated-application-host.js");
|
|
5264
5271
|
if (!existsSync(runtimePath)) {
|
|
5265
5272
|
fail("the compiler-private generated application runtime is missing — reinstall @githolon/dsl");
|
|
5266
5273
|
}
|
|
5267
5274
|
const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
|
|
5275
|
+
const temporaryHeadlessClientPath = path.join(outDir, `.${cfg.name}.headless.source.ts`);
|
|
5268
5276
|
const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
|
|
5269
5277
|
const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
|
|
5278
|
+
const temporaryHeadlessDeclarationPath = temporaryHeadlessClientPath.replace(/\.ts$/, ".d.ts");
|
|
5270
5279
|
const runtimeTypesSource = [
|
|
5271
5280
|
`export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
|
|
5272
5281
|
` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; onPerformance?: (sample: { readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance"; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) => void; lawHash: string; readyCommands?: readonly { readonly domain: string; readonly packageHash: string; readonly directiveId: string }[] },`,
|
|
@@ -5306,6 +5315,8 @@ async function main(): Promise<void> {
|
|
|
5306
5315
|
.update(`${declarationProofFormat}\0`)
|
|
5307
5316
|
.update(normalizedClientSource)
|
|
5308
5317
|
.update("\0")
|
|
5318
|
+
.update(headlessClientSource.replaceAll(domainHash, "<nomos-composed-law-identity>"))
|
|
5319
|
+
.update("\0")
|
|
5309
5320
|
.update(runtimeTypesSource)
|
|
5310
5321
|
.update("\0")
|
|
5311
5322
|
.update(ts.version)
|
|
@@ -5313,25 +5324,37 @@ async function main(): Promise<void> {
|
|
|
5313
5324
|
.update(readFileSync(fileURLToPath(import.meta.url)))
|
|
5314
5325
|
.digest("hex");
|
|
5315
5326
|
const declarationCacheDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "typescript-declarations");
|
|
5316
|
-
const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.d.mts`);
|
|
5317
|
-
const
|
|
5318
|
-
const
|
|
5319
|
-
|
|
5327
|
+
const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.d.mts`);
|
|
5328
|
+
const headlessDeclarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.d.mts`);
|
|
5329
|
+
const declarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.proof.usda`);
|
|
5330
|
+
const headlessDeclarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.proof.usda`);
|
|
5331
|
+
const declarationProofKey = (artifact: "client" | "headless", bytes: Buffer): string => createHash("sha256")
|
|
5332
|
+
.update(`${declarationProofFormat}\0${declarationInputIdentity}\0${artifact}\0`)
|
|
5320
5333
|
.update(bytes)
|
|
5321
5334
|
.digest("hex");
|
|
5322
5335
|
const cachedDeclaration = existsSync(declarationCachePath) ? readFileSync(declarationCachePath) : undefined;
|
|
5336
|
+
const cachedHeadlessDeclaration = existsSync(headlessDeclarationCachePath)
|
|
5337
|
+
? readFileSync(headlessDeclarationCachePath)
|
|
5338
|
+
: undefined;
|
|
5323
5339
|
const declarationCacheHit = cachedDeclaration !== undefined &&
|
|
5340
|
+
cachedHeadlessDeclaration !== undefined &&
|
|
5324
5341
|
declarationProofCodec.readKeyProofUsda(declarationCacheProofPath, declarationProofFormat) ===
|
|
5325
|
-
declarationProofKey(cachedDeclaration)
|
|
5326
|
-
|
|
5342
|
+
declarationProofKey("client", cachedDeclaration) &&
|
|
5343
|
+
declarationProofCodec.readKeyProofUsda(headlessDeclarationCacheProofPath, declarationProofFormat) ===
|
|
5344
|
+
declarationProofKey("headless", cachedHeadlessDeclaration);
|
|
5345
|
+
if (declarationCacheHit) {
|
|
5346
|
+
copyFileSync(declarationCachePath, clientDeclarationPath);
|
|
5347
|
+
copyFileSync(headlessDeclarationCachePath, headlessClientDeclarationPath);
|
|
5348
|
+
}
|
|
5327
5349
|
try {
|
|
5328
5350
|
if (!declarationCacheHit) {
|
|
5329
5351
|
writeFileSync(temporaryClientPath, clientSource, "utf8");
|
|
5352
|
+
writeFileSync(temporaryHeadlessClientPath, headlessClientSource, "utf8");
|
|
5330
5353
|
writeFileSync(temporaryRuntimeTypesPath, runtimeTypesSource, "utf8");
|
|
5331
5354
|
const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
|
|
5332
5355
|
const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
|
|
5333
5356
|
const program = ts.createProgram(
|
|
5334
|
-
[temporaryClientPath, temporaryRuntimeTypesPath],
|
|
5357
|
+
[temporaryClientPath, temporaryHeadlessClientPath, temporaryRuntimeTypesPath],
|
|
5335
5358
|
{
|
|
5336
5359
|
target: ts.ScriptTarget.ES2022,
|
|
5337
5360
|
module: ts.ModuleKind.NodeNext,
|
|
@@ -5351,7 +5374,10 @@ async function main(): Promise<void> {
|
|
|
5351
5374
|
);
|
|
5352
5375
|
const emit = program.emit();
|
|
5353
5376
|
const diagnostics = [...ts.getPreEmitDiagnostics(program), ...emit.diagnostics];
|
|
5354
|
-
if (
|
|
5377
|
+
if (
|
|
5378
|
+
diagnostics.length > 0 || emit.emitSkipped ||
|
|
5379
|
+
!existsSync(temporaryDeclarationPath) || !existsSync(temporaryHeadlessDeclarationPath)
|
|
5380
|
+
) {
|
|
5355
5381
|
const detail = diagnostics.map((diagnostic) => {
|
|
5356
5382
|
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
5357
5383
|
if (diagnostic.file === undefined || diagnostic.start === undefined) return message;
|
|
@@ -5497,14 +5523,23 @@ async function main(): Promise<void> {
|
|
|
5497
5523
|
].join("\n");
|
|
5498
5524
|
writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
|
|
5499
5525
|
renameSync(temporaryDeclarationPath, clientDeclarationPath);
|
|
5526
|
+
renameSync(temporaryHeadlessDeclarationPath, headlessClientDeclarationPath);
|
|
5500
5527
|
mkdirSync(declarationCacheDir, { recursive: true });
|
|
5501
5528
|
const declarationCacheTemporaryPath = `${declarationCachePath}.tmp-${process.pid}`;
|
|
5529
|
+
const headlessDeclarationCacheTemporaryPath = `${headlessDeclarationCachePath}.tmp-${process.pid}`;
|
|
5502
5530
|
copyFileSync(clientDeclarationPath, declarationCacheTemporaryPath);
|
|
5531
|
+
copyFileSync(headlessClientDeclarationPath, headlessDeclarationCacheTemporaryPath);
|
|
5503
5532
|
renameSync(declarationCacheTemporaryPath, declarationCachePath);
|
|
5533
|
+
renameSync(headlessDeclarationCacheTemporaryPath, headlessDeclarationCachePath);
|
|
5504
5534
|
declarationProofCodec.writeKeyProofUsda(
|
|
5505
5535
|
declarationCacheProofPath,
|
|
5506
5536
|
declarationProofFormat,
|
|
5507
|
-
declarationProofKey(readFileSync(declarationCachePath)),
|
|
5537
|
+
declarationProofKey("client", readFileSync(declarationCachePath)),
|
|
5538
|
+
);
|
|
5539
|
+
declarationProofCodec.writeKeyProofUsda(
|
|
5540
|
+
headlessDeclarationCacheProofPath,
|
|
5541
|
+
declarationProofFormat,
|
|
5542
|
+
declarationProofKey("headless", readFileSync(headlessDeclarationCachePath)),
|
|
5508
5543
|
);
|
|
5509
5544
|
}
|
|
5510
5545
|
await esbuildMod.build({
|
|
@@ -5540,12 +5575,44 @@ async function main(): Promise<void> {
|
|
|
5540
5575
|
) {
|
|
5541
5576
|
fail("generated TypeScript application did not seal its private runtime binding");
|
|
5542
5577
|
}
|
|
5578
|
+
await esbuildMod.build({
|
|
5579
|
+
stdin: {
|
|
5580
|
+
contents: headlessClientSource,
|
|
5581
|
+
loader: "ts",
|
|
5582
|
+
resolveDir: outDir,
|
|
5583
|
+
sourcefile: `${cfg.name}.headless.ts`,
|
|
5584
|
+
},
|
|
5585
|
+
outfile: headlessClientPath,
|
|
5586
|
+
bundle: true,
|
|
5587
|
+
packages: "bundle",
|
|
5588
|
+
platform: "node",
|
|
5589
|
+
format: "esm",
|
|
5590
|
+
target: "node20",
|
|
5591
|
+
legalComments: "none",
|
|
5592
|
+
logLevel: "silent",
|
|
5593
|
+
plugins: [{
|
|
5594
|
+
name: "nomos-generated-headless-runtime",
|
|
5595
|
+
setup(build) {
|
|
5596
|
+
build.onResolve(
|
|
5597
|
+
{ filter: /^@githolon\/client\/_generated_application_host_7c1f5e$/ },
|
|
5598
|
+
() => ({ path: runtimePath }),
|
|
5599
|
+
);
|
|
5600
|
+
},
|
|
5601
|
+
}],
|
|
5602
|
+
});
|
|
5603
|
+
const headlessBundled = canonicalizeGeneratedClientJavascript(readFileSync(headlessClientPath, "utf8"));
|
|
5604
|
+
writeFileSync(headlessClientPath, headlessBundled, "utf8");
|
|
5605
|
+
if (!new RegExp(`export\\s*\\{[^}]*${tsClientFactoryName(resolveApplicationTopology(presentationDomainModules).homeDomain)}`, "s").test(headlessBundled)) {
|
|
5606
|
+
fail("generated headless application omitted its structural holon factory");
|
|
5607
|
+
}
|
|
5543
5608
|
} catch (e) {
|
|
5544
5609
|
fail(`generated TypeScript application could not be sealed — this is a Nomos codegen bug: ${(e as Error).message}`);
|
|
5545
5610
|
} finally {
|
|
5546
5611
|
rmSync(temporaryClientPath, { force: true });
|
|
5612
|
+
rmSync(temporaryHeadlessClientPath, { force: true });
|
|
5547
5613
|
rmSync(temporaryRuntimeTypesPath, { force: true });
|
|
5548
5614
|
rmSync(temporaryDeclarationPath, { force: true });
|
|
5615
|
+
rmSync(temporaryHeadlessDeclarationPath, { force: true });
|
|
5549
5616
|
rmSync(path.join(outDir, `${cfg.name}.client.ts`), { force: true });
|
|
5550
5617
|
}
|
|
5551
5618
|
|
|
@@ -5903,6 +5970,7 @@ async function main(): Promise<void> {
|
|
|
5903
5970
|
}
|
|
5904
5971
|
}
|
|
5905
5972
|
console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
|
|
5973
|
+
console.log(` headless ${rel(headlessClientPath)} + ${rel(headlessClientDeclarationPath)} (typed business binding for code-mode/edge holons)`);
|
|
5906
5974
|
for (const note of recursiveNotes) console.log(` note ${note}`);
|
|
5907
5975
|
console.log(` openapi served by the holon from installed USDA (GET /v2/workspaces/<ws>/openapi.json) — no build-time copy to drift`);
|
|
5908
5976
|
if (proofPath !== undefined) {
|
package/src/framework/home.ts
CHANGED
|
@@ -96,12 +96,11 @@ export const ReceivingGroup = aggregate("NomosInternalReceivingGroup7c1f5e", {
|
|
|
96
96
|
* and secure: a live, verified device may add or revoke another device; keyless IdP enrolment is only the
|
|
97
97
|
* pre-group bootstrap lane.
|
|
98
98
|
*
|
|
99
|
-
* ALL-DEVICE-LOSS RECOVERY —
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* the policy fields that design reads.
|
|
99
|
+
* ALL-DEVICE-LOSS RECOVERY — CO2 already recovers through its platform AuthProvider, but the implementation
|
|
100
|
+
* reaches that result by repeating birth after this home's direct enrollment policy refuses. The corrected
|
|
101
|
+
* design (`architecture/guardian_device_recovery.md`, reconciled 2026-08-10) makes recovery authority an
|
|
102
|
+
* explicit tenant-selected policy: platform identity provider or guardian quorum. The missing work is to
|
|
103
|
+
* evaluate that choice at the existing child's frontier and delete the client-side birth-repeat bypass.
|
|
105
104
|
*/
|
|
106
105
|
export const ReceivingGroupPolicy = aggregate("NomosInternalReceivingGroupPolicy7c1f5e", {
|
|
107
106
|
additionalDevice: t.string().merge(Lww),
|
|
@@ -207,15 +206,15 @@ const receivingGroupPolicyRefs = readSet(
|
|
|
207
206
|
);
|
|
208
207
|
|
|
209
208
|
/** An IdP token may bootstrap the parent before the group exists; it may never bypass an existing group's
|
|
210
|
-
* device policy.
|
|
209
|
+
* device policy. An existing group chooses whether recovery is delegated to its platform AuthProvider or to
|
|
210
|
+
* guardians; the current default below says guardian, while CO2's current product behaviour is platform IdP.
|
|
211
211
|
*
|
|
212
212
|
* KNOWN GAP (`architecture/guardian_device_recovery.md`): this invariant is declared `.on("enrollFirstDevice")`
|
|
213
213
|
* and only fires when that directive targets THIS home directly. It is NOT re-applied when the birth
|
|
214
214
|
* offer-effect (`run_birth_effect`, repeating `nomosInternalBirthReceivingGroup` against an ALREADY-founded
|
|
215
|
-
* home) folds a signer seat via the SAME underlying mechanism
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* roster); do not paper over it in the DSL only. */
|
|
215
|
+
* home) folds a signer seat via the SAME underlying mechanism. That is how CO2 recovery works today, but its
|
|
216
|
+
* home frontier still says `guardian-quorum`. The fix is not to ban platform recovery: add the explicit
|
|
217
|
+
* platform-identity-provider policy, make CO2 choose it, and have the child gate verify the chosen evidence. */
|
|
219
218
|
export const receivingGroupRejectsKeylessAdditionalDevice = workspaceInvariant("ReceivingGroupRejectsKeylessAdditionalDevice")
|
|
220
219
|
.on("enrollFirstDevice")
|
|
221
220
|
.reads(receivingGroupPolicyRefs)
|
|
@@ -104,7 +104,7 @@ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
|
|
|
104
104
|
// (this trio) but DELIBERATELY does NOT export the `parentAttestedBirth` era marker — a platform's own 1-link
|
|
105
105
|
// `[platform_cert]` chain would fail `verify_to_root` (>=2 links), so its own verify doors must stay inert
|
|
106
106
|
// while it still carries the cert for its children. Fully additive (new aggregate + directive, no era key ⇒
|
|
107
|
-
// pre-existing chains replay byte-identical). architecture/
|
|
107
|
+
// pre-existing chains replay byte-identical). See architecture/verified_auth_from_genesis.md.
|
|
108
108
|
export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAttestation } from "./home.js";
|
|
109
109
|
// THE ENROLL-ONCE DELEGATION LANE — a platform records each tenant's device-key delegation (signed by its
|
|
110
110
|
// onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
|
package/src/index.ts
CHANGED
|
@@ -464,7 +464,7 @@ export {
|
|
|
464
464
|
// a `requires(cap)` aggregate's cap IS a read relation (synthesized into nomosRelations at compile),
|
|
465
465
|
// and a grant is a `relateSubject({ object:"workspace:self", relation:cap, ... })` tuple. See
|
|
466
466
|
// framework/relations.ts + relation_schema.ts below.
|
|
467
|
-
// NATIVE ZANZIBAR / ReBAC (architecture/
|
|
467
|
+
// NATIVE ZANZIBAR / ReBAC (architecture/authz_relationship_spine.md) — object-keyed
|
|
468
468
|
// relation-tuple events `object#relation@subject`, the clean-slate-native authz spine.
|
|
469
469
|
export {
|
|
470
470
|
RELATION_STATUSES,
|
package/src/law_check.ts
CHANGED
|
@@ -1351,7 +1351,7 @@ export function checkDomainModules(
|
|
|
1351
1351
|
domain,
|
|
1352
1352
|
level: "warn",
|
|
1353
1353
|
what: `birth recipe: ${diag}`,
|
|
1354
|
-
fix: `see
|
|
1354
|
+
fix: `see create-githolon/template/docs/08-births.md — the recipe must install both framework + child law and seed genesis`,
|
|
1355
1355
|
});
|
|
1356
1356
|
}
|
|
1357
1357
|
}
|
package/src/relation_schema.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* relation_schema — THE NATIVE ZANZIBAR RELATION SCHEMA (the userset-rewrite law).
|
|
8
8
|
*
|
|
9
|
-
* This is layer
|
|
9
|
+
* This is the schema layer of the authorization design (see `architecture/authz_relationship_spine.md`):
|
|
10
10
|
* 1. TUPLES = ledger events (`relateSubject`/`RelationTuple`, framework/relations.ts).
|
|
11
11
|
* 2. SCHEMA = THIS FILE — per-type relation REWRITES authored as PURE DATA, lowered to the
|
|
12
12
|
* `nomosRelations` USD era key. NO JavaScript on the schema path: a `.relation(...)` produces
|
package/src/relation_sugar.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* directly(user(), key()) → { rewrite: this, allow:[...] } — the this+allow PAIR so the
|
|
28
28
|
* type restriction CAN'T be forgotten (the #1 footgun).
|
|
29
29
|
*
|
|
30
|
-
* 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `
|
|
30
|
+
* 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `authz_relationship_spine.md`). Builder
|
|
31
31
|
* methods that lower to a relation (or a small set of relations) with the SAFE default baked in:
|
|
32
32
|
* .public(rel) → rel = directly(anyUser()) (user:* ONLY — never key:*)
|
|
33
33
|
* .owned(rel?) → owner = directly(user()); rel = directly(self) ∪ computed(owner)
|
package/src/shape_compat.ts
CHANGED
|
@@ -138,7 +138,7 @@ function payloadFieldKind(spec: PayloadFieldSpec): string {
|
|
|
138
138
|
return "string";
|
|
139
139
|
case "vo":
|
|
140
140
|
case "voList":
|
|
141
|
-
// Interface kinds stay at the base lane for now (
|
|
141
|
+
// Interface kinds stay at the base lane for now (the remaining parity work in dart_codegen_parity.md
|
|
142
142
|
// upgrades these to a first-class "vo" kind) — a VO IS a json leaf structurally.
|
|
143
143
|
return "json";
|
|
144
144
|
case "scalar":
|
package/src/value_object.ts
CHANGED
|
@@ -608,7 +608,7 @@ function describeZod(
|
|
|
608
608
|
const VO_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
609
609
|
|
|
610
610
|
/**
|
|
611
|
-
* THE VO BRAND (
|
|
611
|
+
* THE VO BRAND (vo_first_class.md) — `valueObject()` stamps its zod schema
|
|
612
612
|
* with the VO's {@link ValueObjectFieldMeta} under this symbol. The property itself is
|
|
613
613
|
* non-enumerable, never serialized and never read by the engine's validation path. When attached
|
|
614
614
|
* to an aggregate field, its canonical descriptor deliberately enters that field's stable value contract.
|
|
@@ -1036,7 +1036,7 @@ export const geo = {
|
|
|
1036
1036
|
/**
|
|
1037
1037
|
* Collect the value objects referenced by DIRECTIVE PAYLOADS (top-level fields + array
|
|
1038
1038
|
* elements), via the {@link NOMOS_VO_BRAND} the schemas carry. CODEGEN-ONLY (slice B of
|
|
1039
|
-
*
|
|
1039
|
+
* vo_first_class.md): the result feeds the generated Dart/TS surfaces so a
|
|
1040
1040
|
* payload-only VO still gets its class/interface — it does NOT enter the `nomosValueObjects`
|
|
1041
1041
|
* IR key (that stays aggregate-derived, keeping package bytes / domainHash untouched).
|
|
1042
1042
|
*/
|