@githolon/dsl 0.105.9 → 0.105.12
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/dart/CHANGELOG.md +4 -0
- package/dart/lib/src/runner/node-entry.mjs +11 -20
- package/dart/lib/src/runner/offline-install.mjs +1 -1
- package/dart/lib/src/webview_bridge.dart +1 -1
- package/dart/pubspec.yaml +1 -1
- package/dart/test/node_runner_transport_test.dart +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +10 -19
- package/src/capability_exports.ts +30 -5
- package/src/codegen_proof.ts +3 -3
- package/src/codegen_provider_dart.ts +10 -7
- package/src/compile_package_main.ts +2 -2
- package/src/framework/capability.ts +71 -3
- package/src/framework/impure_capability.ts +69 -39
- package/src/framework/owned_custody.ts +1 -0
- package/src/intent.ts +12 -0
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 0.64.26
|
|
2
|
+
|
|
3
|
+
- Restaged with the current kernel candidate: regenerated vendored assets (kernel release identity is baked into them). Published pub.dev archives are immutable, so regenerated bytes ride their own version.
|
|
4
|
+
|
|
1
5
|
## 0.64.25
|
|
2
6
|
|
|
3
7
|
- Rebuild the vendored Dart runner from `@githolon/client` 0.105.8. Web-client source is an input to
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
4
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
4
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "7602d5c014cbf79a1069ff0f542b7b5dd3dde6d4920f44e56710bbe23ee3257d", version: "kernel-7602d5c0", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 }, testMint: { hash: "6e5e6d2f655c0a951e8a04a2a4334131cc7237aa3ff8605d575c50598c2ee803", version: "kernel-6e5e6d2f", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 } };
|
|
5
5
|
|
|
6
6
|
// runner/node-entry.ts
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
@@ -11103,7 +11103,7 @@ ${String(commit).toLowerCase()}`;
|
|
|
11103
11103
|
}));
|
|
11104
11104
|
await offerDirective(domain, OWNED_RECOVERY_COMPLETE_DIRECTIVE, {
|
|
11105
11105
|
requestId,
|
|
11106
|
-
|
|
11106
|
+
ownedCustody: String(prepared.ownedCustody || ""),
|
|
11107
11107
|
completedAt: Date.now()
|
|
11108
11108
|
}, { domainHash, actor: connectionPrincipal });
|
|
11109
11109
|
}
|
|
@@ -11126,21 +11126,17 @@ ${String(commit).toLowerCase()}`;
|
|
|
11126
11126
|
let orders = await ownedRecoveryOrdersRaw(recoveryPrincipal);
|
|
11127
11127
|
let order = orders.find((row) => row.ownerPrincipal === connectionPrincipal && row.deviceRequest === deviceRequest);
|
|
11128
11128
|
if (!order) {
|
|
11129
|
-
const
|
|
11130
|
-
queryBytes: qBytes({ op: "mint", typeTag: "NomosOwnedRecoveryOrder", nowMillis: Date.now() })
|
|
11131
|
-
}));
|
|
11132
|
-
const requestId2 = String(minted.id || "");
|
|
11133
|
-
const now = Date.now();
|
|
11134
|
-
await offerDirective(domain, OWNED_RECOVERY_REQUEST_DIRECTIVE, {
|
|
11135
|
-
requestId: requestId2,
|
|
11136
|
-
requestedBy: connectionPrincipal,
|
|
11137
|
-
requestedAt: now,
|
|
11138
|
-
scheduledAt: now,
|
|
11139
|
-
deadlineAt: now + 30 * 24 * 60 * 60 * 1e3,
|
|
11129
|
+
const accepted = await offerDirective(domain, OWNED_RECOVERY_REQUEST_DIRECTIVE, {
|
|
11140
11130
|
ownerPrincipal: connectionPrincipal,
|
|
11141
11131
|
recoveryPrincipal,
|
|
11142
11132
|
deviceRequest
|
|
11143
|
-
}, { domainHash, actor: connectionPrincipal });
|
|
11133
|
+
}, { domainHash, actor: connectionPrincipal, returnCreated: true });
|
|
11134
|
+
const requestId2 = accepted.created?.find(
|
|
11135
|
+
(ref) => ref.type === "NomosOwnedRecoveryOrder"
|
|
11136
|
+
)?.id ?? "";
|
|
11137
|
+
if (!requestId2) {
|
|
11138
|
+
throw new Error("owned recovery request was accepted without its created obligation reference");
|
|
11139
|
+
}
|
|
11144
11140
|
orders = await ownedRecoveryOrdersRaw(recoveryPrincipal);
|
|
11145
11141
|
order = orders.find((row) => row.requestId === requestId2) ?? {
|
|
11146
11142
|
id: requestId2,
|
|
@@ -11153,12 +11149,7 @@ ${String(commit).toLowerCase()}`;
|
|
|
11153
11149
|
}
|
|
11154
11150
|
const requestId = String(order.requestId || order.id || "");
|
|
11155
11151
|
if (order.status === "completed") {
|
|
11156
|
-
|
|
11157
|
-
try {
|
|
11158
|
-
receipt = JSON.parse(String(order.resultRef || "{}"));
|
|
11159
|
-
} catch {
|
|
11160
|
-
}
|
|
11161
|
-
const ownedCustody = String(receipt.ownedCustody || "");
|
|
11152
|
+
const ownedCustody = String(order.ownedCustody || "");
|
|
11162
11153
|
if (!ownedCustody) {
|
|
11163
11154
|
throw new OwnedRecoveryPendingError("Recovery is still being prepared; Nomos will continue safely.", {
|
|
11164
11155
|
workspace,
|
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
9
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
9
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "7602d5c014cbf79a1069ff0f542b7b5dd3dde6d4920f44e56710bbe23ee3257d", version: "kernel-7602d5c0", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 }, testMint: { hash: "6e5e6d2f655c0a951e8a04a2a4334131cc7237aa3ff8605d575c50598c2ee803", version: "kernel-6e5e6d2f", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 } };
|
|
10
10
|
|
|
11
11
|
// runner/offline-install.ts
|
|
12
12
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "node:fs";
|
|
@@ -1153,7 +1153,7 @@ extension NomosRefAcquire<TAggregate> on NomosRef<TAggregate> {
|
|
|
1153
1153
|
/// callback, invoked ONLY when the workspace is nowhere on the local plane (never for a local-only or
|
|
1154
1154
|
/// already-mounted workspace). Mirrors the cloud-shaped subset of [NomosBridge.connect]'s parameters.
|
|
1155
1155
|
class NomosSessionCredentials {
|
|
1156
|
-
/// The cloud base URL the workspace syncs from (e.g. `https://nomos.
|
|
1156
|
+
/// The cloud base URL the workspace syncs from (e.g. `https://nomos.cafe`).
|
|
1157
1157
|
final String cloud;
|
|
1158
1158
|
|
|
1159
1159
|
/// The verified principal's access token (`x-nomos-auth`), when the workspace requires one.
|
package/dart/pubspec.yaml
CHANGED
|
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
|
|
|
7
7
|
description: >-
|
|
8
8
|
Nomos 2 Dart client — generated domain values, intent payloads and application
|
|
9
9
|
lifecycle types over the typed Nomos kernel protocol and USDA custody model.
|
|
10
|
-
version: 0.64.
|
|
10
|
+
version: 0.64.26
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
|
@@ -66,7 +66,7 @@ void main() {
|
|
|
66
66
|
() async {
|
|
67
67
|
final ws = Platform.environment['NOMOS_HEADLESS_E2E']!;
|
|
68
68
|
final cloud = Platform.environment['NOMOS_CLOUD'] ??
|
|
69
|
-
'https://nomos.
|
|
69
|
+
'https://nomos.cafe';
|
|
70
70
|
await bridge.ready.timeout(const Duration(seconds: 15));
|
|
71
71
|
await bridge.connect(
|
|
72
72
|
cloud: cloud, workspace: ws, clientId: 'headless-test');
|
package/package.json
CHANGED
|
@@ -10134,7 +10134,7 @@ ${String(commit).toLowerCase()}`;
|
|
|
10134
10134
|
}));
|
|
10135
10135
|
await offerDirective(domain, OWNED_RECOVERY_COMPLETE_DIRECTIVE, {
|
|
10136
10136
|
requestId,
|
|
10137
|
-
|
|
10137
|
+
ownedCustody: String(prepared.ownedCustody || ""),
|
|
10138
10138
|
completedAt: Date.now()
|
|
10139
10139
|
}, { domainHash, actor: connectionPrincipal });
|
|
10140
10140
|
}
|
|
@@ -10157,21 +10157,17 @@ ${String(commit).toLowerCase()}`;
|
|
|
10157
10157
|
let orders = await ownedRecoveryOrdersRaw(recoveryPrincipal);
|
|
10158
10158
|
let order = orders.find((row) => row.ownerPrincipal === connectionPrincipal && row.deviceRequest === deviceRequest);
|
|
10159
10159
|
if (!order) {
|
|
10160
|
-
const
|
|
10161
|
-
queryBytes: qBytes({ op: "mint", typeTag: "NomosOwnedRecoveryOrder", nowMillis: Date.now() })
|
|
10162
|
-
}));
|
|
10163
|
-
const requestId2 = String(minted.id || "");
|
|
10164
|
-
const now = Date.now();
|
|
10165
|
-
await offerDirective(domain, OWNED_RECOVERY_REQUEST_DIRECTIVE, {
|
|
10166
|
-
requestId: requestId2,
|
|
10167
|
-
requestedBy: connectionPrincipal,
|
|
10168
|
-
requestedAt: now,
|
|
10169
|
-
scheduledAt: now,
|
|
10170
|
-
deadlineAt: now + 30 * 24 * 60 * 60 * 1e3,
|
|
10160
|
+
const accepted = await offerDirective(domain, OWNED_RECOVERY_REQUEST_DIRECTIVE, {
|
|
10171
10161
|
ownerPrincipal: connectionPrincipal,
|
|
10172
10162
|
recoveryPrincipal,
|
|
10173
10163
|
deviceRequest
|
|
10174
|
-
}, { domainHash, actor: connectionPrincipal });
|
|
10164
|
+
}, { domainHash, actor: connectionPrincipal, returnCreated: true });
|
|
10165
|
+
const requestId2 = accepted.created?.find(
|
|
10166
|
+
(ref) => ref.type === "NomosOwnedRecoveryOrder"
|
|
10167
|
+
)?.id ?? "";
|
|
10168
|
+
if (!requestId2) {
|
|
10169
|
+
throw new Error("owned recovery request was accepted without its created obligation reference");
|
|
10170
|
+
}
|
|
10175
10171
|
orders = await ownedRecoveryOrdersRaw(recoveryPrincipal);
|
|
10176
10172
|
order = orders.find((row) => row.requestId === requestId2) ?? {
|
|
10177
10173
|
id: requestId2,
|
|
@@ -10184,12 +10180,7 @@ ${String(commit).toLowerCase()}`;
|
|
|
10184
10180
|
}
|
|
10185
10181
|
const requestId = String(order.requestId || order.id || "");
|
|
10186
10182
|
if (order.status === "completed") {
|
|
10187
|
-
|
|
10188
|
-
try {
|
|
10189
|
-
receipt = JSON.parse(String(order.resultRef || "{}"));
|
|
10190
|
-
} catch {
|
|
10191
|
-
}
|
|
10192
|
-
const ownedCustody = String(receipt.ownedCustody || "");
|
|
10183
|
+
const ownedCustody = String(order.ownedCustody || "");
|
|
10193
10184
|
if (!ownedCustody) {
|
|
10194
10185
|
throw new OwnedRecoveryPendingError("Recovery is still being prepared; Nomos will continue safely.", {
|
|
10195
10186
|
workspace,
|
|
@@ -34,6 +34,26 @@ export function isCapabilityDecl(v: unknown): v is Record<string, unknown> {
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Compiler-only link carried by every lifecycle directive produced by `capability()`.
|
|
39
|
+
* A public business intent may wrap or compose that directive; following this typed link
|
|
40
|
+
* lets every compilation plane retain the declaration without a magic second re-export.
|
|
41
|
+
* A symbol keeps the link out of canonical law/property walks while object spread preserves
|
|
42
|
+
* it through the immutable directive and intent builders.
|
|
43
|
+
*/
|
|
44
|
+
export const NOMOS_CAPABILITY_DECLARATIONS = Symbol.for(
|
|
45
|
+
"nomos.capabilityDeclarations",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/** Capability declarations directly exported or carried by a wrapped/composed directive. */
|
|
49
|
+
export function capabilityDeclarationsOf(v: unknown): Record<string, unknown>[] {
|
|
50
|
+
if (isCapabilityDecl(v)) return [v];
|
|
51
|
+
if (typeof v !== "object" || v === null) return [];
|
|
52
|
+
const attached = (v as Record<PropertyKey, unknown>)[NOMOS_CAPABILITY_DECLARATIONS];
|
|
53
|
+
if (!Array.isArray(attached)) return [];
|
|
54
|
+
return attached.filter(isCapabilityDecl);
|
|
55
|
+
}
|
|
56
|
+
|
|
37
57
|
// `relations` carries the receipt fence's relation SCHEMA. It MUST expand with the bundle:
|
|
38
58
|
// `.requires(...)` is inert in a law that declares no relation schema, so omitting it would
|
|
39
59
|
// leave every capability looking fenced while enforcing nothing.
|
|
@@ -47,11 +67,16 @@ const PIECES = ["aggregate", "obligation", "complete", "fail", "block", "deadLet
|
|
|
47
67
|
export function expandCapabilityExports<M extends Record<string, unknown>>(mod: M): M {
|
|
48
68
|
let out: Record<string, unknown> | null = null;
|
|
49
69
|
for (const [k, v] of Object.entries(mod)) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
const capabilities = capabilityDeclarationsOf(v);
|
|
71
|
+
for (let index = 0; index < capabilities.length; index += 1) {
|
|
72
|
+
const capability = capabilities[index]!;
|
|
73
|
+
out ??= { ...mod };
|
|
74
|
+
const stem = capabilities.length === 1 ? k : `${k}$capability${index}`;
|
|
75
|
+
if (capability !== v) out[`${stem}$declaration`] = capability;
|
|
76
|
+
for (const piece of PIECES) {
|
|
77
|
+
const member = capability[piece];
|
|
78
|
+
if (member !== undefined) out[`${stem}$${piece}`] = member;
|
|
79
|
+
}
|
|
55
80
|
}
|
|
56
81
|
}
|
|
57
82
|
return (out ?? mod) as M;
|
package/src/codegen_proof.ts
CHANGED
|
@@ -3326,13 +3326,13 @@ function generateSealedApplicationProof(
|
|
|
3326
3326
|
`const proofPrincipal = proofUid ? "user:" + proofUid : "";`,
|
|
3327
3327
|
`const signIn = async (request: { readonly deviceChallenge?: string } = {}) => {`,
|
|
3328
3328
|
` const session = process.env.NOMOS_AUTH_TOKEN || "";`,
|
|
3329
|
-
` if (!request.deviceChallenge) return session;`,
|
|
3330
3329
|
` if (!proofUid) fail("the generated blank-user proof has no authority subject");`,
|
|
3331
|
-
` const endpoint = process.env.NOMOS_DEVICE_TOKEN_ENDPOINT || "https://id.nomos.
|
|
3330
|
+
` const endpoint = process.env.NOMOS_DEVICE_TOKEN_ENDPOINT || "https://id.nomos.cafe/token";`,
|
|
3332
3331
|
` const localTestIdp = endpoint.endsWith("/dev/mint-token");`,
|
|
3332
|
+
` const nonce = request.deviceChallenge || "0".repeat(64);`,
|
|
3333
3333
|
` const response = await fetch(endpoint, {`,
|
|
3334
3334
|
` method: "POST", headers: { "content-type": "application/json", ...(session ? { authorization: "Bearer " + session } : {}) },`,
|
|
3335
|
-
` body: JSON.stringify(localTestIdp ? { sub: proofUid, nonce
|
|
3335
|
+
` body: JSON.stringify(localTestIdp ? { sub: proofUid, nonce } : { nonce }),`,
|
|
3336
3336
|
` });`,
|
|
3337
3337
|
` const body = await response.json() as { token?: string; error?: string };`,
|
|
3338
3338
|
` if (!response.ok || !body.token) fail("the generated application could not attest its private device: " + (body.error || response.status));`,
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
* daemon / e2e.
|
|
26
26
|
*
|
|
27
27
|
* ── What it emits, per `impureCapability` capability (the STANDARD shape, never invented) ──
|
|
28
|
-
* 1. a typed `XObligation` — the
|
|
29
|
-
* `requestedBy` / `requestedAt` ⊕
|
|
30
|
-
*
|
|
28
|
+
* 1. a typed `XObligation` — the accepted task projection (kernel envelope provenance
|
|
29
|
+
* `requestId` / `requestedBy` / `requestedAt` ⊕ tenant params), with a `fromJson`
|
|
30
|
+
* decoding the accepted Obligation intent. This is intentionally richer than the
|
|
31
|
+
* peer's public request payload, which contains business params only;
|
|
31
32
|
* 2. a typed `XResult` — the success payload the handler returns: the capability's DECLARED
|
|
32
33
|
* receipt fields as TYPED members (they travel to the Receipt as typed USDA values, the
|
|
33
34
|
* mirror of the Obligation's request params), plus an optional `resultRef` naming a
|
|
@@ -76,8 +77,10 @@ export interface ImpureCapabilityDecl {
|
|
|
76
77
|
readonly tasks?: { readonly id: string };
|
|
77
78
|
/** The task aggregate handle; `.id` is the wire type (e.g. `TranscribeVoiceNoteAggregate`). */
|
|
78
79
|
readonly aggregate: { readonly id: string };
|
|
79
|
-
/** The Obligation directive: `.creates`, payload =
|
|
80
|
+
/** The public Obligation directive: `.creates`, payload = tenant business params only. */
|
|
80
81
|
readonly obligation: { readonly id: string; readonly payloadSchema: z.ZodTypeAny };
|
|
82
|
+
/** Accepted task view delivered to providers: kernel envelope ⊕ tenant params. */
|
|
83
|
+
readonly providerPayloadSchema: z.ZodTypeAny;
|
|
81
84
|
/**
|
|
82
85
|
* The COMPLETE (success) Receipt directive: payload = the complete envelope
|
|
83
86
|
* `{requestId, completedAt}` ⊕ the capability's DECLARED receipt fields (typed USDA values,
|
|
@@ -261,8 +264,8 @@ function emitObligationClass(
|
|
|
261
264
|
` });`,
|
|
262
265
|
``,
|
|
263
266
|
` /// Decode an Obligation from the already-\`jsonDecode\`d payload map (the EXACT shape the`,
|
|
264
|
-
` ///
|
|
265
|
-
` ///
|
|
267
|
+
` /// accepted task projection). The public request payload intentionally omits the`,
|
|
268
|
+
` /// framework-owned envelope; types stop here, at the provider serde boundary.`,
|
|
266
269
|
` factory ${cls}.fromJson(Map<String, Object?> j) => ${cls}(`,
|
|
267
270
|
fromJsonAssigns,
|
|
268
271
|
` );`,
|
|
@@ -439,7 +442,7 @@ export function emitProviderTask(
|
|
|
439
442
|
enumDartTypes: Map<string, string>,
|
|
440
443
|
): string {
|
|
441
444
|
const base = taskBaseName(task.obligation.id);
|
|
442
|
-
const specs = introspectPayload(task.
|
|
445
|
+
const specs = introspectPayload(task.providerPayloadSchema, enumDartTypes);
|
|
443
446
|
// The declared receipt fields = the complete payload minus the framework envelope. Typed
|
|
444
447
|
// here so the generated result class states them individually instead of a stringly bag.
|
|
445
448
|
const receiptSpecs = task.complete.payloadSchema
|
|
@@ -1660,8 +1660,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
|
|
|
1660
1660
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1661
1661
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1662
1662
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1663
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1664
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1663
|
+
const NOMOS_CLIENT_DEP = "^0.64.26";
|
|
1664
|
+
const NOMOS_FLUTTER_DEP = "^0.63.50";
|
|
1665
1665
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1666
1666
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1667
1667
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -67,6 +67,8 @@ import {
|
|
|
67
67
|
import { key, relationRef, relationType, self, user } from "../relation_schema.js";
|
|
68
68
|
import { directive } from "../directive.js";
|
|
69
69
|
import { RelationTuple, removeTuple, writeTuple } from "./relations.js";
|
|
70
|
+
import { idOf } from "../value_object.js";
|
|
71
|
+
import { NOMOS_CAPABILITY_DECLARATIONS } from "../capability_exports.js";
|
|
70
72
|
|
|
71
73
|
/** The shipped CapabilityBinding name grammar (worker + governance law agree). */
|
|
72
74
|
export const CAPABILITY_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
@@ -111,6 +113,24 @@ export function capabilityPascal(name: string): string {
|
|
|
111
113
|
/** The zod RAW SHAPE derived from request fields (each Field carries its own zod). */
|
|
112
114
|
type RequestShape<Req extends Record<string, Field>> = { [K in keyof Req]: Req[K]["zod"] };
|
|
113
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Turn a stored field declaration into the equivalent public payload schema without
|
|
118
|
+
* throwing away its nominal identity. A plain `Field.zod` is sufficient for scalar and
|
|
119
|
+
* structured values; an id-stored reference needs `idOf(Target)` so generated callers and
|
|
120
|
+
* the aggregate read model name the same type. Optionality belongs to the field declaration
|
|
121
|
+
* and is preserved here as well.
|
|
122
|
+
*/
|
|
123
|
+
function capabilityPayloadFieldSchema(field: Field): z.ZodTypeAny {
|
|
124
|
+
const target = field.kind === "ref"
|
|
125
|
+
&& field.nomosRef === undefined
|
|
126
|
+
&& field.versionedForeignRef === undefined
|
|
127
|
+
? field.refAggregateId
|
|
128
|
+
: undefined;
|
|
129
|
+
if (target === undefined) return field.zod;
|
|
130
|
+
const schema = idOf(target);
|
|
131
|
+
return field.isOptional ? schema.optional() : schema;
|
|
132
|
+
}
|
|
133
|
+
|
|
114
134
|
/**
|
|
115
135
|
* What `capability()` returns: the full shipped quartet (so every existing
|
|
116
136
|
* consumer — codegen, lowering, the reactor helpers — works unchanged) plus the
|
|
@@ -184,6 +204,17 @@ export function capability<
|
|
|
184
204
|
receipt?: Rec;
|
|
185
205
|
endpoint?: string;
|
|
186
206
|
declassify?: string;
|
|
207
|
+
/** Authorization required to request the capability. Declare it here so the
|
|
208
|
+
* capability's one obligation object remains identical everywhere it is composed. */
|
|
209
|
+
requestRequires?: {
|
|
210
|
+
relation: string;
|
|
211
|
+
object?: string;
|
|
212
|
+
objectFrom?: { recordType: string; idField: string };
|
|
213
|
+
};
|
|
214
|
+
/** Delay before a provider should attempt accepted work. */
|
|
215
|
+
scheduleAfterMs?: number;
|
|
216
|
+
/** Time from acceptance until the obligation is overdue. Defaults to 24h. */
|
|
217
|
+
deadlineAfterMs?: number;
|
|
187
218
|
/**
|
|
188
219
|
* THE ACKNOWLEDGED WAIVER — open this capability's Receipts to any authorised author.
|
|
189
220
|
*
|
|
@@ -235,10 +266,10 @@ export function capability<
|
|
|
235
266
|
const taskAggregate = impureCapabilityAggregate(aggregateId, params);
|
|
236
267
|
|
|
237
268
|
const requestShape = Object.fromEntries(
|
|
238
|
-
Object.entries(spec.request).map(([k, f]) => [k, f
|
|
269
|
+
Object.entries(spec.request).map(([k, f]) => [k, capabilityPayloadFieldSchema(f)]),
|
|
239
270
|
) as RequestShape<Req>;
|
|
240
271
|
const receiptShape = Object.fromEntries(
|
|
241
|
-
Object.entries(receipt).map(([k, f]) => [k, f
|
|
272
|
+
Object.entries(receipt).map(([k, f]) => [k, capabilityPayloadFieldSchema(f)]),
|
|
242
273
|
) as { [K in keyof Rec]: Rec[K]["zod"] };
|
|
243
274
|
const receiptEntries = Object.entries(receipt) as [string, Field][];
|
|
244
275
|
|
|
@@ -271,6 +302,12 @@ export function capability<
|
|
|
271
302
|
}
|
|
272
303
|
: {}),
|
|
273
304
|
capability: name,
|
|
305
|
+
...(spec.scheduleAfterMs !== undefined
|
|
306
|
+
? { scheduleAfterMs: spec.scheduleAfterMs }
|
|
307
|
+
: {}),
|
|
308
|
+
...(spec.deadlineAfterMs !== undefined
|
|
309
|
+
? { deadlineAfterMs: spec.deadlineAfterMs }
|
|
310
|
+
: {}),
|
|
274
311
|
});
|
|
275
312
|
|
|
276
313
|
const camel = pascal[0]!.toLowerCase() + pascal.slice(1);
|
|
@@ -328,8 +365,19 @@ export function capability<
|
|
|
328
365
|
object: capabilityProviderObject(name),
|
|
329
366
|
}) as D);
|
|
330
367
|
|
|
331
|
-
|
|
368
|
+
const obligation = spec.requestRequires === undefined
|
|
369
|
+
? quartet.obligation
|
|
370
|
+
: quartet.obligation.requires(spec.requestRequires.relation, {
|
|
371
|
+
...(spec.requestRequires.object !== undefined
|
|
372
|
+
? { object: spec.requestRequires.object }
|
|
373
|
+
: {}),
|
|
374
|
+
...(spec.requestRequires.objectFrom !== undefined
|
|
375
|
+
? { objectFrom: spec.requestRequires.objectFrom }
|
|
376
|
+
: {}),
|
|
377
|
+
});
|
|
378
|
+
const declaration = {
|
|
332
379
|
...quartet,
|
|
380
|
+
obligation,
|
|
333
381
|
complete: fenced(quartet.complete),
|
|
334
382
|
fail: fenced(quartet.fail),
|
|
335
383
|
block: fenced(quartet.block),
|
|
@@ -353,4 +401,24 @@ export function capability<
|
|
|
353
401
|
// discovers this bundle's aggregate + quartet + scan query without re-exports.
|
|
354
402
|
__isCapabilityDecl: true,
|
|
355
403
|
} as CapabilityDecl<Req, Rec>;
|
|
404
|
+
|
|
405
|
+
// Carry the declaration through any ordinary `intent().performs(...)` or compound
|
|
406
|
+
// `.directs(...).atomically()` wrapper. This is compiler metadata, not law and not a
|
|
407
|
+
// second runtime path: it merely ensures the one declared capability cannot be lost
|
|
408
|
+
// because the tenant sensibly split it into a small file.
|
|
409
|
+
for (const lifecycle of [
|
|
410
|
+
declaration.obligation,
|
|
411
|
+
declaration.complete,
|
|
412
|
+
declaration.fail,
|
|
413
|
+
declaration.block,
|
|
414
|
+
declaration.deadLetter,
|
|
415
|
+
]) {
|
|
416
|
+
Object.defineProperty(lifecycle, NOMOS_CAPABILITY_DECLARATIONS, {
|
|
417
|
+
value: Object.freeze([declaration]),
|
|
418
|
+
enumerable: true,
|
|
419
|
+
configurable: false,
|
|
420
|
+
writable: false,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return declaration;
|
|
356
424
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* an **Obligation → Receipt** pair of ordinary `WireIntent`s with a first-class lifecycle law:
|
|
21
21
|
*
|
|
22
22
|
* • **Obligation** — a normal intent whose role is the request: a peer dispatches
|
|
23
|
-
* `oblige…(
|
|
23
|
+
* `oblige…(businessParams)`,
|
|
24
24
|
* `.creates` a task aggregate keyed by a KERNEL-MINTED obligation id
|
|
25
25
|
* (`<ObligationType>_<uuidv7>`, via the marker-driven front-door; idempotent on
|
|
26
26
|
* replay by capture).
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
* Framework (here): the task LIFECYCLE shape — the `requestId / status / resultRef? /
|
|
49
49
|
* failureReason? / completedAt? / blockedReason? / nextAction? / deadLetterReason?`
|
|
50
50
|
* envelope, the `requested|completed|failed|blocked|deadLettered` status
|
|
51
|
-
* vocabulary,
|
|
52
|
-
*
|
|
51
|
+
* vocabulary, kernel-minted task identity, captured author/clock, and the plan builders
|
|
52
|
+
* that write that envelope. Tenant: the task's
|
|
53
53
|
* NAME (`exportJob`, `thingExport`) and its request PARAMS (which group, which
|
|
54
54
|
* filters) — the domain dev supplies those via `impureCapability({...})`.
|
|
55
55
|
*
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
*/
|
|
68
68
|
import { z } from "zod";
|
|
69
69
|
import { aggregate, instance, type AggregateHandle } from "../aggregate.js";
|
|
70
|
-
import {
|
|
70
|
+
import { create, type AggregateRef } from "../authoring.js";
|
|
71
|
+
import { attributionField, t, type Field } from "../fields.js";
|
|
71
72
|
import { Lww } from "../drivers.js";
|
|
72
73
|
import { set, type PlannedOp } from "../ops.js";
|
|
73
74
|
import { directive, type RequirableDirective } from "../directive.js";
|
|
@@ -75,6 +76,7 @@ import { executeDirectiveToIntent } from "../wire_encode.js";
|
|
|
75
76
|
import type { WireIntent } from "../wire.js";
|
|
76
77
|
import type { Ports } from "../ctx.js";
|
|
77
78
|
import { idOf } from "../value_object.js";
|
|
79
|
+
import type { AttributionPrincipal } from "../principal_evidence.js";
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
82
|
* The impure-capability lifecycle vocabulary — framework-fixed. A task is `requested` when
|
|
@@ -101,6 +103,8 @@ export type ImpureCapabilityStatus = (typeof IMPURE_CAPABILITY_STATUSES)[number]
|
|
|
101
103
|
* create-only gap; modelled Lww).
|
|
102
104
|
* - requestedBy Lww — who obligated the task (provenance). Immutable-after-create.
|
|
103
105
|
* - requestedAt Int — epoch-ms the obligation was authored. Immutable-after-create.
|
|
106
|
+
* - scheduledAt Int — epoch-ms when a provider may start work. Framework-derived.
|
|
107
|
+
* - deadlineAt Int — epoch-ms when accepted work becomes overdue. Framework-derived.
|
|
104
108
|
* - status Lww enum — `requested` → the reactor flips to a terminal status.
|
|
105
109
|
* - resultRef Lww string (OPTIONAL) — the content-addressed ARTIFACT the provider
|
|
106
110
|
* records on completion: a blob ref / download url, and nothing else. It
|
|
@@ -127,7 +131,7 @@ export type ImpureCapabilityStatus = (typeof IMPURE_CAPABILITY_STATUSES)[number]
|
|
|
127
131
|
*/
|
|
128
132
|
export const IMPURE_CAPABILITY_ENVELOPE = {
|
|
129
133
|
requestId: t.string().merge(Lww),
|
|
130
|
-
requestedBy: t.string().merge(Lww),
|
|
134
|
+
requestedBy: attributionField(t.string().merge(Lww)),
|
|
131
135
|
requestedAt: t.int().merge(Lww),
|
|
132
136
|
scheduledAt: t.int().merge(Lww),
|
|
133
137
|
deadlineAt: t.int().merge(Lww),
|
|
@@ -186,21 +190,24 @@ export function impureCapabilityInstanceId(requestId: string): string {
|
|
|
186
190
|
return requestId;
|
|
187
191
|
}
|
|
188
192
|
|
|
189
|
-
/** The
|
|
190
|
-
const
|
|
193
|
+
/** The receipt-COMPLETE envelope every capability carries, whatever its result shape. */
|
|
194
|
+
const completeEnvelopeSchema = z.object({
|
|
191
195
|
requestId: z.string(),
|
|
196
|
+
completedAt: z.number().int(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* What a provider receives after the Obligation has passed the gate and become a task.
|
|
201
|
+
* Deliberately NOT the public request payload: callers supply business params, while the
|
|
202
|
+
* accepted projection adds kernel-owned identity, provenance and lifecycle timing.
|
|
203
|
+
*/
|
|
204
|
+
const providerEnvelopeSchema = z.object({
|
|
192
205
|
requestedBy: z.string(),
|
|
193
206
|
requestedAt: z.number().int(),
|
|
194
207
|
scheduledAt: z.number().int(),
|
|
195
208
|
deadlineAt: z.number().int(),
|
|
196
209
|
});
|
|
197
210
|
|
|
198
|
-
/** The receipt-COMPLETE envelope every capability carries, whatever its result shape. */
|
|
199
|
-
const completeEnvelopeSchema = z.object({
|
|
200
|
-
requestId: z.string(),
|
|
201
|
-
completedAt: z.number().int(),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
211
|
/**
|
|
205
212
|
* The receipt-COMPLETE payload for a capability declaring NO typed receipt fields: the
|
|
206
213
|
* envelope plus a required content-addressed ref to the produced ARTIFACT (blob ref / url).
|
|
@@ -266,7 +273,13 @@ function envelopeInstance(aggregateId: string, requestId: string) {
|
|
|
266
273
|
*/
|
|
267
274
|
export function obligationOps(
|
|
268
275
|
aggregateId: string,
|
|
269
|
-
p:
|
|
276
|
+
p: {
|
|
277
|
+
requestId: string;
|
|
278
|
+
requestedBy: string | AttributionPrincipal;
|
|
279
|
+
requestedAt: number;
|
|
280
|
+
scheduledAt: number;
|
|
281
|
+
deadlineAt: number;
|
|
282
|
+
},
|
|
270
283
|
paramOps: PlannedOp[] = [],
|
|
271
284
|
): PlannedOp[] {
|
|
272
285
|
const taskInstance = envelopeInstance(aggregateId, p.requestId);
|
|
@@ -366,10 +379,11 @@ export function deadLetterOps(
|
|
|
366
379
|
* • `obligation` — `.creates` directive (the authored Obligation),
|
|
367
380
|
* • `complete` — `.mutates` directive (the reactor's success Receipt),
|
|
368
381
|
* • `fail` — `.mutates` directive (the reactor's failure Receipt),
|
|
369
|
-
* plus the deterministic `instanceId` rule. The `obligation` payload
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
382
|
+
* plus the deterministic `instanceId` rule. The public `obligation` payload contains ONLY
|
|
383
|
+
* tenant business params. `providerPayloadSchema` describes the accepted task projection
|
|
384
|
+
* the provider receives: kernel-owned envelope plus those params. The `complete` payload is
|
|
385
|
+
* the complete-envelope merged with the tenant's `receiptSchema` (typed USDA values, never
|
|
386
|
+
* an encoded blob); `fail`/`block`/`deadLetter` are fixed.
|
|
373
387
|
*/
|
|
374
388
|
// Infer from the EXTENDED schema, exactly as ObligationPayload does — a conditional or an
|
|
375
389
|
// intersection around this turns a `.optional()` receipt field into a REQUIRED key of type
|
|
@@ -392,6 +406,8 @@ export interface ImpureCapability<
|
|
|
392
406
|
> {
|
|
393
407
|
readonly aggregate: AggregateHandle<Id, typeof IMPURE_CAPABILITY_ENVELOPE & F>;
|
|
394
408
|
readonly obligation: RequirableDirective<ObligationPayload<ParamsShape>>;
|
|
409
|
+
/** Typed accepted-task view delivered to a provider (kernel envelope plus business params). */
|
|
410
|
+
readonly providerPayloadSchema: z.ZodTypeAny;
|
|
395
411
|
readonly complete: RequirableDirective<CompletePayload<ReceiptShape>>;
|
|
396
412
|
readonly fail: RequirableDirective<z.infer<typeof failSchema>>;
|
|
397
413
|
readonly block: RequirableDirective<z.infer<typeof blockSchema>>;
|
|
@@ -412,13 +428,11 @@ export interface ImpureCapability<
|
|
|
412
428
|
}
|
|
413
429
|
|
|
414
430
|
/**
|
|
415
|
-
* The inferred Obligation payload type
|
|
416
|
-
*
|
|
417
|
-
* produces (so the declared `ImpureCapability.obligation` directive type and the value the factory
|
|
418
|
-
* builds are the same type, with no brittle hand-written intersection).
|
|
431
|
+
* The inferred public Obligation payload type: tenant business params only. Task identity,
|
|
432
|
+
* provenance and lifecycle timing are kernel-owned and appear on the accepted provider view.
|
|
419
433
|
*/
|
|
420
434
|
export type ObligationPayload<ParamsShape extends z.ZodRawShape> = z.infer<
|
|
421
|
-
|
|
435
|
+
z.ZodObject<ParamsShape>
|
|
422
436
|
>;
|
|
423
437
|
|
|
424
438
|
/**
|
|
@@ -507,14 +521,19 @@ export function impureCapability<
|
|
|
507
521
|
* Omit-when-absent — declaring nothing changes nothing on the wire.
|
|
508
522
|
*/
|
|
509
523
|
capability?: string;
|
|
524
|
+
/** Delay before the provider should attempt the task. Captured from the
|
|
525
|
+
* accepted offer's clock; callers never author lifecycle timestamps. */
|
|
526
|
+
scheduleAfterMs?: number;
|
|
527
|
+
/** Time from acceptance until the task is overdue. Defaults to 24 hours. */
|
|
528
|
+
deadlineAfterMs?: number;
|
|
510
529
|
}): ImpureCapability<Id, ParamsShape, F, ReceiptShape> {
|
|
511
530
|
const taskAggregate = impureCapabilityAggregate(spec.aggregateId, spec.params);
|
|
531
|
+
const obligationPayload = z.object(spec.paramsSchema);
|
|
512
532
|
const typedRequestId = { requestId: idOf(taskAggregate) };
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const obligationPayload = obligationPayloadBase.extend(typedRequestId) as typeof obligationPayloadBase;
|
|
533
|
+
const providerPayloadSchema = providerEnvelopeSchema.extend({
|
|
534
|
+
...typedRequestId,
|
|
535
|
+
...spec.paramsSchema,
|
|
536
|
+
});
|
|
518
537
|
// THE RECEIPT IS TYPED LAW, SYMMETRICAL WITH THE REQUEST. A capability that declares
|
|
519
538
|
// receipt fields carries them as typed values merged onto the complete envelope, exactly
|
|
520
539
|
// as the obligation merges its params. `resultRef` then keeps only its honest meaning —
|
|
@@ -536,18 +555,28 @@ export function impureCapability<
|
|
|
536
555
|
)
|
|
537
556
|
.creates(taskAggregate)
|
|
538
557
|
.payload(obligationPayload)
|
|
539
|
-
.plan((p) => {
|
|
540
|
-
// The
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
// schema, so these are present non-undefined values).
|
|
544
|
-
const envelope = obligationEnvelopeSchema.parse(p);
|
|
558
|
+
.plan((p, ctx) => {
|
|
559
|
+
// The caller supplies BUSINESS PARAMS only. Nomos owns the task identity,
|
|
560
|
+
// provenance and lifecycle clock so generated clients cannot accidentally
|
|
561
|
+
// spoof or hand-roll the obligation envelope.
|
|
545
562
|
const params = p as z.infer<z.ZodObject<ParamsShape>>;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
);
|
|
563
|
+
const task = create(taskAggregate);
|
|
564
|
+
const requestId = task.__id;
|
|
565
|
+
const requestedAt = ctx.clock().physical;
|
|
566
|
+
const scheduledAt = requestedAt + (spec.scheduleAfterMs ?? 0);
|
|
567
|
+
const deadlineAt = requestedAt + (spec.deadlineAfterMs ?? 24 * 60 * 60 * 1000);
|
|
568
|
+
// Narrow to the framework-owned facet while writing its envelope. This keeps every
|
|
569
|
+
// field name and value checked without pretending the tenant's generic param keys are
|
|
570
|
+
// known here; the full task still carries both facets on the wire and in generated reads.
|
|
571
|
+
const envelopeTask = task as unknown as AggregateRef<typeof IMPURE_CAPABILITY_ENVELOPE>;
|
|
572
|
+
envelopeTask
|
|
573
|
+
.set("requestId", requestId)
|
|
574
|
+
.set("requestedBy", ctx.principal().asAttribution())
|
|
575
|
+
.set("requestedAt", requestedAt)
|
|
576
|
+
.set("scheduledAt", scheduledAt)
|
|
577
|
+
.set("deadlineAt", deadlineAt)
|
|
578
|
+
.set("status", "requested");
|
|
579
|
+
return spec.writeParams(impureCapabilityInstanceId(requestId), params);
|
|
551
580
|
});
|
|
552
581
|
|
|
553
582
|
// The two branches above are distinct object schemas; the declared payload TYPE is the
|
|
@@ -588,6 +617,7 @@ export function impureCapability<
|
|
|
588
617
|
return {
|
|
589
618
|
aggregate: taskAggregate,
|
|
590
619
|
obligation,
|
|
620
|
+
providerPayloadSchema,
|
|
591
621
|
complete,
|
|
592
622
|
fail,
|
|
593
623
|
block,
|
|
@@ -119,6 +119,7 @@ const ownedRecoveryLifecycle = impureCapability({
|
|
|
119
119
|
// have one typed provider contract; the kernel may then retain the sealed obligation locally until
|
|
120
120
|
// that principal opens the workspace, rather than inventing an untyped host work description.
|
|
121
121
|
capability: "nomos.owned-recovery",
|
|
122
|
+
deadlineAfterMs: 30 * 24 * 60 * 60 * 1000,
|
|
122
123
|
});
|
|
123
124
|
const hiddenRecoveryLifecycle = {
|
|
124
125
|
aggregate: hidden(OwnedRecoveryOrder),
|
package/src/intent.ts
CHANGED
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
} from "./business_birth.js";
|
|
33
33
|
import type { BusinessBirthDecl } from "./births.js";
|
|
34
34
|
import type { WorkspaceSharingPolicy } from "./workspace_sharing.js";
|
|
35
|
+
import {
|
|
36
|
+
capabilityDeclarationsOf,
|
|
37
|
+
NOMOS_CAPABILITY_DECLARATIONS,
|
|
38
|
+
} from "./capability_exports.js";
|
|
35
39
|
|
|
36
40
|
const INTENT_BRAND = "__nomos_business_intent_v1" as const;
|
|
37
41
|
const LANGUAGE_BRAND = "__nomos_application_language_v1" as const;
|
|
@@ -498,6 +502,9 @@ class IntentBuilder<I> {
|
|
|
498
502
|
const businessBirth = (birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.businessBirth;
|
|
499
503
|
const bindBusinessBirthPolicy =
|
|
500
504
|
(birthSteps[0]?.directive as BusinessBirthDirective | undefined)?.[BUSINESS_BIRTH_POLICY_BINDING];
|
|
505
|
+
const carriedCapabilities = [
|
|
506
|
+
...new Set(this.steps.flatMap((step) => capabilityDeclarationsOf(step.directive))),
|
|
507
|
+
];
|
|
501
508
|
return {
|
|
502
509
|
[INTENT_BRAND]: true,
|
|
503
510
|
id: this.id,
|
|
@@ -550,6 +557,11 @@ class IntentBuilder<I> {
|
|
|
550
557
|
publicPayloadSchema: this.contractDeclaration.accepts,
|
|
551
558
|
},
|
|
552
559
|
}),
|
|
560
|
+
...(carriedCapabilities.length === 0
|
|
561
|
+
? {}
|
|
562
|
+
: {
|
|
563
|
+
[NOMOS_CAPABILITY_DECLARATIONS]: Object.freeze(carriedCapabilities),
|
|
564
|
+
}),
|
|
553
565
|
};
|
|
554
566
|
}
|
|
555
567
|
}
|