@githolon/dsl 0.30.0 → 0.31.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.
- package/package.json +1 -1
- package/src/codegen_proof.ts +424 -32
- package/src/framework/platform_admin.ts +285 -0
- package/src/index.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|
package/src/codegen_proof.ts
CHANGED
|
@@ -42,6 +42,7 @@ import type { DomainModule } from "./codegen_dart.js";
|
|
|
42
42
|
import type { QueryDecl } from "./query.js";
|
|
43
43
|
import type { FieldOp } from "./ops.js";
|
|
44
44
|
import type { RelationTypeDecl, Rewrite } from "./relation_schema.js";
|
|
45
|
+
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
45
46
|
import { finishCount } from "./count.js";
|
|
46
47
|
import { mintedCreateField } from "./workspace_routing.js";
|
|
47
48
|
import { deterministicPorts } from "./ctx.js";
|
|
@@ -364,18 +365,24 @@ function relationSeedLegLines(
|
|
|
364
365
|
}
|
|
365
366
|
const ACTOR = `"user:proof-conformance"`;
|
|
366
367
|
const speculative = leg.seedSpeculative === true;
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return overwrite.has(k) || looksSubject
|
|
368
|
+
// A payload literal with subject/founder-conventional fields (+ the routed set) overwritten to the actor —
|
|
369
|
+
// so a seat lands ON the actor AND every write-once/author-binding/founder-binding invariant holds.
|
|
370
|
+
const actorSeatLit = (payload: Record<string, unknown>, routed: readonly string[]): string => {
|
|
371
|
+
const overwrite = new Set<string>(routed);
|
|
372
|
+
const entries = Object.entries(payload).map(([k, v]) =>
|
|
373
|
+
overwrite.has(k) || /^(subject|principal|grantedBy|grantee|member|user|owner|founder)$/i.test(k)
|
|
374
374
|
? `${JSON.stringify(k)}: ${ACTOR}`
|
|
375
|
-
: `${JSON.stringify(k)}: ${JSON.stringify(v)}
|
|
376
|
-
|
|
375
|
+
: `${JSON.stringify(k)}: ${JSON.stringify(v)}`,
|
|
376
|
+
);
|
|
377
377
|
return `{ ${entries.join(", ")} }`;
|
|
378
|
-
}
|
|
378
|
+
};
|
|
379
|
+
const grantPayloadLit = actorSeatLit(leg.grantPayload, [...leg.grantSubjectFields, ...leg.grantGrantedByFields]);
|
|
380
|
+
// The PREREQUISITE-SEED CHAIN dispatches (each as the actor = founder) BEFORE the grant.
|
|
381
|
+
const prereqLines = (leg.prerequisiteSeeds ?? []).flatMap((seed) => [
|
|
382
|
+
` // prerequisite: ${leg.domain}/${seed.directiveId} establishes the state the grant's invariant reads (as founder = ACTOR).`,
|
|
383
|
+
` await ${factory}(holon, { actor: ACTOR }).${seed.directiveId}(${actorSeatLit(seed.payload, [])});`,
|
|
384
|
+
` ok(\`relation bootstrap (prerequisite) — ${leg.domain}/${seed.directiveId} established (as founder \${ACTOR}) before the grant\`);`,
|
|
385
|
+
]);
|
|
379
386
|
const gatedPayloadLit = leg.gatedMintField !== undefined && leg.gatedAggregateId !== undefined
|
|
380
387
|
? `{ ...${JSON.stringify(leg.gatedPayload)}, ${JSON.stringify(leg.gatedMintField)}: await holon.mint(${JSON.stringify(leg.gatedAggregateId)}) }`
|
|
381
388
|
: JSON.stringify(leg.gatedPayload);
|
|
@@ -383,6 +390,27 @@ function relationSeedLegLines(
|
|
|
383
390
|
? ` — a COMPUTED gate: granting "${leg.grantedRelation}" makes the actor satisfy "${leg.relation}" via the rewrite`
|
|
384
391
|
: ``;
|
|
385
392
|
|
|
393
|
+
if (leg.bornContext !== undefined) {
|
|
394
|
+
// LAYER 4 — THE BORN-DOMAIN GENESIS-AUTHORITY PATH (co2 home). R is established ONLY by the birth genesis
|
|
395
|
+
// (e.g. birthHome → seedHomeCreator seats creator), and the seed carries an owner-binding PREREQUISITE a
|
|
396
|
+
// standalone re-seed can't satisfy. This leg is proven by `githolon proof` (offline, on the local wasm):
|
|
397
|
+
// the runner BIRTHS the workspace as the proof actor (= owner), then dispatches the gated directive INSIDE
|
|
398
|
+
// the born workspace as that owner, asserting ADMITTED at the real kernel gate (a refusal degrades to a
|
|
399
|
+
// diagnostic — never a false green). The LIVE `--live` lane proves the underlying birth via the child-birth
|
|
400
|
+
// leg; the gated-directive-in-born-context proof is the offline runner's (birthing a throwaway home per
|
|
401
|
+
// gated directive live would leave orphan workspaces).
|
|
402
|
+
const bc = leg.bornContext;
|
|
403
|
+
return [
|
|
404
|
+
`// ${n()}. RELATION BOOTSTRAP — LAYER 4 (born-domain genesis authority): \`${leg.domain}/${leg.gatedDirectiveId}\``,
|
|
405
|
+
`// is GATED (\`.requires("${leg.relation}")\`), established ONLY by the birth genesis (\`${bc.birthDirectiveId}\` →`,
|
|
406
|
+
`// \`${bc.seatsViaDirectiveId}\` seats "${leg.relation}") — NOT a standalone-replayable seed (its owner-binding`,
|
|
407
|
+
`// prerequisite would false-fail). Proven by \`githolon proof\` (offline): birth the workspace as the owner,`,
|
|
408
|
+
`// then dispatch \`${leg.gatedDirectiveId}\` INSIDE the born workspace as that owner (real-gate admitted).`,
|
|
409
|
+
`console.log("– relation-bootstrap leg (born-context) — \`${leg.domain}/${leg.gatedDirectiveId}\` .requires(\\"${leg.relation}\\") is proven in the BORN workspace by \`githolon proof\` (offline); the birth is proven live by the child-birth leg");`,
|
|
410
|
+
``,
|
|
411
|
+
];
|
|
412
|
+
}
|
|
413
|
+
|
|
386
414
|
if (speculative) {
|
|
387
415
|
// LAYER 3 (seed-path replay). The gate relation is reachable only through ttu/intersection/exclusion — no
|
|
388
416
|
// directly-grantable base. We REPLAY the domain's declared seed and let the REAL GATE arbitrate: if the
|
|
@@ -424,9 +452,12 @@ function relationSeedLegLines(
|
|
|
424
452
|
];
|
|
425
453
|
}
|
|
426
454
|
|
|
455
|
+
const prereqNote = (leg.prerequisiteSeeds ?? []).length > 0
|
|
456
|
+
? ` The grant needs prerequisite seed(s) [${(leg.prerequisiteSeeds ?? []).map((s) => s.directiveId).join(", ")}] run first (as the actor = founder).`
|
|
457
|
+
: ``;
|
|
427
458
|
return [
|
|
428
459
|
`// ${n()}. RELATION BOOTSTRAP (seed-then-prove the POSITIVE): \`${leg.domain}/${leg.gatedDirectiveId}\` is GATED`,
|
|
429
|
-
`// (\`.requires("${leg.relation}")\`). \`${leg.domain}/${leg.grantDirectiveId}\` GRANTS "${leg.grantedRelation}" (${leg.via})${computedNote}
|
|
460
|
+
`// (\`.requires("${leg.relation}")\`). \`${leg.domain}/${leg.grantDirectiveId}\` GRANTS "${leg.grantedRelation}" (${leg.via})${computedNote}.${prereqNote}`,
|
|
430
461
|
`// Grant it to the proof actor, THEN prove the gated write SUCCEEDS under that actor (what the negative`,
|
|
431
462
|
`// conformance leg above can't cover — a seeded holder authoring a gated write).`,
|
|
432
463
|
`{`,
|
|
@@ -434,6 +465,7 @@ function relationSeedLegLines(
|
|
|
434
465
|
` globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: relation-bootstrap touched the network"); }) as typeof fetch;`,
|
|
435
466
|
` try {`,
|
|
436
467
|
` const ACTOR = ${ACTOR};`,
|
|
468
|
+
...prereqLines,
|
|
437
469
|
` // (1) grant "${leg.grantedRelation}" to ACTOR (the grant folds a writeTuple; subject seated on ACTOR).`,
|
|
438
470
|
` await ${factory}(holon, { actor: ACTOR }).${leg.grantDirectiveId}(${grantPayloadLit});`,
|
|
439
471
|
` ok(\`relation bootstrap (grant) — ${leg.domain}/${leg.grantDirectiveId} granted "${leg.grantedRelation}" to \${ACTOR}${leg.grantedRelation !== leg.relation ? ` (⇒ satisfies "${leg.relation}" via rewrite)` : ``}\`);`,
|
|
@@ -493,8 +525,10 @@ export interface RelationBootstrapLeg {
|
|
|
493
525
|
* `writeTuple(R)`), `"computed-base"` (R is computed; the grant seats a rewrite BASE relation the granter
|
|
494
526
|
* was found for), or `"seed-path"` (LAYER 3: R is reachable only through ttu/intersection/exclusion — no
|
|
495
527
|
* directly-grantable base — so a DECLARED SEED directive that establishes root authority is replayed and
|
|
496
|
-
* the REAL KERNEL GATE is the arbiter of whether it authorizes the write)
|
|
497
|
-
|
|
528
|
+
* the REAL KERNEL GATE is the arbiter of whether it authorizes the write), or `"born-context"` (LAYER 4:
|
|
529
|
+
* R is established by the domain's BIRTH GENESIS — the gated directive is proven INSIDE the born workspace
|
|
530
|
+
* as the owner; see {@link BornContextAuthority}). Audit + runner semantics. */
|
|
531
|
+
readonly via: "marker" | "inferred" | "computed-base" | "seed-path" | "born-context";
|
|
498
532
|
/** LAYER-3 ANTI-FALSE-GREEN FLAG. `true` ⇒ this is a SPECULATIVE seed-path leg: codegen could NOT prove
|
|
499
533
|
* (via rewrite resolution) that replaying `grantDirectiveId` authorizes the gated write — it discovered a
|
|
500
534
|
* seed that writes relation tuples and is DEFERRING to the real kernel gate. The runner replays the seed,
|
|
@@ -504,6 +538,55 @@ export interface RelationBootstrapLeg {
|
|
|
504
538
|
readonly seedSpeculative?: boolean;
|
|
505
539
|
/** The relations the discovered seed directive writes (layer-3 audit/diagnostic — what authority it seats). */
|
|
506
540
|
readonly seedWritesRelations?: readonly string[];
|
|
541
|
+
/** LAYER 4 — THE BORN-DOMAIN GENESIS-AUTHORITY PATH. Present iff the gate relation is established by the
|
|
542
|
+
* domain's BIRTH GENESIS (not a standalone-replayable seed): a `.births()` directive whose genesis chain
|
|
543
|
+
* seats R (e.g. co2's `birthHome` → `seedHomeCreator` seats `creator`), and whose born workspace runs the
|
|
544
|
+
* SAME law — so the gated directive can be proven INSIDE the born context, dispatched as the genesis-
|
|
545
|
+
* established OWNER, where the seed's own prerequisite invariants (HomeCreatorSeedBindsOwner) hold. This
|
|
546
|
+
* TAKES PRECEDENCE over a standalone seed (`grantDirectiveId`) for a born domain: a standalone re-seed
|
|
547
|
+
* would trip the seed's owner-binding prerequisite (HomeOwner unset) and FALSE-fail. When present, the
|
|
548
|
+
* runner IGNORES the standalone grant fields and proves via {@link BornContextAuthority}. */
|
|
549
|
+
readonly bornContext?: BornContextAuthority;
|
|
550
|
+
/** THE PREREQUISITE-SEED CHAIN (the composed co2_platform / `seedPlatformAdmin` case). Present iff the
|
|
551
|
+
* granter (`grantDirectiveId`) carries a PREREQUISITE invariant whose state is established NOT by a birth
|
|
552
|
+
* genesis but by a SIBLING SEED in the SAME domain (e.g. `seedPlatformAdmin`'s `PlatformAdminBindsFounder`
|
|
553
|
+
* reads the founder singleton stamped by `seedPlatformFounder`). The runner dispatches these seeds IN ORDER
|
|
554
|
+
* (each as the proof actor = founder, so their own write-once/author-binding invariants hold) BEFORE the
|
|
555
|
+
* grant — then the grant + the gated write succeed. A non-birth analog of {@link BornContextAuthority}. */
|
|
556
|
+
readonly prerequisiteSeeds?: readonly PrerequisiteSeed[];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** One prerequisite seed the runner dispatches (as the proof actor = founder) BEFORE the grant — it
|
|
560
|
+
* establishes the state a granter's prerequisite invariant reads (e.g. the founder singleton). */
|
|
561
|
+
export interface PrerequisiteSeed {
|
|
562
|
+
/** The seed directive id (e.g. `seedPlatformFounder`). */
|
|
563
|
+
readonly directiveId: string;
|
|
564
|
+
/** The synthesized payload; founder/subject-conventional fields are set to the proof actor at dispatch so
|
|
565
|
+
* the seed's own write-once/author-binding invariants pass (founder == the seeding actor). */
|
|
566
|
+
readonly payload: Record<string, unknown>;
|
|
567
|
+
readonly autoStamped: readonly string[];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** THE BORN-DOMAIN GENESIS AUTHORITY (layer 4): the self-birth directive whose genesis seats the gate relation
|
|
571
|
+
* for the OWNER, plus what the runner needs to birth it (as the proof actor = the home owner) and then prove
|
|
572
|
+
* the gated directive INSIDE the born workspace. The real kernel gate is the arbiter — a refusal degrades to
|
|
573
|
+
* a diagnostic (never a false green). */
|
|
574
|
+
export interface BornContextAuthority {
|
|
575
|
+
/** The self-birth directive (`birthHome`) whose genesis chain seats the gate relation. */
|
|
576
|
+
readonly birthDirectiveId: string;
|
|
577
|
+
/** The synthesized birth payload (auto-stamped `…At` omitted; framework/law-hash placeholders removed — the
|
|
578
|
+
* runtime injects the REAL hashes + the proof actor as owner). */
|
|
579
|
+
readonly birthPayload: Record<string, unknown>;
|
|
580
|
+
readonly birthAutoStamped: readonly string[];
|
|
581
|
+
/** The birth payload field carrying the home OWNER — the runtime sets it to the proof actor, so the born
|
|
582
|
+
* home is owned by (and seats the relation for) the actor the gated dispatch then runs as. */
|
|
583
|
+
readonly ownerField?: string;
|
|
584
|
+
/** The birth payload field carrying the framework-controller hash (runtime injects the real one). */
|
|
585
|
+
readonly frameworkHashField?: string;
|
|
586
|
+
/** The birth payload field carrying the child law hash (runtime injects the deployed package hash). */
|
|
587
|
+
readonly lawHashField?: string;
|
|
588
|
+
/** The genesis step directive that actually seats the relation (audit — e.g. `seedHomeCreator`). */
|
|
589
|
+
readonly seatsViaDirectiveId: string;
|
|
507
590
|
}
|
|
508
591
|
|
|
509
592
|
/** Run a directive's plan with a synthesized+probe payload under birth-probe globals and return the drained
|
|
@@ -540,27 +623,61 @@ function probePlanOps(
|
|
|
540
623
|
return { ops, synth, probePayload };
|
|
541
624
|
}
|
|
542
625
|
|
|
543
|
-
/**
|
|
544
|
-
*
|
|
545
|
-
function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
626
|
+
/** The `status` of a `subjects` setEntry leaf (`writeTuple`/`removeTuple` lowers a JSON `{status, …}` leaf).
|
|
627
|
+
* `"active"` = a POSITIVE grant; `"revoked"` = a REVOKE. `undefined` ⇒ not a parseable subject leaf. */
|
|
628
|
+
function subjectLeafStatus(op: FieldOp): string | undefined {
|
|
629
|
+
if (op.kind !== "setEntry" || op.field !== "subjects" || typeof op.value !== "string") return undefined;
|
|
630
|
+
try {
|
|
631
|
+
return (JSON.parse(op.value) as { status?: unknown }).status as string | undefined;
|
|
632
|
+
} catch {
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
549
635
|
}
|
|
550
636
|
|
|
551
|
-
/**
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
|
|
637
|
+
/**
|
|
638
|
+
* The relations a directive's plan POSITIVELY GRANTS — judged PER RELATION-TUPLE (per `aggregateId`), never by
|
|
639
|
+
* relation-name alone. Both a grant (`writeTuple`, `status:"active"`) AND a revoke (`removeTuple`,
|
|
640
|
+
* `status:"revoked"`) emit `set(rt, "relation", R)` on the SAME reltuple aggregate; the ONLY discriminator is
|
|
641
|
+
* the `status` inside that tuple's `subjects` leaf. So a relation counts as GRANTED iff its tuple carries an
|
|
642
|
+
* ACTIVE subject leaf — a revoke of R is INELIGIBLE (bug: `revokeCo2PlatformRole` was inferred as a granter).
|
|
643
|
+
* A directive that writes AND removes DIFFERENT relations is judged per-tuple (each aggregateId independently).
|
|
644
|
+
*/
|
|
645
|
+
function probeGrantedRelations(dir: Directive<unknown>, where: string): string[] {
|
|
555
646
|
const probed = probePlanOps(dir, where);
|
|
556
647
|
if (probed === undefined) return [];
|
|
557
|
-
|
|
648
|
+
// Group ops by the reltuple instance (aggregateId): each (object, relation) tuple is one bucket.
|
|
649
|
+
const byTuple = new Map<string, { relations: Set<string>; hasActive: boolean; hasRevoked: boolean }>();
|
|
558
650
|
for (const o of probed.ops) {
|
|
559
|
-
|
|
651
|
+
const key = o.aggregateId;
|
|
652
|
+
let bucket = byTuple.get(key);
|
|
653
|
+
if (bucket === undefined) { bucket = { relations: new Set(), hasActive: false, hasRevoked: false }; byTuple.set(key, bucket); }
|
|
654
|
+
if (o.kind === "set" && o.field === "relation" && typeof o.value === "string" && o.value.length > 0) bucket.relations.add(o.value);
|
|
655
|
+
const status = subjectLeafStatus(o);
|
|
656
|
+
if (status === "active") bucket.hasActive = true;
|
|
657
|
+
else if (status === "revoked") bucket.hasRevoked = true;
|
|
658
|
+
}
|
|
659
|
+
const out = new Set<string>();
|
|
660
|
+
for (const bucket of byTuple.values()) {
|
|
661
|
+
// A tuple GRANTS its relation(s) only when it seats an ACTIVE subject leaf. A tuple with only a revoked
|
|
662
|
+
// leaf (or no subject leaf at all — a materialize with no active seat) does NOT grant.
|
|
663
|
+
if (bucket.hasActive) for (const r of bucket.relations) out.add(r);
|
|
560
664
|
}
|
|
561
665
|
return [...out];
|
|
562
666
|
}
|
|
563
667
|
|
|
668
|
+
/** Does this directive's plan POSITIVELY GRANT `relation` (an ACTIVE `writeTuple`/`relateSubject` for R)?
|
|
669
|
+
* A revoke (`removeTuple`, `status:"revoked"`) for R is NOT a grant (per-tuple status discriminator). */
|
|
670
|
+
function probeGrantsRelation(dir: Directive<unknown>, relation: string, where: string): boolean {
|
|
671
|
+
return probeGrantedRelations(dir, where).includes(relation);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** ALL relations a directive's plan POSITIVELY GRANTS (active writeTuples) — the layer-3 seed discovery reads
|
|
675
|
+
* this to find a directive that ESTABLISHES relation authority. A revoke-only directive yields `[]` (it seats
|
|
676
|
+
* no authority). `[]` ⇒ writes no ACTIVE relation tuples. */
|
|
677
|
+
function probeRelationsWritten(dir: Directive<unknown>, where: string): string[] {
|
|
678
|
+
return probeGrantedRelations(dir, where);
|
|
679
|
+
}
|
|
680
|
+
|
|
564
681
|
/** Resolve WHICH grant-payload fields route into the tuple subject / grantedBy — by matching the op values
|
|
565
682
|
* the plan emitted (the subject `setEntry`'s entryKey, the `grantedBy` leaf) back to synthesized payload
|
|
566
683
|
* values. Best-effort: an unmatched routing just leaves the field list empty (runtime falls back). */
|
|
@@ -687,6 +804,216 @@ export function resolveGrantableBaseRelations(
|
|
|
687
804
|
return out;
|
|
688
805
|
}
|
|
689
806
|
|
|
807
|
+
// ── LAYER 4: born-domain genesis-authority discovery (the co2 home case) ─────────────────────────────────
|
|
808
|
+
//
|
|
809
|
+
// A gate relation R may be established ONLY by the domain's BIRTH GENESIS — a `.births()` directive whose
|
|
810
|
+
// genesis chain seats R for the owner (co2's `birthHome` genesis runs `seedHomeCreator`, seating `creator`).
|
|
811
|
+
// The seed itself carries a PREREQUISITE (an owner-binding workspaceInvariant reading state set by an EARLIER
|
|
812
|
+
// genesis step), so a standalone re-seed FALSE-fails (the prior state is absent). The law-true authority path
|
|
813
|
+
// is to BIRTH the workspace (replaying the whole genesis, seating R for the owner) and prove the gated
|
|
814
|
+
// directive INSIDE the born workspace, dispatched as that owner. Discovery probes each `.births()` directive's
|
|
815
|
+
// genesis chain for a step that writeTuples R; if found, the born workspace (running the SAME law) is where
|
|
816
|
+
// the gated directive proves.
|
|
817
|
+
|
|
818
|
+
/** Probe a `.births()` directive's genesis chain (birth-probe globals) → the recorded steps + the synthesized
|
|
819
|
+
* parent payload. `undefined` ⇒ not probeable / no recipe. Mirrors {@link discoverChildBirthLeg}'s probe. */
|
|
820
|
+
function probeBirthGenesis(
|
|
821
|
+
birthDir: Directive<unknown>,
|
|
822
|
+
where: string,
|
|
823
|
+
): { chain: Array<{ domain?: string; directiveId?: string; actor?: unknown }>; synth: SynthesizedPayload } | undefined {
|
|
824
|
+
let synth: SynthesizedPayload;
|
|
825
|
+
try {
|
|
826
|
+
synth = synthesizePayload(birthDir.payloadSchema as z.ZodTypeAny, where);
|
|
827
|
+
} catch {
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
const probePayload: Record<string, unknown> = { ...synth.value };
|
|
831
|
+
for (const f of synth.autoStamped) probePayload[f] = "2026-01-01T00:00:00Z";
|
|
832
|
+
let recipe: { genesisChain?: unknown } | undefined;
|
|
833
|
+
try {
|
|
834
|
+
withBirthProbeGlobals(() => {
|
|
835
|
+
__resetAuthoring();
|
|
836
|
+
__resetReads();
|
|
837
|
+
__resetBirths();
|
|
838
|
+
try {
|
|
839
|
+
birthDir.plan(probePayload as never, deterministicPorts({ physical: 1, replica: 1, principal: "principal:probe" }));
|
|
840
|
+
} finally {
|
|
841
|
+
try { __drainAuthoring(); __drainReads(); } catch { /* drained */ }
|
|
842
|
+
}
|
|
843
|
+
recipe = __drainBirths()[0] as typeof recipe;
|
|
844
|
+
});
|
|
845
|
+
} catch {
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
if (recipe === undefined || !Array.isArray(recipe.genesisChain)) return undefined;
|
|
849
|
+
return { chain: recipe.genesisChain as Array<{ domain?: string; directiveId?: string; actor?: unknown }>, synth };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Discover the BORN-DOMAIN genesis authority for a gate `relation`: a `.births()` directive in `mod` whose
|
|
854
|
+
* genesis chain seats `relation` (a genesis step whose directive `writeTuple`s R) AND whose born workspace
|
|
855
|
+
* runs the SAME law (so the gated directive is addressable there). Returns what the runner needs to birth the
|
|
856
|
+
* workspace as the proof actor (= the owner) + the owner/hash payload fields. `undefined` ⇒ no birth seats R.
|
|
857
|
+
*/
|
|
858
|
+
export function discoverBornContextAuthority(
|
|
859
|
+
mod: DomainModule,
|
|
860
|
+
relation: string,
|
|
861
|
+
): BornContextAuthority | undefined {
|
|
862
|
+
const domain = mod.domain ?? mod.name;
|
|
863
|
+
const directives = mod.directives as Directive<unknown>[];
|
|
864
|
+
const byId = new Map(directives.map((d) => [d.id, d]));
|
|
865
|
+
for (const bd of directives) {
|
|
866
|
+
if (bd?.birthsChild !== true || typeof bd.payloadSchema !== "object") continue;
|
|
867
|
+
const probed = probeBirthGenesis(bd, `${domain}/${bd.id}`);
|
|
868
|
+
if (probed === undefined) continue;
|
|
869
|
+
const { chain, synth } = probed;
|
|
870
|
+
// Find the genesis step (in THIS domain) whose directive writeTuples `relation` — that step seats R.
|
|
871
|
+
let seatsVia: string | undefined;
|
|
872
|
+
for (const step of chain) {
|
|
873
|
+
if (step === null || typeof step !== "object" || step.domain !== domain || typeof step.directiveId !== "string") continue;
|
|
874
|
+
const stepDir = byId.get(step.directiveId);
|
|
875
|
+
if (stepDir === undefined) continue;
|
|
876
|
+
if (probeRelationsWritten(stepDir, `${domain}/${stepDir.id}`).includes(relation)) {
|
|
877
|
+
seatsVia = step.directiveId;
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (seatsVia === undefined) continue;
|
|
882
|
+
|
|
883
|
+
// Resolve the owner field (the payload field whose value the genesis routes as the seed's subject/actor)
|
|
884
|
+
// + the framework/law-hash fields (matched by value against the genesis install steps), mirroring
|
|
885
|
+
// discoverChildBirthLeg. The owner field is the payload field carried as a genesis step actor.
|
|
886
|
+
const fieldForValue = (v: unknown): string | undefined =>
|
|
887
|
+
Object.entries(synth.value).find(([, sv]) => sv === v && typeof v === "string" && v.length > 0)?.[0];
|
|
888
|
+
const ownerField = (() => {
|
|
889
|
+
for (const step of chain as Array<{ actor?: unknown }>) {
|
|
890
|
+
const exact = fieldForValue(step?.actor);
|
|
891
|
+
if (exact !== undefined) return exact;
|
|
892
|
+
if (typeof step?.actor === "string") {
|
|
893
|
+
for (const [field, value] of Object.entries(synth.value)) {
|
|
894
|
+
if (typeof value === "string" && value.length > 0 && (step.actor === `user:${value}` || step.actor === `key:${value}`)) return field;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return undefined;
|
|
899
|
+
})();
|
|
900
|
+
const installSteps = chain.filter((s) => s !== null && typeof s === "object" && s.directiveId === "installDomain") as Array<{ domain?: string; payload?: { domainHash?: unknown } }>;
|
|
901
|
+
const frameworkHashField = fieldForValue(installSteps.find((s) => s.domain === "bootstrap")?.payload?.domainHash);
|
|
902
|
+
const lawHashField = fieldForValue(installSteps.find((s) => s.domain === "nomos")?.payload?.domainHash);
|
|
903
|
+
|
|
904
|
+
const birthPayload: Record<string, unknown> = { ...synth.value };
|
|
905
|
+
if (frameworkHashField !== undefined) delete birthPayload[frameworkHashField];
|
|
906
|
+
if (lawHashField !== undefined) delete birthPayload[lawHashField];
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
birthDirectiveId: bd.id,
|
|
910
|
+
birthPayload,
|
|
911
|
+
birthAutoStamped: synth.autoStamped,
|
|
912
|
+
...(ownerField !== undefined ? { ownerField } : {}),
|
|
913
|
+
...(frameworkHashField !== undefined ? { frameworkHashField } : {}),
|
|
914
|
+
...(lawHashField !== undefined ? { lawHashField } : {}),
|
|
915
|
+
seatsViaDirectiveId: seatsVia,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/** Does any workspaceInvariant fire ON `directiveId` — i.e. does that directive carry a PREREQUISITE (a
|
|
922
|
+
* declared invariant reading state another step must establish first)? A standalone replay of such a
|
|
923
|
+
* directive can FALSE-fail (the prior state is absent), so a born domain must prove via the genesis path. */
|
|
924
|
+
function directiveHasPrerequisiteInvariant(mod: DomainModule, directiveId: string): boolean {
|
|
925
|
+
return (mod.workspaceInvariants ?? []).some((inv) => inv.on === directiveId);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/** The AGGREGATE TYPES a directive's plan writes (set/ensure/create ops) — used to find the sibling seed that
|
|
929
|
+
* establishes the state a prerequisite invariant reads. `[]` ⇒ not probeable / writes nothing. */
|
|
930
|
+
function probeWritesAggregateTypes(dir: Directive<unknown>, where: string): Set<string> {
|
|
931
|
+
const probed = probePlanOps(dir, where);
|
|
932
|
+
const out = new Set<string>();
|
|
933
|
+
if (probed === undefined) return out;
|
|
934
|
+
for (const o of probed.ops) if (typeof o.aggregateType === "string" && o.aggregateType.length > 0) out.add(o.aggregateType);
|
|
935
|
+
return out;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** The aggregate TYPES a workspaceInvariant's declared reads depend on — the state its predicate needs to be
|
|
939
|
+
* established first. A `pre:`-prefixed read id is a PRE-apply self-check (write-once), NOT a cross-step
|
|
940
|
+
* dependency, so it's excluded. Best-effort: an un-callable reads fn yields none. */
|
|
941
|
+
function invariantReadAggregateTypes(inv: { reads: (ctx: { intent: Record<string, unknown>; principal?: string }) => Array<{ aggregate: string; id: string }> }): Set<string> {
|
|
942
|
+
const out = new Set<string>();
|
|
943
|
+
let refs: Array<{ aggregate: string; id: string }>;
|
|
944
|
+
try {
|
|
945
|
+
refs = inv.reads({ intent: {}, principal: "" });
|
|
946
|
+
} catch {
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
for (const r of refs) {
|
|
950
|
+
if (r === null || typeof r !== "object") continue;
|
|
951
|
+
if (typeof r.id === "string" && r.id.startsWith("pre:")) continue; // write-once self-check, not a dependency
|
|
952
|
+
if (typeof r.aggregate === "string" && r.aggregate.length > 0) out.add(r.aggregate);
|
|
953
|
+
}
|
|
954
|
+
return out;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Discover the PREREQUISITE-SEED CHAIN for a granter whose prerequisite invariant(s) read state established by
|
|
959
|
+
* SIBLING SEEDS in the same domain (the composed co2_platform / `seedPlatformAdmin` case — `seedPlatformAdmin`
|
|
960
|
+
* needs the founder singleton `seedPlatformFounder` stamps). Returns the ordered seeds to dispatch (as the
|
|
961
|
+
* proof actor = founder) BEFORE the grant, or `undefined` when the prerequisite can't be satisfied by a
|
|
962
|
+
* sibling seed (fail-closed — the caller then falls through to born-context / the diagnostic).
|
|
963
|
+
*
|
|
964
|
+
* Principled: for each invariant on `dir`, resolve the aggregate types its reads depend on; for each type NOT
|
|
965
|
+
* written by `dir` itself (a genuine cross-step dependency), find a sibling seed that writes it, then RECURSE
|
|
966
|
+
* into that seed's own prerequisites (bounded by a visited set + a small depth cap — no cycles, no fabrication).
|
|
967
|
+
*/
|
|
968
|
+
export function discoverPrerequisiteChain(mod: DomainModule, granter: Directive<unknown>): PrerequisiteSeed[] | undefined {
|
|
969
|
+
const domain = mod.domain ?? mod.name;
|
|
970
|
+
const directives = mod.directives as Directive<unknown>[];
|
|
971
|
+
const invariants = (mod.workspaceInvariants ?? []) as Array<WorkspaceInvariantDecl>;
|
|
972
|
+
const chain: PrerequisiteSeed[] = [];
|
|
973
|
+
const inChain = new Set<string>();
|
|
974
|
+
const synthSeed = (dir: Directive<unknown>): PrerequisiteSeed | undefined => {
|
|
975
|
+
let synth: SynthesizedPayload;
|
|
976
|
+
try {
|
|
977
|
+
synth = synthesizePayload(dir.payloadSchema as z.ZodTypeAny, `${domain}/${dir.id}`);
|
|
978
|
+
} catch {
|
|
979
|
+
return undefined;
|
|
980
|
+
}
|
|
981
|
+
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === dir.aggregateId);
|
|
982
|
+
const mintField = agg !== undefined ? mintedCreateField(dir, agg) : undefined;
|
|
983
|
+
const payload: Record<string, unknown> = { ...synth.value };
|
|
984
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
985
|
+
return { directiveId: dir.id, payload, autoStamped: synth.autoStamped };
|
|
986
|
+
};
|
|
987
|
+
// Resolve `dir`'s prerequisites (a sibling seed per cross-step aggregate dependency), recursively. Returns
|
|
988
|
+
// false when ANY prerequisite has no establishing sibling seed (the whole chain is then unsatisfiable).
|
|
989
|
+
const resolve = (dir: Directive<unknown>, depth: number): boolean => {
|
|
990
|
+
if (depth > 8) return false; // guard runaway recursion
|
|
991
|
+
for (const inv of invariants) {
|
|
992
|
+
if (inv.on !== dir.id) continue;
|
|
993
|
+
const dirWrites = probeWritesAggregateTypes(dir, `${domain}/${dir.id}`);
|
|
994
|
+
for (const aggType of invariantReadAggregateTypes(inv)) {
|
|
995
|
+
if (dirWrites.has(aggType)) continue; // self-dependency (dir establishes it in its own apply)
|
|
996
|
+
// Find a sibling seed that WRITES aggType (not `dir`, not the gated directive, not itself gated).
|
|
997
|
+
const seedDir = directives.find(
|
|
998
|
+
(d) => d !== dir && d.requiresRelationGate === undefined && typeof d?.payloadSchema === "object"
|
|
999
|
+
&& d.birthsChild !== true && probeWritesAggregateTypes(d, `${domain}/${d.id}`).has(aggType),
|
|
1000
|
+
);
|
|
1001
|
+
if (seedDir === undefined) return false; // unmet prerequisite → chain unsatisfiable
|
|
1002
|
+
if (!inChain.has(seedDir.id)) {
|
|
1003
|
+
if (!resolve(seedDir, depth + 1)) return false; // establish the seed's OWN prerequisites first
|
|
1004
|
+
const s = synthSeed(seedDir);
|
|
1005
|
+
if (s === undefined) return false;
|
|
1006
|
+
inChain.add(seedDir.id);
|
|
1007
|
+
chain.push(s); // dependency-first order: the seed runs before `dir`
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return true;
|
|
1012
|
+
};
|
|
1013
|
+
if (!resolve(granter, 0)) return undefined;
|
|
1014
|
+
return chain.length > 0 ? chain : undefined;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
690
1017
|
/** Discover the RELATION-BOOTSTRAP leg for a module: the first gated (`.requires(R)`) directive plus the
|
|
691
1018
|
* sibling that ESTABLISHES the actor's authority for R. In priority order: (1) a DIRECT granter of R (a
|
|
692
1019
|
* `.grants(R)` marker or a plan-probed `writeTuple(R)`) — the original lane; (2) REWRITE-AWARE — when R is
|
|
@@ -741,6 +1068,7 @@ export function discoverRelationBootstrapLeg(mod: DomainModule): RelationBootstr
|
|
|
741
1068
|
let via: "marker" | "inferred" | "computed-base" | "seed-path" = "marker";
|
|
742
1069
|
let seedSpeculative = false;
|
|
743
1070
|
let seedWritesRelations: string[] | undefined;
|
|
1071
|
+
let prerequisiteSeeds: PrerequisiteSeed[] | undefined;
|
|
744
1072
|
let found = findGranterFor(relation);
|
|
745
1073
|
if (found !== undefined) {
|
|
746
1074
|
via = found.via;
|
|
@@ -765,14 +1093,59 @@ export function discoverRelationBootstrapLeg(mod: DomainModule): RelationBootstr
|
|
|
765
1093
|
// guard: `seedSpeculative`). Prefer a seed that seats the MOST relations (a genesis/owner seed like
|
|
766
1094
|
// estate's, which lands admin/creator/estateOwner together — the broadest root authority).
|
|
767
1095
|
const seed = discoverSeedDirective(candidates, domain);
|
|
768
|
-
if (seed
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1096
|
+
if (seed !== undefined) {
|
|
1097
|
+
found = { granter: seed.dir, via: "inferred" };
|
|
1098
|
+
grantedRelation = seed.relationsWritten[0] ?? relation; // cosmetic — the seed's primary relation
|
|
1099
|
+
via = "seed-path";
|
|
1100
|
+
seedSpeculative = true;
|
|
1101
|
+
seedWritesRelations = seed.relationsWritten;
|
|
1102
|
+
}
|
|
774
1103
|
}
|
|
775
1104
|
}
|
|
1105
|
+
|
|
1106
|
+
// (4) BORN-DOMAIN GENESIS AUTHORITY (the co2 home case) — precedence over a standalone seed. The standalone
|
|
1107
|
+
// path is "clean" ONLY when a granter was found, it isn't the speculative seed-path lane, AND that granter
|
|
1108
|
+
// carries NO prerequisite invariant (a standalone re-seed of a prerequisite-gated granter — e.g.
|
|
1109
|
+
// seedHomeCreator, gated by HomeCreatorSeedBindsOwner — would FALSE-fail because the prior genesis state is
|
|
1110
|
+
// absent). When the standalone path is NOT clean, prefer proving the gated directive INSIDE the born
|
|
1111
|
+
// workspace, whose genesis seats R for the owner.
|
|
1112
|
+
const standaloneClean =
|
|
1113
|
+
found !== undefined && !seedSpeculative && !directiveHasPrerequisiteInvariant(mod, found.granter.id);
|
|
1114
|
+
if (!standaloneClean) {
|
|
1115
|
+
const bornContext = discoverBornContextAuthority(mod, relation);
|
|
1116
|
+
if (bornContext !== undefined) {
|
|
1117
|
+
return {
|
|
1118
|
+
domain,
|
|
1119
|
+
gatedDirectiveId: gated.id,
|
|
1120
|
+
relation,
|
|
1121
|
+
grantedRelation: relation,
|
|
1122
|
+
gatedPayload,
|
|
1123
|
+
gatedAutoStamped: gatedSynth.autoStamped,
|
|
1124
|
+
...(gatedMintField !== undefined ? { gatedMintField, gatedAggregateId: gated.aggregateId } : {}),
|
|
1125
|
+
// grant fields are UNUSED for a born-context leg (the runner births + dispatches inside); populate them
|
|
1126
|
+
// with the genesis seed's id / empty payload so the shape stays uniform (never dispatched standalone).
|
|
1127
|
+
grantDirectiveId: bornContext.seatsViaDirectiveId,
|
|
1128
|
+
grantPayload: {},
|
|
1129
|
+
grantAutoStamped: [],
|
|
1130
|
+
grantSubjectFields: [],
|
|
1131
|
+
grantGrantedByFields: [],
|
|
1132
|
+
via: "born-context",
|
|
1133
|
+
bornContext,
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
// No born-domain path. A standalone granter with a prerequisite it can't satisfy alone (and not the
|
|
1137
|
+
// speculative lane): try the PREREQUISITE-SEED CHAIN — a sibling seed establishes the prerequisite state
|
|
1138
|
+
// (the composed co2_platform case: `seedPlatformFounder` stamps the founder `seedPlatformAdmin` binds to).
|
|
1139
|
+
// The runner dispatches the chain (as the proof actor = founder) BEFORE the grant. If no chain satisfies
|
|
1140
|
+
// the prerequisite, do NOT emit a doomed passing leg — return undefined so the diagnostic names the gap.
|
|
1141
|
+
// A speculative seed-path stays (it degrades safely at runtime).
|
|
1142
|
+
if (found !== undefined && !seedSpeculative && directiveHasPrerequisiteInvariant(mod, found.granter.id)) {
|
|
1143
|
+
const chain = discoverPrerequisiteChain(mod, found.granter);
|
|
1144
|
+
if (chain === undefined) return undefined;
|
|
1145
|
+
prerequisiteSeeds = chain;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (found === undefined) return undefined; // no granter at all → the caller emits the diagnostic
|
|
776
1149
|
const granter = found.granter;
|
|
777
1150
|
|
|
778
1151
|
// Synthesize the grant payload + resolve which fields route into subject / grantedBy (so runtime seats the
|
|
@@ -804,6 +1177,7 @@ export function discoverRelationBootstrapLeg(mod: DomainModule): RelationBootstr
|
|
|
804
1177
|
via,
|
|
805
1178
|
...(seedSpeculative ? { seedSpeculative: true } : {}),
|
|
806
1179
|
...(seedWritesRelations !== undefined ? { seedWritesRelations } : {}),
|
|
1180
|
+
...(prerequisiteSeeds !== undefined ? { prerequisiteSeeds } : {}),
|
|
807
1181
|
};
|
|
808
1182
|
}
|
|
809
1183
|
|
|
@@ -845,6 +1219,19 @@ export function diagnoseRelationBootstrapGap(mod: DomainModule): string | undefi
|
|
|
845
1219
|
if (discoverRelationBootstrapLeg(mod) !== undefined) return undefined; // resolved — no gap
|
|
846
1220
|
const relation = gated.requiresRelationGate!.relation;
|
|
847
1221
|
const object = gated.requiresRelationGate!.object;
|
|
1222
|
+
// NAME THE UNMET PREREQUISITE (co2 home diagnostic): when a granter of R exists but carries a prerequisite
|
|
1223
|
+
// invariant a standalone re-seed can't satisfy AND no birth genesis seats R, say so precisely.
|
|
1224
|
+
const directivesAll = mod.directives as Directive<unknown>[];
|
|
1225
|
+
const prereqGranter = directivesAll.find(
|
|
1226
|
+
(d) => d !== gated && typeof d?.payloadSchema === "object" && d.birthsChild !== true
|
|
1227
|
+
&& probeGrantsRelation(d, relation, `${domain}/${d.id}`) && directiveHasPrerequisiteInvariant(mod, d.id),
|
|
1228
|
+
);
|
|
1229
|
+
if (prereqGranter !== undefined) {
|
|
1230
|
+
const inv = (mod.workspaceInvariants ?? []).find((i) => i.on === prereqGranter.id);
|
|
1231
|
+
return `${domain}/${gated.id} is gated by .requires("${relation}") but the proof cannot seed authority: the only granter of "${relation}" is `
|
|
1232
|
+
+ `${domain}/${prereqGranter.id}, which carries the prerequisite invariant '${inv?.id ?? "?"}' (it reads state an EARLIER step must establish), so a standalone re-seed FALSE-fails — and no \`.births()\` genesis seats "${relation}" to prove it in a born workspace. `
|
|
1233
|
+
+ `Establish "${relation}" via a birth genesis (a \`.births()\` whose chain runs ${prereqGranter.id} after seeding its prerequisite), or prove ${gated.id} by hand. (The negative conformance leg still proves the gate refuses an actorless offer.)`;
|
|
1234
|
+
}
|
|
848
1235
|
const bases = resolveGrantableBaseRelations(mod.relationTypes, object, relation).filter((b) => b !== relation);
|
|
849
1236
|
const tried = bases.length > 0
|
|
850
1237
|
? `no directive grants "${relation}" directly, nor any of its directly-grantable rewrite bases [${bases.join(", ")}]`
|
|
@@ -1544,7 +1931,7 @@ function generateBirthOnlyProof(
|
|
|
1544
1931
|
`import { readFileSync } from "node:fs";`,
|
|
1545
1932
|
`import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
|
|
1546
1933
|
`import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
|
|
1547
|
-
`void connect; void ${factory};`,
|
|
1934
|
+
`void connect; void encodeIntentOfferBytes; void ${factory};`,
|
|
1548
1935
|
``,
|
|
1549
1936
|
`const CLOUD = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
1550
1937
|
`const WS = process.env.NOMOS_WS || ${JSON.stringify(slug + "-proof-")} + Math.random().toString(36).slice(2, 8);`,
|
|
@@ -1552,6 +1939,11 @@ function generateBirthOnlyProof(
|
|
|
1552
1939
|
...v2IdentityAndCleanupPreamble(opts, hashConst, n),
|
|
1553
1940
|
``,
|
|
1554
1941
|
...liveChildBirthLegLines(cb, opts, n),
|
|
1942
|
+
// THE RELATION-BOOTSTRAP LEG (incl. LAYER 4 born-context): a birth-only domain may ALSO carry a gated
|
|
1943
|
+
// non-birth directive whose authority is established by the birth genesis (co2 home's updateHomeProfile
|
|
1944
|
+
// via birthHome→seedHomeCreator). Emit it so the birth-only proof names it (the born-context proof itself
|
|
1945
|
+
// runs in the offline `githolon proof` lane). No-op when the domain has no gated directive.
|
|
1946
|
+
...relationSeedLegLines(primaryLegs.relationBootstrap, factory, n, primaryLegs.relationBootstrap === undefined ? diagnoseRelationBootstrapGap(mod) : undefined),
|
|
1555
1947
|
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → child birth (\${WS} → the born child) → verified\`);`,
|
|
1556
1948
|
``,
|
|
1557
1949
|
...v2CatchFinally(),
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
|
|
4
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* framework/platform_admin.ts — `seedPlatformAdmin`, THE PLATFORM ANALOG OF `seedHomeCreator`.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS EXISTS. A tenant platform (an org’s self-governing workspace) declares CUSTOM governance roles
|
|
10
|
+
* (`platformAdmin`, `platformMember`, …) as `relationType("workspace").relation("platformAdmin", …)` and gates its
|
|
11
|
+
* directives on them (`grantRole.requires("platformAdmin")`, `revokeRole.requires(
|
|
12
|
+
* "platformAdmin")`). But there is NO in-law bootstrap for the FOUNDING admin: `grantRole` is
|
|
13
|
+
* itself `platformAdmin`-gated (chicken/egg) and `revokeRole` REMOVES authority. So a platform's
|
|
14
|
+
* founding authority was only ever seated IMPLICITLY at platform birth (the `grantAdmin` step inside
|
|
15
|
+
* `buildGenesisChain`, which seats the FRAMEWORK `admin` relation — OUTSIDE the tenant's own domain), a
|
|
16
|
+
* seat a single-domain `githolon proof` can neither see nor establish.
|
|
17
|
+
*
|
|
18
|
+
* `seedPlatformAdmin` front-loads this into the framework, EXACTLY as `seedHomeCreator`
|
|
19
|
+
* (`framework/home.ts` shape) seats a home's `creator`: a first-class, PROVABLE founding-admin bootstrap
|
|
20
|
+
* that seats a TENANT-NAMED governance relation for a founder principal, at genesis, write-once, bound to
|
|
21
|
+
* the verified genesis author. Every platform then gets a consistent, proof-discoverable founding-admin
|
|
22
|
+
* seat tied to a real AuthProvider identity ("bring your own auth").
|
|
23
|
+
*
|
|
24
|
+
* ── BLAST RADIUS: IMPORTABLE, npm-only ──────────────────────────────────────────────────────────
|
|
25
|
+
* This is a TENANT-IMPORTABLE primitive — a directive/aggregate/invariant factory a tenant imports into
|
|
26
|
+
* THEIR OWN platform-governance domain and deploys via THEIR normal `evolve`. It seats a relation on
|
|
27
|
+
* `workspace:self` in the TENANT'S domain; it does NOT touch the running root/platform governance bundle
|
|
28
|
+
* (`framework/workspaces.ts`). This mirrors `home.ts`: `seedHomeOwner`/`birthHome`/`homeNameBindsOwner`
|
|
29
|
+
* are exported framework primitives a tenant composes into a SEPARATE, per-home deployable package — they
|
|
30
|
+
* are NOT baked into root's live governance. Adopting `seedPlatformAdmin` is a tenant recompile + evolve
|
|
31
|
+
* of their platform domain; no governance re-deploy, no re-birth.
|
|
32
|
+
*
|
|
33
|
+
* ── WHAT IT SEATS (design choice (b) — a tenant-NAMED relation, directly) ────────────────────────
|
|
34
|
+
* We seat the tenant's OWN relation (`platformAdmin`) directly, not the framework `admin` relation. Rationale:
|
|
35
|
+
* • Tenant-owned governance — the platform's roles are the platform's law; the founding seat should be a
|
|
36
|
+
* fact IN the tenant's domain, provable from that domain alone (the `githolon proof` subject).
|
|
37
|
+
* • Self-contained per role, no rewrite plumbing required. (For a platform whose roles should ALL derive
|
|
38
|
+
* from one admin, the tenant may instead seat ONE relation here and link the rest by rewrite —
|
|
39
|
+
* `relation("platformMember", { rewrite: union(self(), computed("platformAdmin")) })` — design choice (a). We
|
|
40
|
+
* support that cleanly too: seat once, rewrite the rest. Nothing here forbids it.)
|
|
41
|
+
* • The relation is CLOSED OVER by the factory (LAW), never payload-carried — a payload-carried relation
|
|
42
|
+
* would let a caller seat ANY relation (a forge). The founder subject is payload-carried but bound.
|
|
43
|
+
*
|
|
44
|
+
* ── HOW A TENANT ADOPTS (a platform-governance domain — before → after) ───────────────────────────────────────────
|
|
45
|
+
* BEFORE (the gap): a `platform-governance` domain with `platformAdmin`/`platformMember` relations, `grantRole
|
|
46
|
+
* .requires("platformAdmin")`, `revokeRole.requires("platformAdmin")` — but no in-law way to seat the
|
|
47
|
+
* first `platformAdmin`, so `githolon proof` can't establish authority for the gated directives.
|
|
48
|
+
* AFTER (one import + include, then evolve):
|
|
49
|
+
*
|
|
50
|
+
* import { seedPlatformAdmin } from "@githolon/dsl";
|
|
51
|
+
* export const founding = seedPlatformAdmin({ relation: "platformAdmin" });
|
|
52
|
+
* export const PlatformFounder = founding.PlatformFounder; // the founder singleton
|
|
53
|
+
* export const seedPlatformFounder = founding.seedPlatformFounder; // stamps the founder (genesis)
|
|
54
|
+
* export const seatFoundingAdmin = founding.seedPlatformAdmin; // seats platformAdmin@founder (genesis)
|
|
55
|
+
* export const platformFounderWriteOnce = founding.platformFounderWriteOnce;
|
|
56
|
+
* export const platformAdminBindsFounder = founding.platformAdminBindsFounder;
|
|
57
|
+
* export const platformFounder = founding.platformFounder; // audit query
|
|
58
|
+
* // the platform's genesis chain runs, after the law installs:
|
|
59
|
+
* // [ … installDomain×2, seedGenesis, seedPlatformFounder(founder), seatFoundingAdmin(founder) ]
|
|
60
|
+
*
|
|
61
|
+
* The tenant DEPLOYS this via `evolve` (npm-only recompile + their normal deploy); no root/platform
|
|
62
|
+
* governance re-deploy, no re-birth. A NEW platform seeds it in its birth genesis; an EXISTING platform
|
|
63
|
+
* that was already born with an implicit `admin` seat evolves to ADD the tenant-relation seat (a
|
|
64
|
+
* one-time `seedPlatformFounder`+`seatFoundingAdmin` authored by the existing admin — see the note on the
|
|
65
|
+
* write-once posture below for the existing-platform lane).
|
|
66
|
+
*
|
|
67
|
+
* ── SECURITY POSTURE (mirror `seedHomeCreator`: genesis-only, ungated-but-safe) ──────────────────
|
|
68
|
+
* The seeds carry NO `.requires` (gating the founding seat on the relation it establishes is the very
|
|
69
|
+
* chicken/egg this primitive dissolves). They are safe because they are CONTROL-PLANE SEEDS:
|
|
70
|
+
* 1. WRITE-ONCE — `platformFounderWriteOnce` refuses `seedPlatformFounder` if a founder is ALREADY
|
|
71
|
+
* seated (a `pre:`-apply read of the singleton). So the founder can be stamped exactly once; a
|
|
72
|
+
* replayed/injected re-seat on the open session lane is refused.
|
|
73
|
+
* 2. FOUNDER == VERIFIED GENESIS AUTHOR — `platformFounderWriteOnce` also binds the recorded founder to
|
|
74
|
+
* `ctx.principal` (the captured, peer-identical author). The upstream VERIFICATION is the birth
|
|
75
|
+
* offer's principal (the parent's gate verified an AuthProvider identity before the offer-effect ran
|
|
76
|
+
* the genesis actor as that founder) — the SAME "bring your own auth" boundary `birthHome` relies on.
|
|
77
|
+
* (We do NOT REQUIRE `ctx.principalVerified` inside the child gate: genesis runs on a warrant-free
|
|
78
|
+
* prefix — the warrant lands LAST, `home.ts` "BREAK 1" — so `principalVerified` is not yet true at
|
|
79
|
+
* the seed step; requiring it would make every platform un-birthable. The verified-founder guarantee
|
|
80
|
+
* is therefore the offer principal, asserted upstream, not re-asserted here. See the report.)
|
|
81
|
+
* 3. THE SEAT CAN ONLY GRANT THE FOUNDER — `platformAdminBindsFounder` refuses `seedPlatformAdmin`
|
|
82
|
+
* unless its tuple subject == the recorded founder. So even an ungated seed can seat NO ONE but the
|
|
83
|
+
* write-once founder. The tuple is a positive `writeTuple` grant (never a revoke).
|
|
84
|
+
* Together: the ONLY principal that can ever hold the seeded relation via this primitive is the write-once
|
|
85
|
+
* founder bound to the verified genesis author. This is `seedHomeCreator`'s posture, at platform grain.
|
|
86
|
+
*
|
|
87
|
+
* ── PROVABLE ────────────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
* `seedPlatformAdmin` (the tuple-seating directive) is marked `.grants(relation)` — the producer marker
|
|
89
|
+
* (`codegen_proof.ts`) so the proof's bootstrap discovery finds the granter UNAMBIGUOUSLY (no inference,
|
|
90
|
+
* no guessing) and proves the platform's `.requires(relation)`-gated directives (`grantRole`/`revokeRole`)
|
|
91
|
+
* from it — the born-context / relation-bootstrap proof leg the layer-4 discovery understands.
|
|
92
|
+
*
|
|
93
|
+
* ── ERA-SAFE ────────────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
* A factory: a package that never CALLS `seedPlatformAdmin({…})` emits none of these directives /
|
|
95
|
+
* aggregates / invariants and is byte-identical to before this file existed.
|
|
96
|
+
*/
|
|
97
|
+
import { z } from "zod";
|
|
98
|
+
import { aggregate, instance, type AggregateHandle } from "../aggregate.js";
|
|
99
|
+
import { t } from "../fields.js";
|
|
100
|
+
import { Lww } from "../drivers.js";
|
|
101
|
+
import { directive, type RequirableDirective } from "../directive.js";
|
|
102
|
+
import { set } from "../ops.js";
|
|
103
|
+
import { query, type QueryDecl } from "../query.js";
|
|
104
|
+
import { writeTuple, RelationTuple } from "./relations.js";
|
|
105
|
+
import { workspaceInvariant, refAs, type WorkspaceInvariantDecl } from "./workspace_invariant.js";
|
|
106
|
+
|
|
107
|
+
/** The founder singleton's stable instance id — one founder per platform workspace (`workspace:self`). */
|
|
108
|
+
export const PLATFORM_FOUNDER_ID = "platform-founder:self";
|
|
109
|
+
|
|
110
|
+
/** The object every seat is written against — the self-relative workspace scope (`workspace:self#R`),
|
|
111
|
+
* the SAME self-relative posture the framework `admin`/`creator` relations use (custody guarantees a
|
|
112
|
+
* workspace only ever folds its OWN relation tuples, so the seat is name-plumbing-free). */
|
|
113
|
+
const SELF_WORKSPACE_OBJECT = "workspace:self";
|
|
114
|
+
|
|
115
|
+
/** A tuple SUBJECT: `user:<uid>` (an AuthProvider identity) or `key:<keyHash>` (a control-plane key —
|
|
116
|
+
* e.g. a platform's `sha256(platformAdminKey)`). Mirrors the relation-tuple subject vocabulary; a raw
|
|
117
|
+
* uid carrying `:` / a bare `*` is refused so it can never forge a wildcard/userset. */
|
|
118
|
+
const FOUNDER_SUBJECT_RE = /^(user|key):[A-Za-z0-9][A-Za-z0-9_.:/-]*$/;
|
|
119
|
+
const founderSubject = z.string().regex(FOUNDER_SUBJECT_RE, "founder must be user:<uid> or key:<keyHash>");
|
|
120
|
+
|
|
121
|
+
/** A relation name (the seated edge label) — a bare identifier, like a directive/cap name. */
|
|
122
|
+
const RELATION_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
123
|
+
|
|
124
|
+
/** The bundle a `seedPlatformAdmin({ relation })` call yields — the aggregate, the two genesis seeds, the
|
|
125
|
+
* two binding invariants, and the audit query. Spread the members into a platform-governance domain
|
|
126
|
+
* module (see the adoption sketch at the top of this file). */
|
|
127
|
+
export interface PlatformAdminSeed {
|
|
128
|
+
/** The founder singleton aggregate — records the platform's founding principal (write-once at genesis). */
|
|
129
|
+
readonly PlatformFounder: AggregateHandle;
|
|
130
|
+
/** GENESIS SEED 1 — stamps the founder singleton (write-once, bound to the verified genesis author). */
|
|
131
|
+
readonly seedPlatformFounder: RequirableDirective<{ founder: string; seededAt: string }>;
|
|
132
|
+
/** GENESIS SEED 2 — seats `workspace:self#<relation>@<founder>` (the founding-admin tuple). `.grants(R)`. */
|
|
133
|
+
readonly seedPlatformAdmin: RequirableDirective<{ founder: string; seededAt: string }>;
|
|
134
|
+
/** The write-once + author-binding invariant on {@link seedPlatformFounder}. */
|
|
135
|
+
readonly platformFounderWriteOnce: WorkspaceInvariantDecl;
|
|
136
|
+
/** The subject-binding invariant on {@link seedPlatformAdmin} (the seat can grant NO ONE but the founder). */
|
|
137
|
+
readonly platformAdminBindsFounder: WorkspaceInvariantDecl;
|
|
138
|
+
/** Audit read: "who is this platform's founder?" — the recorded founder + the relation seated. */
|
|
139
|
+
readonly platformFounder: QueryDecl;
|
|
140
|
+
/** The relation this bundle seats (echoed back for the tenant's convenience). */
|
|
141
|
+
readonly relation: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Optional id overrides — a platform seating MORE THAN ONE founding relation from the same founder passes
|
|
145
|
+
* distinct ids per call so the directives/invariants/aggregate don't collide. Defaults suit the common
|
|
146
|
+
* single-founding-relation case. */
|
|
147
|
+
export interface SeedPlatformAdminOptions {
|
|
148
|
+
/** The tenant governance relation to seat for the founder (`"platformAdmin"`). Required. */
|
|
149
|
+
readonly relation: string;
|
|
150
|
+
/** The founder singleton aggregate id (default `"PlatformFounder"`). Share it across multi-relation
|
|
151
|
+
* seats so one founder identity is recorded once and seated into several relations. */
|
|
152
|
+
readonly founderAggregateId?: string;
|
|
153
|
+
/** The founder-stamp directive id (default `"seedPlatformFounder"`). */
|
|
154
|
+
readonly founderDirectiveId?: string;
|
|
155
|
+
/** The tuple-seat directive id (default `"seedPlatformAdmin"`). */
|
|
156
|
+
readonly seatDirectiveId?: string;
|
|
157
|
+
/** The write-once invariant id (default `"PlatformFounderWriteOnce"`). */
|
|
158
|
+
readonly writeOnceInvariantId?: string;
|
|
159
|
+
/** The subject-binding invariant id (default `"PlatformAdminBindsFounder"`). */
|
|
160
|
+
readonly bindsInvariantId?: string;
|
|
161
|
+
/** The audit-query id (default `"platformFounder"`). */
|
|
162
|
+
readonly queryId?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* `seedPlatformAdmin({ relation })` — build the founding-admin bootstrap bundle for a tenant platform.
|
|
167
|
+
* See the file header for the full doctrine, security posture, and adoption sketch.
|
|
168
|
+
*/
|
|
169
|
+
export function seedPlatformAdmin(opts: SeedPlatformAdminOptions): PlatformAdminSeed {
|
|
170
|
+
const relation = opts.relation;
|
|
171
|
+
if (typeof relation !== "string" || !RELATION_RE.test(relation)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`seedPlatformAdmin({ relation }): relation must be a bare relation name (matched ${RELATION_RE}); got ${JSON.stringify(relation)}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const founderAggregateId = opts.founderAggregateId ?? "PlatformFounder";
|
|
177
|
+
const founderDirectiveId = opts.founderDirectiveId ?? "seedPlatformFounder";
|
|
178
|
+
const seatDirectiveId = opts.seatDirectiveId ?? "seedPlatformAdmin";
|
|
179
|
+
const writeOnceInvariantId = opts.writeOnceInvariantId ?? "PlatformFounderWriteOnce";
|
|
180
|
+
const bindsInvariantId = opts.bindsInvariantId ?? "PlatformAdminBindsFounder";
|
|
181
|
+
const queryId = opts.queryId ?? "platformFounder";
|
|
182
|
+
|
|
183
|
+
/** The founder singleton — a platform's founding principal + the relation it was seated into (audit). */
|
|
184
|
+
const PlatformFounder = aggregate(founderAggregateId, {
|
|
185
|
+
/** The founding principal — a `user:<uid>` / `key:<keyHash>` subject; write-once at genesis. */
|
|
186
|
+
principal: t.string().merge(Lww),
|
|
187
|
+
/** The governance relation seated for the founder (echo of the factory's `relation`). */
|
|
188
|
+
relation: t.string().merge(Lww),
|
|
189
|
+
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
190
|
+
seededAt: t.string().merge(Lww),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* GENESIS SEED 1 — stamp the founder singleton at the fixed id. Authored by the platform's birth/genesis
|
|
195
|
+
* chain; on the open session lane it is refused by {@link platformFounderWriteOnce} (a genesis founder is
|
|
196
|
+
* already seated by then) + the author binding. `.ensures` (a deterministic-keyed singleton).
|
|
197
|
+
*/
|
|
198
|
+
const seedPlatformFounder = directive(founderDirectiveId)
|
|
199
|
+
.ensures(PlatformFounder)
|
|
200
|
+
.payload(z.object({ founder: founderSubject, seededAt: z.string().min(1) }))
|
|
201
|
+
.plan((p) => {
|
|
202
|
+
const h = instance(PlatformFounder, PLATFORM_FOUNDER_ID);
|
|
203
|
+
return [set(h, "principal", p.founder), set(h, "relation", relation), set(h, "seededAt", p.seededAt)];
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* WRITE-ONCE + AUTHOR-BINDING invariant on {@link seedPlatformFounder}. Reads the PRE-apply singleton
|
|
208
|
+
* (the `pre:` id lane — see `workspaces.ts::retireOwnOnly`): a founder may be stamped exactly ONCE, and
|
|
209
|
+
* only for the VERIFIED genesis author (`ctx.principal` — the captured, peer-identical author identity;
|
|
210
|
+
* the upstream verification is the birth offer's principal, see the file header). Runs at the ONE gate
|
|
211
|
+
* on every lane (author / edge admission / verify_chain).
|
|
212
|
+
*/
|
|
213
|
+
const platformFounderWriteOnce = workspaceInvariant(writeOnceInvariantId)
|
|
214
|
+
.on(founderDirectiveId)
|
|
215
|
+
.reads(() => [refAs("prior", founderAggregateId, "pre:" + PLATFORM_FOUNDER_ID)])
|
|
216
|
+
.assert(({ prior }, ctx) => {
|
|
217
|
+
const seated = (prior ?? {}) as { principal?: string };
|
|
218
|
+
// WRITE-ONCE: a founder already recorded ⇒ refuse a re-seat (the seat is immutable after genesis).
|
|
219
|
+
if (seated.principal) {
|
|
220
|
+
return { reject: "platform-founder-write-once: a founding admin is already seated for this platform" };
|
|
221
|
+
}
|
|
222
|
+
// AUTHOR BINDING: the recorded founder MUST be the captured, verified author of this seed. So a seed
|
|
223
|
+
// can only stamp ITS OWN author as the founder — never a third party.
|
|
224
|
+
const author = ctx?.principal ?? "";
|
|
225
|
+
const intent = (ctx?.intent ?? {}) as { founder?: string };
|
|
226
|
+
return author && intent.founder === author
|
|
227
|
+
? { accept: true }
|
|
228
|
+
: { reject: "platform-founder-must-bind-verified-author: the seeded founder must equal the seeding principal" };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* GENESIS SEED 2 — seat the founding tuple `workspace:self#<relation>@<founder>` (a POSITIVE grant, never
|
|
233
|
+
* a revoke). Marked `.grants(relation)` so the proof's bootstrap discovery finds it unambiguously. Gated
|
|
234
|
+
* by {@link platformAdminBindsFounder} — the tuple subject can be NO ONE but the recorded founder.
|
|
235
|
+
*/
|
|
236
|
+
const seedPlatformAdminDirective = directive(seatDirectiveId)
|
|
237
|
+
// `.ensures(RelationTuple)` — the deterministic-keyed (object, relation) tuple aggregate is admitted by
|
|
238
|
+
// the id-mint gate (upsert-by-deterministic-key); the tenant includes `RelationTuple` in its aggregates.
|
|
239
|
+
.ensures(RelationTuple)
|
|
240
|
+
.payload(z.object({ founder: founderSubject, seededAt: z.string().min(1) }))
|
|
241
|
+
.plan((p) =>
|
|
242
|
+
writeTuple({
|
|
243
|
+
object: SELF_WORKSPACE_OBJECT,
|
|
244
|
+
relation,
|
|
245
|
+
subject: p.founder,
|
|
246
|
+
grantedBy: p.founder,
|
|
247
|
+
grantedAt: p.seededAt,
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
.grants(relation);
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* SUBJECT-BINDING invariant on {@link seedPlatformAdminDirective}. Reads the founder singleton (stamped
|
|
254
|
+
* by seed 1 earlier in the genesis chain) and refuses unless the tuple's subject == the recorded founder.
|
|
255
|
+
* So the founding relation can be seated for the write-once founder and NO ONE ELSE — even though the
|
|
256
|
+
* seed itself carries no `.requires`.
|
|
257
|
+
*/
|
|
258
|
+
const platformAdminBindsFounder = workspaceInvariant(bindsInvariantId)
|
|
259
|
+
.on(seatDirectiveId)
|
|
260
|
+
.reads(() => [refAs("founder", founderAggregateId, PLATFORM_FOUNDER_ID)])
|
|
261
|
+
.assert(({ founder }, ctx) => {
|
|
262
|
+
const rec = (founder ?? {}) as { principal?: string };
|
|
263
|
+
const intent = (ctx?.intent ?? {}) as { founder?: string };
|
|
264
|
+
// The seat's subject MUST be the recorded (write-once, author-bound) founder.
|
|
265
|
+
if (!rec.principal) {
|
|
266
|
+
return { reject: "platform-admin-seat-needs-founder: seed the platform founder before seating its relation" };
|
|
267
|
+
}
|
|
268
|
+
return rec.principal === intent.founder
|
|
269
|
+
? { accept: true }
|
|
270
|
+
: { reject: "platform-admin-binds-founder: the seated relation may be granted only to the recorded founder" };
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
/** Audit read — "who is this platform's founder + which relation was seated". */
|
|
274
|
+
const platformFounder = query(queryId).key("principal").returns(PlatformFounder);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
PlatformFounder,
|
|
278
|
+
seedPlatformFounder,
|
|
279
|
+
seedPlatformAdmin: seedPlatformAdminDirective,
|
|
280
|
+
platformFounderWriteOnce,
|
|
281
|
+
platformAdminBindsFounder,
|
|
282
|
+
platformFounder,
|
|
283
|
+
relation,
|
|
284
|
+
};
|
|
285
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -312,6 +312,14 @@ export {
|
|
|
312
312
|
birthAttestation,
|
|
313
313
|
parentAttestedBirth,
|
|
314
314
|
} from "./framework/home.js";
|
|
315
|
+
// THE PLATFORM FOUNDING-ADMIN BOOTSTRAP (the platform analog of seedHomeCreator) — a tenant-importable
|
|
316
|
+
// factory that seats a tenant-named governance relation for a write-once, author-bound founder at genesis.
|
|
317
|
+
export {
|
|
318
|
+
seedPlatformAdmin,
|
|
319
|
+
PLATFORM_FOUNDER_ID,
|
|
320
|
+
type PlatformAdminSeed,
|
|
321
|
+
type SeedPlatformAdminOptions,
|
|
322
|
+
} from "./framework/platform_admin.js";
|
|
315
323
|
// THE GENESIS WARRANT (identity_keys.md step 0) — the in-history birth fact that lets
|
|
316
324
|
// the gate's genesis clause admit a founding control-plane grant on a virgin chain.
|
|
317
325
|
export {
|