@githolon/dsl 0.70.0 → 0.70.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/codegen_dart.ts +65 -63
- package/src/codegen_dart_client.ts +14 -2
- package/src/codegen_proof.ts +32 -9
- package/src/codegen_ts.ts +1 -1
- package/src/compile_package_main.ts +7 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.70.
|
|
3
|
+
"version": "0.70.2",
|
|
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_dart.ts
CHANGED
|
@@ -4275,61 +4275,10 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4275
4275
|
]
|
|
4276
4276
|
: [];
|
|
4277
4277
|
|
|
4278
|
-
// ROLE SUGAR — per-(role × aggregate) `canEdit<Agg>`/`canView<Agg>` UI-HINT helpers
|
|
4279
|
-
//
|
|
4280
|
-
//
|
|
4281
|
-
|
|
4282
|
-
const editRels = new Map<string, Set<string>>();
|
|
4283
|
-
const viewRels = new Map<string, Set<string>>();
|
|
4284
|
-
for (const r of mod.roles ?? []) {
|
|
4285
|
-
if (r.editRelation) for (const agg of r.editAggregates) (editRels.get(agg) ?? editRels.set(agg, new Set()).get(agg)!).add(r.editRelation);
|
|
4286
|
-
if (r.viewRelation) for (const agg of r.viewAggregates) {
|
|
4287
|
-
const s = viewRels.get(agg) ?? viewRels.set(agg, new Set()).get(agg)!;
|
|
4288
|
-
s.add(r.viewRelation);
|
|
4289
|
-
if (r.editRelation && r.editAggregates.includes(agg)) s.add(r.editRelation);
|
|
4290
|
-
}
|
|
4291
|
-
}
|
|
4292
|
-
if (editRels.size === 0 && viewRels.size === 0) return "";
|
|
4293
|
-
const lines: string[] = [
|
|
4294
|
-
`/// True iff [principal] holds ANY of [relations] as an ACTIVE tuple in [rows] (or a \`user:*\` wildcard).`,
|
|
4295
|
-
`bool _holdsAnyRelation(List<Map<String, dynamic>>? rows, Set<String> relations, String principal) {`,
|
|
4296
|
-
` for (final r in rows ?? const []) {`,
|
|
4297
|
-
` final data = r['data'];`,
|
|
4298
|
-
` if (data is! Map) continue;`,
|
|
4299
|
-
` final rel = data['relation'];`,
|
|
4300
|
-
` if (rel is! String || !relations.contains(rel)) continue;`,
|
|
4301
|
-
` final subjects = data['subjects'];`,
|
|
4302
|
-
` if (subjects is! Map) continue;`,
|
|
4303
|
-
` for (final entry in subjects.entries) {`,
|
|
4304
|
-
` final subj = entry.key.toString();`,
|
|
4305
|
-
` dynamic leaf = entry.value;`,
|
|
4306
|
-
` if (leaf is String) { try { leaf = jsonDecode(leaf); } catch (_) { leaf = const {}; } }`,
|
|
4307
|
-
` final status = leaf is Map ? leaf['status'] : null;`,
|
|
4308
|
-
` if (status != null && status != 'active') continue;`,
|
|
4309
|
-
` if (subj == 'user:\$principal' || subj == 'user:*') return true;`,
|
|
4310
|
-
` }`,
|
|
4311
|
-
` }`,
|
|
4312
|
-
` return false;`,
|
|
4313
|
-
`}`,
|
|
4314
|
-
];
|
|
4315
|
-
const dartSet = (s: Set<string>): string => `{${[...s].sort().map((x) => `'${x}'`).join(", ")}}`;
|
|
4316
|
-
for (const [agg, rels] of [...editRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
4317
|
-
lines.push(
|
|
4318
|
-
`/// UI HINT — may [principal] EDIT a ${agg}? Mirrors the kernel's ReBAC check; INFORMATIONAL only`,
|
|
4319
|
-
`/// (the one wasm gate is the authority and refuses regardless).`,
|
|
4320
|
-
`bool canEdit${cap(agg)}(List<Map<String, dynamic>>? relations, String principal) =>`,
|
|
4321
|
-
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
4322
|
-
);
|
|
4323
|
-
}
|
|
4324
|
-
for (const [agg, rels] of [...viewRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
4325
|
-
lines.push(
|
|
4326
|
-
`/// UI HINT — may [principal] VIEW a ${agg}? Mirrors the kernel's ReBAC read gate; INFORMATIONAL only.`,
|
|
4327
|
-
`bool canView${cap(agg)}(List<Map<String, dynamic>>? relations, String principal) =>`,
|
|
4328
|
-
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
4329
|
-
);
|
|
4330
|
-
}
|
|
4331
|
-
return lines.join("\n");
|
|
4332
|
-
})();
|
|
4278
|
+
// ROLE SUGAR — the per-(role × aggregate) `canEdit<Agg>`/`canView<Agg>` UI-HINT helpers are NOT emitted
|
|
4279
|
+
// into this TYPES file: they walk the folded RelationTuple projection and need `dart:convert` (jsonDecode),
|
|
4280
|
+
// which the layered types-package PURITY GATE forbids. They live in the CLIENT package (`<name>.client.dart`,
|
|
4281
|
+
// which may import runtime) via {@link dartRoleHelpers}. Flat mode emits them there too.
|
|
4333
4282
|
|
|
4334
4283
|
return [
|
|
4335
4284
|
`// GENERATED by nomos2/dsl/src/codegen_dart.ts — DO NOT EDIT BY HAND.`,
|
|
@@ -4342,7 +4291,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4342
4291
|
`// ignore_for_file: unused_import`,
|
|
4343
4292
|
``,
|
|
4344
4293
|
// A nullable plain-`t.json()` read-model slot decode re-stringifies via `jsonEncode`.
|
|
4345
|
-
...(needsJsonConvert
|
|
4294
|
+
...(needsJsonConvert ? [`import 'dart:convert';`] : []),
|
|
4346
4295
|
// The generated tenant domains live in the `co2_nomos_domains` package and depend
|
|
4347
4296
|
// on the framework `nomos_client` package by its PUBLIC package: URI — NOT a relative
|
|
4348
4297
|
// `../wire.dart` path into the framework (which broke when the generated dir moved
|
|
@@ -4416,12 +4365,65 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4416
4365
|
]
|
|
4417
4366
|
: []),
|
|
4418
4367
|
...helperBlock,
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
``,
|
|
4422
|
-
`// ---- role policy (UI hints — the KERNEL GATE is the authority) ----`,
|
|
4423
|
-
roleBlock,
|
|
4424
|
-
]
|
|
4425
|
-
: []),
|
|
4368
|
+
// NOTE: role UI-hint helpers (canEdit/canView) are emitted into the CLIENT package by dartRoleHelpers,
|
|
4369
|
+
// NOT here — they need dart:convert, which the types-package purity gate forbids.
|
|
4426
4370
|
].join("\n");
|
|
4427
4371
|
}
|
|
4372
|
+
|
|
4373
|
+
/** The role UI-hint helpers (`canEdit<Agg>`/`canView<Agg>` + `_holdsAnyRelation`) for the CLIENT package — a
|
|
4374
|
+
* pure client-side MIRROR of the kernel ReBAC check over the folded RelationTuple projection. INFORMATIONAL
|
|
4375
|
+
* only (the one wasm gate is the authority and refuses regardless). Uses `dart:convert` (jsonDecode of the
|
|
4376
|
+
* per-subject leaf), so it may NOT live in the pure types file — the caller emits it into `<name>.client.dart`
|
|
4377
|
+
* and ensures a `dart:convert` import. Returns "" when the domain declares no roles (era-safe, no import). */
|
|
4378
|
+
export function dartRoleHelpers(mod: DomainModule): string {
|
|
4379
|
+
const editRels = new Map<string, Set<string>>();
|
|
4380
|
+
const viewRels = new Map<string, Set<string>>();
|
|
4381
|
+
for (const r of mod.roles ?? []) {
|
|
4382
|
+
if (r.editRelation) for (const agg of r.editAggregates) (editRels.get(agg) ?? editRels.set(agg, new Set()).get(agg)!).add(r.editRelation);
|
|
4383
|
+
if (r.viewRelation) for (const agg of r.viewAggregates) {
|
|
4384
|
+
const s = viewRels.get(agg) ?? viewRels.set(agg, new Set()).get(agg)!;
|
|
4385
|
+
s.add(r.viewRelation);
|
|
4386
|
+
if (r.editRelation && r.editAggregates.includes(agg)) s.add(r.editRelation);
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
if (editRels.size === 0 && viewRels.size === 0) return "";
|
|
4390
|
+
const lines: string[] = [
|
|
4391
|
+
`/// True iff [principal] holds ANY of [relations] as an ACTIVE tuple in [rows] (or a \`user:*\` wildcard).`,
|
|
4392
|
+
`bool _holdsAnyRelation(List<Map<String, dynamic>>? rows, Set<String> relations, String principal) {`,
|
|
4393
|
+
` for (final r in rows ?? const []) {`,
|
|
4394
|
+
` final data = r['data'];`,
|
|
4395
|
+
` if (data is! Map) continue;`,
|
|
4396
|
+
` final rel = data['relation'];`,
|
|
4397
|
+
` if (rel is! String || !relations.contains(rel)) continue;`,
|
|
4398
|
+
` final subjects = data['subjects'];`,
|
|
4399
|
+
` if (subjects is! Map) continue;`,
|
|
4400
|
+
` for (final entry in subjects.entries) {`,
|
|
4401
|
+
` final subj = entry.key.toString();`,
|
|
4402
|
+
` dynamic leaf = entry.value;`,
|
|
4403
|
+
` if (leaf is String) { try { leaf = jsonDecode(leaf); } catch (_) { leaf = const {}; } }`,
|
|
4404
|
+
` final status = leaf is Map ? leaf['status'] : null;`,
|
|
4405
|
+
` if (status != null && status != 'active') continue;`,
|
|
4406
|
+
` if (subj == 'user:\$principal' || subj == 'user:*') return true;`,
|
|
4407
|
+
` }`,
|
|
4408
|
+
` }`,
|
|
4409
|
+
` return false;`,
|
|
4410
|
+
`}`,
|
|
4411
|
+
];
|
|
4412
|
+
const dartSet = (s: Set<string>): string => `{${[...s].sort().map((x) => `'${x}'`).join(", ")}}`;
|
|
4413
|
+
for (const [agg, rels] of [...editRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
4414
|
+
lines.push(
|
|
4415
|
+
`/// UI HINT — may [principal] EDIT a ${agg}? Mirrors the kernel's ReBAC check; INFORMATIONAL only`,
|
|
4416
|
+
`/// (the one wasm gate is the authority and refuses regardless).`,
|
|
4417
|
+
`bool canEdit${cap(agg)}(List<Map<String, dynamic>>? relations, String principal) =>`,
|
|
4418
|
+
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
for (const [agg, rels] of [...viewRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
4422
|
+
lines.push(
|
|
4423
|
+
`/// UI HINT — may [principal] VIEW a ${agg}? Mirrors the kernel's ReBAC read gate; INFORMATIONAL only.`,
|
|
4424
|
+
`bool canView${cap(agg)}(List<Map<String, dynamic>>? relations, String principal) =>`,
|
|
4425
|
+
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
return lines.join("\n");
|
|
4429
|
+
}
|
|
@@ -33,7 +33,7 @@ import type { Field } from "./fields.js";
|
|
|
33
33
|
import type { Directive } from "./directive.js";
|
|
34
34
|
import type { QueryDecl } from "./query.js";
|
|
35
35
|
import type { DomainModule } from "./codegen_dart.js";
|
|
36
|
-
import { cap, readModelClassName } from "./codegen_dart.js";
|
|
36
|
+
import { cap, readModelClassName, dartRoleHelpers } from "./codegen_dart.js";
|
|
37
37
|
import { finishCount } from "./count.js";
|
|
38
38
|
import { finishSum } from "./sum.js";
|
|
39
39
|
import { autoStampFields, domainEncryptionMap, type EncDomainMap, type InterfaceAggShape } from "./codegen_ts.js";
|
|
@@ -149,6 +149,11 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
149
149
|
const aggById = new Map<string, AggregateHandle>();
|
|
150
150
|
for (const a of mod.aggregates) aggById.set(a.id, a);
|
|
151
151
|
|
|
152
|
+
// ROLE SUGAR — the `canEdit<Agg>`/`canView<Agg>` UI-hint helpers live HERE (the client package, which may
|
|
153
|
+
// import runtime), NOT in the pure types file: they `jsonDecode` the folded RelationTuple leaf, and the
|
|
154
|
+
// layered types-package purity gate forbids `dart:convert`. Empty ⇒ no roles (era-safe; no extra import).
|
|
155
|
+
const roleHelpers = dartRoleHelpers(mod);
|
|
156
|
+
|
|
152
157
|
// THE E2E-SEALING MAP (the sealing client) — non-null iff a field is `.encrypted()`. When null NOTHING
|
|
153
158
|
// below emits (the generated Dart client is byte-identical to a pre-sealing compile). Same map + sids as
|
|
154
159
|
// the TS client (one source of truth: `domainEncryptionMap`); requires the baked interface for the sids.
|
|
@@ -407,7 +412,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
407
412
|
``,
|
|
408
413
|
// `dart:convert` is needed ONLY for the interface block's `jsonDecode` — the offer lane no longer encodes
|
|
409
414
|
// any envelope (it routes through `bridge.offerDirective` → THE ONE ENCODER). Omit it when no interface.
|
|
410
|
-
...(opts.interface !== undefined ? [`import 'dart:convert';`, ``] : []),
|
|
415
|
+
...(opts.interface !== undefined || roleHelpers !== "" ? [`import 'dart:convert';`, ``] : []),
|
|
411
416
|
`import '${supportImport}';`,
|
|
412
417
|
// Layered-packages mode: the sibling types live in their OWN package — one barrel import
|
|
413
418
|
// replaces both the relative types import and the relative framework import.
|
|
@@ -531,6 +536,13 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
531
536
|
...methods,
|
|
532
537
|
`}`,
|
|
533
538
|
``,
|
|
539
|
+
...(roleHelpers !== ""
|
|
540
|
+
? [
|
|
541
|
+
`// ---- role policy (UI hints — the KERNEL GATE is the authority) ----`,
|
|
542
|
+
roleHelpers,
|
|
543
|
+
``,
|
|
544
|
+
]
|
|
545
|
+
: []),
|
|
534
546
|
].join("\n");
|
|
535
547
|
}
|
|
536
548
|
|
package/src/codegen_proof.ts
CHANGED
|
@@ -508,19 +508,25 @@ function relationSeedLegLines(
|
|
|
508
508
|
];
|
|
509
509
|
}
|
|
510
510
|
const ACTOR = `"user:proof-conformance"`;
|
|
511
|
+
const BARE_ACTOR = `"proof-conformance"`; // the uid without the `user:` prefix — for WRAPPED subject fields
|
|
511
512
|
const speculative = leg.seedSpeculative === true;
|
|
512
513
|
// A payload literal with subject/founder-conventional fields (+ the routed set) overwritten to the actor —
|
|
513
|
-
// so a seat lands ON the actor AND every write-once/author-binding/founder-binding invariant holds.
|
|
514
|
-
|
|
514
|
+
// so a seat lands ON the actor AND every write-once/author-binding/founder-binding invariant holds. A WRAPPED
|
|
515
|
+
// subject field (the plan lifts a bare uid into `user:${field}`) gets the BARE uid — else the re-wrap yields
|
|
516
|
+
// a double-prefixed `user:user:<uid>` the gate never stamped.
|
|
517
|
+
const actorSeatLit = (payload: Record<string, unknown>, routed: readonly string[], wrapped: readonly string[] = []): string => {
|
|
515
518
|
const overwrite = new Set<string>(routed);
|
|
519
|
+
const wrappedSet = new Set<string>(wrapped);
|
|
516
520
|
const entries = Object.entries(payload).map(([k, v]) =>
|
|
517
|
-
|
|
518
|
-
? `${JSON.stringify(k)}: ${
|
|
519
|
-
:
|
|
521
|
+
wrappedSet.has(k)
|
|
522
|
+
? `${JSON.stringify(k)}: ${BARE_ACTOR}`
|
|
523
|
+
: overwrite.has(k) || /^(subject|principal|grantedBy|grantee|member|user|owner|founder)$/i.test(k)
|
|
524
|
+
? `${JSON.stringify(k)}: ${ACTOR}`
|
|
525
|
+
: `${JSON.stringify(k)}: ${JSON.stringify(v)}`,
|
|
520
526
|
);
|
|
521
527
|
return `{ ${entries.join(", ")} }`;
|
|
522
528
|
};
|
|
523
|
-
const grantPayloadLit = actorSeatLit(leg.grantPayload, [...leg.grantSubjectFields, ...leg.grantGrantedByFields]);
|
|
529
|
+
const grantPayloadLit = actorSeatLit(leg.grantPayload, [...leg.grantSubjectFields, ...leg.grantGrantedByFields], leg.grantSubjectWrappedFields ?? []);
|
|
524
530
|
// The PREREQUISITE-SEED CHAIN dispatches (each as the actor = founder) BEFORE the grant.
|
|
525
531
|
const prereqLines = (leg.prerequisiteSeeds ?? []).flatMap((seed) => [
|
|
526
532
|
` // prerequisite: ${leg.domain}/${seed.directiveId} establishes the state the grant's invariant reads (as founder = ACTOR).`,
|
|
@@ -665,6 +671,10 @@ export interface RelationBootstrapLeg {
|
|
|
665
671
|
readonly grantSubjectFields: readonly string[];
|
|
666
672
|
/** Grant-payload field(s) routed into the tuple `grantedBy` — set to the proof actor (free attribution). */
|
|
667
673
|
readonly grantGrantedByFields: readonly string[];
|
|
674
|
+
/** Grant-payload field(s) the plan WRAPPED into a `<prefix>:<uid>` subject (`user:${principal}`) — the
|
|
675
|
+
* runner seats the BARE actor uid here (else the re-wrap double-prefixes to `user:user:<uid>` and the seat
|
|
676
|
+
* lands on nobody the gate stamped). The role-sugar + framework grant/seed shape. */
|
|
677
|
+
readonly grantSubjectWrappedFields?: readonly string[];
|
|
668
678
|
/** How the granter was resolved — `"marker"` (`.grants(R)` on R directly), `"inferred"` (a plan-probed
|
|
669
679
|
* `writeTuple(R)`), `"computed-base"` (R is computed; the grant seats a rewrite BASE relation the granter
|
|
670
680
|
* was found for), or `"seed-path"` (LAYER 3: R is reachable only through ttu/intersection/exclusion — no
|
|
@@ -887,7 +897,7 @@ function probeRelationsWritten(dir: Directive<unknown>, where: string): string[]
|
|
|
887
897
|
* values. Best-effort: an unmatched routing just leaves the field list empty (runtime falls back). */
|
|
888
898
|
function resolveGrantSubjectFields(
|
|
889
899
|
probed: { ops: FieldOp[]; synth: SynthesizedPayload },
|
|
890
|
-
): { subjectFields: string[]; grantedByFields: string[] } {
|
|
900
|
+
): { subjectFields: string[]; grantedByFields: string[]; wrappedSubjectFields: string[] } {
|
|
891
901
|
const { ops, synth } = probed;
|
|
892
902
|
const fieldForValue = (v: unknown): string | undefined =>
|
|
893
903
|
typeof v === "string" && v.length > 0
|
|
@@ -895,11 +905,23 @@ function resolveGrantSubjectFields(
|
|
|
895
905
|
: undefined;
|
|
896
906
|
const subjectFields = new Set<string>();
|
|
897
907
|
const grantedByFields = new Set<string>();
|
|
908
|
+
// WRAPPED subject fields: the plan lifted a BARE payload uid into a `<prefix>:<uid>` subject wire form
|
|
909
|
+
// (`writeTuple({ subject: `user:${p.principal}` })` — the role-sugar + framework grant shape). The runner
|
|
910
|
+
// must seat the BARE actor uid into these (not the full `user:` subject), else the plan re-wraps it into a
|
|
911
|
+
// double-prefixed `user:user:<uid>` that never matches the actor the gate stamped. Distinct from a DIRECT
|
|
912
|
+
// subject field (entryKey === the payload value verbatim — a full `user:<uid>` payload), which stays full.
|
|
913
|
+
const wrappedSubjectFields = new Set<string>();
|
|
898
914
|
for (const o of ops) {
|
|
899
915
|
// The subject rides the tuple map entry KEY (`setEntry(rt, "subjects", <subjectRaw>, leaf)`).
|
|
900
916
|
if (o.kind === "setEntry" && o.field === "subjects" && typeof o.entryKey === "string") {
|
|
901
917
|
const f = fieldForValue(o.entryKey);
|
|
902
918
|
if (f !== undefined) subjectFields.add(f);
|
|
919
|
+
else {
|
|
920
|
+
// entryKey `<prefix>:<uid>` (e.g. `user:sample-principal`) where <uid> is a payload value ⇒ wrapped.
|
|
921
|
+
const m = /^[a-z]+:(.+)$/.exec(o.entryKey);
|
|
922
|
+
const inner = m !== null ? fieldForValue(m[1]) : undefined;
|
|
923
|
+
if (inner !== undefined) wrappedSubjectFields.add(inner);
|
|
924
|
+
}
|
|
903
925
|
// grantedBy rides the JSON leaf value — scan the serialized leaf for any synthesized payload value.
|
|
904
926
|
if (typeof o.value === "string") {
|
|
905
927
|
for (const [name, sv] of Object.entries(synth.value)) {
|
|
@@ -910,7 +932,7 @@ function resolveGrantSubjectFields(
|
|
|
910
932
|
}
|
|
911
933
|
}
|
|
912
934
|
}
|
|
913
|
-
return { subjectFields: [...subjectFields], grantedByFields: [...grantedByFields] };
|
|
935
|
+
return { subjectFields: [...subjectFields], grantedByFields: [...grantedByFields], wrappedSubjectFields: [...wrappedSubjectFields] };
|
|
914
936
|
}
|
|
915
937
|
|
|
916
938
|
// ── rewrite-aware base-relation resolution (the COMPUTED-authority lane) ──────────────────────────────
|
|
@@ -1382,7 +1404,7 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1382
1404
|
}
|
|
1383
1405
|
const grantPayload: Record<string, unknown> = { ...grantSynth.value };
|
|
1384
1406
|
const probed = probePlanOps(granter, `${domain}/${granter.id}`);
|
|
1385
|
-
const routed = probed !== undefined ? resolveGrantSubjectFields(probed) : { subjectFields: [], grantedByFields: [] };
|
|
1407
|
+
const routed = probed !== undefined ? resolveGrantSubjectFields(probed) : { subjectFields: [], grantedByFields: [], wrappedSubjectFields: [] };
|
|
1386
1408
|
|
|
1387
1409
|
return {
|
|
1388
1410
|
domain,
|
|
@@ -1397,6 +1419,7 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1397
1419
|
grantAutoStamped: grantSynth.autoStamped,
|
|
1398
1420
|
grantSubjectFields: routed.subjectFields,
|
|
1399
1421
|
grantGrantedByFields: routed.grantedByFields,
|
|
1422
|
+
...(routed.wrappedSubjectFields.length > 0 ? { grantSubjectWrappedFields: routed.wrappedSubjectFields } : {}),
|
|
1400
1423
|
via,
|
|
1401
1424
|
...(seedSpeculative ? { seedSpeculative: true } : {}),
|
|
1402
1425
|
...(seedWritesRelations !== undefined ? { seedWritesRelations } : {}),
|
package/src/codegen_ts.ts
CHANGED
|
@@ -950,7 +950,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
950
950
|
` /** THE ONE ENCODER offer (the single v2-envelope codec lives in @githolon/client \`encodeIntentOfferBytes\`):`,
|
|
951
951
|
` * hand the holon a PLANNED directive + the verified \`actor\`; the holon encodes + offers. The generated`,
|
|
952
952
|
` * client calls THIS, never building offer bytes itself — so there is exactly ONE copy of the wire format. */`,
|
|
953
|
-
` offerDirective(domain: string, directiveId: string, payload: unknown, opts?: { domainHash?: string; actor?: string; returnCreated?: boolean }): Promise<string>;`,
|
|
953
|
+
` offerDirective(domain: string, directiveId: string, payload: unknown, opts?: { domainHash?: string; actor?: string; returnCreated?: boolean }): Promise<string | { head: string; created?: { type: string; id: string }[]; workspace?: string }>;`,
|
|
954
954
|
` /** Opaque-bytes offer — retained for carriers that already hold an encoded envelope (back-compat). */`,
|
|
955
955
|
` offer(intentBytes: string | Uint8Array): Promise<string>;`,
|
|
956
956
|
` /** The workspace this holon is bound to — the \`workspace\` of every [NomosRef] this client composes. */`,
|
|
@@ -865,11 +865,13 @@ function emitDart(
|
|
|
865
865
|
|
|
866
866
|
/** The pure-core support import generated TYPES files carry in layered-packages mode. */
|
|
867
867
|
const DART_TYPES_IMPORT = `import 'package:nomos_types/nomos_types.dart';`;
|
|
868
|
-
/** Version constraints the scaffolded pubspecs pin — move together with the Dart packages' releases.
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
//
|
|
872
|
-
const
|
|
868
|
+
/** Version constraints the scaffolded pubspecs pin — move together with the Dart packages' releases.
|
|
869
|
+
* Caret on 0.x is EXACT-MINOR (`^0.45.0` = `<0.46.0`), so these must track the CURRENT release, not
|
|
870
|
+
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
871
|
+
// 0.1.4: custody-lifecycle types (CustodyBreakPolicy et al).
|
|
872
|
+
const NOMOS_TYPES_DEP = "^0.1.4";
|
|
873
|
+
// 0.46.0: custody lifecycle on NomosBridge/NomosSession (onCustodyBreak/custodyStatus/salvage lanes).
|
|
874
|
+
const NOMOS_CLIENT_DEP = "^0.46.0";
|
|
873
875
|
|
|
874
876
|
const isPubPackageName = (s: string): boolean => /^[a-z][a-z0-9_]*$/.test(s);
|
|
875
877
|
|