@githolon/dsl 0.95.2 → 0.97.0

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.
@@ -24,6 +24,7 @@
24
24
  import 'dart:convert';
25
25
  import 'dart:io';
26
26
 
27
+ import 'package:crypto/crypto.dart' show sha256;
27
28
  import 'package:nomos_client/nomos_headless.dart';
28
29
  import 'package:test/test.dart';
29
30
 
@@ -55,7 +56,7 @@ void main() {
55
56
  final diag = Platform.environment['NOMOS_HARNESS_DIAG'] == '1'
56
57
  ? (String l) => stderr.writeln(l)
57
58
  : null;
58
- final cloud = await NomosTestCloud.start(serverEntry: _devServer, onDiagnostic: diag);
59
+ final cloud = await NomosTestCloud.start(serverEntry: _devServer, platform: 'four-layer-platform', deployJsonPath: _deploy, onDiagnostic: diag);
59
60
  NomosTestHarness? harness1;
60
61
  NomosTestHarness? harness2;
61
62
  try {
@@ -87,6 +88,7 @@ void main() {
87
88
  'frameworkHash': harness1.frameworkHash,
88
89
  'lawHash': harness1.lawHashes(home1.workspace).first,
89
90
  'bornAt': '2026-07-15T00:00:00Z',
91
+ 'ownerKeyHash': sha256.convert(base64Decode(harness1.devicePublicKey(home1.workspace)!)).toString(),
90
92
  },
91
93
  actor: _subject1,
92
94
  );
@@ -103,6 +105,7 @@ void main() {
103
105
  'frameworkHash': harness1.frameworkHash,
104
106
  'lawHash': harness1.lawHashes(home1.workspace).first,
105
107
  'bornAt': '2026-07-15T00:00:01Z',
108
+ 'ownerKeyHash': sha256.convert(base64Decode(harness1.devicePublicKey(home1.workspace)!)).toString(),
106
109
  },
107
110
  actor: _subject1,
108
111
  );
