@githolon/dsl 0.66.0 → 0.68.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.
@@ -56,6 +56,8 @@ import type { AggregateHandle } from "./aggregate.js";
56
56
  import {
57
57
  generateDartDomain,
58
58
  generateDartContracts,
59
+ generateDartActivityRegistry,
60
+ collectActivityDescriptors,
59
61
  type DartImport,
60
62
  type DartRefImport,
61
63
  type DomainModule,
@@ -428,6 +430,8 @@ const DART_SUPPORT_CORE_SRC = [
428
430
  // The foreign-ref locator runtime (ForeignRef<T>/NomosForeignReader/…, t.foreignRef) — a generated read
429
431
  // model with a `t.foreignRef` field references `ForeignRef<Target>` through the barrel, so it must vendor.
430
432
  "foreign_ref.dart",
433
+ "activity.dart",
434
+ "nomos_ref.dart",
431
435
  ] as const;
432
436
  // The RUNTIME support files, vendored from `nomos_client` (dsl/dart/lib/src): the async WebView
433
437
  // bridge the generated `<name>.client.dart` drives, and the provider surface. Their
@@ -450,6 +454,10 @@ const FRAMEWORK_FILE = "nomos_framework";
450
454
  /** The shared basename of the domain-contracts DTO file emitted once per package (domain_contracts.md). */
451
455
  const CONTRACTS_FILE = "contracts";
452
456
 
457
+ /** The shared basename of the ACTIVITY REGISTRY file emitted once per package (activity_descriptors.md).
458
+ * Absent when no directive declares `.activity(...)` — an activity-free package emits no `activity_registry.dart`. */
459
+ const ACTIVITY_FILE = "activity_registry";
460
+
453
461
  /** Collect every domain contract across the package's modules, deduped by id (later-wins). Sorted by id.
454
462
  * Empty when no domain declares a contract (contract-free packages emit no `contracts.dart`). */
455
463
  function collectPackageContracts(modules: readonly DomainModule[]): ContractHandle[] {
@@ -731,6 +739,14 @@ function emitDart(
731
739
  if (packageContracts.length > 0) {
732
740
  writeGenerated(CONTRACTS_FILE, generateDartContracts(packageContracts));
733
741
  }
742
+ // 2a-ter. the shared ACTIVITY REGISTRY (`activity_registry.dart`) — the pure `renderActivity`, ONCE. Emitted
743
+ // iff a directive opts in via `.activity(...)` (activity-free packages stay byte-identical). Pure
744
+ // (nomos_types), so the client folds typed activity rows from the intent log with no runtime import.
745
+ const packageActivity = collectActivityDescriptors(modules);
746
+ const activityImportUri = packageActivity.length > 0 ? `${ACTIVITY_FILE}.dart` : undefined;
747
+ if (packageActivity.length > 0) {
748
+ writeGenerated(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
749
+ }
734
750
  // CROSS-DOMAIN `t.foreignRef` (auto import). A foreignRef field typed `ForeignRef<CatalogueListing>` +
735
751
  // `CatalogueListing.fromJson` needs the TARGET aggregate's Dart class — which lives in the target DOMAIN's
736
752
  // generated file. Within ONE package the compiler knows which domain owns each aggregate, so we auto-emit
@@ -794,6 +810,7 @@ function emitDart(
794
810
  ...(compat !== undefined ? { compat } : {}),
795
811
  supportImport,
796
812
  ...(domainFrameworkImport !== undefined ? { frameworkImportUri: domainFrameworkImport } : {}),
813
+ ...(packageActivity.length > 0 ? { emitActivity: true, ...(activityImportUri !== undefined ? { activityImportUri } : {}) } : {}),
797
814
  });
798
815
  const cp = path.join(genDir, `${mod.name}.client.dart`);
799
816
  writeFileSync(cp, clientSrc, "utf8");
@@ -820,6 +837,7 @@ function emitDart(
820
837
  : `export 'package:nomos_client/nomos_client.dart';`,
821
838
  ...(hasFrameworkSymbols ? [`export '${genRel}/${FRAMEWORK_FILE}.dart';`] : []),
822
839
  ...(contractsImportUri !== undefined ? [`export '${genRel}/${CONTRACTS_FILE}.dart';`] : []),
840
+ ...(activityImportUri !== undefined ? [`export '${genRel}/${ACTIVITY_FILE}.dart';`] : []),
823
841
  ...modules.flatMap((m) => [
824
842
  `export '${genRel}/${m.name}.dart';`,
825
843
  `export '${genRel}/${m.name}.client.dart';`,
@@ -844,8 +862,10 @@ function emitDart(
844
862
  /** The pure-core support import generated TYPES files carry in layered-packages mode. */
845
863
  const DART_TYPES_IMPORT = `import 'package:nomos_types/nomos_types.dart';`;
846
864
  /** Version constraints the scaffolded pubspecs pin — move together with the Dart packages' releases. */
847
- const NOMOS_TYPES_DEP = "^0.1.0";
848
- const NOMOS_CLIENT_DEP = "^0.38.0";
865
+ // FLOOR = the release carrying every symbol generated code may reference (0.1.3: NomosRef).
866
+ const NOMOS_TYPES_DEP = "^0.1.3";
867
+ // FLOOR = the release carrying the bridge methods generated clients call (0.45.0: offerOutcome/NomosCreated.ref).
868
+ const NOMOS_CLIENT_DEP = "^0.45.0";
849
869
 
850
870
  const isPubPackageName = (s: string): boolean => /^[a-z][a-z0-9_]*$/.test(s);
851
871
 
@@ -960,7 +980,7 @@ function emitDartPackages(
960
980
  // field as the target aggregate's class, which lives in the target domain's file). A sibling types file is
961
981
  // still PURE (no runtime/bridge surface — the purity gate exists to keep nomos_client out, not to isolate
962
982
  // domain files from each other; Dart allows type-import cycles for mutual refs).
963
- const siblingTypeFiles = new Set<string>([...modules.map((m) => `${m.name}.dart`), `${FRAMEWORK_FILE}.dart`, `${CONTRACTS_FILE}.dart`]);
983
+ const siblingTypeFiles = new Set<string>([...modules.map((m) => `${m.name}.dart`), `${FRAMEWORK_FILE}.dart`, `${CONTRACTS_FILE}.dart`, `${ACTIVITY_FILE}.dart`]);
964
984
  const writeTypes = (fileBase: string, src: string): void => {
965
985
  if (!src.includes(DART_PACKAGE_IMPORT)) {
966
986
  fail(`generated Dart for '${fileBase}' does not carry the expected support import — codegen_dart header drifted`);
@@ -994,6 +1014,10 @@ function emitDartPackages(
994
1014
  const packageContracts = collectPackageContracts(modules);
995
1015
  const contractsImportUri = packageContracts.length > 0 ? `${CONTRACTS_FILE}.dart` : undefined;
996
1016
  if (packageContracts.length > 0) writeTypes(CONTRACTS_FILE, generateDartContracts(packageContracts));
1017
+ // ACTIVITY REGISTRY (layered): the pure `renderActivity` in the TYPES package (nomos_types only). The
1018
+ // client resolves it through the types-package barrel (which re-exports every types file). Opt-in only.
1019
+ const packageActivity = collectActivityDescriptors(modules);
1020
+ if (packageActivity.length > 0) writeTypes(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
997
1021
  // CROSS-DOMAIN foreignRef (layered): map aggregate id → its domain's type file, so a foreignRef field's
998
1022
  // target class resolves via a sibling import (the purity gate now permits sibling type files).
999
1023
  const aggIdToTypeFile = new Map<string, string>();
@@ -1085,6 +1109,7 @@ function emitDartPackages(
1085
1109
  ...(compat !== undefined ? { compat } : {}),
1086
1110
  supportImport: "package",
1087
1111
  typesImportUri,
1112
+ ...(packageActivity.length > 0 ? { emitActivity: true } : {}),
1088
1113
  });
1089
1114
  const cp = path.join(clientGen, `${mod.name}.client.dart`);
1090
1115
  writeFileSync(cp, clientSrc, "utf8");
package/src/directive.ts CHANGED
@@ -23,6 +23,7 @@ import type { Ports } from "./ctx.js";
23
23
  import type { CertifiedReadDecl } from "./certified_read.js";
24
24
  import type { RelationDecl } from "./relation.js";
25
25
  import type { QueryDecl } from "./query.js";
26
+ import { normalizeActivity, type ActivityInput, type ActivityDescriptor } from "./activity.js";
26
27
  import { BIRTH_METER_QUERIES } from "./birth_meter.js";
27
28
  import {
28
29
  isFromPayloadRef,
@@ -115,6 +116,15 @@ export interface Directive<P = unknown> {
115
116
  * byte-identical in the canonical manifest to before `description` existed).
116
117
  */
117
118
  readonly description?: string;
119
+ /**
120
+ * OPT-IN user-facing ACTIVITY metadata (`architecture/activity_descriptors.md`) — the kind/title/message
121
+ * template, refs, params, category/icon/severity a client uses to render a typed activity row from this
122
+ * directive's intents (no string-enricher). Set by `.activity({...})`; NON-LAW, additive (same class as
123
+ * `description` — a directive that never declares activity is byte-identical). Normalized + validated at
124
+ * author time. `undefined` ⇒ this directive contributes no activity row. (The setter is `.activity({...})`;
125
+ * this stored field is `activityDescriptor` to avoid a method/field name clash on `RequirableDirective`.)
126
+ */
127
+ readonly activityDescriptor?: ActivityDescriptor;
118
128
  /**
119
129
  * NATIVE ZANZIBAR write-authz (P4): the RELATION the captured author must HOLD to author this
120
130
  * directive, and the OBJECT it is checked on. The ONE gate runs `engine.check(schema, object,
@@ -414,6 +424,14 @@ export interface RequirableDirective<P> extends Directive<P> {
414
424
  * calls this declares no prose (codegen falls back to a default derived from the id).
415
425
  */
416
426
  doc(description: string): RequirableDirective<P>;
427
+ /**
428
+ * Declare OPT-IN user-facing ACTIVITY metadata (`architecture/activity_descriptors.md`) — a stable `kind`,
429
+ * a `{actor}`+params message template, navigation `refs`, display `params` (payload field paths), and
430
+ * category/icon/severity. Codegen emits a typed activity descriptor + `renderActivity` so a client folds
431
+ * typed activity rows from the intent log (no string-enricher). Validated at author time (unknown template
432
+ * var / missing payload field refuse). Non-law, additive; a directive that never calls it is byte-identical.
433
+ */
434
+ activity(input: ActivityInput): RequirableDirective<P>;
417
435
  /**
418
436
  * Declare the directive's CertifiedReads (survival Constraint 5; `certified_read.md`
419
437
  * §"Tenant authoring") — the profiled, named write-path reads its `decide()` consults, the
@@ -810,6 +828,16 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
810
828
  doc(description: string): RequirableDirective<P> {
811
829
  return makeRequirable({ ...base, description });
812
830
  },
831
+ activity(input: ActivityInput): RequirableDirective<P> {
832
+ // Normalize + validate against this directive's payload (unknown template var / missing payload field
833
+ // refuse here). "@created" refs are checked against the aggregates this directive CREATES (the primary
834
+ // .creates target + any .alsoCreates). Non-law, additive — stored like `description`.
835
+ const createdIds = [
836
+ ...(base.marker === "creates" ? [base.aggregateId] : []),
837
+ ...(base.secondaryOutputs ?? []).filter((o) => o.marker === "creates").map((o) => o.aggregateId),
838
+ ];
839
+ return makeRequirable({ ...base, activityDescriptor: normalizeActivity(base.id, input, base.payloadSchema, createdIds) });
840
+ },
813
841
  readsCertified(reads: Record<string, CertifiedReadDecl>): RequirableDirective<P> {
814
842
  // Accumulate onto any prior declared CertifiedReads (a repeated local name's later decl
815
843
  // wins) — additive, non-destructive. The local binding NAME (the key) is what `decide`
package/src/index.ts CHANGED
@@ -136,6 +136,7 @@ export {
136
136
  // surface). The dev never sees a "prim" — that is the GitHolon's internal OpenUSD IR.
137
137
  export { create, existing, ensure, type AggregateRef } from "./authoring.js";
138
138
  export { contract, isContractHandle, satisfiesContract, type ContractHandle, type ContractField, type ContractSatisfaction } from "./contract.js";
139
+ export { normalizeActivity, renderActivityTemplate, activityKey, type ActivityInput, type ActivityDescriptor, type ActivityRefInput, type ActivitySeverity } from "./activity.js";
139
140
  // THE CAPTURED READ — a plan reads O(1) DSL queries via `read(query, args)`; the result is committed as
140
141
  // the intent's read footprint (deterministic replay + stale-premise detection). The dev calls typed DSL
141
142
  // queries; the library routes them here. The host capability is `nomos.read` (twin of `nomos.rng`).