@lostgradient/weft 0.23.1 → 0.24.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/README.md +72 -4
- package/dist/cli/codegen-emit-dedup.d.ts +81 -0
- package/dist/cli/codegen-emit-dedup.js +45 -0
- package/dist/cli/codegen-emit-registry.d.ts +90 -0
- package/dist/cli/codegen-emit-registry.js +50 -0
- package/dist/cli/codegen-emit.d.ts +26 -39
- package/dist/cli/codegen-emit.js +4 -27
- package/dist/cli/codegen-validate.d.ts +49 -0
- package/dist/cli/codegen-validate.js +186 -0
- package/dist/cli/codegen.js +6 -87
- package/dist/cli/generated/operation-catalog.snapshot.json +287 -3
- package/dist/cli/generated/operation-client.generated.d.ts +324 -10
- package/dist/cli/generated/operation-client.generated.js +12 -0
- package/dist/cli/output.js +1 -1
- package/dist/cli/storage-factory.js +2 -0
- package/dist/cli/workflow-commands.js +2 -0
- package/dist/cli-main.js +1 -1
- package/dist/core/application-payload-digest.d.ts +42 -0
- package/dist/core/application-payload-digest.js +76 -0
- package/dist/core/application-primitive-abort.d.ts +50 -0
- package/dist/core/application-primitive-abort.js +40 -0
- package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
- package/dist/core/application-primitive-attempt-registry.js +73 -0
- package/dist/core/application-primitive-codec.d.ts +36 -0
- package/dist/core/application-primitive-codec.js +56 -0
- package/dist/core/application-primitive-commit.d.ts +67 -0
- package/dist/core/application-primitive-commit.js +66 -0
- package/dist/core/application-primitive-guards.d.ts +62 -0
- package/dist/core/application-primitive-guards.js +89 -0
- package/dist/core/application-primitive-payload.d.ts +29 -0
- package/dist/core/application-primitive-payload.js +65 -0
- package/dist/core/application-primitive-timing.d.ts +18 -0
- package/dist/core/application-primitive-timing.js +22 -0
- package/dist/core/atomic-state.js +2 -0
- package/dist/core/catalog/codec.d.ts +40 -0
- package/dist/core/catalog/codec.js +45 -0
- package/dist/core/catalog/errors.d.ts +104 -0
- package/dist/core/catalog/errors.js +41 -0
- package/dist/core/catalog/index.d.ts +25 -0
- package/dist/core/catalog/index.js +15 -0
- package/dist/core/catalog/reference-counts.d.ts +105 -0
- package/dist/core/catalog/reference-counts.js +28 -0
- package/dist/core/catalog/removal.d.ts +52 -0
- package/dist/core/catalog/removal.js +14 -0
- package/dist/core/catalog/storage-io.d.ts +78 -0
- package/dist/core/catalog/storage-io.js +106 -0
- package/dist/core/catalog/types.d.ts +108 -0
- package/dist/core/catalog/types.js +0 -0
- package/dist/core/catalog/workflow-catalog.d.ts +165 -0
- package/dist/core/catalog/workflow-catalog.js +198 -0
- package/dist/core/compare-codepoint.d.ts +13 -0
- package/dist/core/compare-codepoint.js +7 -0
- package/dist/core/compression.js +2 -0
- package/dist/core/context/attributes.d.ts +1 -1
- package/dist/core/context/durable-operations.d.ts +2 -2
- package/dist/core/context/index.js +5 -3
- package/dist/core/context/state-namespace.d.ts +1 -1
- package/dist/core/contract/build.d.ts +64 -0
- package/dist/core/contract/build.js +99 -0
- package/dist/core/contract/compatibility.d.ts +187 -0
- package/dist/core/contract/compatibility.js +37 -0
- package/dist/core/contract/failure.d.ts +64 -0
- package/dist/core/contract/failure.js +4 -0
- package/dist/core/contract/hash.d.ts +92 -0
- package/dist/core/contract/hash.js +34 -0
- package/dist/core/contract/index.d.ts +21 -0
- package/dist/core/contract/index.js +22 -0
- package/dist/core/contract/limits.d.ts +74 -0
- package/dist/core/contract/limits.js +1 -0
- package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
- package/dist/core/contract/manifest-parse-schema.js +106 -0
- package/dist/core/contract/manifest-parse.d.ts +76 -0
- package/dist/core/contract/manifest-parse.js +189 -0
- package/dist/core/contract/manifest.d.ts +56 -0
- package/dist/core/contract/manifest.js +31 -0
- package/dist/core/contract/normalize.d.ts +97 -0
- package/dist/core/contract/normalize.js +127 -0
- package/dist/core/contract/revision.d.ts +29 -0
- package/dist/core/contract/revision.js +5 -0
- package/dist/core/contract/types.d.ts +274 -0
- package/dist/core/contract/types.js +1 -0
- package/dist/core/engine/catalog-activation.d.ts +40 -0
- package/dist/core/engine/catalog-activation.js +17 -0
- package/dist/core/engine/catalog-events.d.ts +36 -0
- package/dist/core/engine/catalog-events.js +15 -0
- package/dist/core/engine/catalog-readiness.d.ts +49 -0
- package/dist/core/engine/catalog-readiness.js +59 -0
- package/dist/core/engine/catalog-removal.d.ts +132 -0
- package/dist/core/engine/catalog-removal.js +70 -0
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/construction.js +4 -2
- package/dist/core/engine/disposal.js +9 -0
- package/dist/core/engine/engine-workflows-namespace.d.ts +130 -0
- package/dist/core/engine/engine-workflows-namespace.js +49 -0
- package/dist/core/engine/finalizer-metadata.d.ts +22 -0
- package/dist/core/engine/finalizer-metadata.js +12 -0
- package/dist/core/engine/handle-result.js +2 -0
- package/dist/core/engine/index.d.ts +83 -0
- package/dist/core/engine/index.js +61 -0
- package/dist/core/engine/internals.d.ts +44 -1
- package/dist/core/engine/internals.js +6 -0
- package/dist/core/engine/lifecycle/start.js +3 -0
- package/dist/core/engine/registration.d.ts +76 -0
- package/dist/core/engine/registration.js +12 -6
- package/dist/core/engine/source-registration.d.ts +42 -0
- package/dist/core/engine/source-registration.js +36 -0
- package/dist/core/engine/source-resolution.d.ts +85 -0
- package/dist/core/engine/source-resolution.js +119 -0
- package/dist/core/engine/validation.js +2 -2
- package/dist/core/events/catalog-events.d.ts +117 -0
- package/dist/core/events/catalog-events.js +62 -0
- package/dist/core/events/event-map.d.ts +6 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/mailbox-admission.d.ts +26 -0
- package/dist/core/mailbox-admission.js +130 -0
- package/dist/core/mailbox-claims.d.ts +251 -0
- package/dist/core/mailbox-claims.js +0 -0
- package/dist/core/mailbox-codec.d.ts +32 -0
- package/dist/core/mailbox-codec.js +221 -0
- package/dist/core/mailbox-contract.d.ts +290 -0
- package/dist/core/mailbox-contract.js +0 -0
- package/dist/core/mailbox-delivery.d.ts +37 -0
- package/dist/core/mailbox-delivery.js +193 -0
- package/dist/core/mailbox-guards.d.ts +78 -0
- package/dist/core/mailbox-guards.js +58 -0
- package/dist/core/mailbox-index-codec.d.ts +41 -0
- package/dist/core/mailbox-index-codec.js +71 -0
- package/dist/core/mailbox-internals.d.ts +156 -0
- package/dist/core/mailbox-internals.js +154 -0
- package/dist/core/mailbox-maintenance.d.ts +29 -0
- package/dist/core/mailbox-maintenance.js +196 -0
- package/dist/core/mailbox-settlement.d.ts +69 -0
- package/dist/core/mailbox-settlement.js +206 -0
- package/dist/core/mailbox-storage.d.ts +117 -0
- package/dist/core/mailbox-storage.js +115 -0
- package/dist/core/mailbox-transition-helpers.d.ts +70 -0
- package/dist/core/mailbox-transition-helpers.js +43 -0
- package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
- package/dist/core/mailbox-transitions-recovery.js +64 -0
- package/dist/core/mailbox-transitions.d.ts +138 -0
- package/dist/core/mailbox-transitions.js +180 -0
- package/dist/core/mailbox-types.d.ts +406 -0
- package/dist/core/mailbox-types.js +16 -0
- package/dist/core/mailbox-validation.d.ts +65 -0
- package/dist/core/mailbox-validation.js +73 -0
- package/dist/core/mailbox-waits.d.ts +50 -0
- package/dist/core/mailbox-waits.js +97 -0
- package/dist/core/mailbox.d.ts +163 -0
- package/dist/core/mailbox.js +211 -0
- package/dist/core/outbox-claims.d.ts +307 -0
- package/dist/core/outbox-claims.js +0 -0
- package/dist/core/outbox-codec.d.ts +24 -0
- package/dist/core/outbox-codec.js +257 -0
- package/dist/core/outbox-contract.d.ts +346 -0
- package/dist/core/outbox-contract.js +0 -0
- package/dist/core/outbox-delivery.d.ts +34 -0
- package/dist/core/outbox-delivery.js +149 -0
- package/dist/core/outbox-drain.d.ts +29 -0
- package/dist/core/outbox-drain.js +158 -0
- package/dist/core/outbox-enqueue.d.ts +24 -0
- package/dist/core/outbox-enqueue.js +115 -0
- package/dist/core/outbox-guards.d.ts +76 -0
- package/dist/core/outbox-guards.js +60 -0
- package/dist/core/outbox-index-codec.d.ts +33 -0
- package/dist/core/outbox-index-codec.js +71 -0
- package/dist/core/outbox-internals.d.ts +99 -0
- package/dist/core/outbox-internals.js +159 -0
- package/dist/core/outbox-listing.d.ts +12 -0
- package/dist/core/outbox-listing.js +38 -0
- package/dist/core/outbox-maintenance.d.ts +28 -0
- package/dist/core/outbox-maintenance.js +191 -0
- package/dist/core/outbox-operations.d.ts +18 -0
- package/dist/core/outbox-operations.js +46 -0
- package/dist/core/outbox-runner.d.ts +30 -0
- package/dist/core/outbox-runner.js +217 -0
- package/dist/core/outbox-settlement.d.ts +57 -0
- package/dist/core/outbox-settlement.js +183 -0
- package/dist/core/outbox-storage.d.ts +107 -0
- package/dist/core/outbox-storage.js +127 -0
- package/dist/core/outbox-transition-helpers.d.ts +83 -0
- package/dist/core/outbox-transition-helpers.js +63 -0
- package/dist/core/outbox-transitions-recovery.d.ts +30 -0
- package/dist/core/outbox-transitions-recovery.js +42 -0
- package/dist/core/outbox-transitions.d.ts +145 -0
- package/dist/core/outbox-transitions.js +236 -0
- package/dist/core/outbox-types.d.ts +400 -0
- package/dist/core/outbox-types.js +20 -0
- package/dist/core/outbox-validation.d.ts +101 -0
- package/dist/core/outbox-validation.js +127 -0
- package/dist/core/outbox-waits.d.ts +39 -0
- package/dist/core/outbox-waits.js +88 -0
- package/dist/core/outbox.d.ts +156 -0
- package/dist/core/outbox.js +234 -0
- package/dist/core/registry-limits.d.ts +49 -0
- package/dist/core/registry-limits.js +10 -0
- package/dist/core/registry-schema-conversion.d.ts +20 -0
- package/dist/core/registry-schema-conversion.js +22 -0
- package/dist/core/registry-snapshot.d.ts +91 -42
- package/dist/core/registry-snapshot.js +39 -77
- package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
- package/dist/core/registry-workflow-contract-draft.js +28 -0
- package/dist/core/registry-workflow-manifest.d.ts +122 -0
- package/dist/core/registry-workflow-manifest.js +92 -0
- package/dist/core/source/errors.d.ts +55 -0
- package/dist/core/source/errors.js +13 -0
- package/dist/core/source/index.d.ts +19 -0
- package/dist/core/source/index.js +4 -0
- package/dist/core/source/resolvers.d.ts +45 -0
- package/dist/core/source/resolvers.js +9 -0
- package/dist/core/source/types.d.ts +211 -0
- package/dist/core/source/types.js +0 -0
- package/dist/core/source/validate.d.ts +91 -0
- package/dist/core/source/validate.js +62 -0
- package/dist/core/source/workflow-source.d.ts +43 -0
- package/dist/core/source/workflow-source.js +16 -0
- package/dist/core/types/workflow-registries.d.ts +7 -0
- package/dist/core/types/workflow-registry.d.ts +12 -0
- package/dist/core/versioning.js +2 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +9 -1
- package/dist/http.js +2 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +50 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +3 -3
- package/dist/mcp/resources.js +2 -0
- package/dist/runtime/portable.d.ts +9 -1
- package/dist/server/fault-to-json-rpc.js +3 -1
- package/dist/server/handler/auth-context-principal.js +4 -0
- package/dist/server/json-rpc-http.js +2 -0
- package/dist/server/json-rpc-websocket.js +6 -4
- package/dist/server/operation-catalog/index.d.ts +1 -1
- package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
- package/dist/server/operation-catalog/types.d.ts +17 -0
- package/dist/server/operation-fault.d.ts +3 -0
- package/dist/server/operation-fault.js +3 -1
- package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
- package/dist/server/operations/activate-workflow-revision.js +69 -0
- package/dist/server/operations/fleet-events-subscription.js +1 -1
- package/dist/server/operations/fork-workflow.js +1 -1
- package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
- package/dist/server/operations/get-active-workflow-revision.js +46 -0
- package/dist/server/operations/get-catalog-diagnostics.d.ts +85 -0
- package/dist/server/operations/get-catalog-diagnostics.js +57 -0
- package/dist/server/operations/get-registry.js +17 -4
- package/dist/server/operations/get-workflow-revision.d.ts +25 -0
- package/dist/server/operations/get-workflow-revision.js +50 -0
- package/dist/server/operations/install-workflow-revision.d.ts +27 -0
- package/dist/server/operations/install-workflow-revision.js +46 -0
- package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
- package/dist/server/operations/list-workflow-revisions.js +40 -0
- package/dist/server/operations/start-or-signal-workflow.js +1 -1
- package/dist/server/operations/start-workflow.js +1 -1
- package/dist/server/operations/static-registrations.js +36 -0
- package/dist/server/operations/storage.js +2 -2
- package/dist/server/operations/submit-review-decision.js +1 -1
- package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
- package/dist/server/operations/workflow-catalog-operation-helpers.js +135 -0
- package/dist/server/runtime/websocket-upgrade.js +2 -0
- package/dist/storage/bun-sql.js +49 -6
- package/dist/storage/catalog-keys.d.ts +45 -0
- package/dist/storage/catalog-keys.js +6 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/derived-operations.js +1 -1
- package/dist/storage/index.d.ts +26 -1
- package/dist/storage/interface.d.ts +26 -1
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-encoding.d.ts +15 -0
- package/dist/storage/key-encoding.js +3 -0
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +15 -0
- package/dist/storage/lmdb.d.ts +26 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/mailbox-keys.d.ts +80 -0
- package/dist/storage/mailbox-keys.js +14 -0
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +2 -2
- package/dist/storage/node-sqlite-loader.d.ts +1 -1
- package/dist/storage/node-sqlite.js +49 -6
- package/dist/storage/outbox-keys.d.ts +77 -0
- package/dist/storage/outbox-keys.js +14 -0
- package/dist/storage/postgres.js +2 -2
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +6 -0
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
- package/dist/worker/manifest/registry-contract-builder.js +40 -31
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic normalization and canonical serialization for workflow
|
|
3
|
+
* contracts.
|
|
4
|
+
*
|
|
5
|
+
* Two contracts that describe the same workflow must produce byte-identical
|
|
6
|
+
* canonical output regardless of how their object keys were ordered in the
|
|
7
|
+
* source, so `contractHash()` and `deriveWorkflowRevision()` identify the
|
|
8
|
+
* *content* rather than an accident of construction. Serialization walks the
|
|
9
|
+
* declared contract shape explicitly instead of generically stringifying the
|
|
10
|
+
* input, which means an unknown extra property can never leak into the
|
|
11
|
+
* canonical bytes and change a hash.
|
|
12
|
+
*
|
|
13
|
+
* @module core/contract/normalize
|
|
14
|
+
*/
|
|
15
|
+
import type { WorkflowContract, WorkflowMessageContract } from './types.ts';
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a workflow contract into its canonical in-memory form.
|
|
18
|
+
*
|
|
19
|
+
* Field order follows the declared shape, every open-ended record is
|
|
20
|
+
* rebuilt with its keys sorted (and omitted entirely when empty), and every
|
|
21
|
+
* schema fragment is deep-cloned onto a null-prototype object. The result is
|
|
22
|
+
* a fresh value; the input is not mutated.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { normalizeWorkflowContract } from '@lostgradient/weft';
|
|
27
|
+
*
|
|
28
|
+
* const normalized = normalizeWorkflowContract({
|
|
29
|
+
* name: 'checkout',
|
|
30
|
+
* workflowVersion: '2.1.0',
|
|
31
|
+
* signals: {
|
|
32
|
+
* zeta: {},
|
|
33
|
+
* alpha: {},
|
|
34
|
+
* },
|
|
35
|
+
* });
|
|
36
|
+
* console.log(Object.keys(normalized.signals ?? {})); // ['alpha', 'zeta']
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeWorkflowContract(contract: WorkflowContract): WorkflowContract;
|
|
40
|
+
/**
|
|
41
|
+
* Serialize one signal/update/query/activity contract's schema pair.
|
|
42
|
+
* Shared between {@link canonicalWorkflowContractJson} (the full-identity
|
|
43
|
+
* form) and `hash.ts`'s payload-only serialization, since both walk the same
|
|
44
|
+
* `{ inputSchema?, outputSchema? }` shape — the only difference between the
|
|
45
|
+
* two identities is which *top-level* fields are included, not how a single
|
|
46
|
+
* message/activity entry serializes.
|
|
47
|
+
*
|
|
48
|
+
* @internal Exported for reuse within `core/contract/*`; not part of the
|
|
49
|
+
* package's public API.
|
|
50
|
+
*/
|
|
51
|
+
export declare function canonicalMessageContractJson(entry: WorkflowMessageContract): string;
|
|
52
|
+
/**
|
|
53
|
+
* @internal Exported for reuse within `core/contract/*`; not part of the
|
|
54
|
+
* package's public API.
|
|
55
|
+
*/
|
|
56
|
+
export declare function canonicalContractRecordJson(record: Readonly<Record<string, WorkflowMessageContract>>): string;
|
|
57
|
+
/**
|
|
58
|
+
* @internal Exported for reuse within `core/contract/*` (`hash.ts`'s
|
|
59
|
+
* payload-only serialization shares this field-appending logic); not part of
|
|
60
|
+
* the package's public API.
|
|
61
|
+
*/
|
|
62
|
+
export declare function appendContractRecordField(fields: string[], label: 'signals' | 'updates' | 'queries' | 'activities', record: Readonly<Record<string, WorkflowMessageContract>> | undefined): void;
|
|
63
|
+
/**
|
|
64
|
+
* @internal Exported for reuse within `core/contract/*`; not part of the
|
|
65
|
+
* package's public API.
|
|
66
|
+
*/
|
|
67
|
+
export declare function appendSchemaField(fields: string[], label: 'inputSchema' | 'outputSchema', schema: Record<string, unknown> | undefined): void;
|
|
68
|
+
/**
|
|
69
|
+
* Serialize a workflow contract to its canonical, full-identity JSON string
|
|
70
|
+
* — the digest input {@link deriveWorkflowRevision} uses, and the value
|
|
71
|
+
* returned as `WorkflowRevisionManifestParseSuccess.canonicalJson`.
|
|
72
|
+
*
|
|
73
|
+
* This is a *different* identity from {@link contractHash}'s payload-only
|
|
74
|
+
* serialization: it includes `name`, `workflowVersion`, `description`, and
|
|
75
|
+
* `tags`, so a documentation edit changes this output (and therefore
|
|
76
|
+
* `revision`) without changing `contractHash`. The output is byte-identical
|
|
77
|
+
* for equivalent contracts regardless of source key order, and every field
|
|
78
|
+
* this project cares about changes it.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { canonicalWorkflowContractJson } from '@lostgradient/weft';
|
|
83
|
+
*
|
|
84
|
+
* const left = canonicalWorkflowContractJson({
|
|
85
|
+
* name: 'checkout',
|
|
86
|
+
* workflowVersion: '2.1.0',
|
|
87
|
+
* signals: { alpha: {}, zeta: {} },
|
|
88
|
+
* });
|
|
89
|
+
* const right = canonicalWorkflowContractJson({
|
|
90
|
+
* name: 'checkout',
|
|
91
|
+
* workflowVersion: '2.1.0',
|
|
92
|
+
* signals: { zeta: {}, alpha: {} },
|
|
93
|
+
* });
|
|
94
|
+
* console.log(left === right); // true
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function canonicalWorkflowContractJson(contract: WorkflowContract): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { canonicalJsonStringify } from "../../worker/manifest/canonical-json.js";
|
|
2
|
+
import { WORKFLOW_CONTRACT_VERSION } from "./types.js";
|
|
3
|
+
function sortedKeys(record) {
|
|
4
|
+
return Object.keys(record).toSorted();
|
|
5
|
+
}
|
|
6
|
+
function cloneSchemaFragment(value) {
|
|
7
|
+
if (value === null || typeof value !== "object")
|
|
8
|
+
return value;
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map((entry) => cloneSchemaFragment(entry));
|
|
11
|
+
const record = value, cloned = Object.create(null);
|
|
12
|
+
for (const key of Object.keys(record))
|
|
13
|
+
cloned[key] = cloneSchemaFragment(record[key]);
|
|
14
|
+
return cloned;
|
|
15
|
+
}
|
|
16
|
+
function cloneSchemaRecord(value) {
|
|
17
|
+
return value === void 0 ? void 0 : cloneSchemaFragment(value);
|
|
18
|
+
}
|
|
19
|
+
function normalizeMessageContract(entry) {
|
|
20
|
+
const normalized = {}, inputSchema = cloneSchemaRecord(entry.inputSchema);
|
|
21
|
+
if (inputSchema !== void 0)
|
|
22
|
+
normalized.inputSchema = inputSchema;
|
|
23
|
+
const outputSchema = cloneSchemaRecord(entry.outputSchema);
|
|
24
|
+
if (outputSchema !== void 0)
|
|
25
|
+
normalized.outputSchema = outputSchema;
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
function normalizeActivityContract(entry) {
|
|
29
|
+
return normalizeMessageContract(entry);
|
|
30
|
+
}
|
|
31
|
+
function sortedContractRecord(record, normalizeValue) {
|
|
32
|
+
if (record === void 0)
|
|
33
|
+
return;
|
|
34
|
+
const keys = sortedKeys(record);
|
|
35
|
+
if (keys.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
const normalized = Object.create(null);
|
|
38
|
+
for (const key of keys)
|
|
39
|
+
normalized[key] = normalizeValue(record[key]);
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
function applyDescriptionAndTags(draft, contract) {
|
|
43
|
+
if (contract.description !== void 0)
|
|
44
|
+
draft.description = contract.description;
|
|
45
|
+
if (contract.tags !== void 0 && contract.tags.length > 0)
|
|
46
|
+
draft.tags = [...contract.tags].toSorted();
|
|
47
|
+
}
|
|
48
|
+
function applySchemas(draft, contract) {
|
|
49
|
+
const inputSchema = cloneSchemaRecord(contract.inputSchema);
|
|
50
|
+
if (inputSchema !== void 0)
|
|
51
|
+
draft.inputSchema = inputSchema;
|
|
52
|
+
const outputSchema = cloneSchemaRecord(contract.outputSchema);
|
|
53
|
+
if (outputSchema !== void 0)
|
|
54
|
+
draft.outputSchema = outputSchema;
|
|
55
|
+
}
|
|
56
|
+
function applyMessageRecords(draft, contract) {
|
|
57
|
+
const signals = sortedContractRecord(contract.signals, normalizeMessageContract);
|
|
58
|
+
if (signals !== void 0)
|
|
59
|
+
draft.signals = signals;
|
|
60
|
+
const updates = sortedContractRecord(contract.updates, normalizeMessageContract);
|
|
61
|
+
if (updates !== void 0)
|
|
62
|
+
draft.updates = updates;
|
|
63
|
+
const queries = sortedContractRecord(contract.queries, normalizeMessageContract);
|
|
64
|
+
if (queries !== void 0)
|
|
65
|
+
draft.queries = queries;
|
|
66
|
+
}
|
|
67
|
+
export function normalizeWorkflowContract(contract) {
|
|
68
|
+
const draft = {
|
|
69
|
+
name: contract.name,
|
|
70
|
+
workflowVersion: contract.workflowVersion
|
|
71
|
+
};
|
|
72
|
+
applyDescriptionAndTags(draft, contract);
|
|
73
|
+
applySchemas(draft, contract);
|
|
74
|
+
applyMessageRecords(draft, contract);
|
|
75
|
+
const activities = sortedContractRecord(contract.activities, normalizeActivityContract);
|
|
76
|
+
if (activities !== void 0)
|
|
77
|
+
draft.activities = activities;
|
|
78
|
+
if (contract.finalizer !== void 0)
|
|
79
|
+
draft.finalizer = normalizeActivityContract(contract.finalizer);
|
|
80
|
+
return draft;
|
|
81
|
+
}
|
|
82
|
+
export function canonicalMessageContractJson(entry) {
|
|
83
|
+
const fields = [];
|
|
84
|
+
if (entry.inputSchema !== void 0)
|
|
85
|
+
fields.push(`"inputSchema":${canonicalJsonStringify(entry.inputSchema)}`);
|
|
86
|
+
if (entry.outputSchema !== void 0)
|
|
87
|
+
fields.push(`"outputSchema":${canonicalJsonStringify(entry.outputSchema)}`);
|
|
88
|
+
return `{${fields.join(",")}}`;
|
|
89
|
+
}
|
|
90
|
+
export function canonicalContractRecordJson(record) {
|
|
91
|
+
return `{${sortedKeys(record).map((key) => `${JSON.stringify(key)}:${canonicalMessageContractJson(record[key])}`).join(",")}}`;
|
|
92
|
+
}
|
|
93
|
+
export function appendContractRecordField(fields, label, record) {
|
|
94
|
+
if (record === void 0 || Object.keys(record).length === 0)
|
|
95
|
+
return;
|
|
96
|
+
fields.push(`"${label}":${canonicalContractRecordJson(record)}`);
|
|
97
|
+
}
|
|
98
|
+
export function appendSchemaField(fields, label, schema) {
|
|
99
|
+
if (schema === void 0)
|
|
100
|
+
return;
|
|
101
|
+
fields.push(`"${label}":${canonicalJsonStringify(schema)}`);
|
|
102
|
+
}
|
|
103
|
+
function appendTagsField(fields, tags) {
|
|
104
|
+
if (tags === void 0 || tags.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const sortedTags = [...tags].toSorted().map((tag) => JSON.stringify(tag));
|
|
107
|
+
fields.push(`"tags":[${sortedTags.join(",")}]`);
|
|
108
|
+
}
|
|
109
|
+
export function canonicalWorkflowContractJson(contract) {
|
|
110
|
+
const fields = [
|
|
111
|
+
`"contractVersion":${JSON.stringify(WORKFLOW_CONTRACT_VERSION)}`,
|
|
112
|
+
`"name":${JSON.stringify(contract.name)}`,
|
|
113
|
+
`"workflowVersion":${JSON.stringify(contract.workflowVersion)}`
|
|
114
|
+
];
|
|
115
|
+
if (contract.description !== void 0)
|
|
116
|
+
fields.push(`"description":${JSON.stringify(contract.description)}`);
|
|
117
|
+
appendTagsField(fields, contract.tags);
|
|
118
|
+
appendSchemaField(fields, "inputSchema", contract.inputSchema);
|
|
119
|
+
appendSchemaField(fields, "outputSchema", contract.outputSchema);
|
|
120
|
+
appendContractRecordField(fields, "signals", contract.signals);
|
|
121
|
+
appendContractRecordField(fields, "updates", contract.updates);
|
|
122
|
+
appendContractRecordField(fields, "queries", contract.queries);
|
|
123
|
+
appendContractRecordField(fields, "activities", contract.activities);
|
|
124
|
+
if (contract.finalizer !== void 0)
|
|
125
|
+
fields.push(`"finalizer":${canonicalMessageContractJson(contract.finalizer)}`);
|
|
126
|
+
return `{${fields.join(",")}}`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-derived workflow revision identity.
|
|
3
|
+
*
|
|
4
|
+
* `revision` is the broader identity companion to `contractHash`: it hashes
|
|
5
|
+
* the *entire* normalized contract — `name`, `workflowVersion`,
|
|
6
|
+
* `description`, and `tags` included — so it changes whenever anything
|
|
7
|
+
* about the definition changes, including documentation. This is the same
|
|
8
|
+
* formula the (now-refactored) `buildWorkerManifestFromRegistry()` used for
|
|
9
|
+
* its `workflowRevision` field before this module existed.
|
|
10
|
+
*
|
|
11
|
+
* @module core/contract/revision
|
|
12
|
+
*/
|
|
13
|
+
import type { WorkflowContract } from './types.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Derive a content-addressed revision identity for a workflow contract.
|
|
16
|
+
*
|
|
17
|
+
* Two calls on an equivalent contract (regardless of source key order)
|
|
18
|
+
* produce the same revision, and any field the full canonical serialization
|
|
19
|
+
* carries — including `description` and `tags` — changes it.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { deriveWorkflowRevision } from '@lostgradient/weft';
|
|
24
|
+
*
|
|
25
|
+
* const revision = await deriveWorkflowRevision({ name: 'checkout', workflowVersion: '2.1.0' });
|
|
26
|
+
* console.log(revision.startsWith('sha256:')); // true
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function deriveWorkflowRevision(contract: WorkflowContract): Promise<string>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { digestCanonicalWorkflowContract } from "./hash.js";
|
|
2
|
+
import { canonicalWorkflowContractJson } from "./normalize.js";
|
|
3
|
+
export async function deriveWorkflowRevision(contract) {
|
|
4
|
+
return digestCanonicalWorkflowContract(canonicalWorkflowContractJson(contract));
|
|
5
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical, normalized workflow contract vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A `WorkflowContract` is the single normalized representation that feeds
|
|
5
|
+
* both TypeScript code generation (`weft codegen`) and `contractHash()` — the
|
|
6
|
+
* same data, described once, rather than two independent derivations that
|
|
7
|
+
* could silently drift apart. `WorkflowRevisionManifest` pairs that contract
|
|
8
|
+
* with the two identity questions a consumer asks about a workflow revision:
|
|
9
|
+
*
|
|
10
|
+
* | Field | Question answered | Stability |
|
|
11
|
+
* | -------------- | ----------------------------------------------------- | ----------------------------- |
|
|
12
|
+
* | `contractHash` | Which public payload contract does this revision use? | Deterministic, payload-only |
|
|
13
|
+
* | `revision` | Which contract-metadata identity (name, version, docs) is this? | Deterministic or author-set |
|
|
14
|
+
*
|
|
15
|
+
* `contractHash` deliberately excludes `name`, `workflowVersion`,
|
|
16
|
+
* `description`, and `tags` — those identify *which* workflow and *how it is
|
|
17
|
+
* documented*, not *what callers may send and expect back*. `revision` is the
|
|
18
|
+
* broader identity: it changes on a documentation edit even when the payload
|
|
19
|
+
* contract does not, which is what lets a caller detect "the same contract,
|
|
20
|
+
* redeployed" versus "a genuinely different definition was loaded."
|
|
21
|
+
*
|
|
22
|
+
* @module core/contract/types
|
|
23
|
+
*/
|
|
24
|
+
import type { DefinitionSchema } from '../types/definition-schema.ts';
|
|
25
|
+
/**
|
|
26
|
+
* Domain separator folded into every `contractHash()`/`deriveWorkflowRevision()`
|
|
27
|
+
* digest. Bumping this constant is how a future normalization change is
|
|
28
|
+
* guaranteed to produce a different hash rather than silently colliding with
|
|
29
|
+
* one computed under the old rules.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { WORKFLOW_CONTRACT_VERSION } from '@lostgradient/weft';
|
|
34
|
+
*
|
|
35
|
+
* console.log(WORKFLOW_CONTRACT_VERSION); // 1
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const WORKFLOW_CONTRACT_VERSION = 1;
|
|
39
|
+
/**
|
|
40
|
+
* Normalized JSON Schema pair carried by one workflow signal, update, or
|
|
41
|
+
* query.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import type { WorkflowMessageContract } from '@lostgradient/weft';
|
|
46
|
+
*
|
|
47
|
+
* const approval: WorkflowMessageContract = {
|
|
48
|
+
* inputSchema: { type: 'object', properties: { approved: { type: 'boolean' } } },
|
|
49
|
+
* };
|
|
50
|
+
* console.log(approval.inputSchema?.['type']);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export type WorkflowMessageContract = Readonly<{
|
|
54
|
+
/** Normalized JSON Schema describing accepted input, when the message accepts one. */
|
|
55
|
+
inputSchema?: Record<string, unknown>;
|
|
56
|
+
/** Normalized JSON Schema describing the returned value, when the message returns one. */
|
|
57
|
+
outputSchema?: Record<string, unknown>;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Normalized JSON Schema pair carried by one activity a workflow contract
|
|
61
|
+
* exposes.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import type { WorkflowActivityContract } from '@lostgradient/weft';
|
|
66
|
+
*
|
|
67
|
+
* const charge: WorkflowActivityContract = {
|
|
68
|
+
* inputSchema: { type: 'object', properties: { amount: { type: 'number' } } },
|
|
69
|
+
* outputSchema: { type: 'object', properties: { id: { type: 'string' } } },
|
|
70
|
+
* };
|
|
71
|
+
* console.log(charge.inputSchema?.['type']);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export type WorkflowActivityContract = Readonly<{
|
|
75
|
+
/** Normalized JSON Schema describing accepted input, when the activity accepts one. */
|
|
76
|
+
inputSchema?: Record<string, unknown>;
|
|
77
|
+
/** Normalized JSON Schema describing the returned value, when the activity returns one. */
|
|
78
|
+
outputSchema?: Record<string, unknown>;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Canonical, normalized representation of one workflow's public contract —
|
|
82
|
+
* everything callers may send it and expect back, plus enough identity
|
|
83
|
+
* metadata (`name`, `workflowVersion`, `description`, `tags`) to describe
|
|
84
|
+
* which workflow this is. This is the single input to both TypeScript
|
|
85
|
+
* emission and `contractHash()`.
|
|
86
|
+
*
|
|
87
|
+
* Every open-ended record (`signals`, `updates`, `queries`, `activities`) is
|
|
88
|
+
* omitted entirely rather than present-but-empty, matching the registry
|
|
89
|
+
* snapshot's "absent fields omitted, never `null` or `{}`" convention — see
|
|
90
|
+
* {@link normalizeWorkflowContract}.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import type { WorkflowContract } from '@lostgradient/weft';
|
|
95
|
+
*
|
|
96
|
+
* const checkout: WorkflowContract = {
|
|
97
|
+
* name: 'checkout',
|
|
98
|
+
* workflowVersion: '2.1.0',
|
|
99
|
+
* inputSchema: { type: 'object', properties: { cartId: { type: 'string' } } },
|
|
100
|
+
* activities: {
|
|
101
|
+
* charge: { inputSchema: { type: 'object', properties: { amount: { type: 'number' } } } },
|
|
102
|
+
* },
|
|
103
|
+
* };
|
|
104
|
+
* console.log(checkout.name);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export type WorkflowContract = Readonly<{
|
|
108
|
+
/** Wire-safe workflow name. */
|
|
109
|
+
name: string;
|
|
110
|
+
/** Semantic replay-compatibility boundary declared by the workflow author. */
|
|
111
|
+
workflowVersion: string;
|
|
112
|
+
/** User-facing description. Excluded from `contractHash()`; included in `revision`. */
|
|
113
|
+
description?: string;
|
|
114
|
+
/** User-facing grouping tags. Excluded from `contractHash()`; included in `revision`. */
|
|
115
|
+
tags?: ReadonlyArray<string>;
|
|
116
|
+
/** Normalized JSON Schema describing accepted workflow input, when declared. */
|
|
117
|
+
inputSchema?: Record<string, unknown>;
|
|
118
|
+
/** Normalized JSON Schema describing the workflow result, when declared. */
|
|
119
|
+
outputSchema?: Record<string, unknown>;
|
|
120
|
+
/** Signal contracts keyed by canonical signal name. */
|
|
121
|
+
signals?: Readonly<Record<string, WorkflowMessageContract>>;
|
|
122
|
+
/** Update contracts keyed by canonical update name. */
|
|
123
|
+
updates?: Readonly<Record<string, WorkflowMessageContract>>;
|
|
124
|
+
/** Query contracts keyed by canonical query name. */
|
|
125
|
+
queries?: Readonly<Record<string, WorkflowMessageContract>>;
|
|
126
|
+
/** Activity contracts keyed by canonical activity name. */
|
|
127
|
+
activities?: Readonly<Record<string, WorkflowActivityContract>>;
|
|
128
|
+
/** Contract of the workflow's definition-level finalizer activity, when declared. */
|
|
129
|
+
finalizer?: WorkflowActivityContract;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Structural shape one signal, update, or query source must satisfy for
|
|
133
|
+
* {@link buildWorkflowContract} to convert it. Deliberately narrower than
|
|
134
|
+
* `SignalDefinition`/`UpdateDefinition`/`QueryDefinition` — only the schema
|
|
135
|
+
* metadata those conversions need — so any of the three handle types (and
|
|
136
|
+
* any structurally equivalent value) satisfies it without a cast.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* import { signal } from '@lostgradient/weft';
|
|
141
|
+
* import type { WorkflowContractMessageSource } from '@lostgradient/weft';
|
|
142
|
+
*
|
|
143
|
+
* const approval = signal('approval');
|
|
144
|
+
* const source: WorkflowContractMessageSource = approval;
|
|
145
|
+
* console.log(source.inputSchema === undefined);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export interface WorkflowContractMessageSource {
|
|
149
|
+
/**
|
|
150
|
+
* The message's own wire name, as recorded by `signal()`/`update()`/`query()`
|
|
151
|
+
* and used by `normalizeMessageDefinitions()` (`core/engine/registration.ts`)
|
|
152
|
+
* to key the registered message — not necessarily the JS object key the
|
|
153
|
+
* caller used in `.signals({...})`/`.updates({...})`/`.queries({...})`,
|
|
154
|
+
* which may alias a different local name. {@link buildWorkflowContract}
|
|
155
|
+
* keys `WorkflowContract.signals`/`updates`/`queries` by this field so the
|
|
156
|
+
* built contract matches what the registry and codegen actually expose.
|
|
157
|
+
*/
|
|
158
|
+
readonly name: string;
|
|
159
|
+
readonly inputSchema?: DefinitionSchema;
|
|
160
|
+
readonly outputSchema?: DefinitionSchema;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Structural shape one activity or finalizer source must satisfy for
|
|
164
|
+
* {@link buildWorkflowContract} to convert it. Requires `name` — shared with
|
|
165
|
+
* every real activity source type — specifically so a `WorkflowDefinition`'s
|
|
166
|
+
* `finalizer` field (declared as the narrower `AnyActivityDefinition`, which
|
|
167
|
+
* does not itself declare `inputSchema`/`outputSchema`) remains structurally
|
|
168
|
+
* assignable here: TypeScript's weak-type check requires at least one
|
|
169
|
+
* property in common between source and target when every target property is
|
|
170
|
+
* optional, and `name` is that shared property.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import { activity } from '@lostgradient/weft';
|
|
175
|
+
* import type { WorkflowContractActivitySource } from '@lostgradient/weft';
|
|
176
|
+
*
|
|
177
|
+
* const charge = activity({ name: 'charge', execute: async (input: number) => input });
|
|
178
|
+
* const source: WorkflowContractActivitySource = charge;
|
|
179
|
+
* console.log(source.name);
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export interface WorkflowContractActivitySource {
|
|
183
|
+
readonly name: string;
|
|
184
|
+
readonly inputSchema?: DefinitionSchema;
|
|
185
|
+
readonly outputSchema?: DefinitionSchema;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Authoring-time input accepted by {@link buildWorkflowContract} — the
|
|
189
|
+
* narrowest structural shape that both `WorkflowDefinition` and
|
|
190
|
+
* `BuiltWorkflowDefinition` already satisfy, so neither type is imported here
|
|
191
|
+
* and no coupling is introduced between this module and the workflow builder.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* import { workflow } from '@lostgradient/weft';
|
|
196
|
+
* import type { WorkflowContractSource } from '@lostgradient/weft';
|
|
197
|
+
*
|
|
198
|
+
* const greet = workflow({ name: 'greet', version: '1.0.0' }).execute(
|
|
199
|
+
* async function* (_ctx, input: string) {
|
|
200
|
+
* return `hello ${input}`;
|
|
201
|
+
* },
|
|
202
|
+
* );
|
|
203
|
+
* const source: WorkflowContractSource = greet;
|
|
204
|
+
* console.log(source.name);
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export interface WorkflowContractSource {
|
|
208
|
+
readonly name: string;
|
|
209
|
+
readonly version?: string;
|
|
210
|
+
readonly description?: string;
|
|
211
|
+
readonly tags?: ReadonlyArray<string>;
|
|
212
|
+
readonly inputSchema?: DefinitionSchema;
|
|
213
|
+
readonly outputSchema?: DefinitionSchema;
|
|
214
|
+
readonly signals?: Readonly<Record<string, WorkflowContractMessageSource>>;
|
|
215
|
+
readonly updates?: Readonly<Record<string, WorkflowContractMessageSource>>;
|
|
216
|
+
readonly queries?: Readonly<Record<string, WorkflowContractMessageSource>>;
|
|
217
|
+
readonly activities?: Readonly<Record<string, WorkflowContractActivitySource>>;
|
|
218
|
+
readonly finalizer?: WorkflowContractActivitySource;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Current workflow revision manifest schema version. An unknown value is
|
|
222
|
+
* rejected by {@link parseWorkflowRevisionManifest} before any other field is
|
|
223
|
+
* read, the same "reject rather than best-effort parse" contract
|
|
224
|
+
* `WORKER_MANIFEST_VERSION` uses.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* import { WORKFLOW_REVISION_MANIFEST_VERSION } from '@lostgradient/weft';
|
|
229
|
+
*
|
|
230
|
+
* console.log(WORKFLOW_REVISION_MANIFEST_VERSION); // 1
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export declare const WORKFLOW_REVISION_MANIFEST_VERSION = 1;
|
|
234
|
+
/**
|
|
235
|
+
* A workflow contract paired with its two identity answers: the
|
|
236
|
+
* payload-only `contractHash` and the broader `revision`.
|
|
237
|
+
*
|
|
238
|
+
* `revision` is an opaque label: content-derived by {@link deriveWorkflowRevision}
|
|
239
|
+
* by default, or explicitly supplied. {@link parseWorkflowRevisionManifest}
|
|
240
|
+
* recomputes and checks `contractHash` on every parse, but never recomputes
|
|
241
|
+
* `revision` — an explicitly supplied revision is a caller assertion, not a
|
|
242
|
+
* value this module can verify.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { WORKFLOW_REVISION_MANIFEST_VERSION, buildWorkflowContract } from '@lostgradient/weft';
|
|
247
|
+
* import type { WorkflowRevisionManifest } from '@lostgradient/weft';
|
|
248
|
+
*
|
|
249
|
+
* const contract = buildWorkflowContract({ name: 'checkout', version: '2.1.0' });
|
|
250
|
+
* const manifest: WorkflowRevisionManifest = {
|
|
251
|
+
* manifestVersion: WORKFLOW_REVISION_MANIFEST_VERSION,
|
|
252
|
+
* name: contract.name,
|
|
253
|
+
* workflowVersion: contract.workflowVersion,
|
|
254
|
+
* revision: 'sha256:placeholder',
|
|
255
|
+
* contractHash: 'sha256:placeholder',
|
|
256
|
+
* contract,
|
|
257
|
+
* };
|
|
258
|
+
* console.log(manifest.name);
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export type WorkflowRevisionManifest = Readonly<{
|
|
262
|
+
/** Manifest schema version; unknown values are rejected, not tolerated. */
|
|
263
|
+
manifestVersion: typeof WORKFLOW_REVISION_MANIFEST_VERSION;
|
|
264
|
+
/** Wire-safe workflow name; must equal `contract.name`. */
|
|
265
|
+
name: string;
|
|
266
|
+
/** Semantic replay-compatibility boundary; must equal `contract.workflowVersion`. */
|
|
267
|
+
workflowVersion: string;
|
|
268
|
+
/** Content-derived or explicitly supplied opaque revision identity. */
|
|
269
|
+
revision: string;
|
|
270
|
+
/** Deterministic payload-only contract identity, verified on every parse. */
|
|
271
|
+
contractHash: string;
|
|
272
|
+
/** The normalized workflow contract this manifest describes. */
|
|
273
|
+
contract: WorkflowContract;
|
|
274
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WORKFLOW_CONTRACT_VERSION = 1, WORKFLOW_REVISION_MANIFEST_VERSION = 1;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `activateCatalogRevisionCandidate` — a thin engine-level wrapper around
|
|
3
|
+
* `WorkflowCatalog.activateCandidate()`, the guarded activation primitive
|
|
4
|
+
* (WFT-9/WFT-10). Awaits catalog readiness, reads pre-activation state,
|
|
5
|
+
* calls the guarded primitive, dispatches the correct catalog event(s), and
|
|
6
|
+
* returns the primitive's own structured result verbatim:
|
|
7
|
+
* the shared installed/activated/draining helper on success, or
|
|
8
|
+
* `WorkflowRevisionActivationRejectedEvent` directly on refusal.
|
|
9
|
+
*
|
|
10
|
+
* `engine.workflows.activate()` (WFT-11,
|
|
11
|
+
* `core/engine/engine-workflows-namespace.ts`) is this wrapper's production
|
|
12
|
+
* caller — the only practical way an external caller reaches the guarded
|
|
13
|
+
* candidate-activation primitive, so routing it through here (rather than
|
|
14
|
+
* calling `WorkflowCatalog.activateCandidate()` directly) is what makes a
|
|
15
|
+
* manual activation dispatch `catalog:revision-installed`/
|
|
16
|
+
* `catalog:revision-activated`/`catalog:revision-draining`/
|
|
17
|
+
* `catalog:activation-rejected` the same way `engine.register()`'s
|
|
18
|
+
* drain path already does.
|
|
19
|
+
*
|
|
20
|
+
* Never imports `./index.ts` as a value (only as a type, matching
|
|
21
|
+
* `catalog-readiness.ts`'s own import-cycle discipline for itself) — the
|
|
22
|
+
* `Engine` class import here is `import type`, erased at build time.
|
|
23
|
+
*
|
|
24
|
+
* @module core/engine/catalog-activation
|
|
25
|
+
*/
|
|
26
|
+
import type { ActivateCandidateOptions, WorkflowCatalogActivationResult } from '../catalog/index.ts';
|
|
27
|
+
import type { WorkflowRevisionManifest } from '../contract/types.ts';
|
|
28
|
+
import type { Engine } from './index.ts';
|
|
29
|
+
/**
|
|
30
|
+
* Activate `candidateManifest` for `name` through the catalog's guarded
|
|
31
|
+
* primitive (`checkWorkflowCompatibility`-gated, single-shot CAS), and
|
|
32
|
+
* return its structured result verbatim. On success, dispatches
|
|
33
|
+
* `catalog:revision-installed` (when genuinely new content),
|
|
34
|
+
* `catalog:revision-draining` (when displacing a prior active revision),
|
|
35
|
+
* and `catalog:revision-activated`. On refusal, dispatches
|
|
36
|
+
* `catalog:activation-rejected` carrying only the bounded `reason` code
|
|
37
|
+
* (and, for `'incompatible'`, the bounded `incompatibilityReasons` array) —
|
|
38
|
+
* never the full `WorkflowCompatibilityVerdict` object.
|
|
39
|
+
*/
|
|
40
|
+
export declare function activateCatalogRevisionCandidate(engine: Engine, name: string, candidateManifest: WorkflowRevisionManifest, options?: ActivateCandidateOptions): Promise<WorkflowCatalogActivationResult>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WorkflowRevisionActivationRejectedEvent } from "../events/catalog-events.js";
|
|
2
|
+
import { dispatchCatalogInstallAndActivatedEvents } from "./catalog-events.js";
|
|
3
|
+
import { ensureWorkflowCatalogReady, getWorkflowCatalog } from "./catalog-readiness.js";
|
|
4
|
+
export async function activateCatalogRevisionCandidate(engine, name, candidateManifest, options) {
|
|
5
|
+
await ensureWorkflowCatalogReady(engine);
|
|
6
|
+
const catalog = getWorkflowCatalog(engine), preExisting = await catalog.hasInstalled(name, candidateManifest.revision), pointerBefore = await catalog.resolveActiveDurable(name) ?? null, result = await catalog.activateCandidate(name, candidateManifest, options);
|
|
7
|
+
if (result.applied) {
|
|
8
|
+
dispatchCatalogInstallAndActivatedEvents(engine, name, candidateManifest.revision, preExisting, pointerBefore, result.pointer);
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
if (result.reason === "incompatible") {
|
|
12
|
+
engine.dispatchEvent(new WorkflowRevisionActivationRejectedEvent(name, candidateManifest.revision, result.reason, result.verdict.compatible ? void 0 : result.verdict.reasons));
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
engine.dispatchEvent(new WorkflowRevisionActivationRejectedEvent(name, candidateManifest.revision, result.reason));
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared installed/activated/draining event-dispatch helper (WFT-12), used
|
|
3
|
+
* by BOTH producers of catalog activation: `catalog-readiness.ts`'s
|
|
4
|
+
* `drainPendingCatalogInstalls` (the `engine.register()`-drain path) and
|
|
5
|
+
* `catalog-activation.ts`'s `activateCatalogRevisionCandidate` (the guarded
|
|
6
|
+
* primitive). The two producers return different result shapes
|
|
7
|
+
* (`activateRegistered` always succeeds and returns a bare pointer;
|
|
8
|
+
* `activateCandidate` returns a union that can also refuse) — this helper
|
|
9
|
+
* takes already-normalized before/after pointer values instead of either
|
|
10
|
+
* raw return type, so it stays agnostic to which producer called it.
|
|
11
|
+
* `activation-rejected` is NOT dispatched here — only `activateCandidate`
|
|
12
|
+
* can refuse, so its one caller dispatches that event itself.
|
|
13
|
+
*
|
|
14
|
+
* @module core/engine/catalog-events
|
|
15
|
+
*/
|
|
16
|
+
import type { WorkflowCatalogActivePointer } from '../catalog/index.ts';
|
|
17
|
+
import type { Engine } from './index.ts';
|
|
18
|
+
/**
|
|
19
|
+
* Dispatch `catalog:revision-installed` (only when `preExisting` is
|
|
20
|
+
* `false` — genuinely new content, not a byte-identical reinstall or a
|
|
21
|
+
* cross-process durable adoption of already-present content), then decide
|
|
22
|
+
* activated/draining from how `pointerBefore` and `pointerAfter` compare:
|
|
23
|
+
*
|
|
24
|
+
* - Identical `revision` AND `generation` (a true no-op — nothing was
|
|
25
|
+
* durably written, e.g. `activateRegistered`'s own no-op branch): neither
|
|
26
|
+
* fires.
|
|
27
|
+
* - Same `revision`, different `generation` (a reactivation that still
|
|
28
|
+
* bumped the fencing counter, e.g. `activateCandidate` re-activating the
|
|
29
|
+
* currently active revision): `catalog:revision-activated` fires with
|
|
30
|
+
* `previousRevision: undefined` — nothing was actually displaced, so
|
|
31
|
+
* there is nothing to drain.
|
|
32
|
+
* - Different `revision`: `catalog:revision-draining` for `pointerBefore`
|
|
33
|
+
* (when it existed) followed by `catalog:revision-activated` for
|
|
34
|
+
* `pointerAfter`, `previousRevision` set to the displaced revision.
|
|
35
|
+
*/
|
|
36
|
+
export declare function dispatchCatalogInstallAndActivatedEvents(engine: Engine, name: string, revision: string, preExisting: boolean, pointerBefore: WorkflowCatalogActivePointer | null, pointerAfter: WorkflowCatalogActivePointer): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkflowRevisionActivatedEvent,
|
|
3
|
+
WorkflowRevisionDrainingEvent,
|
|
4
|
+
WorkflowRevisionInstalledEvent
|
|
5
|
+
} from "../events/catalog-events.js";
|
|
6
|
+
export function dispatchCatalogInstallAndActivatedEvents(engine, name, revision, preExisting, pointerBefore, pointerAfter) {
|
|
7
|
+
if (!preExisting)
|
|
8
|
+
engine.dispatchEvent(new WorkflowRevisionInstalledEvent(name, revision));
|
|
9
|
+
if (pointerBefore !== null && pointerBefore.revision === pointerAfter.revision && pointerBefore.generation === pointerAfter.generation)
|
|
10
|
+
return;
|
|
11
|
+
const previousRevision = pointerBefore !== null && pointerBefore.revision !== pointerAfter.revision ? pointerBefore.revision : void 0;
|
|
12
|
+
if (previousRevision !== void 0)
|
|
13
|
+
engine.dispatchEvent(new WorkflowRevisionDrainingEvent(name, previousRevision));
|
|
14
|
+
engine.dispatchEvent(new WorkflowRevisionActivatedEvent(name, pointerAfter.revision, pointerAfter.generation, previousRevision));
|
|
15
|
+
}
|