@githolon/dsl 0.7.1 → 0.8.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/aggregate.ts +19 -16
- package/src/build_package.ts +7 -0
- package/src/codegen_dart.ts +9 -0
- package/src/codegen_ts.ts +12 -10
- package/src/compile_package_main.ts +10 -0
- package/src/directive.ts +15 -18
- package/src/engine_entry.ts +8 -2
- package/src/framework/encryption_identity.ts +3 -3
- package/src/framework/genesis.ts +88 -0
- package/src/framework/impure_capability.ts +3 -3
- package/src/framework/relations.ts +28 -4
- package/src/framework/signing_identity.ts +97 -0
- package/src/framework/workspace_invariant.ts +5 -3
- package/src/framework/workspaces.ts +158 -103
- package/src/index.ts +72 -18
- package/src/manifest.ts +0 -6
- package/src/relation_schema.ts +298 -0
- package/src/relation_sugar.ts +324 -0
- package/src/usd.ts +142 -7
- package/src/usd_layers.ts +53 -0
- package/src/framework/rbac.ts +0 -418
|
@@ -66,12 +66,77 @@ export const GOVERNANCE_REVISION = "2026-06-12-capability-bindings"; // hash-bea
|
|
|
66
66
|
*/
|
|
67
67
|
import { z } from "zod";
|
|
68
68
|
import { aggregate, instance } from "../aggregate.js";
|
|
69
|
-
import { create } from "../authoring.js";
|
|
69
|
+
import { create, ensure } from "../authoring.js";
|
|
70
70
|
import { directive } from "../directive.js";
|
|
71
71
|
import { Lww } from "../drivers.js";
|
|
72
72
|
import { t } from "../fields.js";
|
|
73
73
|
import { set, withMarker } from "../ops.js";
|
|
74
74
|
import { query } from "../query.js";
|
|
75
|
+
import { workspaceInvariant, refAs } from "./workspace_invariant.js";
|
|
76
|
+
import { writeTuple, removeTuple, RelationTuple } from "./relations.js";
|
|
77
|
+
import { relationType, self, user, key } from "../relation_schema.js";
|
|
78
|
+
|
|
79
|
+
// ── WORKSPACE SELF-IDENTITY (the law reads its OWN install-context) ──────────────────────────────────
|
|
80
|
+
// The governance bundle runs on root AND every born platform. A workspace knows its ROLE via this
|
|
81
|
+
// singleton, at a CONSTANT fixed instance id (the RelationTuple discipline — a literal, never minted, so
|
|
82
|
+
// the id is identical on every peer + the sid is additive/era-safe). Seeded by the birth plan (ungated).
|
|
83
|
+
// The gate keys authority off the CONSTANT object literal `workspace:self` — because custody guarantees a
|
|
84
|
+
// workspace only ever folds its OWN relations table, `workspace:root#admin` collapses to `workspace:self#
|
|
85
|
+
// admin`, so the gate is self-relative with NO name plumbing. A tenant runs no governance ⇒ no WorkspaceSelf.
|
|
86
|
+
export const WORKSPACE_ROLES = ["cloud-root", "platform", "tenant"] as const;
|
|
87
|
+
const WORKSPACE_SELF_ID = "workspaceself-self";
|
|
88
|
+
const SELF_ADMIN_OBJECT = "workspace:self";
|
|
89
|
+
export const WorkspaceSelf = aggregate("WorkspaceSelf", {
|
|
90
|
+
role: t.enum(WORKSPACE_ROLES).merge(Lww),
|
|
91
|
+
name: t.string().merge(Lww),
|
|
92
|
+
});
|
|
93
|
+
// The one-off SELF-IDENTITY backfill for an already-born workspace (root predates WorkspaceSelf). UNGATED
|
|
94
|
+
// by the admin gate — it must run BEFORE the admin tuple/gate are live — but WRITE-ONCE (the invariant
|
|
95
|
+
// below refuses a re-set once the role exists), so a race or a malicious re-set cannot flip the role and
|
|
96
|
+
// freeze governance. The HOST authors this in the serialized genesis re-entry, winning any race.
|
|
97
|
+
export const ensureWorkspaceSelf = directive("ensureWorkspaceSelf")
|
|
98
|
+
.ensures(WorkspaceSelf)
|
|
99
|
+
.payload(z.object({ role: z.enum(WORKSPACE_ROLES), name: z.string().min(1) }))
|
|
100
|
+
.plan((p) => {
|
|
101
|
+
ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("role", p.role).set("name", p.name);
|
|
102
|
+
return [];
|
|
103
|
+
});
|
|
104
|
+
export const workspaceSelfWriteOnce = workspaceInvariant("WorkspaceSelfWriteOnce")
|
|
105
|
+
// BARE directive id — the gate filters `on == directiveId` and the engine dispatches by the
|
|
106
|
+
// BARE id within a domain slice (`directive("ensureWorkspaceSelf")`), so the produced intent
|
|
107
|
+
// carries `directiveId:"ensureWorkspaceSelf"`. A domain-QUALIFIED `on` ("workspaces.…") would
|
|
108
|
+
// NEVER match the bare runtime id ⇒ the gate stays silently INERT (the #266 side-door bug,
|
|
109
|
+
// 2026-06-15). Bare matches — exactly the convention the sharding invariants already fire under.
|
|
110
|
+
.on("ensureWorkspaceSelf")
|
|
111
|
+
.reads(() => [refAs("self", "WorkspaceSelf", "pre:" + WORKSPACE_SELF_ID)])
|
|
112
|
+
.assert(({ self }) =>
|
|
113
|
+
(self as { role?: string })?.role ? { reject: "workspace-self-already-set" } : { accept: true },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// ── THE GOVERNANCE RELATION SCHEMA (full-native Zanzibar — the ONE authz model) ──────────────────────
|
|
117
|
+
// The control-plane authority of the cloud is a NATIVE Zanzibar namespace. A directive declares its
|
|
118
|
+
// requirement with `.requires("<relation>")` (object defaults to the self-relative `"workspace:self"`),
|
|
119
|
+
// and the ONE wasm gate runs `engine.check(schema, "workspace:self", relation, principal)` over the
|
|
120
|
+
// pre-apply folded relation tuples — refusing any author who does not HOLD the relation. The schema +
|
|
121
|
+
// the `.requires` together arm the gate (the `nomosRelations` + `nomosRelationGate` era keys); the
|
|
122
|
+
// bespoke `workspaceInvariant` authz gates are GONE (this IS their replacement, as real native law).
|
|
123
|
+
//
|
|
124
|
+
// THE CONTROL-PLANE CARVE-OUT — LEAF-ONLY (`allow: [user(), key()]`, never a userset/wildcard). A
|
|
125
|
+
// `workspace:self#admin@<group>#member` or `@<type>:*` would let a group/wildcard escalate to admin
|
|
126
|
+
// (the admin-via-group forge); the fail-closed `allow` makes such a subject UNSTORABLE + (the Check
|
|
127
|
+
// side) DISCARDED at evaluation. Each relation is a flat `self()` (directly-stored tuples) — NO rewrite
|
|
128
|
+
// algebra (no computed/from/union): control-plane authority is direct membership only.
|
|
129
|
+
// * admin — grantAdmin/revokeAdmin + every admin-mutating governance directive.
|
|
130
|
+
// * creator — createWorkspace (under invite/closed posture; see the host's _creationLawOk).
|
|
131
|
+
// * platformCreator — birthPlatform (always invite-only).
|
|
132
|
+
// SELF-RELATIVE: custody guarantees a workspace only ever folds its OWN relations table, so
|
|
133
|
+
// `workspace:root#admin` collapses to `workspace:self#admin` — the SAME law governs root and every
|
|
134
|
+
// born platform with no name plumbing. (A pure tenant runs no governance ⇒ no schema ⇒ the gate is
|
|
135
|
+
// inert there; an un-backfilled workspace with no admin tuple refuses fail-closed.)
|
|
136
|
+
export const workspaceRelations = relationType("workspace")
|
|
137
|
+
.relation("admin", { rewrite: self(), allow: [user(), key()] })
|
|
138
|
+
.relation("creator", { rewrite: self(), allow: [user(), key()] })
|
|
139
|
+
.relation("platformCreator", { rewrite: self(), allow: [user(), key()] });
|
|
75
140
|
|
|
76
141
|
// --- vocabulary ----------------------------------------------------------------
|
|
77
142
|
|
|
@@ -161,43 +226,11 @@ export const CloudWorkspace = aggregate("CloudWorkspace", {
|
|
|
161
226
|
pool: t.int().merge(Lww).optional(),
|
|
162
227
|
});
|
|
163
228
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
*/
|
|
170
|
-
export const AdminGrant = aggregate("AdminGrant", {
|
|
171
|
-
/** sha256 hex of the admin bearer secret (the secret itself never lands here). */
|
|
172
|
-
keyHash: t.string().merge(Lww),
|
|
173
|
-
/** Human label for audit ("jack", "ci-deployer", ...). */
|
|
174
|
-
label: t.string().merge(Lww),
|
|
175
|
-
/** The principal that authored the grant (attribution, on the ledger forever). */
|
|
176
|
-
grantedBy: t.string().merge(Lww),
|
|
177
|
-
/** Epoch-ms the grant was authored (caller-stamped). */
|
|
178
|
-
grantedAt: t.int().merge(Lww),
|
|
179
|
-
/** `active` | `revoked` — revocation is the law's kill switch for a key. */
|
|
180
|
-
status: t.enum(GRANT_STATUSES).merge(Lww),
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* CreationGrant — the narrower power to CREATE workspaces (the invite key,
|
|
185
|
-
* lawful edition). Same shape and same enforcement path as AdminGrant, but the
|
|
186
|
-
* worker honours it ONLY for `createWorkspace`-shaped access — separation of the
|
|
187
|
-
* "may invite tenants" power from the "may govern the cloud" power.
|
|
188
|
-
*/
|
|
189
|
-
export const CreationGrant = aggregate("CreationGrant", {
|
|
190
|
-
/** sha256 hex of the creation bearer secret. */
|
|
191
|
-
keyHash: t.string().merge(Lww),
|
|
192
|
-
/** Human label for audit. */
|
|
193
|
-
label: t.string().merge(Lww),
|
|
194
|
-
/** The principal that authored the grant. */
|
|
195
|
-
grantedBy: t.string().merge(Lww),
|
|
196
|
-
/** Epoch-ms the grant was authored (caller-stamped). */
|
|
197
|
-
grantedAt: t.int().merge(Lww),
|
|
198
|
-
/** `active` | `revoked`. */
|
|
199
|
-
status: t.enum(GRANT_STATUSES).merge(Lww),
|
|
200
|
-
});
|
|
229
|
+
// AdminGrant / CreationGrant aggregates: DELETED (full-native Zanzibar). Admin/creation authority is
|
|
230
|
+
// now a NATIVE RELATION TUPLE — `workspace:self#admin@key:<keyHash>` / `…#creator@key:<keyHash>` —
|
|
231
|
+
// written by grantAdmin/grantCreation via `writeTuple`, gated by `.requires("admin")`, and read by the
|
|
232
|
+
// host's `_rootCheck` (the wasm `check` op) and the one gate's `engine.check`. A grant WAS just a
|
|
233
|
+
// relation; the aggregate + its `adminGrantByKeyHash`/`creationGrantByKeyHash` queries are gone.
|
|
201
234
|
|
|
202
235
|
/**
|
|
203
236
|
* CloudPolicy — THE QUOTA LAW, a law-state singleton (id `cloud-policy:cloud`).
|
|
@@ -342,7 +375,8 @@ export const createWorkspace = directive("createWorkspace")
|
|
|
342
375
|
.set("createdBy", p.requestedBy)
|
|
343
376
|
.set("createdAt", p.requestedAt);
|
|
344
377
|
return [];
|
|
345
|
-
})
|
|
378
|
+
})
|
|
379
|
+
.requires("creator"); // posture-gated creation: only a #creator holder may request a workspace
|
|
346
380
|
|
|
347
381
|
/**
|
|
348
382
|
* birthCloud — THE CLOUD'S OWN BIRTH INTENT. Root authors this once, into its own
|
|
@@ -360,6 +394,9 @@ export const birthCloud = directive("birthCloud")
|
|
|
360
394
|
/** The genesis auth-provider principal uid upon which the world rests. */
|
|
361
395
|
principal: z.string().min(1),
|
|
362
396
|
name: z.string().min(1).default("root"),
|
|
397
|
+
/** The workspace's ROLE for the governance domain — the host stamps "cloud-root" at root genesis,
|
|
398
|
+
* "platform" when birthing a platform's own chain. Drives the self-relative authz gate. */
|
|
399
|
+
role: z.enum(WORKSPACE_ROLES).default("cloud-root"),
|
|
363
400
|
tier: z.enum(WORKSPACE_TIERS).default("edge"),
|
|
364
401
|
bornAt: epochMs,
|
|
365
402
|
}),
|
|
@@ -375,6 +412,9 @@ export const birthCloud = directive("birthCloud")
|
|
|
375
412
|
.set("createdBy", p.principal)
|
|
376
413
|
.set("createdAt", p.bornAt)
|
|
377
414
|
.set("provisionedAt", p.bornAt);
|
|
415
|
+
// The self-identity singleton at the CONSTANT id (ungated — birthCloud is not admin-mutating), so the
|
|
416
|
+
// gate's self-record exists before any admin write. ensure() = deterministic-id sibling birth.
|
|
417
|
+
ensure(WorkspaceSelf, WORKSPACE_SELF_ID).set("role", p.role).set("name", p.name);
|
|
378
418
|
return [];
|
|
379
419
|
});
|
|
380
420
|
|
|
@@ -530,7 +570,8 @@ export const birthPlatform = directive("birthPlatform")
|
|
|
530
570
|
.set("createdBy", p.requestedBy)
|
|
531
571
|
.set("createdAt", p.requestedAt);
|
|
532
572
|
return [];
|
|
533
|
-
})
|
|
573
|
+
})
|
|
574
|
+
.requires("platformCreator"); // invite-only: an active #platformCreator tuple admits the birth
|
|
534
575
|
|
|
535
576
|
/** setPlatformPool — the pool edition of a granted quota request. */
|
|
536
577
|
export const setPlatformPool = directive("setPlatformPool")
|
|
@@ -546,14 +587,18 @@ export const setPlatformPool = directive("setPlatformPool")
|
|
|
546
587
|
.plan((p) => {
|
|
547
588
|
const ws = instance(CloudWorkspace, p.workspaceId);
|
|
548
589
|
return [set(ws, "pool", p.pool)];
|
|
549
|
-
})
|
|
590
|
+
})
|
|
591
|
+
.requires("admin");
|
|
550
592
|
|
|
551
593
|
/**
|
|
552
594
|
* grantPlatformCreation / revokePlatformCreation — open and close the
|
|
553
595
|
* become-a-platform door for one principal (see {@link PlatformGrant}).
|
|
554
596
|
*/
|
|
597
|
+
// grantPlatformCreation — become-a-platform authority IS the self-relative `#platformCreator` tuple
|
|
598
|
+
// (one authz spine, all the way down). Writes `workspace:self#platformCreator@user:<principal>` (no
|
|
599
|
+
// PlatformGrant aggregate) — the host's platform-birth invite check reads the SAME tuple via Check.
|
|
555
600
|
export const grantPlatformCreation = directive("grantPlatformCreation")
|
|
556
|
-
.
|
|
601
|
+
.ensures(RelationTuple)
|
|
557
602
|
.payload(
|
|
558
603
|
z.object({
|
|
559
604
|
principal: z.string().min(1),
|
|
@@ -562,29 +607,22 @@ export const grantPlatformCreation = directive("grantPlatformCreation")
|
|
|
562
607
|
grantedAt: epochMs,
|
|
563
608
|
}),
|
|
564
609
|
)
|
|
565
|
-
.plan((p) => {
|
|
566
|
-
|
|
567
|
-
.set("principal", p.principal)
|
|
568
|
-
.set("label", p.label)
|
|
569
|
-
.set("grantedBy", p.grantedBy)
|
|
570
|
-
.set("grantedAt", p.grantedAt)
|
|
571
|
-
.set("status", "active" as const);
|
|
572
|
-
return [];
|
|
573
|
-
});
|
|
610
|
+
.plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "platformCreator", subject: "user:" + p.principal, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
|
|
611
|
+
.requires("admin"); // only an admin opens the become-a-platform door
|
|
574
612
|
|
|
613
|
+
// revokePlatformCreation — flip the `#platformCreator@user:<principal>` leaf to revoked. BREAKING vs the
|
|
614
|
+
// aggregate model: keyed by PRINCIPAL (the tuple subject), not a minted grantId.
|
|
575
615
|
export const revokePlatformCreation = directive("revokePlatformCreation")
|
|
576
|
-
.
|
|
616
|
+
.ensures(RelationTuple)
|
|
577
617
|
.payload(
|
|
578
618
|
z.object({
|
|
579
|
-
|
|
619
|
+
principal: z.string().min(1),
|
|
580
620
|
revokedBy: z.string().min(1),
|
|
581
621
|
revokedAt: epochMs,
|
|
582
622
|
}),
|
|
583
623
|
)
|
|
584
|
-
.plan((p) => {
|
|
585
|
-
|
|
586
|
-
return [set(grant, "status", "revoked" as const)];
|
|
587
|
-
});
|
|
624
|
+
.plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "platformCreator", subject: "user:" + p.principal, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
|
|
625
|
+
.requires("admin");
|
|
588
626
|
|
|
589
627
|
/**
|
|
590
628
|
* AuthProvider — BYOAuth: WHO VOUCHES FOR THIS SUBTREE'S PRINCIPALS. A law-state
|
|
@@ -638,7 +676,8 @@ export const setAuthProvider = directive("setAuthProvider")
|
|
|
638
676
|
set(ap, "setBy", p.setBy),
|
|
639
677
|
set(ap, "setAt", p.setAt),
|
|
640
678
|
];
|
|
641
|
-
})
|
|
679
|
+
})
|
|
680
|
+
.requires("admin");
|
|
642
681
|
|
|
643
682
|
/**
|
|
644
683
|
* HostnameClaim — a CUSTOM HOSTNAME bound to a platform: `api.acme.com` serving
|
|
@@ -789,7 +828,8 @@ export const setCloudPolicy = directive("setCloudPolicy")
|
|
|
789
828
|
set(pol, "setBy", p.setBy),
|
|
790
829
|
set(pol, "setAt", p.setAt),
|
|
791
830
|
];
|
|
792
|
-
})
|
|
831
|
+
})
|
|
832
|
+
.requires("admin");
|
|
793
833
|
|
|
794
834
|
/**
|
|
795
835
|
* grantQuota — grant a quota request: a principal's workspace allowance. The
|
|
@@ -812,7 +852,8 @@ export const grantQuota = directive("grantQuota")
|
|
|
812
852
|
.set("grantedBy", p.grantedBy)
|
|
813
853
|
.set("grantedAt", p.grantedAt);
|
|
814
854
|
return [];
|
|
815
|
-
})
|
|
855
|
+
})
|
|
856
|
+
.requires("admin");
|
|
816
857
|
|
|
817
858
|
/**
|
|
818
859
|
* setWorkspaceByteCap — grant one workspace a bigger (or smaller) ledger than the
|
|
@@ -831,7 +872,8 @@ export const setWorkspaceByteCap = directive("setWorkspaceByteCap")
|
|
|
831
872
|
.plan((p) => {
|
|
832
873
|
const ws = instance(CloudWorkspace, p.workspaceId);
|
|
833
874
|
return [set(ws, "byteCap", p.byteCap)];
|
|
834
|
-
})
|
|
875
|
+
})
|
|
876
|
+
.requires("admin");
|
|
835
877
|
|
|
836
878
|
// --- grant directives ---------------------------------------------------------------
|
|
837
879
|
|
|
@@ -840,8 +882,12 @@ export const setWorkspaceByteCap = directive("setWorkspaceByteCap")
|
|
|
840
882
|
* authors the FIRST one from the bootstrap env var — which is then spent forever;
|
|
841
883
|
* every later grant is an admin granting, attributed and admitted under this law.
|
|
842
884
|
*/
|
|
885
|
+
// ZANZIBAR ALL THE WAY DOWN: admin authority IS the self-relative tuple the gate reads. grantAdmin writes
|
|
886
|
+
// `workspace:self#admin@key:<keyHash>` (no AdminGrant aggregate) — a typed alias for relateSubject(#admin);
|
|
887
|
+
// the AuthorHoldsAdmin gate's PRE-APPLY roster prevents self-grant. `label` stays in the payload contract
|
|
888
|
+
// (provenance) though the keyHash-keyed tuple leaf does not carry it.
|
|
843
889
|
export const grantAdmin = directive("grantAdmin")
|
|
844
|
-
.
|
|
890
|
+
.ensures(RelationTuple)
|
|
845
891
|
.payload(
|
|
846
892
|
z.object({
|
|
847
893
|
keyHash: sha256Hex,
|
|
@@ -850,42 +896,39 @@ export const grantAdmin = directive("grantAdmin")
|
|
|
850
896
|
grantedAt: epochMs,
|
|
851
897
|
}),
|
|
852
898
|
)
|
|
853
|
-
.plan((p) => {
|
|
854
|
-
|
|
855
|
-
.set("keyHash", p.keyHash)
|
|
856
|
-
.set("label", p.label)
|
|
857
|
-
.set("grantedBy", p.grantedBy)
|
|
858
|
-
.set("grantedAt", p.grantedAt)
|
|
859
|
-
.set("status", "active" as const);
|
|
860
|
-
return [];
|
|
861
|
-
});
|
|
899
|
+
.plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
|
|
900
|
+
.requires("admin"); // only an admin grants admin (pre-apply roster ⇒ no self-grant)
|
|
862
901
|
|
|
863
902
|
/**
|
|
864
903
|
* revokeAdmin — flip an AdminGrant to `revoked`. The key stops verifying at fold
|
|
865
904
|
* time; the grant record (who granted it, when, who killed it) stays on the
|
|
866
905
|
* ledger — audit is structural, not a log file.
|
|
867
906
|
*/
|
|
907
|
+
// revokeAdmin — flip the `#admin@key:<keyHash>` subject leaf to revoked (an Lww write over the same map
|
|
908
|
+
// entry; the projection drops the row, Check fails closed). Keyed by keyHash now (the tuple subject), not
|
|
909
|
+
// a minted grantId. Audit stays structural — the revoked leaf rides the chain forever.
|
|
868
910
|
export const revokeAdmin = directive("revokeAdmin")
|
|
869
|
-
.
|
|
911
|
+
.ensures(RelationTuple)
|
|
870
912
|
.payload(
|
|
871
913
|
z.object({
|
|
872
|
-
|
|
914
|
+
keyHash: sha256Hex,
|
|
873
915
|
revokedBy: z.string().min(1),
|
|
874
916
|
revokedAt: epochMs,
|
|
875
917
|
}),
|
|
876
918
|
)
|
|
877
|
-
.plan((p) => {
|
|
878
|
-
|
|
879
|
-
return [set(grant, "status", "revoked" as const)];
|
|
880
|
-
});
|
|
919
|
+
.plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "admin", subject: "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
|
|
920
|
+
.requires("admin");
|
|
881
921
|
|
|
882
922
|
/**
|
|
883
923
|
* grantCreation — mint a workspace-creation credential record (`status:"active"`).
|
|
884
924
|
* The lawful replacement for the `NOMOS_CLOUD_INVITE_KEY` env var: who may invite
|
|
885
925
|
* tenants is law state, grantable and revocable on the record.
|
|
886
926
|
*/
|
|
927
|
+
// grantCreation — creation authority IS the self-relative `#creator` tuple the CreationGate reads.
|
|
928
|
+
// Writes `workspace:self#creator@key:<keyHash>` (no CreationGrant aggregate). Under posture invite/closed
|
|
929
|
+
// the gate admits only holders of an active #creator subject; under open/unset it admits anyone.
|
|
887
930
|
export const grantCreation = directive("grantCreation")
|
|
888
|
-
.
|
|
931
|
+
.ensures(RelationTuple)
|
|
889
932
|
.payload(
|
|
890
933
|
z.object({
|
|
891
934
|
keyHash: sha256Hex,
|
|
@@ -894,30 +937,21 @@ export const grantCreation = directive("grantCreation")
|
|
|
894
937
|
grantedAt: epochMs,
|
|
895
938
|
}),
|
|
896
939
|
)
|
|
897
|
-
.plan((p) => {
|
|
898
|
-
|
|
899
|
-
.set("keyHash", p.keyHash)
|
|
900
|
-
.set("label", p.label)
|
|
901
|
-
.set("grantedBy", p.grantedBy)
|
|
902
|
-
.set("grantedAt", p.grantedAt)
|
|
903
|
-
.set("status", "active" as const);
|
|
904
|
-
return [];
|
|
905
|
-
});
|
|
940
|
+
.plan((p) => writeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: "key:" + p.keyHash, grantedBy: p.grantedBy, grantedAt: String(p.grantedAt) }))
|
|
941
|
+
.requires("admin"); // only an admin grants creation authority
|
|
906
942
|
|
|
907
|
-
/** revokeCreation — flip
|
|
943
|
+
/** revokeCreation — flip the `#creator@key:<keyHash>` leaf to revoked (see {@link revokeAdmin}). */
|
|
908
944
|
export const revokeCreation = directive("revokeCreation")
|
|
909
|
-
.
|
|
945
|
+
.ensures(RelationTuple)
|
|
910
946
|
.payload(
|
|
911
947
|
z.object({
|
|
912
|
-
|
|
948
|
+
keyHash: sha256Hex,
|
|
913
949
|
revokedBy: z.string().min(1),
|
|
914
950
|
revokedAt: epochMs,
|
|
915
951
|
}),
|
|
916
952
|
)
|
|
917
|
-
.plan((p) => {
|
|
918
|
-
|
|
919
|
-
return [set(grant, "status", "revoked" as const)];
|
|
920
|
-
});
|
|
953
|
+
.plan((p) => removeTuple({ object: SELF_ADMIN_OBJECT, relation: "creator", subject: "key:" + p.keyHash, grantedBy: p.revokedBy, grantedAt: String(p.revokedAt), revokedBy: p.revokedBy, revokedAt: String(p.revokedAt) }))
|
|
954
|
+
.requires("admin");
|
|
921
955
|
|
|
922
956
|
// --- declared read queries (the worker's ENTIRE enforcement surface) -----------------
|
|
923
957
|
//
|
|
@@ -928,15 +962,10 @@ export const revokeCreation = directive("revokeCreation")
|
|
|
928
962
|
/** Resolve a workspace by its unique name (the `:ws` URL segment → the law record). */
|
|
929
963
|
export const workspaceByName = query("workspaceByName").key("name").returns(CloudWorkspace);
|
|
930
964
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
/** "May this bearer create workspaces?" — probe by sha256(bearer). */
|
|
937
|
-
export const creationGrantByKeyHash = query("creationGrantByKeyHash")
|
|
938
|
-
.key("keyHash")
|
|
939
|
-
.returns(CreationGrant);
|
|
965
|
+
// adminGrantByKeyHash / creationGrantByKeyHash queries: DELETED (full-native Zanzibar). "Is this bearer
|
|
966
|
+
// an admin / a creator?" is now `Check(workspace:self#admin@key:<keyHash>)` / `…#creator@…` over the
|
|
967
|
+
// projected relation tuples (the wasm `check` op / the one gate's `engine.check`) — never an aggregate
|
|
968
|
+
// probe. The AdminGrant/CreationGrant aggregates that backed these are gone; the tuple IS the grant.
|
|
940
969
|
|
|
941
970
|
/** All children of a workspace (`""` = the roots) — the workspace tree's read side. */
|
|
942
971
|
export const workspacesByParent = query("workspacesByParent")
|
|
@@ -983,3 +1012,29 @@ export const capabilityBindingsByWorkspace = query("capabilityBindingsByWorkspac
|
|
|
983
1012
|
export const capabilityBindingByKeyHash = query("capabilityBindingByKeyHash")
|
|
984
1013
|
.key("keyHash")
|
|
985
1014
|
.returns(CapabilityBinding);
|
|
1015
|
+
|
|
1016
|
+
// ── THE ZANZIBAR SPINE comes to the CONTROL PLANE (governance → tuples) ──────────────────────────
|
|
1017
|
+
// The authority grants above ARE relation tuples in disguise (the same smell as the deleted
|
|
1018
|
+
// UserPermission): admin=`workspace:self#admin@key:<keyHash>`, creator=`…#creator@key:…`,
|
|
1019
|
+
// platformCreator=`workspace:self#platformCreator@user:<principal>`. grantAdmin/grantCreation/
|
|
1020
|
+
// grantPlatformCreation write them via `writeTuple`; the bespoke AdminGrant/CreationGrant aggregates +
|
|
1021
|
+
// their queries are GONE. Re-export the framework tuple surface so the COMPILED governance package mints
|
|
1022
|
+
// RelationTuple's stable id into the lockfile + identity manifest, the engine registers relateSubject/
|
|
1023
|
+
// unrelateSubject for dispatch, and the projected relationTuplesByObject read routes. (QuotaGrant stays
|
|
1024
|
+
// an aggregate — a numeric allowance, not a membership.)
|
|
1025
|
+
export { relateSubject, unrelateSubject, relationTuplesByObject } from "./relations.js";
|
|
1026
|
+
export { RelationTuple };
|
|
1027
|
+
|
|
1028
|
+
// ── GOVERNANCE WRITE-AUTHZ IS NATIVE ZANZIBAR — the ONE gate, no bespoke predicates ───────────────────
|
|
1029
|
+
// The bespoke `workspaceInvariant` authz gates (AuthorHoldsAdmin / CreationGate / PlatformCreationGate)
|
|
1030
|
+
// are DELETED. Their job is now done by the full-native relation gate: each governance directive carries
|
|
1031
|
+
// `.requires("<relation>")` (above), the compiler emits `nomosRelations` (the schema) + `nomosRelationGate`
|
|
1032
|
+
// (directive→relation), and the ONE wasm gate runs `engine.check(schema, "workspace:self", relation,
|
|
1033
|
+
// principal)` over the PRE-APPLY folded relation tuples — refusing any author who does not HOLD the
|
|
1034
|
+
// relation, on every door (author / edge admission / verify_chain replay). The author is the CAPTURED
|
|
1035
|
+
// principal (`key:<sha256(bearer)>` for admin/creator, `user:<uid>` for platformCreator), pinned to the
|
|
1036
|
+
// sealed Attribution.actor on every peer — so "who may grant" is a LAW fact, never a host pre-check. The
|
|
1037
|
+
// pre-apply roster means `grantAdmin(@key:self)` cannot self-grant (the not-yet-written subject is absent
|
|
1038
|
+
// in the pre-apply state). SELF-RELATIVE: custody guarantees a workspace only folds its OWN tuples, so the
|
|
1039
|
+
// SAME law governs root and every born platform. The host's `_adminOk`/`_creationLawOk`/`_platformCheck`
|
|
1040
|
+
// pre-checks are GONE too (a pure bailiff) — the native gate refuses a non-holder at admission.
|
package/src/index.ts
CHANGED
|
@@ -191,24 +191,11 @@ export {
|
|
|
191
191
|
type EqFilter,
|
|
192
192
|
type RequiredIndex,
|
|
193
193
|
} from "./query/compile.js";
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
permissionId,
|
|
200
|
-
grant,
|
|
201
|
-
revoke,
|
|
202
|
-
delegate,
|
|
203
|
-
revokeDelegation,
|
|
204
|
-
updateMetadata,
|
|
205
|
-
roleCatalogue,
|
|
206
|
-
type PermissionStatus,
|
|
207
|
-
type FrameworkRoleBinding,
|
|
208
|
-
type UserPermissionAggregateHandle,
|
|
209
|
-
type Scope,
|
|
210
|
-
type RoleCatalogue,
|
|
211
|
-
} from "./framework/rbac.js";
|
|
194
|
+
// THE LEGACY RBAC (framework/rbac.ts: userPermissionAggregate / grant / revoke / delegate /
|
|
195
|
+
// roleCatalogue) is DELETED — the one-model teardown. Read-visibility + grants are NATIVE relations:
|
|
196
|
+
// a `requires(cap)` aggregate's cap IS a read relation (synthesized into nomosRelations at compile),
|
|
197
|
+
// and a grant is a `relateSubject({ object:"workspace:self", relation:cap, ... })` tuple. See
|
|
198
|
+
// framework/relations.ts + relation_schema.ts below.
|
|
212
199
|
// NATIVE ZANZIBAR / ReBAC (architecture/zanzibar_native_plan.md, slice 1) — object-keyed
|
|
213
200
|
// relation-tuple events `object#relation@subject`, the clean-slate-native authz spine.
|
|
214
201
|
export {
|
|
@@ -234,6 +221,25 @@ export {
|
|
|
234
221
|
encKeyByPrincipal,
|
|
235
222
|
type EncKeyHandle,
|
|
236
223
|
} from "./framework/encryption_identity.js";
|
|
224
|
+
// IDENTITY KEYS (identity_keys.md) — the `signer` RELATION: the key↔identity binding is
|
|
225
|
+
// native Zanzibar (`identity:<principal>#signer@key:<hash>`, enrolled/revoked via
|
|
226
|
+
// relateSubject/unrelateSubject, checked by the engine), NOT a bespoke roster. The only
|
|
227
|
+
// sub-Zanzibar primitive is the SIGNATURE itself (the crypto attestation).
|
|
228
|
+
export {
|
|
229
|
+
identityRelations,
|
|
230
|
+
signerObject,
|
|
231
|
+
SIGNER_RELATION,
|
|
232
|
+
enrollSigner,
|
|
233
|
+
} from "./framework/signing_identity.js";
|
|
234
|
+
// THE GENESIS WARRANT (identity_keys.md step 0) — the in-history birth fact that lets
|
|
235
|
+
// the gate's genesis clause admit a founding control-plane grant on a virgin chain.
|
|
236
|
+
export {
|
|
237
|
+
GenesisWarrant,
|
|
238
|
+
GENESIS_WARRANT_ID,
|
|
239
|
+
writeGenesisWarrant,
|
|
240
|
+
seedGenesis,
|
|
241
|
+
type GenesisWarrantHandle,
|
|
242
|
+
} from "./framework/genesis.js";
|
|
237
243
|
// E2E ENCRYPTION (slice 4) — the group-key events (RFC 9420 sender-keys on the ledger).
|
|
238
244
|
export {
|
|
239
245
|
KeyScope,
|
|
@@ -307,6 +313,54 @@ export {
|
|
|
307
313
|
type WorkspaceInvariantAssert,
|
|
308
314
|
type InvariantRef,
|
|
309
315
|
} from "./framework/workspace_invariant.js";
|
|
316
|
+
// NATIVE ZANZIBAR RELATION SCHEMA (P1) — the userset-rewrite law as pure data.
|
|
317
|
+
export {
|
|
318
|
+
relationType,
|
|
319
|
+
isRelationTypeDecl,
|
|
320
|
+
validateRelationType,
|
|
321
|
+
self,
|
|
322
|
+
computed,
|
|
323
|
+
via,
|
|
324
|
+
viaAll,
|
|
325
|
+
union,
|
|
326
|
+
intersection,
|
|
327
|
+
exclusion,
|
|
328
|
+
user,
|
|
329
|
+
key,
|
|
330
|
+
object,
|
|
331
|
+
anyUser,
|
|
332
|
+
group,
|
|
333
|
+
DEFAULT_RELATION_CAPS,
|
|
334
|
+
type RelationTypeDecl,
|
|
335
|
+
type RelationTypeBuilder,
|
|
336
|
+
type RelationDefAst,
|
|
337
|
+
type Rewrite,
|
|
338
|
+
type AllowedSubject,
|
|
339
|
+
type RelationCaps,
|
|
340
|
+
} from "./relation_schema.js";
|
|
341
|
+
// SECURE-BY-DEFAULT SUGAR over the relation schema — plain-English combinators, audiences, presets.
|
|
342
|
+
// Pure sugar: every export here BUILDS the existing Rewrite/RelationTypeDecl AST (no new wire shape).
|
|
343
|
+
export {
|
|
344
|
+
// tier 1 — plain-English combinator aliases (OpenFGA-shaped)
|
|
345
|
+
directly,
|
|
346
|
+
inheritedFrom,
|
|
347
|
+
fromParent,
|
|
348
|
+
fromEveryParent,
|
|
349
|
+
or,
|
|
350
|
+
and,
|
|
351
|
+
butNot,
|
|
352
|
+
// tier 1.5 — the un-forgettable type-restriction pairing
|
|
353
|
+
storedFor,
|
|
354
|
+
roles,
|
|
355
|
+
// tier 2 — secure-by-default audiences
|
|
356
|
+
withAudiences,
|
|
357
|
+
secureType,
|
|
358
|
+
// tier 3 — one-shot patterns
|
|
359
|
+
roleHierarchy,
|
|
360
|
+
docSharingModel,
|
|
361
|
+
type RelationOpts,
|
|
362
|
+
type SugaredRelationTypeBuilder,
|
|
363
|
+
} from "./relation_sugar.js";
|
|
310
364
|
export { csv, csvRows } from "./report/csv.js";
|
|
311
365
|
// CertifiedRead — the DECLARED write-path read surface (survival Constraint 5;
|
|
312
366
|
// `certified_read.md`). A directive declares profiled reads via `.readsCertified({ name:
|
package/src/manifest.ts
CHANGED
|
@@ -113,10 +113,6 @@ export interface CanonicalDirective {
|
|
|
113
113
|
/** The target aggregate id. */
|
|
114
114
|
readonly target: string;
|
|
115
115
|
readonly marker: WireRefMode;
|
|
116
|
-
/** The required capability — defaults to the directive id. */
|
|
117
|
-
readonly requiresCapability: string;
|
|
118
|
-
/** Whether the directive declares a `scopeFrom` (presence only, not behaviour). */
|
|
119
|
-
readonly scopeFrom: boolean;
|
|
120
116
|
/** The payload's top-level object shape as SORTED `[name, type]` pairs. */
|
|
121
117
|
readonly payload: CanonicalPayloadField[];
|
|
122
118
|
/**
|
|
@@ -1046,8 +1042,6 @@ export function domainManifest(mod: DomainModule): CanonicalManifest {
|
|
|
1046
1042
|
id: d.id,
|
|
1047
1043
|
target: d.aggregateId,
|
|
1048
1044
|
marker: encodeRefMode(d.marker),
|
|
1049
|
-
requiresCapability: d.requiresCapability ?? d.id,
|
|
1050
|
-
scopeFrom: d.scopeFrom !== undefined,
|
|
1051
1045
|
payload: canonicalPayload(d.payloadSchema),
|
|
1052
1046
|
payloadJsonSchema: canonicalPayloadJsonSchema(d.payloadSchema),
|
|
1053
1047
|
// Omit-when-empty: a directive declaring NO boundary contributes neither key,
|