@@ -111,8 +114,8 @@ void main() {
111
114
  expect(projectWs, startsWith('project-'));
112
115
 
113
116
  // LAYER 3+4: Open both space and project as sibling sessions + make WRITES while offline
114
- final space = await harness1.bridge.acquireSession(spaceWs);
115
- final project = await harness1.bridge.acquireSession(projectWs);
117
+ final space = await harness1.bridge.acquireSession(spaceWs, authorSecret: harness1.deviceSecret(home1.workspace));
118
+ final project = await harness1.bridge.acquireSession(projectWs, authorSecret: harness1.deviceSecret(home1.workspace));
116
119
 
117
120
  // Write to space
118
121
  await space.offerCreates(
@@ -186,11 +189,12 @@ void main() {
186
189
  'frameworkHash': harness2.frameworkHash,
187
190
  'lawHash': harness2.lawHashes(home2.workspace).first,
188
191
  'bornAt': '2026-07-15T00:00:04Z',
192
+ 'ownerKeyHash': sha256.convert(base64Decode(harness2.devicePublicKey(home2.workspace)!)).toString(),
189
193
  },
190
194
  actor: _subject2,
191
195
  );
192
196
  final space2Ws = space2Outcome.born.single;
193
- final space2 = await harness2.bridge.acquireSession(space2Ws);
197
+ final space2 = await harness2.bridge.acquireSession(space2Ws, authorSecret: harness2.deviceSecret(home2.workspace));
194
198
 
195
199
  // User2 writes to its own space while partitioned
196
200
  await space2.offerCreates(
@@ -101,7 +101,8 @@ void main() {
101
101
  // 3. THE REPAIR — idempotent; the signing directive is DISCOVERED from the installed law
102
102
  // (this fixture composes it under domain "home", NOT the framework "workspaces").
103
103
  final r1 = await session.ensureParentSignerEnrollment(
104
- parent: _ws, principal: _principal, publicKey: pub, authorSecret: secret);
104
+ // era-0: BOTH domains compose enrollSigner (the estate is signer-ready too) — name the parent's.
105
+ parent: _ws, principal: _principal, publicKey: pub, authorSecret: secret, domain: 'home');
105
106
  expect(r1['enrolled'], true);
106
107
  expect(r1['repaired'], true);
107
108
  expect(r1['domain'], 'home',
@@ -109,7 +110,7 @@ void main() {
109
110
 
110
111
  // 4. Re-run = pure no-op (the idempotence contract).
111
112
  final r2 = await session.ensureParentSignerEnrollment(
112
- parent: _ws, principal: _principal, publicKey: pub, authorSecret: secret);
113
+ parent: _ws, principal: _principal, publicKey: pub, authorSecret: secret, domain: 'home');
113
114
  expect(r2['enrolled'], true);
114
115
  expect(r2['repaired'], false);
115
116
 
@@ -13,8 +13,11 @@
13
13
  @TestOn('vm')
14
14
  library;
15
15
 
16
+ import 'dart:convert';
16
17
  import 'dart:io';
17
18
 
19
+ import 'package:crypto/crypto.dart' show sha256;
20
+
18
21
  // ignore: deprecated_member_use
19
22
  import 'package:nomos_client/nomos_headless.dart';
20
23
  import 'package:test/test.dart';
@@ -52,6 +55,11 @@ String? _missingPrereq() {
52
55
  return null;
53
56
  }
54
57
 
58
+ /// The owner-device keyHash (the harness device the install lane minted) — the estate enrols it at birth
59
+ /// so the born child accepts the owner's signed writes (era-0 signer-ready births).
60
+ String _ownerKeyHash(NomosTestHarness h) =>
61
+ sha256.convert(base64Decode(h.devicePublicKey(h.workspace)!)).toString();
62
+
55
63
  Future<NomosTestHarness> _harness() => NomosTestHarness.ephemeral(
56
64
  deployJsonPaths: [_deployJson],
57
65
  nodeEntry: _repoEntry,
@@ -86,6 +94,7 @@ void main() {
86
94
  frameworkHash: harness.frameworkHash!,
87
95
  lawHash: harness.lawHashes(harness.workspace).first,
88
96
  bornAt: DateTime.now().toUtc().toIso8601String(),
97
+ ownerKeyHash: _ownerKeyHash(harness),
89
98
  ),
90
99
  );
91
100
  expect(birth.workspace, harness.workspace,
@@ -95,7 +104,7 @@ void main() {
95
104
  expect(birth.birthOutcomes.first['status'], 'born');
96
105
 
97
106
  // FOLLOW THE REF (ask 8): NomosRef in, mounted session out — never a string pair.
98
- final estate = await harness.bridge.follow(birth.bornRefs.single);
107
+ final estate = await harness.bridge.follow(birth.bornRefs.single, authorSecret: harness.deviceSecret(harness.workspace));
99
108
  expect(estate.workspace, birth.born.single);
100
109
 
101
110
  // SECOND CLIENT + a typed read AND a directive on it.
@@ -144,6 +153,7 @@ void main() {
144
153
  frameworkHash: harness.frameworkHash!,
145
154
  lawHash: harness.lawHashes(harness.workspace).first,
146
155
  bornAt: DateTime.now().toUtc().toIso8601String(),
156
+ ownerKeyHash: _ownerKeyHash(harness),
147
157
  ),
148
158
  );
149
159
  final rows = await first.timeout(const Duration(seconds: 30));
@@ -0,0 +1,17 @@
1
+ # watch_surfaces_test.dart + co2_acceptance_test.dart — QUARANTINED 2026-07-16 (removed, not skipped)
2
+
3
+ The test was committed importing a generated Dart client from
4
+ `bench/scale/fixtures/co2-acceptance/build/dart/src/generated/estate.client.dart` — a path that has
5
+ NEVER existed (the fixture's dart codegen emits layered packages under
6
+ `dart/co2_nomos_domain_client/lib/...`), from a fixture whose SOURCE was never tracked. It could not
7
+ run anywhere but its author's machine, and it broke `dart analyze` (and therefore the CI dart-gauntlet)
8
+ for everyone else. Removing it makes CI reflect reality: the typed reactive surfaces it exercised
9
+ (watchSpatial bbox stream / watchCompatibility across an evolve / watchActivity) are currently
10
+ UNCOVERED at the Dart layer — stated here loudly, not masked.
11
+
12
+ RECONSTRUCTION (tracked task): re-author against a SMALL tracked fixture (grow
13
+ `bench/scale/fixtures/estate-geo` with the SiteLocation VO + a `sitesByBounds` spatial + dart codegen),
14
+ compile it in the gauntlet's fixture step, and import the layered client via a path dependency in a
15
+ dedicated test pubspec (the layered `co2_nomos_types` path-dep does not resolve from `dsl/dart` itself).
16
+ Original test bodies: `git show 78ccf013:dsl/dart/test/watch_surfaces_test.dart` and `git show 78ccf013:dsl/dart/test/co2_acceptance_test.dart` (same phantom import, same fixture).
17
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.95.2",
3
+ "version": "0.97.0",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -75,6 +75,7 @@ import { collectValueObjectDescriptors } from "./value_object.js";
75
75
  import { emitUsd } from "./usd.js";
76
76
  import { expandCapabilityExports } from "./capability_exports.js";
77
77
  import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
78
+ import { AuthProvider, setAuthProvider, authProvider } from "./framework/workspaces.js";
78
79
  import { parentAttestedBirthMarker } from "./births.js";
79
80
  import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secondary_ensure.js";
80
81
  import { assertIdOfLint } from "./idof_lint.js";
@@ -209,7 +210,24 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
209
210
  // insertion order does not perturb the hash. Era-safe: only a relation-using domain gains the aggregate,
210
211
  // so no existing (relation-free) manifest hash drifts, and a tenant that already had the explicit export
211
212
  // sees NO further move (identical bytes).
213
+ // AUTH-PROVIDER AUTO-COMPOSE — the "a private workspace can always declare a verifier" DX fix (kills the
214
+ // `no-authprovider-verifier` dead-workspace trap). A package declaring ANY private read (an aggregate
215
+ // `visibility: requires(cap)` — also the ONLY host for `.encrypted()` fields) MUST be able to author its
216
+ // identity verifier, else it is born PERMANENTLY unable to verify a reader's token. Auto-carry the framework
217
+ // `AuthProvider` aggregate + the admin-gated `setAuthProvider` directive (its `.requires("admin")` gate
218
+ // travels with it, so a SHARED private workspace can't let a non-admin swap the verifier) + the
219
+ // `authProvider` query — exactly as RelationTuple is carried. Placed BEFORE the usesRelations check so
220
+ // setAuthProvider's admin gate pulls RelationTuple in too. ERA-SAFE: a PUBLIC package declares no private
221
+ // aggregate, gains nothing, stays byte-identical; a private package recompiled under this DSL moves hash anyway.
222
+ const declaresPrivateRead = [...byId.values()].some(
223
+ (a) => (a as { visibility?: unknown }).visibility !== undefined,
224
+ );
225
+ if (declaresPrivateRead) {
226
+ if (!byId.has(AuthProvider.id)) byId.set(AuthProvider.id, AuthProvider);
227
+ if (!directives.some((d) => d.id === setAuthProvider.id)) directives.push(setAuthProvider);
228
+ }
212
229
  const usesRelations =
230
+ declaresPrivateRead ||
213
231
  (spec.relationTypes ?? []).length > 0 ||
214
232
  directives.some((d) => (d as { requiresRelationGate?: unknown }).requiresRelationGate !== undefined) ||
215
233
  directives.some((d) => d.aggregateId === RelationTuple.id);
@@ -311,6 +329,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
311
329
  // `usesRelations` was computed above the aggregate snapshot (so RelationTuple could be injected into the
312
330
  // schema); reuse it here to append the audit query. The two MUST agree — the aggregate and the query it
313
331
  // returns have to enter the manifest together, or the read projection can't resolve the return schema.
332
+ // Auth-provider auto-compose (sibling of the RelationTuple query carry): a private package gets the
333
+ // `authProvider` read so the workspace can surface its declared verifier.
334
+ if (declaresPrivateRead && !queries.some((q) => q.id === authProvider.id)) {
335
+ queries.push(authProvider);
336
+ }
314
337
  if (usesRelations && !queries.some((q) => q.id === relationTuplesByObject.id)) {
315
338
  queries.push(relationTuplesByObject);
316
339
  }
@@ -1051,7 +1051,7 @@ const NOMOS_TYPES_DEP = "^0.6.0";
1051
1051
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1052
1052
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1053
1053
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1054
- const NOMOS_CLIENT_DEP = "^0.60.0";
1054
+ const NOMOS_CLIENT_DEP = "^0.61.0";
1055
1055
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1056
1056
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1057
1057
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -81,11 +81,11 @@ export const seedGenesis = directive("seedGenesis")
81
81
  .payload(
82
82
  z.object({
83
83
  principal: z.string().min(1),
84
- // `seedGenesis` is GENERIC over the warrant: it records whatever self-id the genesis recipe hands it.
85
- // EMPTY ⇒ the workspace stays UNWARRANTED (host-stamped authorship) — the LEGACY/compat lane: a
86
- // pre-VA chain or a recipe that explicitly opted out (`requiresWarrant: false`) replays byte-identical.
87
- // NON-EMPTY ⇒ WARRANTED: the self-id is the source binding author signatures cover, so the kernel
88
- // enforces SIGNED control-plane authorship on every lane (author / receive / verify_chain).
84
+ // `seedGenesis` records the self-id the genesis recipe hands it. ERA 0 — every birth is WARRANTED:
85
+ // the self-id is the real workspace name (NON-EMPTY), the source binding author signatures cover, so
86
+ // the kernel enforces SIGNED control-plane authorship on every lane (author / receive / verify_chain).
87
+ // (The empty-self-id UNWARRANTED lane was the legacy host-stamped compat path — purged; no pre-VA
88
+ // chain survives to need it.)
89
89
  // THE WARRANT-FLIP (THE GOVERNANCE DEFAULT, 2026-06-27): `buildGenesisChain` now seeds a NON-EMPTY
90
90
  // self-id (the workspace name) by default for every governance/platform birth — control-plane authority
91
91
  // is born warranted unless the recipe explicitly opts out. The choice lives in the recipe builder, not
@@ -26,6 +26,8 @@
26
26
  */
27
27
  import { z } from "zod";
28
28
  import { aggregate, instance } from "../aggregate.js";
29
+ // The device-key-hash wire shape (same validator signing_identity/workspaces use module-locally).
30
+ const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the owner's device pubkey hash)");
29
31
  import { t } from "../fields.js";
30
32
  import { Lww } from "../drivers.js";
31
33
  import { directive } from "../directive.js";
@@ -195,12 +197,21 @@ export const birthHome = directive("birthHome")
195
197
  lawHash: z.string().min(1),
196
198
  /** ISO-8601 birth time, caller-stamped (the timestamp doctrine — the plan reads no clock). */
197
199
  bornAt: z.string().min(1),
200
+ /** THE OWNER'S DEVICE KEY HASH (sha256hex of the ed25519 public half — the SAME key `homeCert` is signed
201
+ * by and the `signer` relation enrols). The platform holds it from the user's onboarding and ENROLS it
202
+ * in the home's genesis, so the born home is SIGNER-READY: it can sign + birth its own children (estates,
203
+ * catalogues) from intent 0. WITHOUT this the home is born warranted + owned but signer-less — it can
204
+ * never act (the co2 estate-birth bug). Mirrors `buildGenesisChain`'s `enrollSigner` for a platform.
205
+ * REQUIRED — a payload without it refuses at compile/validation, not an hour into a live deploy with a
206
+ * bricked signer-less home. (The kernel injecting it from the verified cert's `parent_key` is the
207
+ * eventual generalization; until then the field is loud.) */
208
+ ownerKeyHash: sha256Hex,
198
209
  /** THE DEVICE-SIGNED HOME CERT (JSON `executor::birth_cert::BirthCert`), signed CLIENT-SIDE by the
199
- * owner's device key `S_U` (the SAME key the `signer` relation enrolls). The kernel's birth offer-
200
- * effect VERIFIES this cert's signature against U's enrolled signer pubkey `P_U` AND that its
201
- * `founder == ctx.principal()` (the gate verdict, CAS-as-law) BEFORE accepting it as `links[0]` of
202
- * the travelling chain — the kernel signs nothing (it holds no secret; it only verifies). Optional:
203
- * absent ⇒ the era stays inert for this birth (no warranted home cert is seeded; pre-feature lane). */
210
+ * owner's device key `S_U` (the SAME key `ownerKeyHash` hashes + the `signer` relation enrols). The
211
+ * kernel's birth offer-effect VERIFIES this cert's signature against U's enrolled signer pubkey `P_U`
212
+ * AND that its `founder == ctx.principal()` (the gate verdict, CAS-as-law) BEFORE accepting it as
213
+ * `links[0]` of the travelling chain — the kernel signs nothing (it holds no secret; it only verifies).
214
+ * Optional: absent ⇒ the era stays inert for this birth (pre-feature lane). */
204
215
  homeCert: z.string().optional(),
205
216
  }),
206
217
  )
@@ -214,15 +225,17 @@ export const birthHome = directive("birthHome")
214
225
  { domain: "nomos", directiveId: "installDomain", actor: founder, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
215
226
  // SEED THE OWNER — the home is born OWNED; the homeNameBindsOwner invariant in the child's law proves
216
227
  // homeName == home-<sha256(owner)> at the child's own gate, so the slot can ONLY be its rightful owner's.
217
- // THE GENESIS ORDER IS THE CONTRACT (BREAK 1): the parent's offer-effect APPENDS `seedBirthAttestation`
218
- // (the travelling cert) and THEN `seedGenesis` (the WARRANT) after this step — so the warrant lands LAST,
219
- // exactly like `enrollSigner → seedGenesis → grantAdmin(SIGNED)`: every genesis seed runs UNSIGNED on a
220
- // still-warrant-free prefix (door + signed-author both inert), the warrant lands at the very end, and
221
- // ALL post-genesis writes are then signed AND cert-gated. The home is born WARRANTED (its self-id ==
222
- // homeName), so `workspace_self_id` resolves and the parent-attested-birth cert door FIRES on every
223
- // post-genesis write (without a warrant the door was inert for every home it protects). ERA-SAFE: a
224
- // non-AuthProvider / legacy chain never born under THIS recipe carries no warrant + replays byte-identical.
225
228
  { domain: HOME_LAW_DOMAIN, directiveId: "seedHomeOwner", actor: founder, payload: { principal: p.owner, homeName, bornAt: p.bornAt } },
229
+ // ENROL THE OWNER'S DEVICE on the warrant-free prefix (when supplied) — enrollSigner self-binds to
230
+ // ctx.principal() (the owner), so this binds `ownerKeyHash` as the owner's signer BEFORE the warrant
231
+ // closes bootstrap. This is what makes the born home SIGNER-READY (able to sign + self-serve-birth its own
232
+ // estates/catalogues). Mirrors buildGenesisChain's founding-admin enrollSigner for a platform — homes were
233
+ // the inconsistent one. THE GENESIS ORDER IS THE CONTRACT (BREAK 1): the parent's offer-effect APPENDS
234
+ // `seedBirthAttestation` + `seedGenesis` (the WARRANT) AFTER these steps — the warrant lands LAST (E→W
235
+ // ladder: every seed runs UNSIGNED on the still-warrant-free prefix; the warrant closes it). ERA-SAFE: a
236
+ // home born earlier (pre-feature law, no such step in its recipe) replays byte-identical — the era rule
237
+ // rides the LAW hash, not this branch.
238
+ { domain: HOME_LAW_DOMAIN, directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.ownerKeyHash, grantedBy: p.owner, grantedAt: 1 } },
226
239
  ];
227
240
  // THE DECLARED BIRTH EFFECT (the unix model): the recipe rides the committed intent as a declared output
228
241
  // (captured_ports.births). The kernel — reading the `.births()` declaration off the law manifest — takes
@@ -86,6 +86,12 @@ import { signerObject, SIGNER_RELATION } from "./signing_identity.js";
86
86
  // them up as part of the `workspaces` domain. (architecture/platform_birth_genesis_bootstrap.md)
87
87
  export { GenesisWarrant, seedGenesis } from "./genesis.js";
88
88
  export { identityRelations, enrollSigner } from "./signing_identity.js";
89
+ // ERA 0 — the keyless verified first-device door rides the GOVERNANCE law too: a freshly BORN platform
90
+ // has a virgin roster and a cert-ATTESTED AuthProvider (the S_root-signed platformCert), so its founder's
91
+ // FIRST action is `enrollFirstDevice` (token-verified against the attested provider) followed by a SIGNED
92
+ // self-`grantAdmin` (admitted by the kernel genesis clause). Without this export a born platform is
93
+ // un-administrable — the admin bootstrap has no door.
94
+ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
89
95
  // THE BIRTH-ATTESTATION CARRIER (the K_root attestation spine for platform-hosted homes). A platform must
90
96
  // CARRY its own `platform_cert` at `birth-attestation:self` so the kernel's `assemble_home_cert_chain`
91
97
  // PREPENDS `[home_cert, delegation]` to it and the child home roots at K_root. Governance carries the cert
@@ -522,24 +528,6 @@ export function buildGenesisChain(p: {
522
528
  * from there. Absent ⇒ the recipe resolves an already-installed law by shape (pre-G3 / test lanes). */
523
529
  frameworkHash?: string;
524
530
  lawHash?: string;
525
- /**
526
- * THE WARRANT-FLIP (VA-1, architecture/identity_keys.md). Controls whether `seedGenesis` records a REAL
527
- * `workspaceId` (the workspace name as the unique self-id) so the chain is born WARRANTED — meaning
528
- * `verify_captured_principal` enforces SIGNED control-plane authorship on every lane (author / edge
529
- * receive / verify_chain). An empty `workspaceId` keeps the workspace UNWARRANTED (host-stamped authorship,
530
- * the legacy lane), so a control-plane offer (grantAdmin etc.) is taken at its word — the forge that was
531
- * open on unwarranted root/co2.
532
- *
533
- * THE GOVERNANCE DEFAULT (THE WARRANT-FLIP, 2026-06-27): `buildGenesisChain` only ever runs for a
534
- * PLATFORM/GOVERNANCE birth (every call sits inside the `if (p.foundingAdminHash)` guard — a genesis that
535
- * carries control-plane authority: grantAdmin/grantCreation). Control-plane authority is EXACTLY what the
536
- * signed-author regime protects, so a governance birth is born WARRANTED by DEFAULT (`requiresWarrant`
537
- * undefined/true ⇒ warranted). The ONLY escape is an explicit `requiresWarrant === false`, retained as an
538
- * era-safe compat hatch (a pre-VA chain re-built byte-identical). NO flag-day: the era is per-workspace,
539
- * decided at genesis — existing UNWARRANTED chains keep replaying; only newly-born/re-born governance
540
- * chains arm the signature requirement.
541
- */
542
- requiresWarrant?: boolean;
543
531
  /**
544
532
  * METERED BIRTH (slice A3 — the descendant budget). When present, the child's genesis includes a
545
533
  * `seedBirthPolicy` step so it is BORN BOUNDED (its own default birth allowance per subject).
@@ -601,13 +589,25 @@ export function buildGenesisChain(p: {
601
589
  return JSON.stringify([
602
590
  ...installSteps,
603
591
  ...poolSteps,
604
- // THE WARRANT-FLIP (THE GOVERNANCE DEFAULT): a governance birth is born WARRANTED by default — a REAL
605
- // self-id (the workspace name) so signed control-plane authorship is enforced on every lane. The ONLY
606
- // way to stay on the legacy host-stamped lane ("" self-id, byte-identical to a pre-VA chain) is an
607
- // EXPLICIT `requiresWarrant === false` (the era-safe compat hatch). `p.requiresWarrant !== false`
608
- // ⇒ warranted whenever the flag is undefined OR true.
609
- { domain: "workspaces", directiveId: "seedGenesis", actor: founder, payload: { principal: founder, workspaceId: p.requiresWarrant !== false ? p.name : "", bornAt: p.bornAt } },
610
- { domain: "workspaces", directiveId: "grantAdmin", actor: founder, payload: { keyHash: p.foundingAdminHash, label: `founding admin (born for ${p.principal})`, grantedBy: p.principal, grantedAt: p.requestedAt } },
592
+ // ERA 0 — THE GENESIS-ORDER CONTRACT (the birthHome ordering, unified): every recipe seed below runs
593
+ // UNSIGNED on the still-WARRANT-FREE prefix; the recipe carries NO seedGenesis and NO grantAdmin. The
594
+ // KERNEL's birth effect — after verifying the offer's S_root-signed platformCert against the pinned
595
+ // K_root — appends `seedBirthAttestation` (the travelling cert) and THEN `seedGenesis` (the warrant,
596
+ // self-id == name) LAST, so the platform is born WARRANTED + ATTESTED and every post-genesis write is
597
+ // signed AND cert-gated. A birth whose cert fails verification seeds neither and is REFUSED atomically
598
+ // (the era-0 gate) — there is no unwarranted platform lane.
599
+ //
600
+ // THE FOUNDING KEY — ENROLLED ON THE WARRANT-FREE PREFIX (root's own E→W ladder, uniform): the
601
+ // founder's device key (`foundingAdminHash` = sha256 of its public half) self-binds BEFORE the warrant
602
+ // lands, exactly like root's `enrollSigner`. Once the kernel seeds the warrant LAST, the platform's
603
+ // signed-author regime is armed with this key already enrolled — so the FOUNDING ADMIN arrives
604
+ // POST-BIRTH as one SIGNED `grantAdmin` by this key, admitted by the kernel genesis clause
605
+ // (`genesis_admits`: a virgin #admin roster + the warrant binding the captured principal). A baked
606
+ // in-genesis grantAdmin can never fold (post-warrant writes need a signature the birth effect cannot
607
+ // produce — the pre-era-0 recipe was structurally unbornable); pre-warrant enroll + post-birth signed
608
+ // self-grant is the lawful shape. (The keyless variant — enrollFirstDevice against the platformCert's
609
+ // ATTESTED AuthProvider — remains for founders with an IdP instead of a key.)
610
+ { domain: "workspaces", directiveId: "enrollSigner", actor: founder, payload: { keyHash: p.foundingAdminHash, grantedBy: p.principal, grantedAt: p.requestedAt } },
611
611
  // No `role` — it was a vestigial node-class label (collapsed with `kind`); birthCloud ignores it now.
612
612
  { domain: "workspaces", directiveId: "birthCloud", actor: "", payload: { principal: p.principal, name: p.name, tier: p.tier, bornAt: p.requestedAt, bornUnder, ...(p.rebornFrom ? { rebornFrom: p.rebornFrom } : {}) } },
613
613
  ]);
@@ -1263,10 +1263,13 @@ export const birthChild = directive("birthChild")
1263
1263
  * own custody (never the host). Absent ⇒ the recipe resolves an already-installed law by shape. */
1264
1264
  frameworkHash: sha256Hex.optional(),
1265
1265
  lawHash: sha256Hex.optional(),
1266
- /** THE WARRANT-FLIP (VA-1): TRUE iff the child's genesis recipe declares an AuthProvider — the child
1267
- * is born WARRANTED (signed authorship enforced on every lane). Absent/false ⇒ the legacy
1268
- * host-stamped lane (byte-identical to a pre-VA birth). Only meaningful with a genesis. */
1269
- requiresWarrant: z.boolean().optional(),
1266
+ /** THE PLATFORM ATTESTATION (era 0 — one attested birth lane for every holon kind): the 1-link
1267
+ * platform_cert JSON (`executor::birth_cert::BirthCert`), SIGNED OFFLINE BY S_root (the K_root
1268
+ * holder). The kernel's birth effect VERIFIES it (child == name, founder == `user:<principal>`,
1269
+ * signature vs the pinned K_root) and injects `seedBirthAttestation` + `seedGenesis` (the warrant)
1270
+ * LAST into the child's genesis. REQUIRED for a genesis birth to complete — a birth without a
1271
+ * valid cert is refused atomically (no unwarranted platform lane survives era 0). */
1272
+ platformCert: z.string().optional(),
1270
1273
  /** METERED BIRTH (slice A3): the child's own descendant budget — when present, the genesis
1271
1274
  * recipe seeds the child's BirthPolicy (`seedBirthPolicy`) so it is BORN BOUNDED. OPT-IN:
1272
1275
  * only pass it when the `lawHash` the order names carries `seedBirthPolicy` (a governance
@@ -1292,11 +1295,9 @@ export const birthChild = directive("birthChild")
1292
1295
  // LAW AUTHORS THE GENESIS RECIPE (P1): a genesis birth carries its child's [installDomain×2 →]
1293
1296
  // seedGenesis→grantAdmin→birthCloud chain. (refine guarantees principal + bornAt here.)
1294
1297
  if (p.foundingAdminHash) {
1295
- // THE GOVERNANCE DEFAULT (THE WARRANT-FLIP): a governance birth is warranted unless the request
1296
- // EXPLICITLY opts out (`requiresWarrant === false`). Thread the flag through ONLY when the request set
1297
- // it — undefined falls to buildGenesisChain's warranted default; an explicit `false` rides the compat
1298
- // hatch (an era-safe legacy re-build).
1299
- const genesisChain = buildGenesisChain({ name: p.name, principal: p.principal, parent: p.parent, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt, ...(p.frameworkHash ? { frameworkHash: p.frameworkHash } : {}), ...(p.lawHash ? { lawHash: p.lawHash } : {}), ...(p.requiresWarrant !== undefined ? { requiresWarrant: p.requiresWarrant } : {}), ...(p.descendantPool !== undefined ? { descendantPool: p.descendantPool } : {}) });
1298
+ // ERA 0 — every governance/platform birth is WARRANTED (no compat hatch to thread; buildGenesisChain
1299
+ // always records the real self-id).
1300
+ const genesisChain = buildGenesisChain({ name: p.name, principal: p.principal, parent: p.parent, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt, ...(p.frameworkHash ? { frameworkHash: p.frameworkHash } : {}), ...(p.lawHash ? { lawHash: p.lawHash } : {}), ...(p.descendantPool !== undefined ? { descendantPool: p.descendantPool } : {}) });
1300
1301
  // THE GENESIS RECIPE IS ALSO A LINEAGE/AUDIT FACT on the parent's chain (the parent records what it
1301
1302
  // ORDERED — law naming law, legitimate). The kernel does NOT read this field to detect the birth.
1302
1303
  rec.set("genesisChain", genesisChain);
@@ -1,101 +0,0 @@
1
- // THE CO2 ACCEPTANCE — DART LEG (task #32). Their feedback's Dart snippet, near-verbatim:
2
- // construct a rich nested DrawingGeoreference with typed constructors, dispatch it through the
3
- // generated client against REAL law on a local holon, and read BOTH aggregates back typed —
4
- // the primary georeference row AND the sibling EstateDrawing.transform write.
5
- @TestOn('vm')
6
- library;
7
-
8
- import 'dart:io';
9
-
10
- import 'package:nomos_client/nomos_headless.dart';
11
- import 'package:test/test.dart';
12
-
13
- import '../../../bench/scale/fixtures/co2-acceptance/build/dart/src/generated/estate.client.dart';
14
-
15
- String get _root => '${Directory.current.path}/../..';
16
- String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
17
- String get _deployJson =>
18
- '$_root/bench/scale/fixtures/co2-acceptance/build/co2-acceptance.deploy.json';
19
- String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
20
-
21
- String? _missingPrereq() {
22
- if (!File(_repoEntry).existsSync()) return 'in-repo runner not found at $_repoEntry';
23
- if (!File(_deployJson).existsSync()) {
24
- return 'compile the fixture: cd bench/scale/fixtures/co2-acceptance && npx nomos-compile';
25
- }
26
- if (!File('$_runtimeDir/holon.wasm').existsSync() ||
27
- !File('$_runtimeDir/packages.json').existsSync()) {
28
- return 'warm ~/.holon/runtime first (run any `githolon` command once)';
29
- }
30
- return null;
31
- }
32
-
33
- void main() {
34
- test('co2 acceptance: rich typed VO construction → multi-aggregate write → typed reads',
35
- () async {
36
- final skip = _missingPrereq();
37
- if (skip != null) {
38
- markTestSkipped(skip);
39
- return;
40
- }
41
- final harness = await NomosTestHarness.ephemeral(
42
- deployJsonPaths: [_deployJson],
43
- nodeEntry: _repoEntry,
44
- runtimeDir: _runtimeDir,
45
- workspace: 'co2-acceptance',
46
- );
47
- try {
48
- final estate = EstateClient(harness.bridge, actor: 'user:ada');
49
-
50
- // Register a site whose location composes address + geo point (nested VOs).
51
- await estate.registerSite(RegisterSitePayload(
52
- name: 'Trafford Depot',
53
- location: SiteLocation(
54
- address: PostalAddress(
55
- line1: '1 Wharf Rd', city: 'Manchester', postcode: 'M17 1AB', countryCode: 'GB'),
56
- point: GeoPoint(coordinates: [-2.24, 53.48]),
57
- source: SiteLocationSource.addressSearch,
58
- ),
59
- registeredAt: DateTime.now().toUtc().toIso8601String(),
60
- ));
61
- await estate.addDrawing(AddDrawingPayload(
62
- title: 'Level 2 plan', addedAt: DateTime.now().toUtc().toIso8601String()));
63
-
64
- final drawings = await estate.drawingsByTitle(title: 'Level 2 plan');
65
- expect(drawings, hasLength(1));
66
- final drawingId = drawings.single.aggregateId.value;
67
- final sites = await estate.sitesByName(name: 'Trafford Depot');
68
- expect(sites, hasLength(1));
69
-
70
- // ── THEIR SNIPPET, VERBATIM SHAPE ──
71
- await estate.recordDrawingGeoreference(RecordDrawingGeoreferencePayload(
72
- drawingId: drawingId,
73
- siteId: sites.single.aggregateId.value,
74
- georeference: DrawingGeoreference(
75
- transform: DrawingTransform2d(rotationDeg: 2.4),
76
- controlPoints: [
77
- DrawingGeoControlPoint(
78
- drawingPosition: DrawingPositionMm(xMm: 1200, yMm: 800),
79
- geoPoint: GeoPoint(coordinates: [-2.24, 53.48]),
80
- ),
81
- ],
82
- ),
83
- recordedAt: DateTime.now().toUtc().toIso8601String(),
84
- ));
85
-
86
- // Primary aggregate: the full nested VO reads back TYPED.
87
- final georefs = await estate.georefsByDrawing(drawingId: drawingId);
88
- expect(georefs, hasLength(1));
89
- final g = georefs.single.georeference!;
90
- expect(g.transform!.rotationDeg, 2.4);
91
- expect(g.controlPoints.single.drawingPosition.xMm, 1200);
92
- expect(g.controlPoints.single.geoPoint.coordinates.first, -2.24);
93
-
94
- // THE SIBLING WRITE: EstateDrawing.transform got the VO subfield, typed.
95
- final again = await estate.drawingsByTitle(title: 'Level 2 plan');
96
- expect(again.single.transform!.rotationDeg, 2.4);
97
- } finally {
98
- await harness.dispose();
99
- }
100
- }, timeout: const Timeout(Duration(minutes: 3)));
101
- }