@graphrefly/ts 0.0.0 → 0.0.1
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 +135 -0
- package/dist/adapters/index.cjs +105 -66
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +11 -136
- package/dist/adapters/index.d.ts +11 -136
- package/dist/adapters/index.js +27 -225
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/observe-storage.d.cts +4 -4
- package/dist/adapters/observe-storage.d.ts +4 -4
- package/dist/adapters/observe-storage.js +1 -0
- package/dist/adapters/observe-storage.js.map +1 -1
- package/dist/adapters/react.cjs +166 -0
- package/dist/adapters/react.cjs.map +1 -0
- package/dist/adapters/react.d.cts +31 -0
- package/dist/adapters/react.d.ts +31 -0
- package/dist/adapters/react.js +51 -0
- package/dist/adapters/react.js.map +1 -0
- package/dist/adapters/solid.cjs +157 -0
- package/dist/adapters/solid.cjs.map +1 -0
- package/dist/adapters/solid.d.cts +26 -0
- package/dist/adapters/solid.d.ts +26 -0
- package/dist/adapters/solid.js +48 -0
- package/dist/adapters/solid.js.map +1 -0
- package/dist/adapters/svelte.cjs +148 -0
- package/dist/adapters/svelte.cjs.map +1 -0
- package/dist/adapters/svelte.d.cts +31 -0
- package/dist/adapters/svelte.d.ts +31 -0
- package/dist/adapters/svelte.js +46 -0
- package/dist/adapters/svelte.js.map +1 -0
- package/dist/adapters/vue.cjs +155 -0
- package/dist/adapters/vue.cjs.map +1 -0
- package/dist/adapters/vue.d.cts +26 -0
- package/dist/adapters/vue.d.ts +26 -0
- package/dist/adapters/vue.js +46 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/agent-runtime-BesGDZcB.d.ts +555 -0
- package/dist/agent-runtime-DZTJ3C-p.d.cts +555 -0
- package/dist/agentic-memory-BmRCvh0d.d.ts +517 -0
- package/dist/agentic-memory-DfuVZ-lY.d.cts +517 -0
- package/dist/capability-admission-B0BzqAm5.d.cts +102 -0
- package/dist/capability-admission-g0mn9hpq.d.ts +102 -0
- package/dist/{chunk-JR4IKCR2.js → chunk-2747WN4Y.js} +5 -5
- package/dist/{chunk-2ILL7HQI.js → chunk-3JR3EDB2.js} +17 -17
- package/dist/{chunk-2ILL7HQI.js.map → chunk-3JR3EDB2.js.map} +1 -1
- package/dist/{chunk-GDL26N2T.js → chunk-3KN7NJDR.js} +2 -2
- package/dist/{chunk-DJC4CHLQ.js → chunk-43BKLFZU.js} +9 -9
- package/dist/chunk-6XAIOL3X.js +2407 -0
- package/dist/chunk-6XAIOL3X.js.map +1 -0
- package/dist/{chunk-SCRFRCGR.js → chunk-BSQGE5PS.js} +1 -1
- package/dist/{chunk-Y3ZLRGHC.js → chunk-CZNV3VXM.js} +16 -16
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/{chunk-JIMUDQN3.js → chunk-FBUU43PM.js} +2 -2
- package/dist/chunk-H75VXTGT.js +219 -0
- package/dist/chunk-H75VXTGT.js.map +1 -0
- package/dist/{chunk-RIIOUTGM.js → chunk-HKK2JD7Z.js} +12 -9
- package/dist/chunk-HKK2JD7Z.js.map +1 -0
- package/dist/{chunk-D6BG7U6A.js → chunk-IXZBUWSP.js} +3 -3
- package/dist/chunk-PC3BTTBE.js +3217 -0
- package/dist/chunk-PC3BTTBE.js.map +1 -0
- package/dist/chunk-QFY4AFY4.js +879 -0
- package/dist/chunk-QFY4AFY4.js.map +1 -0
- package/dist/{chunk-DK554R7P.js → chunk-S6HXABGL.js} +2 -1
- package/dist/chunk-S6HXABGL.js.map +1 -0
- package/dist/{chunk-PKVBHGBM.js → chunk-SS2A464I.js} +7 -7
- package/dist/chunk-UT3MHJCW.js +49 -0
- package/dist/chunk-UT3MHJCW.js.map +1 -0
- package/dist/{chunk-G4M3NHRJ.js → chunk-V7CIJE4B.js} +3 -3
- package/dist/{chunk-TEW47IVR.js → chunk-VPEFSDME.js} +2 -2
- package/dist/chunk-VTHLCFN4.js +368 -0
- package/dist/chunk-VTHLCFN4.js.map +1 -0
- package/dist/{chunk-SWTLEQL6.js → chunk-WQIA3GFB.js} +2 -2
- package/dist/chunk-WQIA3GFB.js.map +1 -0
- package/dist/{chunk-I3IHOHCJ.js → chunk-XKXWK2KP.js} +108 -64
- package/dist/chunk-XKXWK2KP.js.map +1 -0
- package/dist/{chunk-COHO25CZ.js → chunk-YDSIVHRR.js} +553 -7
- package/dist/chunk-YDSIVHRR.js.map +1 -0
- package/dist/chunk-YXMZ2N6J.js +1884 -0
- package/dist/chunk-YXMZ2N6J.js.map +1 -0
- package/dist/composition/index.cjs +102 -57
- package/dist/composition/index.cjs.map +1 -1
- package/dist/composition/index.d.cts +4 -4
- package/dist/composition/index.d.ts +4 -4
- package/dist/composition/index.js +6 -5
- package/dist/core/index.cjs +102 -57
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +5 -4
- package/dist/cqrs/index.cjs.map +1 -1
- package/dist/cqrs/index.d.cts +4 -4
- package/dist/cqrs/index.d.ts +4 -4
- package/dist/cqrs/index.js +3 -2
- package/dist/cqrs/index.js.map +1 -1
- package/dist/cqrs/messaging.cjs +312 -0
- package/dist/cqrs/messaging.cjs.map +1 -0
- package/dist/cqrs/messaging.d.cts +57 -0
- package/dist/cqrs/messaging.d.ts +57 -0
- package/dist/cqrs/messaging.js +274 -0
- package/dist/cqrs/messaging.js.map +1 -0
- package/dist/cqrs/work-queue.cjs +329 -0
- package/dist/cqrs/work-queue.cjs.map +1 -0
- package/dist/cqrs/work-queue.d.cts +90 -0
- package/dist/cqrs/work-queue.d.ts +90 -0
- package/dist/cqrs/work-queue.js +292 -0
- package/dist/cqrs/work-queue.js.map +1 -0
- package/dist/data/index.cjs +19 -0
- package/dist/data/index.cjs.map +1 -0
- package/dist/data/index.d.cts +36 -0
- package/dist/data/index.d.ts +36 -0
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data-structures/index.cjs +105 -57
- package/dist/data-structures/index.cjs.map +1 -1
- package/dist/data-structures/index.d.cts +8 -8
- package/dist/data-structures/index.d.ts +8 -8
- package/dist/data-structures/index.js +7 -6
- package/dist/{describe-BZb7AS2y.d.ts → describe-BpyJuWkw.d.ts} +1 -1
- package/dist/{describe-Ciu8SUnN.d.cts → describe-DYMw_l1C.d.cts} +1 -1
- package/dist/executors/work-queue.cjs +342 -0
- package/dist/executors/work-queue.cjs.map +1 -0
- package/dist/executors/work-queue.d.cts +90 -0
- package/dist/executors/work-queue.d.ts +90 -0
- package/dist/executors/work-queue.js +306 -0
- package/dist/executors/work-queue.js.map +1 -0
- package/dist/graph/index.cjs +112 -58
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +9 -9
- package/dist/graph/index.d.ts +9 -9
- package/dist/graph/index.js +18 -17
- package/dist/{graph-DfdF9zy4.d.ts → graph-D-1-SQeC.d.ts} +3 -3
- package/dist/{graph-s23RbbFO.d.cts → graph-DCEWh4T2.d.cts} +3 -3
- package/dist/{index-CAEmLCYF.d.cts → index-72VZC0-E.d.cts} +2 -2
- package/dist/{index-SnzZu3x7.d.ts → index-Bmxeu0nK.d.ts} +2 -2
- package/dist/index.cjs +112 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.js +118 -113
- package/dist/inspection/boundary.cjs +98 -0
- package/dist/inspection/boundary.cjs.map +1 -0
- package/dist/inspection/boundary.d.cts +61 -0
- package/dist/inspection/boundary.d.ts +61 -0
- package/dist/inspection/boundary.js +75 -0
- package/dist/inspection/boundary.js.map +1 -0
- package/dist/{messages-BZqrGGm1.d.ts → messages-CAzUPPJM.d.cts} +10 -5
- package/dist/{messages-BZqrGGm1.d.cts → messages-CAzUPPJM.d.ts} +10 -5
- package/dist/messaging/index.cjs +744 -613
- package/dist/messaging/index.cjs.map +1 -1
- package/dist/messaging/index.d.cts +216 -160
- package/dist/messaging/index.d.ts +216 -160
- package/dist/messaging/index.js +19 -749
- package/dist/messaging/index.js.map +1 -1
- package/dist/operators/index.cjs +102 -57
- package/dist/operators/index.cjs.map +1 -1
- package/dist/operators/index.d.cts +6 -6
- package/dist/operators/index.d.ts +6 -6
- package/dist/operators/index.js +8 -7
- package/dist/orchestration/index.cjs +3333 -59
- package/dist/orchestration/index.cjs.map +1 -1
- package/dist/orchestration/index.d.cts +7 -4
- package/dist/orchestration/index.d.ts +7 -4
- package/dist/orchestration/index.js +49 -8
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/messaging.cjs +314 -0
- package/dist/orchestration/messaging.cjs.map +1 -0
- package/dist/orchestration/messaging.d.cts +59 -0
- package/dist/orchestration/messaging.d.ts +59 -0
- package/dist/orchestration/messaging.js +276 -0
- package/dist/orchestration/messaging.js.map +1 -0
- package/dist/orchestration/work-queue.cjs +262 -0
- package/dist/orchestration/work-queue.cjs.map +1 -0
- package/dist/orchestration/work-queue.d.cts +85 -0
- package/dist/orchestration/work-queue.d.ts +85 -0
- package/dist/orchestration/work-queue.js +226 -0
- package/dist/orchestration/work-queue.js.map +1 -0
- package/dist/patterns/event-flow.cjs +447 -0
- package/dist/patterns/event-flow.cjs.map +1 -0
- package/dist/patterns/event-flow.d.cts +115 -0
- package/dist/patterns/event-flow.d.ts +115 -0
- package/dist/patterns/event-flow.js +14 -0
- package/dist/patterns/event-flow.js.map +1 -0
- package/dist/patterns/index.cjs +423 -18
- package/dist/patterns/index.cjs.map +1 -1
- package/dist/patterns/index.d.cts +8 -5
- package/dist/patterns/index.d.ts +8 -5
- package/dist/patterns/index.js +13 -4
- package/dist/{reactive-collection-Cb1II1Fi.d.ts → reactive-collection-BAmfrGjd.d.ts} +9 -9
- package/dist/{reactive-collection-BxBqtIOE.d.cts → reactive-collection-DNV0-gFi.d.cts} +9 -9
- package/dist/{reactive-collection-storage-D-KmjE0J.d.cts → reactive-collection-storage-BNEldDyi.d.cts} +5 -5
- package/dist/{reactive-collection-storage-BNiEc-Gd.d.ts → reactive-collection-storage-YZsAQsWK.d.ts} +5 -5
- package/dist/{reactive-map-D-g3dv8I.d.cts → reactive-map-DRD1LJ2H.d.cts} +6 -6
- package/dist/{reactive-map-BcB4KsQR.d.ts → reactive-map-EsDiCmJk.d.ts} +6 -6
- package/dist/render/index.d.cts +3 -3
- package/dist/render/index.d.ts +3 -3
- package/dist/render/index.js +1 -0
- package/dist/{semantic-memory-graph-BYoIlP35.d.cts → semantic-memory-graph-DKuNzuWw.d.cts} +2 -2
- package/dist/{semantic-memory-graph-CxyXLms3.d.ts → semantic-memory-graph-uwbwJdhe.d.ts} +2 -2
- package/dist/solutions/index.cjs +4478 -2
- package/dist/solutions/index.cjs.map +1 -1
- package/dist/solutions/index.d.cts +13 -519
- package/dist/solutions/index.d.ts +13 -519
- package/dist/solutions/index.js +90 -6
- package/dist/solutions/reactive-layout/browser/index.cjs +449 -0
- package/dist/solutions/reactive-layout/browser/index.cjs.map +1 -0
- package/dist/solutions/reactive-layout/browser/index.d.cts +44 -0
- package/dist/solutions/reactive-layout/browser/index.d.ts +44 -0
- package/dist/solutions/reactive-layout/browser/index.js +69 -0
- package/dist/solutions/reactive-layout/browser/index.js.map +1 -0
- package/dist/solutions/reactive-layout/index.cjs +4266 -0
- package/dist/solutions/reactive-layout/index.cjs.map +1 -0
- package/dist/solutions/reactive-layout/index.d.cts +581 -0
- package/dist/solutions/reactive-layout/index.d.ts +581 -0
- package/dist/solutions/reactive-layout/index.js +86 -0
- package/dist/solutions/reactive-layout/index.js.map +1 -0
- package/dist/solutions/reactive-layout/node-canvas/index.cjs +545 -0
- package/dist/solutions/reactive-layout/node-canvas/index.cjs.map +1 -0
- package/dist/solutions/reactive-layout/node-canvas/index.d.cts +54 -0
- package/dist/solutions/reactive-layout/node-canvas/index.d.ts +54 -0
- package/dist/solutions/reactive-layout/node-canvas/index.js +131 -0
- package/dist/solutions/reactive-layout/node-canvas/index.js.map +1 -0
- package/dist/solutions/reactive-layout/react-native/index.cjs +494 -0
- package/dist/solutions/reactive-layout/react-native/index.cjs.map +1 -0
- package/dist/solutions/reactive-layout/react-native/index.d.cts +51 -0
- package/dist/solutions/reactive-layout/react-native/index.d.ts +51 -0
- package/dist/solutions/reactive-layout/react-native/index.js +77 -0
- package/dist/solutions/reactive-layout/react-native/index.js.map +1 -0
- package/dist/solutions/reactive-layout/skia/index.cjs +547 -0
- package/dist/solutions/reactive-layout/skia/index.cjs.map +1 -0
- package/dist/solutions/reactive-layout/skia/index.d.cts +64 -0
- package/dist/solutions/reactive-layout/skia/index.d.ts +64 -0
- package/dist/solutions/reactive-layout/skia/index.js +70 -0
- package/dist/solutions/reactive-layout/skia/index.js.map +1 -0
- package/dist/solutions/work-item/actions.cjs +2164 -0
- package/dist/solutions/work-item/actions.cjs.map +1 -0
- package/dist/solutions/work-item/actions.d.cts +201 -0
- package/dist/solutions/work-item/actions.d.ts +201 -0
- package/dist/solutions/work-item/actions.js +1454 -0
- package/dist/solutions/work-item/actions.js.map +1 -0
- package/dist/solutions/work-item/scheduling.cjs +2454 -0
- package/dist/solutions/work-item/scheduling.cjs.map +1 -0
- package/dist/solutions/work-item/scheduling.d.cts +433 -0
- package/dist/solutions/work-item/scheduling.d.ts +433 -0
- package/dist/solutions/work-item/scheduling.js +32 -0
- package/dist/solutions/work-item/scheduling.js.map +1 -0
- package/dist/solutions/work-item/work-queue.cjs +306 -0
- package/dist/solutions/work-item/work-queue.cjs.map +1 -0
- package/dist/solutions/work-item/work-queue.d.cts +71 -0
- package/dist/solutions/work-item/work-queue.d.ts +71 -0
- package/dist/solutions/work-item/work-queue.js +270 -0
- package/dist/solutions/work-item/work-queue.js.map +1 -0
- package/dist/sources/browser.cjs +169 -57
- package/dist/sources/browser.cjs.map +1 -1
- package/dist/sources/browser.d.cts +33 -6
- package/dist/sources/browser.d.ts +33 -6
- package/dist/sources/browser.js +73 -6
- package/dist/sources/browser.js.map +1 -1
- package/dist/sources/index.cjs +102 -57
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.d.cts +5 -5
- package/dist/sources/index.d.ts +5 -5
- package/dist/sources/index.js +6 -5
- package/dist/sources/node.cjs.map +1 -1
- package/dist/sources/node.d.cts +4 -4
- package/dist/sources/node.d.ts +4 -4
- package/dist/sources/node.js +2 -1
- package/dist/sources/node.js.map +1 -1
- package/dist/storage/browser.js +1 -0
- package/dist/storage/browser.js.map +1 -1
- package/dist/storage/index.d.cts +5 -5
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/index.js +5 -4
- package/dist/storage/node.js +1 -0
- package/dist/storage/node.js.map +1 -1
- package/dist/store-C-joM4gh.d.ts +123 -0
- package/dist/store-CNJwmT-h.d.cts +123 -0
- package/dist/testing/index.cjs +1 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/{types-h12mNYbq.d.ts → types-BsJvElYZ.d.ts} +31 -31
- package/dist/{types-bNMSWKqF.d.cts → types-CcPIn7sy.d.cts} +31 -31
- package/dist/work-item-runtime-C3M79vE9.d.ts +228 -0
- package/dist/work-item-runtime-qaVgsL0T.d.cts +228 -0
- package/dist/work-queue/index.cjs +1132 -0
- package/dist/work-queue/index.cjs.map +1 -0
- package/dist/work-queue/index.d.cts +369 -0
- package/dist/work-queue/index.d.ts +369 -0
- package/dist/work-queue/index.js +1078 -0
- package/dist/work-queue/index.js.map +1 -0
- package/package.json +266 -2
- package/dist/chunk-COHO25CZ.js.map +0 -1
- package/dist/chunk-DK554R7P.js.map +0 -1
- package/dist/chunk-I3IHOHCJ.js.map +0 -1
- package/dist/chunk-RIIOUTGM.js.map +0 -1
- package/dist/chunk-SWTLEQL6.js.map +0 -1
- /package/dist/{chunk-JR4IKCR2.js.map → chunk-2747WN4Y.js.map} +0 -0
- /package/dist/{chunk-GDL26N2T.js.map → chunk-3KN7NJDR.js.map} +0 -0
- /package/dist/{chunk-DJC4CHLQ.js.map → chunk-43BKLFZU.js.map} +0 -0
- /package/dist/{chunk-SCRFRCGR.js.map → chunk-BSQGE5PS.js.map} +0 -0
- /package/dist/{chunk-Y3ZLRGHC.js.map → chunk-CZNV3VXM.js.map} +0 -0
- /package/dist/{chunk-JIMUDQN3.js.map → chunk-FBUU43PM.js.map} +0 -0
- /package/dist/{chunk-D6BG7U6A.js.map → chunk-IXZBUWSP.js.map} +0 -0
- /package/dist/{chunk-PKVBHGBM.js.map → chunk-SS2A464I.js.map} +0 -0
- /package/dist/{chunk-G4M3NHRJ.js.map → chunk-V7CIJE4B.js.map} +0 -0
- /package/dist/{chunk-TEW47IVR.js.map → chunk-VPEFSDME.js.map} +0 -0
|
@@ -0,0 +1,2164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/solutions/work-item/actions.ts
|
|
21
|
+
var actions_exports = {};
|
|
22
|
+
__export(actions_exports, {
|
|
23
|
+
workItemDomainActionAdmissionProjector: () => workItemDomainActionAdmissionProjector,
|
|
24
|
+
workItemDomainActionApplicationProjector: () => workItemDomainActionApplicationProjector,
|
|
25
|
+
workItemDomainActionApplyPolicy: () => workItemDomainActionApplyPolicy,
|
|
26
|
+
workItemDomainActionCapabilityGuardProjector: () => workItemDomainActionCapabilityGuardProjector,
|
|
27
|
+
workItemDomainActionProposal: () => workItemDomainActionProposal,
|
|
28
|
+
workItemDomainActionProposalIntake: () => workItemDomainActionProposalIntake,
|
|
29
|
+
workItemDomainActionProposalIntakeProjector: () => workItemDomainActionProposalIntakeProjector
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(actions_exports);
|
|
32
|
+
|
|
33
|
+
// src/protocol/messages.ts
|
|
34
|
+
var SENTINEL = void 0;
|
|
35
|
+
|
|
36
|
+
// src/ctx/types.ts
|
|
37
|
+
function depWaves(ctx, depIndex) {
|
|
38
|
+
return ctx.waveData[depIndex] ?? [];
|
|
39
|
+
}
|
|
40
|
+
function depBatch(ctx, depIndex) {
|
|
41
|
+
const waves = depWaves(ctx, depIndex);
|
|
42
|
+
if (waves.length === 0) return null;
|
|
43
|
+
const flattened = waves.flat().filter((v) => v !== SENTINEL);
|
|
44
|
+
return flattened.length === 0 ? [] : flattened;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/solutions/work-item/scheduling.ts
|
|
48
|
+
function validateAcceptanceCriteria(criteria, opts = {}) {
|
|
49
|
+
const out = [];
|
|
50
|
+
const seen = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const criterion of criteria) {
|
|
52
|
+
if (!isRecord(criterion)) {
|
|
53
|
+
out.push(issue("malformed-draft", "AcceptanceCriterion must be an object", opts.workItemId));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (typeof criterion.criterionId !== "string" || criterion.criterionId === "") {
|
|
57
|
+
out.push(
|
|
58
|
+
issue(
|
|
59
|
+
"missing-required-field",
|
|
60
|
+
"AcceptanceCriterion.criterionId is required",
|
|
61
|
+
opts.workItemId
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (seen.has(criterion.criterionId)) {
|
|
67
|
+
out.push(
|
|
68
|
+
issue(
|
|
69
|
+
"duplicate-id",
|
|
70
|
+
`Duplicate acceptance criterion id '${criterion.criterionId}'`,
|
|
71
|
+
opts.workItemId,
|
|
72
|
+
{ criterionId: criterion.criterionId }
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
seen.add(criterion.criterionId);
|
|
77
|
+
if (typeof criterion.statement !== "string" || criterion.statement.trim() === "") {
|
|
78
|
+
out.push(
|
|
79
|
+
issue(
|
|
80
|
+
"missing-required-field",
|
|
81
|
+
"AcceptanceCriterion.statement is required",
|
|
82
|
+
opts.workItemId,
|
|
83
|
+
{ criterionId: criterion.criterionId }
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function validateVerificationPlan(plan, criteria = [], opts = {}) {
|
|
91
|
+
const out = [];
|
|
92
|
+
if (!isRecord(plan) || !Array.isArray(plan.steps)) {
|
|
93
|
+
return [issue("malformed-draft", "VerificationPlan.steps must be an array", opts.workItemId)];
|
|
94
|
+
}
|
|
95
|
+
const criterionIds = new Set(criteria.map((criterion) => criterion.criterionId));
|
|
96
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
97
|
+
const modes = new Set(opts.allowedModes ?? ["auto", "manual", "hybrid"]);
|
|
98
|
+
const effectKinds = new Set(opts.allowedEffectKinds ?? ["verification"]);
|
|
99
|
+
for (const step of plan.steps) {
|
|
100
|
+
if (!isRecord(step)) {
|
|
101
|
+
out.push(issue("malformed-draft", "VerificationStep must be an object", opts.workItemId));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (typeof step.stepId !== "string" || step.stepId === "") {
|
|
105
|
+
out.push(
|
|
106
|
+
issue("missing-required-field", "VerificationStep.stepId is required", opts.workItemId)
|
|
107
|
+
);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (stepIds.has(step.stepId)) {
|
|
111
|
+
out.push(
|
|
112
|
+
issue("duplicate-id", `Duplicate verification step id '${step.stepId}'`, opts.workItemId, {
|
|
113
|
+
stepId: step.stepId
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
stepIds.add(step.stepId);
|
|
118
|
+
if (typeof step.mode !== "string" || !modes.has(step.mode)) {
|
|
119
|
+
out.push(
|
|
120
|
+
issue(
|
|
121
|
+
"unsupported-mode",
|
|
122
|
+
`Unsupported verification step mode '${step.mode}'`,
|
|
123
|
+
opts.workItemId,
|
|
124
|
+
{ stepId: step.stepId }
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (step.effectKind !== void 0 && (typeof step.effectKind !== "string" || !effectKinds.has(step.effectKind))) {
|
|
129
|
+
out.push(
|
|
130
|
+
issue(
|
|
131
|
+
"unsupported-effect-kind",
|
|
132
|
+
`Unsupported verification effect kind '${step.effectKind}'`,
|
|
133
|
+
opts.workItemId,
|
|
134
|
+
{ stepId: step.stepId }
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const verifiesCriteriaIds = Array.isArray(step.verifiesCriteriaIds) ? step.verifiesCriteriaIds : [];
|
|
139
|
+
for (const criterionId of verifiesCriteriaIds) {
|
|
140
|
+
if (!criterionIds.has(criterionId)) {
|
|
141
|
+
out.push(
|
|
142
|
+
issue("dangling-ref", `Unknown acceptance criterion '${criterionId}'`, opts.workItemId, {
|
|
143
|
+
stepId: step.stepId,
|
|
144
|
+
criterionId
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const step of plan.steps) {
|
|
151
|
+
if (!isRecord(step) || typeof step.stepId !== "string") continue;
|
|
152
|
+
const dependsOnStepIds = Array.isArray(step.dependsOnStepIds) ? step.dependsOnStepIds : [];
|
|
153
|
+
for (const depId of dependsOnStepIds) {
|
|
154
|
+
if (!stepIds.has(depId)) {
|
|
155
|
+
out.push(
|
|
156
|
+
issue("dangling-ref", `Unknown verification dependency '${depId}'`, opts.workItemId, {
|
|
157
|
+
stepId: step.stepId
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const cycle = findCycle(
|
|
164
|
+
plan.steps.filter(
|
|
165
|
+
(step) => isRecord(step) && typeof step.stepId === "string" && Array.isArray(step.dependsOnStepIds)
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
if (cycle !== void 0) {
|
|
169
|
+
out.push(
|
|
170
|
+
issue(
|
|
171
|
+
"cyclic-dependency",
|
|
172
|
+
`Verification step cycle detected: ${cycle.join(" -> ")}`,
|
|
173
|
+
opts.workItemId,
|
|
174
|
+
{ stepId: cycle[0] }
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function findCycle(steps) {
|
|
181
|
+
const byId = new Map(steps.map((step) => [step.stepId, step.dependsOnStepIds ?? []]));
|
|
182
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
183
|
+
const visited = /* @__PURE__ */ new Set();
|
|
184
|
+
const stack = [];
|
|
185
|
+
const visit = (id) => {
|
|
186
|
+
if (visiting.has(id)) return [...stack.slice(stack.indexOf(id)), id];
|
|
187
|
+
if (visited.has(id)) return void 0;
|
|
188
|
+
visiting.add(id);
|
|
189
|
+
stack.push(id);
|
|
190
|
+
for (const dep of byId.get(id) ?? []) {
|
|
191
|
+
if (!byId.has(dep)) continue;
|
|
192
|
+
const cycle = visit(dep);
|
|
193
|
+
if (cycle !== void 0) return cycle;
|
|
194
|
+
}
|
|
195
|
+
stack.pop();
|
|
196
|
+
visiting.delete(id);
|
|
197
|
+
visited.add(id);
|
|
198
|
+
return void 0;
|
|
199
|
+
};
|
|
200
|
+
for (const step of steps) {
|
|
201
|
+
const cycle = visit(step.stepId);
|
|
202
|
+
if (cycle !== void 0) return cycle;
|
|
203
|
+
}
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
function issue(code, message, workItemId, metadata) {
|
|
207
|
+
return {
|
|
208
|
+
kind: "issue",
|
|
209
|
+
code,
|
|
210
|
+
message,
|
|
211
|
+
severity: "error",
|
|
212
|
+
source: "work-item-scheduling",
|
|
213
|
+
subjectId: workItemId,
|
|
214
|
+
metadata
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function isRecord(value) {
|
|
218
|
+
return value !== null && typeof value === "object";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/orchestration/work-item-runtime.ts
|
|
222
|
+
function workItemDomainActionAdmissionProjector(graph, opts) {
|
|
223
|
+
const name = opts.name ?? "workItemDomainActionAdmissions";
|
|
224
|
+
const policyDeps = opts.admissionPolicies ?? [];
|
|
225
|
+
const policyStart = 2;
|
|
226
|
+
const now = opts.now ?? Date.now;
|
|
227
|
+
const runtime = graph.node(
|
|
228
|
+
[opts.proposals, opts.decisions, ...policyDeps],
|
|
229
|
+
(ctx) => {
|
|
230
|
+
const state = ctx.state.get() ?? emptyWorkItemDomainActionAdmissionState();
|
|
231
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
232
|
+
const proposal = raw;
|
|
233
|
+
if (state.proposals.has(proposal.proposalId)) {
|
|
234
|
+
emitWorkItemAdmissionIssue(
|
|
235
|
+
ctx,
|
|
236
|
+
state,
|
|
237
|
+
`duplicate-admission-proposal:${proposal.proposalId}`,
|
|
238
|
+
"duplicate-work-item-domain-action-proposal",
|
|
239
|
+
`WorkItemDomainActionProposal '${proposal.proposalId}' was already seen by the admission projector`,
|
|
240
|
+
proposal.workItemId,
|
|
241
|
+
workItemAdmissionProposalRefs(proposal)
|
|
242
|
+
);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
state.proposals.set(proposal.proposalId, proposal);
|
|
246
|
+
}
|
|
247
|
+
forEachPolicyDepBatch(ctx, policyStart, policyDeps.length, (raw) => {
|
|
248
|
+
const policy = raw;
|
|
249
|
+
state.policies.set(policy.policyId, policy);
|
|
250
|
+
});
|
|
251
|
+
for (const raw of depBatch(ctx, 1) ?? []) {
|
|
252
|
+
const decision = raw;
|
|
253
|
+
if (state.decisions.has(decision.decisionId)) {
|
|
254
|
+
emitWorkItemAdmissionIssue(
|
|
255
|
+
ctx,
|
|
256
|
+
state,
|
|
257
|
+
`duplicate-admission-decision:${decision.decisionId}`,
|
|
258
|
+
"duplicate-work-item-domain-action-admission-decision",
|
|
259
|
+
`WorkItemDomainActionAdmissionDecision '${decision.decisionId}' was already seen by the admission projector`,
|
|
260
|
+
decision.proposalId,
|
|
261
|
+
workItemAdmissionDecisionRefs(decision)
|
|
262
|
+
);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
state.decisions.set(decision.decisionId, decision);
|
|
266
|
+
}
|
|
267
|
+
evaluateWorkItemDomainActionAdmissions(ctx, state, now());
|
|
268
|
+
ctx.state.set(state);
|
|
269
|
+
},
|
|
270
|
+
{ name: `${name}/runtime`, factory: "workItemDomainActionAdmissionProjector", partial: true }
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
admissions: projectRuntimeFact(
|
|
274
|
+
graph,
|
|
275
|
+
runtime,
|
|
276
|
+
`${name}/admissions`,
|
|
277
|
+
"workItemDomainActionAdmissions",
|
|
278
|
+
(fact) => fact.kind === "admission" ? fact.admission : void 0
|
|
279
|
+
),
|
|
280
|
+
status: projectRuntimeFact(
|
|
281
|
+
graph,
|
|
282
|
+
runtime,
|
|
283
|
+
`${name}/status`,
|
|
284
|
+
"workItemDomainActionAdmissionStatus",
|
|
285
|
+
(fact) => fact.kind === "status" ? fact.status : void 0
|
|
286
|
+
),
|
|
287
|
+
issues: projectRuntimeFact(
|
|
288
|
+
graph,
|
|
289
|
+
runtime,
|
|
290
|
+
`${name}/issues`,
|
|
291
|
+
"workItemDomainActionAdmissionIssues",
|
|
292
|
+
(fact) => fact.kind === "issue" ? fact.issue : void 0
|
|
293
|
+
),
|
|
294
|
+
audit: projectRuntimeFact(
|
|
295
|
+
graph,
|
|
296
|
+
runtime,
|
|
297
|
+
`${name}/audit`,
|
|
298
|
+
"workItemDomainActionAdmissionAudit",
|
|
299
|
+
(fact) => fact.kind === "audit" ? fact.audit : void 0
|
|
300
|
+
),
|
|
301
|
+
views: graph.node(
|
|
302
|
+
[runtime],
|
|
303
|
+
(ctx) => {
|
|
304
|
+
const state = ctx.state.get() ?? {
|
|
305
|
+
admissionsByProposal: /* @__PURE__ */ new Map(),
|
|
306
|
+
admissionsByWorkItem: /* @__PURE__ */ new Map(),
|
|
307
|
+
issues: [],
|
|
308
|
+
audit: []
|
|
309
|
+
};
|
|
310
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
311
|
+
const fact = raw;
|
|
312
|
+
if (fact.kind === "admission") {
|
|
313
|
+
const admission = fact.admission;
|
|
314
|
+
state.admissionsByProposal.set(admission.proposalId, admission);
|
|
315
|
+
const byWorkItem = state.admissionsByWorkItem.get(admission.workItemId) ?? [];
|
|
316
|
+
byWorkItem.push(admission);
|
|
317
|
+
state.admissionsByWorkItem.set(admission.workItemId, byWorkItem);
|
|
318
|
+
} else if (fact.kind === "issue") state.issues.push(fact.issue);
|
|
319
|
+
else if (fact.kind === "audit") state.audit.push(fact.audit);
|
|
320
|
+
}
|
|
321
|
+
ctx.state.set(state);
|
|
322
|
+
ctx.down([["DATA", freezeWorkItemDomainActionAdmissionViews(state)]]);
|
|
323
|
+
},
|
|
324
|
+
{ name: `${name}/views`, factory: "workItemDomainActionAdmissionViews" }
|
|
325
|
+
)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function evaluateWorkItemDomainActionAdmissions(ctx, state, admittedAtMs) {
|
|
329
|
+
let emitted = true;
|
|
330
|
+
let passes = 0;
|
|
331
|
+
while (emitted && passes <= state.decisions.size) {
|
|
332
|
+
emitted = false;
|
|
333
|
+
passes += 1;
|
|
334
|
+
for (const decision of state.decisions.values()) {
|
|
335
|
+
if (state.terminalDecisionIds.has(decision.decisionId)) continue;
|
|
336
|
+
emitted = evaluateWorkItemDomainActionAdmission(ctx, state, decision, admittedAtMs) || emitted;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function evaluateWorkItemDomainActionAdmission(ctx, state, decision, admittedAtMs) {
|
|
341
|
+
const proposal = state.proposals.get(decision.proposalId);
|
|
342
|
+
const proposalRefs = workItemAdmissionDecisionRefs(decision, proposal);
|
|
343
|
+
const subjectId = proposal?.workItemId ?? decision.proposalId;
|
|
344
|
+
if (proposal === void 0) {
|
|
345
|
+
emitWorkItemAdmissionIssue(
|
|
346
|
+
ctx,
|
|
347
|
+
state,
|
|
348
|
+
`missing-admission-proposal:${decision.decisionId}:${decision.proposalId}`,
|
|
349
|
+
"missing-work-item-domain-action-admission-proposal",
|
|
350
|
+
`WorkItemDomainActionAdmissionDecision '${decision.decisionId}' references missing proposal '${decision.proposalId}'`,
|
|
351
|
+
subjectId,
|
|
352
|
+
proposalRefs
|
|
353
|
+
);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const staleProposalRef = (decision.sourceRefs ?? []).find(
|
|
357
|
+
(sourceRef) => sourceRef.kind === "work-item-domain-action-proposal" && sourceRef.id !== decision.proposalId && sourceRef.id !== decision.targetProposalId
|
|
358
|
+
);
|
|
359
|
+
if (staleProposalRef !== void 0) {
|
|
360
|
+
emitWorkItemAdmissionIssue(
|
|
361
|
+
ctx,
|
|
362
|
+
state,
|
|
363
|
+
`stale-admission-proposal-ref:${decision.decisionId}:${staleProposalRef.id}`,
|
|
364
|
+
"stale-work-item-domain-action-admission-proposal-ref",
|
|
365
|
+
`WorkItemDomainActionAdmissionDecision '${decision.decisionId}' carries stale proposal ref '${staleProposalRef.id}'`,
|
|
366
|
+
proposal.workItemId,
|
|
367
|
+
proposalRefs
|
|
368
|
+
);
|
|
369
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
const policy = admissionPolicyForDecision(state, decision);
|
|
373
|
+
if (typeof policy === "string") {
|
|
374
|
+
emitWorkItemAdmissionIssue(
|
|
375
|
+
ctx,
|
|
376
|
+
state,
|
|
377
|
+
`${policy}:${decision.decisionId}:${decision.policyId ?? "missing"}`,
|
|
378
|
+
policy,
|
|
379
|
+
admissionPolicyIssueMessage(policy, decision),
|
|
380
|
+
proposal.workItemId,
|
|
381
|
+
proposalRefs
|
|
382
|
+
);
|
|
383
|
+
if (policy === "stale-work-item-domain-action-admission-policy-ref")
|
|
384
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
if (policy !== void 0) {
|
|
388
|
+
const policyIssue = validateWorkItemAdmissionPolicy(policy, decision, proposal);
|
|
389
|
+
if (policyIssue !== void 0) {
|
|
390
|
+
emitWorkItemAdmissionIssue(
|
|
391
|
+
ctx,
|
|
392
|
+
state,
|
|
393
|
+
`${policyIssue.code}:${decision.decisionId}:${policy.policyId}`,
|
|
394
|
+
policyIssue.code,
|
|
395
|
+
policyIssue.message,
|
|
396
|
+
proposal.workItemId,
|
|
397
|
+
workItemAdmissionDecisionRefs(decision, proposal, policy)
|
|
398
|
+
);
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const mergeIssue = validateWorkItemAdmissionMergeTarget(state, decision);
|
|
403
|
+
if (mergeIssue !== void 0) {
|
|
404
|
+
emitWorkItemAdmissionIssue(
|
|
405
|
+
ctx,
|
|
406
|
+
state,
|
|
407
|
+
`${mergeIssue.code}:${decision.decisionId}:${decision.targetProposalId ?? ""}:${decision.targetAdmissionId ?? ""}`,
|
|
408
|
+
mergeIssue.code,
|
|
409
|
+
mergeIssue.message,
|
|
410
|
+
proposal.workItemId,
|
|
411
|
+
proposalRefs
|
|
412
|
+
);
|
|
413
|
+
if (mergeIssue.code !== "unknown-work-item-domain-action-admission-merge-target")
|
|
414
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if (state.admissionsById.has(decision.admissionId)) {
|
|
418
|
+
emitWorkItemAdmissionIssue(
|
|
419
|
+
ctx,
|
|
420
|
+
state,
|
|
421
|
+
`duplicate-admission-id:${decision.admissionId}`,
|
|
422
|
+
"duplicate-work-item-domain-action-admission",
|
|
423
|
+
`WorkItemDomainActionAdmission '${decision.admissionId}' was already emitted`,
|
|
424
|
+
proposal.workItemId,
|
|
425
|
+
proposalRefs
|
|
426
|
+
);
|
|
427
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
if (state.admissionsByProposal.has(decision.proposalId)) {
|
|
431
|
+
emitWorkItemAdmissionIssue(
|
|
432
|
+
ctx,
|
|
433
|
+
state,
|
|
434
|
+
`duplicate-admission-proposal-decision:${decision.proposalId}:${decision.decisionId}`,
|
|
435
|
+
"duplicate-work-item-domain-action-admission-proposal",
|
|
436
|
+
`WorkItemDomainActionProposal '${decision.proposalId}' already has an admission decision`,
|
|
437
|
+
proposal.workItemId,
|
|
438
|
+
proposalRefs
|
|
439
|
+
);
|
|
440
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
const admissionState = admissionStateForOutcome(decision.outcome);
|
|
444
|
+
if (admissionState === void 0) {
|
|
445
|
+
emitWorkItemAdmissionIssue(
|
|
446
|
+
ctx,
|
|
447
|
+
state,
|
|
448
|
+
`unsupported-admission-outcome:${decision.decisionId}:${String(decision.outcome)}`,
|
|
449
|
+
"unsupported-work-item-domain-action-admission-outcome",
|
|
450
|
+
`WorkItemDomainActionAdmissionDecision '${decision.decisionId}' uses unsupported outcome '${String(decision.outcome)}'`,
|
|
451
|
+
proposal.workItemId,
|
|
452
|
+
proposalRefs
|
|
453
|
+
);
|
|
454
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const admission = {
|
|
458
|
+
kind: "work-item-domain-action-admission",
|
|
459
|
+
admissionId: decision.admissionId,
|
|
460
|
+
proposalId: proposal.proposalId,
|
|
461
|
+
workItemId: proposal.workItemId,
|
|
462
|
+
actionKind: proposal.actionKind,
|
|
463
|
+
state: admissionState,
|
|
464
|
+
decisionId: decision.decisionId,
|
|
465
|
+
policyId: decision.policyId,
|
|
466
|
+
reason: decision.reason,
|
|
467
|
+
targetProposalId: decision.targetProposalId,
|
|
468
|
+
targetAdmissionId: decision.targetAdmissionId,
|
|
469
|
+
sourceRefs: workItemAdmissionDecisionRefs(decision, proposal, policy),
|
|
470
|
+
admittedAtMs: decision.decidedAtMs ?? admittedAtMs,
|
|
471
|
+
metadata: {
|
|
472
|
+
...proposal.metadata ?? {},
|
|
473
|
+
...policy?.metadata ?? {},
|
|
474
|
+
...decision.metadata ?? {}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
state.admissionsById.set(admission.admissionId, admission);
|
|
478
|
+
state.admissionsByProposal.set(admission.proposalId, admission);
|
|
479
|
+
state.terminalDecisionIds.add(decision.decisionId);
|
|
480
|
+
state.statusSeq += 1;
|
|
481
|
+
state.auditSeq += 1;
|
|
482
|
+
const statusState = workItemAdmissionStatusState(admission.state);
|
|
483
|
+
const status = {
|
|
484
|
+
kind: "work-item-status",
|
|
485
|
+
statusId: `${proposal.workItemId}:${statusState}:${state.statusSeq}`,
|
|
486
|
+
workItemId: proposal.workItemId,
|
|
487
|
+
state: statusState,
|
|
488
|
+
sourceRefs: admission.sourceRefs,
|
|
489
|
+
effectRunId: proposal.effectRunId,
|
|
490
|
+
evidenceId: proposal.evidenceId,
|
|
491
|
+
proposalId: proposal.proposalId,
|
|
492
|
+
metadata: {
|
|
493
|
+
actionKind: proposal.actionKind,
|
|
494
|
+
admissionId: admission.admissionId,
|
|
495
|
+
decisionId: decision.decisionId,
|
|
496
|
+
policyId: decision.policyId
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
const audit = {
|
|
500
|
+
id: `${proposal.workItemId}:${statusState}:${state.auditSeq}`,
|
|
501
|
+
kind: `work-item-domain-action-${admission.state}`,
|
|
502
|
+
subjectId: proposal.workItemId,
|
|
503
|
+
sourceRefs: admission.sourceRefs,
|
|
504
|
+
metadata: {
|
|
505
|
+
actionKind: proposal.actionKind,
|
|
506
|
+
admissionId: admission.admissionId,
|
|
507
|
+
decisionId: decision.decisionId,
|
|
508
|
+
policyId: decision.policyId,
|
|
509
|
+
proposalId: proposal.proposalId
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
ctx.down([
|
|
513
|
+
["DATA", { kind: "admission", admission }],
|
|
514
|
+
["DATA", { kind: "status", status }],
|
|
515
|
+
["DATA", { kind: "audit", audit }]
|
|
516
|
+
]);
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
function admissionPolicyForDecision(state, decision) {
|
|
520
|
+
if (decision.policyId === void 0) return void 0;
|
|
521
|
+
const stalePolicyRef = (decision.sourceRefs ?? []).find(
|
|
522
|
+
(sourceRef) => sourceRef.kind === "work-item-domain-action-admission-policy" && sourceRef.id !== decision.policyId
|
|
523
|
+
);
|
|
524
|
+
if (stalePolicyRef !== void 0) return "stale-work-item-domain-action-admission-policy-ref";
|
|
525
|
+
return state.policies.get(decision.policyId) ?? "missing-work-item-domain-action-admission-policy";
|
|
526
|
+
}
|
|
527
|
+
function admissionPolicyIssueMessage(code, decision) {
|
|
528
|
+
if (code === "missing-work-item-domain-action-admission-policy") {
|
|
529
|
+
return `WorkItemDomainActionAdmissionPolicy '${decision.policyId}' was referenced for WorkItem action admission but not present`;
|
|
530
|
+
}
|
|
531
|
+
return `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' carries a stale admission policy source ref`;
|
|
532
|
+
}
|
|
533
|
+
function validateWorkItemAdmissionPolicy(policy, decision, proposal) {
|
|
534
|
+
if (policy.actionKinds !== void 0 && !policy.actionKinds.includes(proposal.actionKind)) {
|
|
535
|
+
return {
|
|
536
|
+
code: "work-item-domain-action-admission-policy-mismatch",
|
|
537
|
+
message: `WorkItemDomainActionAdmissionPolicy '${policy.policyId}' does not apply to actionKind '${proposal.actionKind}'`
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (policy.allowedOutcomes !== void 0 && !policy.allowedOutcomes.includes(decision.outcome)) {
|
|
541
|
+
return {
|
|
542
|
+
code: "unsupported-work-item-domain-action-admission-outcome",
|
|
543
|
+
message: `WorkItemDomainActionAdmissionPolicy '${policy.policyId}' does not allow outcome '${decision.outcome}'`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
function validateWorkItemAdmissionMergeTarget(state, decision) {
|
|
549
|
+
const hasTargetProposal = decision.targetProposalId !== void 0;
|
|
550
|
+
const hasTargetAdmission = decision.targetAdmissionId !== void 0;
|
|
551
|
+
if (decision.outcome !== "merge") {
|
|
552
|
+
if (hasTargetProposal || hasTargetAdmission) {
|
|
553
|
+
return {
|
|
554
|
+
code: "ambiguous-work-item-domain-action-admission-merge-target",
|
|
555
|
+
message: `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' carries a merge target for non-merge outcome '${decision.outcome}'`
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return void 0;
|
|
559
|
+
}
|
|
560
|
+
if (hasTargetProposal === hasTargetAdmission) {
|
|
561
|
+
return {
|
|
562
|
+
code: "ambiguous-work-item-domain-action-admission-merge-target",
|
|
563
|
+
message: `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' must reference exactly one merge target`
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (decision.targetProposalId === decision.proposalId) {
|
|
567
|
+
return {
|
|
568
|
+
code: "ambiguous-work-item-domain-action-admission-merge-target",
|
|
569
|
+
message: `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' cannot merge a proposal into itself`
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (decision.targetProposalId !== void 0 && !state.proposals.has(decision.targetProposalId)) {
|
|
573
|
+
return {
|
|
574
|
+
code: "unknown-work-item-domain-action-admission-merge-target",
|
|
575
|
+
message: `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' references unknown target proposal '${decision.targetProposalId}'`
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
if (decision.targetAdmissionId !== void 0 && !state.admissionsById.has(decision.targetAdmissionId)) {
|
|
579
|
+
return {
|
|
580
|
+
code: "unknown-work-item-domain-action-admission-merge-target",
|
|
581
|
+
message: `WorkItemDomainActionAdmissionDecision '${decision.decisionId}' references unknown target admission '${decision.targetAdmissionId}'`
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return void 0;
|
|
585
|
+
}
|
|
586
|
+
function admissionStateForOutcome(outcome) {
|
|
587
|
+
if (outcome === "admit") return "admitted";
|
|
588
|
+
if (outcome === "reject") return "rejected";
|
|
589
|
+
if (outcome === "defer") return "deferred";
|
|
590
|
+
if (outcome === "merge") return "merged";
|
|
591
|
+
return void 0;
|
|
592
|
+
}
|
|
593
|
+
function workItemAdmissionStatusState(state) {
|
|
594
|
+
if (state === "admitted") return "domain-action-admitted";
|
|
595
|
+
if (state === "rejected") return "domain-action-rejected";
|
|
596
|
+
if (state === "deferred") return "domain-action-deferred";
|
|
597
|
+
return "domain-action-merged";
|
|
598
|
+
}
|
|
599
|
+
function emitWorkItemAdmissionIssue(ctx, state, key, code, message, subjectId, sourceRefs2) {
|
|
600
|
+
if (state.issueKeys.has(key)) return;
|
|
601
|
+
state.issueKeys.add(key);
|
|
602
|
+
state.issueSeq += 1;
|
|
603
|
+
state.statusSeq += 1;
|
|
604
|
+
state.auditSeq += 1;
|
|
605
|
+
const issue2 = dataIssue(code, message, { subjectId, refs: sourceRefs2 });
|
|
606
|
+
const status = {
|
|
607
|
+
kind: "work-item-status",
|
|
608
|
+
statusId: `${subjectId ?? "work-item"}:mapping-issue:${state.statusSeq}`,
|
|
609
|
+
workItemId: subjectId ?? "unknown",
|
|
610
|
+
state: "mapping-issue",
|
|
611
|
+
sourceRefs: sourceRefs2,
|
|
612
|
+
issues: [issue2]
|
|
613
|
+
};
|
|
614
|
+
const audit = {
|
|
615
|
+
id: `${subjectId ?? "work-item"}:${code}:${state.auditSeq}`,
|
|
616
|
+
kind: "work-item-domain-action-admission-issue",
|
|
617
|
+
subjectId,
|
|
618
|
+
issueCode: code,
|
|
619
|
+
message,
|
|
620
|
+
sourceRefs: sourceRefs2
|
|
621
|
+
};
|
|
622
|
+
ctx.down([
|
|
623
|
+
["DATA", { kind: "issue", issue: issue2 }],
|
|
624
|
+
["DATA", { kind: "status", status }],
|
|
625
|
+
["DATA", { kind: "audit", audit }]
|
|
626
|
+
]);
|
|
627
|
+
}
|
|
628
|
+
function emptyWorkItemDomainActionAdmissionState() {
|
|
629
|
+
return {
|
|
630
|
+
proposals: /* @__PURE__ */ new Map(),
|
|
631
|
+
policies: /* @__PURE__ */ new Map(),
|
|
632
|
+
decisions: /* @__PURE__ */ new Map(),
|
|
633
|
+
admissionsById: /* @__PURE__ */ new Map(),
|
|
634
|
+
admissionsByProposal: /* @__PURE__ */ new Map(),
|
|
635
|
+
terminalDecisionIds: /* @__PURE__ */ new Set(),
|
|
636
|
+
issueKeys: /* @__PURE__ */ new Set(),
|
|
637
|
+
statusSeq: 0,
|
|
638
|
+
issueSeq: 0,
|
|
639
|
+
auditSeq: 0
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function freezeWorkItemDomainActionAdmissionViews(state) {
|
|
643
|
+
return {
|
|
644
|
+
admissionsByProposal: new Map(state.admissionsByProposal),
|
|
645
|
+
admissionsByWorkItem: new Map(
|
|
646
|
+
Array.from(state.admissionsByWorkItem, ([key, value]) => [key, Object.freeze([...value])])
|
|
647
|
+
),
|
|
648
|
+
issues: Object.freeze([...state.issues]),
|
|
649
|
+
audit: Object.freeze([...state.audit])
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function workItemAdmissionProposalRefs(proposal) {
|
|
653
|
+
return uniqueSourceRefs([
|
|
654
|
+
ref("work-item", proposal.workItemId),
|
|
655
|
+
ref("work-item-domain-action-proposal", proposal.proposalId),
|
|
656
|
+
ref("effect-run", proposal.effectRunId),
|
|
657
|
+
ref("effect-run-result", proposal.effectRunResultId),
|
|
658
|
+
ref("work-item-evidence", proposal.evidenceId),
|
|
659
|
+
ref("work-item-effect-mapping-policy", proposal.policyId),
|
|
660
|
+
...proposal.sourceRefs ?? []
|
|
661
|
+
]);
|
|
662
|
+
}
|
|
663
|
+
function workItemAdmissionDecisionRefs(decision, proposal, policy) {
|
|
664
|
+
return uniqueSourceRefs([
|
|
665
|
+
...proposal === void 0 ? [] : workItemAdmissionProposalRefs(proposal),
|
|
666
|
+
ref("work-item-domain-action-admission-decision", decision.decisionId),
|
|
667
|
+
ref("work-item-domain-action-admission", decision.admissionId),
|
|
668
|
+
ref("work-item-domain-action-proposal", decision.proposalId),
|
|
669
|
+
...decision.policyId === void 0 ? [] : [ref("work-item-domain-action-admission-policy", decision.policyId)],
|
|
670
|
+
...policy === void 0 ? [] : [ref("work-item-domain-action-admission-policy", policy.policyId)],
|
|
671
|
+
...decision.targetProposalId === void 0 ? [] : [ref("work-item-domain-action-proposal", decision.targetProposalId)],
|
|
672
|
+
...decision.targetAdmissionId === void 0 ? [] : [ref("work-item-domain-action-admission", decision.targetAdmissionId)],
|
|
673
|
+
...decision.sourceRefs ?? []
|
|
674
|
+
]);
|
|
675
|
+
}
|
|
676
|
+
function uniqueSourceRefs(sourceRefs2) {
|
|
677
|
+
const seen = /* @__PURE__ */ new Set();
|
|
678
|
+
const unique = [];
|
|
679
|
+
for (const sourceRef of sourceRefs2) {
|
|
680
|
+
const key = sourceRefKey(sourceRef);
|
|
681
|
+
if (seen.has(key)) continue;
|
|
682
|
+
seen.add(key);
|
|
683
|
+
unique.push(sourceRef);
|
|
684
|
+
}
|
|
685
|
+
return unique;
|
|
686
|
+
}
|
|
687
|
+
function sourceRefKey(sourceRef) {
|
|
688
|
+
return `${sourceRef.kind}:${sourceRef.id}:${JSON.stringify(sourceRef.metadata ?? {})}`;
|
|
689
|
+
}
|
|
690
|
+
function projectRuntimeFact(graph, runtime, name, factory, pick) {
|
|
691
|
+
return graph.node(
|
|
692
|
+
[runtime],
|
|
693
|
+
(ctx) => {
|
|
694
|
+
for (const fact of depBatch(ctx, 0) ?? []) {
|
|
695
|
+
const typed = fact;
|
|
696
|
+
const value = pick(typed);
|
|
697
|
+
if (value !== void 0) ctx.down([["DATA", value]]);
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{ name, factory }
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
function forEachPolicyDepBatch(ctx, start, count, fn) {
|
|
704
|
+
for (let i = 0; i < count; i += 1) {
|
|
705
|
+
for (const value of depBatch(ctx, start + i) ?? []) fn(value);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function dataIssue(code, message, opts = {}) {
|
|
709
|
+
return {
|
|
710
|
+
kind: "issue",
|
|
711
|
+
code,
|
|
712
|
+
message,
|
|
713
|
+
severity: opts.severity ?? "error",
|
|
714
|
+
subjectId: opts.subjectId,
|
|
715
|
+
refs: opts.refs?.map((r) => `${r.kind}:${r.id}`),
|
|
716
|
+
details: opts.details
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function ref(kind, id) {
|
|
720
|
+
return { kind, id };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/solutions/work-item/actions.ts
|
|
724
|
+
var patchFieldKeys = /* @__PURE__ */ new Set([
|
|
725
|
+
"summary",
|
|
726
|
+
"detail",
|
|
727
|
+
"detailRefs",
|
|
728
|
+
"kind",
|
|
729
|
+
"priority",
|
|
730
|
+
"tags",
|
|
731
|
+
"owner",
|
|
732
|
+
"assignee",
|
|
733
|
+
"deadlineMs",
|
|
734
|
+
"customFields",
|
|
735
|
+
"sourceRefs",
|
|
736
|
+
"metadata"
|
|
737
|
+
]);
|
|
738
|
+
function workItemDomainActionProposal(proposalId, workItemId, actionKind, opts = {}) {
|
|
739
|
+
return {
|
|
740
|
+
kind: "work-item-domain-action-proposal",
|
|
741
|
+
proposalId,
|
|
742
|
+
workItemId,
|
|
743
|
+
actionKind,
|
|
744
|
+
effectRunId: opts.effectRunId ?? `domain-action:${proposalId}:effect-run`,
|
|
745
|
+
effectRunResultId: opts.effectRunResultId ?? `domain-action:${proposalId}:effect-run-result`,
|
|
746
|
+
evidenceId: opts.evidenceId ?? `domain-action:${proposalId}:evidence`,
|
|
747
|
+
policyId: opts.policyId ?? "domain-action-proposal",
|
|
748
|
+
payload: opts.payload,
|
|
749
|
+
reason: opts.reason,
|
|
750
|
+
sourceRefs: opts.sourceRefs,
|
|
751
|
+
proposedAtMs: opts.proposedAtMs,
|
|
752
|
+
metadata: opts.metadata
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function workItemDomainActionProposalIntake(proposalId, workItemId, actionKind, opts = {}) {
|
|
756
|
+
return {
|
|
757
|
+
kind: "work-item-domain-action-proposal-intake",
|
|
758
|
+
proposalId,
|
|
759
|
+
workItemId,
|
|
760
|
+
actionKind,
|
|
761
|
+
payload: opts.payload,
|
|
762
|
+
reason: opts.reason,
|
|
763
|
+
policyId: opts.policyId,
|
|
764
|
+
effectRunId: opts.effectRunId,
|
|
765
|
+
effectRunResultId: opts.effectRunResultId,
|
|
766
|
+
evidenceId: opts.evidenceId,
|
|
767
|
+
proposedAtMs: opts.proposedAtMs,
|
|
768
|
+
sourceRefs: opts.sourceRefs,
|
|
769
|
+
metadata: opts.metadata
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function workItemDomainActionApplyPolicy(policyId, opts = {}) {
|
|
773
|
+
return {
|
|
774
|
+
kind: "work-item-domain-action-apply-policy",
|
|
775
|
+
policyId,
|
|
776
|
+
actionKinds: opts.actionKinds,
|
|
777
|
+
patch: opts.patch,
|
|
778
|
+
spawn: opts.spawn,
|
|
779
|
+
metadata: opts.metadata
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function workItemDomainActionProposalIntakeProjector(graph, opts) {
|
|
783
|
+
const name = opts.name ?? "workItemDomainActionIntake";
|
|
784
|
+
const now = opts.now ?? Date.now;
|
|
785
|
+
const runtime = graph.node(
|
|
786
|
+
[opts.proposals],
|
|
787
|
+
(ctx) => {
|
|
788
|
+
const state = ctx.state.get() ?? {
|
|
789
|
+
proposals: /* @__PURE__ */ new Set(),
|
|
790
|
+
statusSeq: 0,
|
|
791
|
+
auditSeq: 0
|
|
792
|
+
};
|
|
793
|
+
for (const raw of depBatch(ctx, 0) ?? []) reduceProposalIntake(ctx, state, raw, now());
|
|
794
|
+
ctx.state.set(state);
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: `${name}/runtime`,
|
|
798
|
+
factory: "workItemDomainActionProposalIntakeProjector",
|
|
799
|
+
partial: true,
|
|
800
|
+
completeWhenDepsComplete: false,
|
|
801
|
+
errorWhenDepsError: false
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
return {
|
|
805
|
+
proposals: project(
|
|
806
|
+
graph,
|
|
807
|
+
runtime,
|
|
808
|
+
`${name}/proposals`,
|
|
809
|
+
"workItemDomainActionProposals",
|
|
810
|
+
(fact) => fact.kind === "proposal" ? fact.value : void 0
|
|
811
|
+
),
|
|
812
|
+
status: project(
|
|
813
|
+
graph,
|
|
814
|
+
runtime,
|
|
815
|
+
`${name}/status`,
|
|
816
|
+
"workItemDomainActionStatus",
|
|
817
|
+
(fact) => fact.kind === "status" ? fact.value : void 0
|
|
818
|
+
),
|
|
819
|
+
issues: project(
|
|
820
|
+
graph,
|
|
821
|
+
runtime,
|
|
822
|
+
`${name}/issues`,
|
|
823
|
+
"workItemDomainActionIssues",
|
|
824
|
+
(fact) => fact.kind === "issue" ? fact.value : void 0
|
|
825
|
+
),
|
|
826
|
+
audit: project(
|
|
827
|
+
graph,
|
|
828
|
+
runtime,
|
|
829
|
+
`${name}/audit`,
|
|
830
|
+
"workItemDomainActionAudit",
|
|
831
|
+
(fact) => fact.kind === "audit" ? fact.value : void 0
|
|
832
|
+
)
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function workItemDomainActionApplicationProjector(graph, opts) {
|
|
836
|
+
const name = opts.name ?? "workItemDomainActionApplication";
|
|
837
|
+
const deps = opts.applyPolicies === void 0 ? [opts.proposals, opts.admissions, opts.workItems] : [opts.proposals, opts.admissions, opts.workItems, opts.applyPolicies];
|
|
838
|
+
const runtime = graph.node(
|
|
839
|
+
deps,
|
|
840
|
+
(ctx) => {
|
|
841
|
+
const state = ctx.state.get() ?? {
|
|
842
|
+
proposals: /* @__PURE__ */ new Map(),
|
|
843
|
+
admissions: /* @__PURE__ */ new Map(),
|
|
844
|
+
workItems: /* @__PURE__ */ new Map(),
|
|
845
|
+
policies: /* @__PURE__ */ new Map(),
|
|
846
|
+
appliedAdmissions: /* @__PURE__ */ new Set(),
|
|
847
|
+
emittedFactIds: /* @__PURE__ */ new Set(),
|
|
848
|
+
issueKeys: /* @__PURE__ */ new Set(),
|
|
849
|
+
statusSeq: 0,
|
|
850
|
+
auditSeq: 0
|
|
851
|
+
};
|
|
852
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
853
|
+
const proposal = raw;
|
|
854
|
+
if (state.proposals.has(proposal.proposalId)) {
|
|
855
|
+
emitApplicationIssue(
|
|
856
|
+
ctx,
|
|
857
|
+
state,
|
|
858
|
+
`duplicate-proposal:${proposal.proposalId}`,
|
|
859
|
+
"duplicate-suppressed",
|
|
860
|
+
`Duplicate WorkItem domain action proposal '${proposal.proposalId}' suppressed`,
|
|
861
|
+
proposal.workItemId,
|
|
862
|
+
proposal.sourceRefs
|
|
863
|
+
);
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
state.proposals.set(proposal.proposalId, proposal);
|
|
867
|
+
}
|
|
868
|
+
for (const raw of depBatch(ctx, 2) ?? []) {
|
|
869
|
+
const workItem = raw;
|
|
870
|
+
state.workItems.set(workItem.workItemId, workItem);
|
|
871
|
+
}
|
|
872
|
+
if (opts.applyPolicies !== void 0) {
|
|
873
|
+
for (const raw of depBatch(ctx, 3) ?? []) {
|
|
874
|
+
const policy = raw;
|
|
875
|
+
state.policies.set(policy.policyId, policy);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
for (const raw of depBatch(ctx, 1) ?? []) {
|
|
879
|
+
const admission = raw;
|
|
880
|
+
if (state.admissions.has(admission.admissionId)) {
|
|
881
|
+
emitApplicationIssue(
|
|
882
|
+
ctx,
|
|
883
|
+
state,
|
|
884
|
+
`duplicate-admission:${admission.admissionId}`,
|
|
885
|
+
"duplicate-suppressed",
|
|
886
|
+
`Duplicate WorkItem domain action admission '${admission.admissionId}' suppressed`,
|
|
887
|
+
admission.workItemId,
|
|
888
|
+
admissionRefs(admission)
|
|
889
|
+
);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
state.admissions.set(admission.admissionId, admission);
|
|
893
|
+
}
|
|
894
|
+
for (const admission of state.admissions.values()) applyAdmission(ctx, state, admission);
|
|
895
|
+
ctx.state.set(state);
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
name: `${name}/runtime`,
|
|
899
|
+
factory: "workItemDomainActionApplicationProjector",
|
|
900
|
+
partial: true,
|
|
901
|
+
completeWhenDepsComplete: false,
|
|
902
|
+
errorWhenDepsError: false
|
|
903
|
+
}
|
|
904
|
+
);
|
|
905
|
+
return {
|
|
906
|
+
authoringFacts: project(
|
|
907
|
+
graph,
|
|
908
|
+
runtime,
|
|
909
|
+
`${name}/authoringFacts`,
|
|
910
|
+
"workItemDomainActionAuthoringFacts",
|
|
911
|
+
(fact) => fact.kind === "authoring-fact" ? fact.value : void 0
|
|
912
|
+
),
|
|
913
|
+
applications: project(
|
|
914
|
+
graph,
|
|
915
|
+
runtime,
|
|
916
|
+
`${name}/applications`,
|
|
917
|
+
"workItemDomainActionApplications",
|
|
918
|
+
(fact) => fact.kind === "application" ? fact.value : void 0
|
|
919
|
+
),
|
|
920
|
+
status: project(
|
|
921
|
+
graph,
|
|
922
|
+
runtime,
|
|
923
|
+
`${name}/status`,
|
|
924
|
+
"workItemDomainActionStatus",
|
|
925
|
+
(fact) => fact.kind === "status" ? fact.value : void 0
|
|
926
|
+
),
|
|
927
|
+
issues: project(
|
|
928
|
+
graph,
|
|
929
|
+
runtime,
|
|
930
|
+
`${name}/issues`,
|
|
931
|
+
"workItemDomainActionIssues",
|
|
932
|
+
(fact) => fact.kind === "issue" ? fact.value : void 0
|
|
933
|
+
),
|
|
934
|
+
audit: project(
|
|
935
|
+
graph,
|
|
936
|
+
runtime,
|
|
937
|
+
`${name}/audit`,
|
|
938
|
+
"workItemDomainActionAudit",
|
|
939
|
+
(fact) => fact.kind === "audit" ? fact.value : void 0
|
|
940
|
+
)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function workItemDomainActionCapabilityGuardProjector(graph, opts) {
|
|
944
|
+
const name = opts.name ?? "workItemDomainActionCapabilityGuard";
|
|
945
|
+
const deps = opts.capabilityAdmissionStatus === void 0 ? [opts.proposals, opts.capabilityAdmissions, opts.guardPolicies] : [
|
|
946
|
+
opts.proposals,
|
|
947
|
+
opts.capabilityAdmissions,
|
|
948
|
+
opts.guardPolicies,
|
|
949
|
+
opts.capabilityAdmissionStatus
|
|
950
|
+
];
|
|
951
|
+
const statusIndex = opts.capabilityAdmissionStatus === void 0 ? -1 : 3;
|
|
952
|
+
const now = opts.now ?? Date.now;
|
|
953
|
+
const runtime = graph.node(
|
|
954
|
+
deps,
|
|
955
|
+
(ctx) => {
|
|
956
|
+
const state = ctx.state.get() ?? {
|
|
957
|
+
proposals: /* @__PURE__ */ new Map(),
|
|
958
|
+
admissionsByCapability: /* @__PURE__ */ new Map(),
|
|
959
|
+
issueStatusesByCapability: /* @__PURE__ */ new Map(),
|
|
960
|
+
issueStatusesWithoutCapability: [],
|
|
961
|
+
policies: /* @__PURE__ */ new Map(),
|
|
962
|
+
emittedProposalDecisions: /* @__PURE__ */ new Set(),
|
|
963
|
+
emittedStatusKeys: /* @__PURE__ */ new Set(),
|
|
964
|
+
issueKeys: /* @__PURE__ */ new Set(),
|
|
965
|
+
statusSeq: 0,
|
|
966
|
+
auditSeq: 0
|
|
967
|
+
};
|
|
968
|
+
for (const raw of depBatch(ctx, 2) ?? []) {
|
|
969
|
+
const policyIssue = capabilityGuardPolicyIssue(raw);
|
|
970
|
+
if (policyIssue !== void 0) {
|
|
971
|
+
emitCapabilityGuardIssue(
|
|
972
|
+
ctx,
|
|
973
|
+
state,
|
|
974
|
+
`malformed-policy:${recordString(raw, "policyId") ?? "unknown"}`,
|
|
975
|
+
policyIssue
|
|
976
|
+
);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const policy = raw;
|
|
980
|
+
state.policies.set(policy.policyId, policy);
|
|
981
|
+
}
|
|
982
|
+
for (const raw of depBatch(ctx, 1) ?? []) {
|
|
983
|
+
const admissionIssue = capabilityAdmissionIssue(raw);
|
|
984
|
+
if (admissionIssue !== void 0) {
|
|
985
|
+
emitCapabilityGuardIssue(
|
|
986
|
+
ctx,
|
|
987
|
+
state,
|
|
988
|
+
`malformed-admission:${recordString(raw, "admissionId") ?? "unknown"}`,
|
|
989
|
+
admissionIssue
|
|
990
|
+
);
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const admission = raw;
|
|
994
|
+
const capabilityKey = capabilityBoundaryRefId(admission.capability);
|
|
995
|
+
const byCapability = state.admissionsByCapability.get(capabilityKey) ?? [];
|
|
996
|
+
if (!byCapability.some((item) => item.admissionId === admission.admissionId)) {
|
|
997
|
+
state.admissionsByCapability.set(capabilityKey, [...byCapability, admission]);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (statusIndex >= 0) {
|
|
1001
|
+
for (const raw of depBatch(ctx, statusIndex) ?? []) {
|
|
1002
|
+
const statusIssue = capabilityAdmissionStatusIssue(raw);
|
|
1003
|
+
if (statusIssue !== void 0) {
|
|
1004
|
+
emitCapabilityGuardIssue(
|
|
1005
|
+
ctx,
|
|
1006
|
+
state,
|
|
1007
|
+
`malformed-capability-status:${recordString(raw, "statusId") ?? "unknown"}`,
|
|
1008
|
+
statusIssue
|
|
1009
|
+
);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const status = raw;
|
|
1013
|
+
if (status.state !== "capability-admission-issue") continue;
|
|
1014
|
+
const capabilityId = status.capabilityId;
|
|
1015
|
+
const capabilityKind = status.capabilityKind;
|
|
1016
|
+
if (capabilityId !== void 0 && capabilityKind !== void 0) {
|
|
1017
|
+
const capabilityKey = capabilityBoundaryRefId({
|
|
1018
|
+
id: capabilityId,
|
|
1019
|
+
kind: capabilityKind
|
|
1020
|
+
});
|
|
1021
|
+
const statuses = state.issueStatusesByCapability.get(capabilityKey) ?? [];
|
|
1022
|
+
if (!statuses.some((item) => item.statusId === status.statusId)) {
|
|
1023
|
+
state.issueStatusesByCapability.set(capabilityKey, [...statuses, status]);
|
|
1024
|
+
}
|
|
1025
|
+
} else if (!state.issueStatusesWithoutCapability.some((item) => item.statusId === status.statusId)) {
|
|
1026
|
+
state.issueStatusesWithoutCapability.push(status);
|
|
1027
|
+
}
|
|
1028
|
+
for (const proposal of state.proposals.values())
|
|
1029
|
+
emitCapabilityStatusIssueForProposal(ctx, state, proposal, status);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
1033
|
+
const proposalIssue = capabilityGuardProposalIssue(raw);
|
|
1034
|
+
if (proposalIssue !== void 0) {
|
|
1035
|
+
emitCapabilityGuardIssue(
|
|
1036
|
+
ctx,
|
|
1037
|
+
state,
|
|
1038
|
+
`malformed-proposal:${recordString(raw, "proposalId") ?? "unknown"}`,
|
|
1039
|
+
proposalIssue
|
|
1040
|
+
);
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
const proposal = raw;
|
|
1044
|
+
state.proposals.set(proposal.proposalId, proposal);
|
|
1045
|
+
}
|
|
1046
|
+
for (const proposal of state.proposals.values())
|
|
1047
|
+
evaluateCapabilityGuardProposal(ctx, state, proposal, now());
|
|
1048
|
+
ctx.state.set(state);
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
name: `${name}/runtime`,
|
|
1052
|
+
factory: "workItemDomainActionCapabilityGuardProjector",
|
|
1053
|
+
partial: true,
|
|
1054
|
+
completeWhenDepsComplete: false,
|
|
1055
|
+
errorWhenDepsError: false
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
return {
|
|
1059
|
+
decisions: project(
|
|
1060
|
+
graph,
|
|
1061
|
+
runtime,
|
|
1062
|
+
`${name}/decisions`,
|
|
1063
|
+
"workItemDomainActionCapabilityGuardDecisions",
|
|
1064
|
+
(fact) => fact.kind === "decision" ? fact.value : void 0
|
|
1065
|
+
),
|
|
1066
|
+
status: project(
|
|
1067
|
+
graph,
|
|
1068
|
+
runtime,
|
|
1069
|
+
`${name}/status`,
|
|
1070
|
+
"workItemDomainActionCapabilityGuardStatus",
|
|
1071
|
+
(fact) => fact.kind === "status" ? fact.value : void 0
|
|
1072
|
+
),
|
|
1073
|
+
issues: project(
|
|
1074
|
+
graph,
|
|
1075
|
+
runtime,
|
|
1076
|
+
`${name}/issues`,
|
|
1077
|
+
"workItemDomainActionCapabilityGuardIssues",
|
|
1078
|
+
(fact) => fact.kind === "issue" ? fact.value : void 0
|
|
1079
|
+
),
|
|
1080
|
+
audit: project(
|
|
1081
|
+
graph,
|
|
1082
|
+
runtime,
|
|
1083
|
+
`${name}/audit`,
|
|
1084
|
+
"workItemDomainActionCapabilityGuardAudit",
|
|
1085
|
+
(fact) => fact.kind === "audit" ? fact.value : void 0
|
|
1086
|
+
)
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function evaluateCapabilityGuardProposal(ctx, state, proposal, decidedAtMs) {
|
|
1090
|
+
if (state.emittedProposalDecisions.has(proposal.proposalId)) return;
|
|
1091
|
+
const policy = selectCapabilityGuardPolicy(state, proposal);
|
|
1092
|
+
if (typeof policy === "string") {
|
|
1093
|
+
emitCapabilityGuardIssue(
|
|
1094
|
+
ctx,
|
|
1095
|
+
state,
|
|
1096
|
+
`policy:${proposal.proposalId}:${policy}`,
|
|
1097
|
+
dataIssue2(policy, capabilityGuardPolicyMessage(policy, proposal), {
|
|
1098
|
+
subjectId: proposal.workItemId,
|
|
1099
|
+
refs: [ref2("work-item-domain-action-proposal", proposal.proposalId)]
|
|
1100
|
+
})
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
for (const status of state.issueStatusesWithoutCapability)
|
|
1105
|
+
emitCapabilityStatusIssueForProposal(ctx, state, proposal, status);
|
|
1106
|
+
for (const capabilityRef of policy.capabilityRefs) {
|
|
1107
|
+
for (const status of state.issueStatusesByCapability.get(
|
|
1108
|
+
capabilityBoundaryRefId(capabilityRef)
|
|
1109
|
+
) ?? [])
|
|
1110
|
+
emitCapabilityStatusIssueForProposal(ctx, state, proposal, status);
|
|
1111
|
+
}
|
|
1112
|
+
const admissions = policy.capabilityRefs.map(
|
|
1113
|
+
(capabilityRef) => latestCapabilityAdmission(state, policy, capabilityRef)
|
|
1114
|
+
);
|
|
1115
|
+
const missingCapabilityRefs = policy.capabilityRefs.filter(
|
|
1116
|
+
(_, index) => admissions[index] === void 0
|
|
1117
|
+
);
|
|
1118
|
+
const deferredCapabilityRefs = admissions.filter((admission) => admission !== void 0).filter((admission) => admission.state === "deferred").map((admission) => capabilityBoundaryRefId(admission.capability));
|
|
1119
|
+
if (missingCapabilityRefs.length > 0 || deferredCapabilityRefs.length > 0) {
|
|
1120
|
+
emitCapabilityGuardStatusOnce(
|
|
1121
|
+
ctx,
|
|
1122
|
+
state,
|
|
1123
|
+
`deferred:${proposal.proposalId}:${policy.policyId}:missing=${missingCapabilityRefs.map(capabilityBoundaryRefId).join(",")}:deferred=${deferredCapabilityRefs.join(",")}`,
|
|
1124
|
+
{
|
|
1125
|
+
state: "deferred",
|
|
1126
|
+
workItemId: proposal.workItemId,
|
|
1127
|
+
proposalId: proposal.proposalId,
|
|
1128
|
+
actionKind: proposal.actionKind,
|
|
1129
|
+
policyId: policy.policyId,
|
|
1130
|
+
capabilityRefs: policy.capabilityRefs,
|
|
1131
|
+
admissionSubjectIds: policy.admissionSubjectIds,
|
|
1132
|
+
message: "WorkItem domain action is waiting for capability admission",
|
|
1133
|
+
sourceRefs: capabilityGuardRefs(proposal, policy, admissions),
|
|
1134
|
+
metadata: {
|
|
1135
|
+
missingCapabilityRefs: missingCapabilityRefs.map(capabilityBoundaryRefId),
|
|
1136
|
+
deferredCapabilityRefs
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const presentAdmissions = admissions.filter(
|
|
1143
|
+
(admission) => admission !== void 0
|
|
1144
|
+
);
|
|
1145
|
+
const blocked = presentAdmissions.filter((admission) => admission.state === "blocked");
|
|
1146
|
+
const outcome = blocked.length === 0 ? "admit" : "reject";
|
|
1147
|
+
const decision = {
|
|
1148
|
+
kind: "work-item-domain-action-admission-decision",
|
|
1149
|
+
decisionId: `capability-guard:${proposal.proposalId}:decision`,
|
|
1150
|
+
admissionId: `capability-guard:${proposal.proposalId}:admission`,
|
|
1151
|
+
proposalId: proposal.proposalId,
|
|
1152
|
+
outcome,
|
|
1153
|
+
reason: blocked.length === 0 ? "Capability guard admitted required capabilities" : "Capability guard rejected blocked capabilities",
|
|
1154
|
+
decidedAtMs,
|
|
1155
|
+
sourceRefs: capabilityGuardRefs(proposal, policy, presentAdmissions),
|
|
1156
|
+
metadata: {
|
|
1157
|
+
capabilityGuardPolicyId: policy.policyId,
|
|
1158
|
+
capabilityRefs: policy.capabilityRefs.map(capabilityBoundaryRefId),
|
|
1159
|
+
admissionSubjectIds: policy.admissionSubjectIds,
|
|
1160
|
+
blockedCapabilityRefs: blocked.map(
|
|
1161
|
+
(admission) => capabilityBoundaryRefId(admission.capability)
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
state.emittedProposalDecisions.add(proposal.proposalId);
|
|
1166
|
+
emitCapabilityGuard(ctx, "decision", decision);
|
|
1167
|
+
emitCapabilityGuardStatus(ctx, state, {
|
|
1168
|
+
state: outcome === "admit" ? "admitted" : "rejected",
|
|
1169
|
+
workItemId: proposal.workItemId,
|
|
1170
|
+
proposalId: proposal.proposalId,
|
|
1171
|
+
actionKind: proposal.actionKind,
|
|
1172
|
+
policyId: policy.policyId,
|
|
1173
|
+
capabilityRefs: policy.capabilityRefs,
|
|
1174
|
+
admissionSubjectIds: policy.admissionSubjectIds,
|
|
1175
|
+
admissionIds: presentAdmissions.map((admission) => admission.admissionId),
|
|
1176
|
+
sourceRefs: decision.sourceRefs,
|
|
1177
|
+
metadata: { decisionId: decision.decisionId, admissionId: decision.admissionId }
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
function capabilityGuardProposalIssue(proposal) {
|
|
1181
|
+
if (!isRecord2(proposal) || proposal.kind !== "work-item-domain-action-proposal" || typeof proposal.proposalId !== "string" || proposal.proposalId.length === 0 || typeof proposal.workItemId !== "string" || proposal.workItemId.length === 0 || typeof proposal.actionKind !== "string" || proposal.actionKind.length === 0 || proposal.sourceRefs !== void 0 && !isSourceRefArray(proposal.sourceRefs)) {
|
|
1182
|
+
return dataIssue2(
|
|
1183
|
+
"malformed-work-item-domain-action-capability-guard-proposal",
|
|
1184
|
+
"WorkItem domain action capability guard requires valid proposal DATA facts",
|
|
1185
|
+
{ subjectId: recordString(proposal, "workItemId") }
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
return void 0;
|
|
1189
|
+
}
|
|
1190
|
+
function capabilityGuardPolicyIssue(policy) {
|
|
1191
|
+
if (!isRecord2(policy) || policy.kind !== "work-item-domain-action-capability-guard-policy" || typeof policy.policyId !== "string" || policy.policyId.length === 0 || !Array.isArray(policy.capabilityRefs) || policy.capabilityRefs.length === 0 || !policy.capabilityRefs.every(isBoundaryCapabilityRef) || !Array.isArray(policy.admissionSubjectIds) || policy.admissionSubjectIds.length === 0 || !policy.admissionSubjectIds.every((id) => typeof id === "string" && id.length > 0) || policy.actionKinds !== void 0 && (!Array.isArray(policy.actionKinds) || !policy.actionKinds.every((kind) => typeof kind === "string" && kind.length > 0))) {
|
|
1192
|
+
return dataIssue2(
|
|
1193
|
+
"malformed-work-item-domain-action-capability-guard-policy",
|
|
1194
|
+
"WorkItem domain action capability guard policy requires policyId, capabilityRefs, and admissionSubjectIds",
|
|
1195
|
+
{
|
|
1196
|
+
subjectId: recordString(policy, "policyId")
|
|
1197
|
+
}
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
return void 0;
|
|
1201
|
+
}
|
|
1202
|
+
function capabilityAdmissionIssue(admission) {
|
|
1203
|
+
if (!isRecord2(admission) || admission.kind !== "capability-admission" || typeof admission.admissionId !== "string" || admission.admissionId.length === 0 || typeof admission.proposalId !== "string" || admission.proposalId.length === 0 || typeof admission.subjectId !== "string" || admission.subjectId.length === 0 || !isBoundaryCapabilityRef(admission.capability) || !isCapabilityAdmissionState(admission.state) || typeof admission.decisionId !== "string" || admission.decisionId.length === 0 || admission.sourceRefs !== void 0 && !isSourceRefArray(admission.sourceRefs)) {
|
|
1204
|
+
return dataIssue2(
|
|
1205
|
+
"malformed-capability-admission",
|
|
1206
|
+
"WorkItem domain action capability guard requires valid CapabilityAdmission DATA facts",
|
|
1207
|
+
{ subjectId: recordString(admission, "subjectId") }
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
return void 0;
|
|
1211
|
+
}
|
|
1212
|
+
function capabilityAdmissionStatusIssue(status) {
|
|
1213
|
+
if (!isRecord2(status) || status.kind !== "capability-admission-status" || typeof status.statusId !== "string" || status.statusId.length === 0 || !isCapabilityAdmissionStatusState(status.state)) {
|
|
1214
|
+
return dataIssue2(
|
|
1215
|
+
"malformed-capability-admission-status",
|
|
1216
|
+
"WorkItem domain action capability guard requires valid CapabilityAdmissionStatus DATA facts",
|
|
1217
|
+
{ subjectId: recordString(status, "subjectId") }
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
const hasCapabilityId = status.capabilityId !== void 0;
|
|
1221
|
+
const hasCapabilityKind = status.capabilityKind !== void 0;
|
|
1222
|
+
if (hasCapabilityId !== hasCapabilityKind || hasCapabilityId && (typeof status.capabilityId !== "string" || status.capabilityId.length === 0 || !isBoundaryCapabilityKind(status.capabilityKind)) || status.sourceRefs !== void 0 && !isSourceRefArray(status.sourceRefs) || status.issues !== void 0 && !isDataIssueArray(status.issues)) {
|
|
1223
|
+
return dataIssue2(
|
|
1224
|
+
"malformed-capability-admission-status",
|
|
1225
|
+
"WorkItem domain action capability guard requires valid CapabilityAdmissionStatus DATA facts",
|
|
1226
|
+
{ subjectId: recordString(status, "subjectId") }
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
return void 0;
|
|
1230
|
+
}
|
|
1231
|
+
function isCapabilityAdmissionState(value) {
|
|
1232
|
+
return value === "allowed" || value === "blocked" || value === "deferred";
|
|
1233
|
+
}
|
|
1234
|
+
function isCapabilityAdmissionStatusState(value) {
|
|
1235
|
+
return value === "capability-admission-allowed" || value === "capability-admission-blocked" || value === "capability-admission-deferred" || value === "capability-admission-issue";
|
|
1236
|
+
}
|
|
1237
|
+
function isSourceRefArray(value) {
|
|
1238
|
+
return Array.isArray(value) && value.every(
|
|
1239
|
+
(item) => isRecord2(item) && typeof item.kind === "string" && typeof item.id === "string"
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
function isDataIssueArray(value) {
|
|
1243
|
+
return Array.isArray(value) && value.every(isDataIssue);
|
|
1244
|
+
}
|
|
1245
|
+
function isDataIssue(value) {
|
|
1246
|
+
return isRecord2(value) && value.kind === "issue" && typeof value.code === "string" && value.code.length > 0 && typeof value.message === "string" && value.message.length > 0;
|
|
1247
|
+
}
|
|
1248
|
+
function isBoundaryCapabilityRef(value) {
|
|
1249
|
+
return isRecord2(value) && typeof value.id === "string" && value.id.length > 0 && isBoundaryCapabilityKind(value.kind) && typeof value.required === "boolean" && (value.sourceRefs === void 0 || Array.isArray(value.sourceRefs) && value.sourceRefs.every((sourceRef) => typeof sourceRef === "string"));
|
|
1250
|
+
}
|
|
1251
|
+
function isBoundaryCapabilityKind(value) {
|
|
1252
|
+
return value === "auth" || value === "permission" || value === "config" || value === "resource";
|
|
1253
|
+
}
|
|
1254
|
+
function selectCapabilityGuardPolicy(state, proposal) {
|
|
1255
|
+
const explicitId = stringMetadata(proposal.metadata, "capabilityGuardPolicyId") ?? proposal.sourceRefs?.find(
|
|
1256
|
+
(sourceRef) => sourceRef.kind === "work-item-domain-action-capability-guard-policy"
|
|
1257
|
+
)?.id;
|
|
1258
|
+
if (explicitId !== void 0)
|
|
1259
|
+
return state.policies.get(explicitId) ?? "unknown-work-item-domain-action-capability-guard-policy";
|
|
1260
|
+
const matching = [...state.policies.values()].filter(
|
|
1261
|
+
(policy) => policy.actionKinds === void 0 || policy.actionKinds.includes(proposal.actionKind)
|
|
1262
|
+
);
|
|
1263
|
+
if (matching.length === 0) return "missing-policy";
|
|
1264
|
+
if (matching.length > 1) return "policy-mismatch";
|
|
1265
|
+
return matching[0];
|
|
1266
|
+
}
|
|
1267
|
+
function latestCapabilityAdmission(state, policy, capabilityRef) {
|
|
1268
|
+
const admissions = state.admissionsByCapability.get(capabilityBoundaryRefId(capabilityRef)) ?? [];
|
|
1269
|
+
for (let index = admissions.length - 1; index >= 0; index -= 1) {
|
|
1270
|
+
const admission = admissions[index];
|
|
1271
|
+
if (admission !== void 0 && policy.admissionSubjectIds.includes(admission.subjectId))
|
|
1272
|
+
return admission;
|
|
1273
|
+
}
|
|
1274
|
+
return void 0;
|
|
1275
|
+
}
|
|
1276
|
+
function emitCapabilityStatusIssueForProposal(ctx, state, proposal, status) {
|
|
1277
|
+
const policy = selectCapabilityGuardPolicy(state, proposal);
|
|
1278
|
+
if (typeof policy === "string") return;
|
|
1279
|
+
if (status.subjectId !== void 0 && !policy.admissionSubjectIds.includes(status.subjectId))
|
|
1280
|
+
return;
|
|
1281
|
+
const capabilityId = status.capabilityId;
|
|
1282
|
+
const capabilityKind = status.capabilityKind;
|
|
1283
|
+
const statusCapabilityKey = capabilityId === void 0 || capabilityKind === void 0 ? void 0 : capabilityBoundaryRefId({ id: capabilityId, kind: capabilityKind });
|
|
1284
|
+
if (statusCapabilityKey !== void 0 && !policy.capabilityRefs.some(
|
|
1285
|
+
(capabilityRef) => capabilityBoundaryRefId(capabilityRef) === statusCapabilityKey
|
|
1286
|
+
))
|
|
1287
|
+
return;
|
|
1288
|
+
const issue2 = status.issues?.[0] ?? dataIssue2(
|
|
1289
|
+
"capability-admission-status-issue",
|
|
1290
|
+
"Capability admission status reported an issue",
|
|
1291
|
+
{ subjectId: proposal.workItemId }
|
|
1292
|
+
);
|
|
1293
|
+
emitCapabilityGuardIssue(
|
|
1294
|
+
ctx,
|
|
1295
|
+
state,
|
|
1296
|
+
`capability-status:${proposal.proposalId}:${status.statusId}`,
|
|
1297
|
+
dataIssue2(issue2.code, issue2.message, {
|
|
1298
|
+
subjectId: proposal.workItemId,
|
|
1299
|
+
refs: uniqueSourceRefs2([
|
|
1300
|
+
...capabilityGuardRefs(proposal, policy),
|
|
1301
|
+
...status.sourceRefs ?? []
|
|
1302
|
+
]),
|
|
1303
|
+
metadata: {
|
|
1304
|
+
...statusCapabilityKey === void 0 ? {} : { capabilityRef: statusCapabilityKey },
|
|
1305
|
+
capabilityAdmissionStatusId: status.statusId
|
|
1306
|
+
}
|
|
1307
|
+
})
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
function capabilityGuardPolicyMessage(code, proposal) {
|
|
1311
|
+
if (code === "missing-policy")
|
|
1312
|
+
return `WorkItem domain action '${proposal.proposalId}' has no capability guard policy`;
|
|
1313
|
+
if (code === "unknown-work-item-domain-action-capability-guard-policy")
|
|
1314
|
+
return `WorkItem domain action '${proposal.proposalId}' references a missing capability guard policy`;
|
|
1315
|
+
return `WorkItem domain action '${proposal.proposalId}' matches multiple capability guard policies`;
|
|
1316
|
+
}
|
|
1317
|
+
function capabilityGuardRefs(proposal, policy, admissions = []) {
|
|
1318
|
+
return uniqueSourceRefs2([
|
|
1319
|
+
ref2("work-item", proposal.workItemId),
|
|
1320
|
+
ref2("work-item-domain-action-proposal", proposal.proposalId),
|
|
1321
|
+
ref2("work-item-domain-action-capability-guard-policy", policy.policyId),
|
|
1322
|
+
...admissions.filter((admission) => admission !== void 0).flatMap((admission) => [
|
|
1323
|
+
ref2("capability-admission", admission.admissionId),
|
|
1324
|
+
ref2("capability-admission-proposal", admission.proposalId),
|
|
1325
|
+
ref2("boundary-capability", capabilityBoundaryRefId(admission.capability)),
|
|
1326
|
+
...admission.sourceRefs ?? []
|
|
1327
|
+
]),
|
|
1328
|
+
...proposal.sourceRefs ?? []
|
|
1329
|
+
]);
|
|
1330
|
+
}
|
|
1331
|
+
function capabilityBoundaryRefId(capability) {
|
|
1332
|
+
return `${capability.kind}:${capability.id}`;
|
|
1333
|
+
}
|
|
1334
|
+
function emitCapabilityGuardStatus(ctx, state, status) {
|
|
1335
|
+
state.statusSeq += 1;
|
|
1336
|
+
const statusFact = {
|
|
1337
|
+
kind: "work-item-domain-action-capability-guard-status",
|
|
1338
|
+
statusId: `work-item-domain-action-capability-guard-status:${state.statusSeq}`,
|
|
1339
|
+
...status
|
|
1340
|
+
};
|
|
1341
|
+
emitCapabilityGuard(ctx, "status", statusFact);
|
|
1342
|
+
state.auditSeq += 1;
|
|
1343
|
+
emitCapabilityGuard(ctx, "audit", {
|
|
1344
|
+
id: `work-item-domain-action-capability-guard-status:${state.auditSeq}`,
|
|
1345
|
+
kind: "work-item-domain-action-capability-guard-status",
|
|
1346
|
+
subjectId: status.workItemId,
|
|
1347
|
+
message: status.message,
|
|
1348
|
+
sourceRefs: status.sourceRefs,
|
|
1349
|
+
metadata: {
|
|
1350
|
+
statusId: statusFact.statusId,
|
|
1351
|
+
state: status.state,
|
|
1352
|
+
proposalId: status.proposalId,
|
|
1353
|
+
policyId: status.policyId,
|
|
1354
|
+
...status.metadata ?? {}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
function emitCapabilityGuardStatusOnce(ctx, state, key, status) {
|
|
1359
|
+
if (state.emittedStatusKeys.has(key)) return;
|
|
1360
|
+
state.emittedStatusKeys.add(key);
|
|
1361
|
+
emitCapabilityGuardStatus(ctx, state, status);
|
|
1362
|
+
}
|
|
1363
|
+
function emitCapabilityGuardIssue(ctx, state, key, issue2) {
|
|
1364
|
+
if (state.issueKeys.has(key)) return;
|
|
1365
|
+
state.issueKeys.add(key);
|
|
1366
|
+
emitCapabilityGuard(ctx, "issue", issue2);
|
|
1367
|
+
emitCapabilityGuardStatus(ctx, state, {
|
|
1368
|
+
state: "issue",
|
|
1369
|
+
workItemId: issue2.subjectId,
|
|
1370
|
+
issues: [issue2],
|
|
1371
|
+
message: issue2.message,
|
|
1372
|
+
metadata: { issueCode: issue2.code }
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
function emitCapabilityGuard(ctx, kind, value) {
|
|
1376
|
+
ctx.down([["DATA", { kind, value }]]);
|
|
1377
|
+
}
|
|
1378
|
+
function reduceProposalIntake(ctx, state, input, proposedAtMs) {
|
|
1379
|
+
const proposal = normalizeProposal(input, proposedAtMs);
|
|
1380
|
+
if (typeof proposal === "string") {
|
|
1381
|
+
const issue2 = dataIssue2("malformed-domain-action-proposal", proposal);
|
|
1382
|
+
emitIntake(ctx, "issue", issue2);
|
|
1383
|
+
emitIntakeStatus(ctx, state, {
|
|
1384
|
+
state: "rejected",
|
|
1385
|
+
code: issue2.code,
|
|
1386
|
+
message: issue2.message
|
|
1387
|
+
});
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (state.proposals.has(proposal.proposalId)) {
|
|
1391
|
+
emitIntakeStatus(ctx, state, {
|
|
1392
|
+
state: "duplicate",
|
|
1393
|
+
code: "duplicate-suppressed",
|
|
1394
|
+
workItemId: proposal.workItemId,
|
|
1395
|
+
proposalId: proposal.proposalId,
|
|
1396
|
+
actionKind: proposal.actionKind,
|
|
1397
|
+
message: `Duplicate WorkItem domain action proposal '${proposal.proposalId}' suppressed`
|
|
1398
|
+
});
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
state.proposals.add(proposal.proposalId);
|
|
1402
|
+
emitIntake(ctx, "proposal", proposal);
|
|
1403
|
+
emitIntakeStatus(ctx, state, {
|
|
1404
|
+
state: "proposed",
|
|
1405
|
+
workItemId: proposal.workItemId,
|
|
1406
|
+
proposalId: proposal.proposalId,
|
|
1407
|
+
actionKind: proposal.actionKind,
|
|
1408
|
+
sourceRefs: proposal.sourceRefs
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function normalizeProposal(input, proposedAtMs) {
|
|
1412
|
+
if (!isRecord2(input)) return "WorkItemDomainActionProposal intake must be an object";
|
|
1413
|
+
if (input.kind !== "work-item-domain-action-proposal" && input.kind !== "work-item-domain-action-proposal-intake")
|
|
1414
|
+
return "WorkItemDomainActionProposal intake has unsupported kind";
|
|
1415
|
+
if (typeof input.proposalId !== "string" || input.proposalId.trim() === "")
|
|
1416
|
+
return "WorkItemDomainActionProposal.proposalId is required";
|
|
1417
|
+
if (typeof input.workItemId !== "string" || input.workItemId.trim() === "")
|
|
1418
|
+
return "WorkItemDomainActionProposal.workItemId is required";
|
|
1419
|
+
if (typeof input.actionKind !== "string" || input.actionKind.trim() === "")
|
|
1420
|
+
return "WorkItemDomainActionProposal.actionKind is required";
|
|
1421
|
+
if (input.kind === "work-item-domain-action-proposal") {
|
|
1422
|
+
if (typeof input.effectRunId !== "string")
|
|
1423
|
+
return "WorkItemDomainActionProposal.effectRunId is required";
|
|
1424
|
+
if (typeof input.effectRunResultId !== "string")
|
|
1425
|
+
return "WorkItemDomainActionProposal.effectRunResultId is required";
|
|
1426
|
+
if (typeof input.evidenceId !== "string")
|
|
1427
|
+
return "WorkItemDomainActionProposal.evidenceId is required";
|
|
1428
|
+
if (typeof input.policyId !== "string")
|
|
1429
|
+
return "WorkItemDomainActionProposal.policyId is required";
|
|
1430
|
+
return input;
|
|
1431
|
+
}
|
|
1432
|
+
return workItemDomainActionProposal(
|
|
1433
|
+
input.proposalId,
|
|
1434
|
+
input.workItemId,
|
|
1435
|
+
input.actionKind,
|
|
1436
|
+
{
|
|
1437
|
+
payload: input.payload,
|
|
1438
|
+
reason: typeof input.reason === "string" ? input.reason : void 0,
|
|
1439
|
+
policyId: typeof input.policyId === "string" ? input.policyId : void 0,
|
|
1440
|
+
effectRunId: typeof input.effectRunId === "string" ? input.effectRunId : void 0,
|
|
1441
|
+
effectRunResultId: typeof input.effectRunResultId === "string" ? input.effectRunResultId : void 0,
|
|
1442
|
+
evidenceId: typeof input.evidenceId === "string" ? input.evidenceId : void 0,
|
|
1443
|
+
proposedAtMs: typeof input.proposedAtMs === "number" ? input.proposedAtMs : proposedAtMs,
|
|
1444
|
+
sourceRefs: sourceRefs(input.sourceRefs),
|
|
1445
|
+
metadata: isRecord2(input.metadata) ? input.metadata : void 0
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
function applyAdmission(ctx, state, admission) {
|
|
1450
|
+
if (state.appliedAdmissions.has(admission.admissionId)) return;
|
|
1451
|
+
const proposal = state.proposals.get(admission.proposalId);
|
|
1452
|
+
if (proposal === void 0) {
|
|
1453
|
+
emitApplicationIssue(
|
|
1454
|
+
ctx,
|
|
1455
|
+
state,
|
|
1456
|
+
`missing-proposal:${admission.admissionId}:${admission.proposalId}`,
|
|
1457
|
+
"dangling-ref",
|
|
1458
|
+
`Admission '${admission.admissionId}' cannot apply without proposal '${admission.proposalId}'`,
|
|
1459
|
+
admission.workItemId,
|
|
1460
|
+
admissionRefs(admission)
|
|
1461
|
+
);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (admission.state !== "admitted") {
|
|
1465
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1466
|
+
emitApplication(ctx, state, admission, proposal, admission.state, []);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const mismatch = admissionProposalMismatch(admission, proposal);
|
|
1470
|
+
if (mismatch !== void 0) {
|
|
1471
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1472
|
+
emitApplicationIssue(
|
|
1473
|
+
ctx,
|
|
1474
|
+
state,
|
|
1475
|
+
`admission-proposal-mismatch:${admission.admissionId}:${mismatch.field}`,
|
|
1476
|
+
"dangling-ref",
|
|
1477
|
+
mismatch.message,
|
|
1478
|
+
admission.workItemId,
|
|
1479
|
+
admissionRefs(admission, proposal),
|
|
1480
|
+
{
|
|
1481
|
+
field: mismatch.field,
|
|
1482
|
+
admissionValue: mismatch.admissionValue,
|
|
1483
|
+
proposalValue: mismatch.proposalValue
|
|
1484
|
+
}
|
|
1485
|
+
);
|
|
1486
|
+
emitApplication(ctx, state, admission, proposal, "rejected", [], {
|
|
1487
|
+
code: "dangling-ref",
|
|
1488
|
+
message: mismatch.message
|
|
1489
|
+
});
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
const workItem = state.workItems.get(admission.workItemId);
|
|
1493
|
+
if (workItem === void 0) {
|
|
1494
|
+
emitApplicationIssue(
|
|
1495
|
+
ctx,
|
|
1496
|
+
state,
|
|
1497
|
+
`missing-work-item:${admission.admissionId}:${admission.workItemId}`,
|
|
1498
|
+
"dangling-ref",
|
|
1499
|
+
`Admission '${admission.admissionId}' references unknown WorkItem '${admission.workItemId}'`,
|
|
1500
|
+
admission.workItemId,
|
|
1501
|
+
admissionRefs(admission, proposal)
|
|
1502
|
+
);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
const metadataConflict = conflictingApplicationMetadata(proposal, admission);
|
|
1506
|
+
if (metadataConflict !== void 0) {
|
|
1507
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1508
|
+
emitApplicationIssue(
|
|
1509
|
+
ctx,
|
|
1510
|
+
state,
|
|
1511
|
+
`metadata-conflict:${admission.admissionId}:${metadataConflict.field}`,
|
|
1512
|
+
metadataConflict.code,
|
|
1513
|
+
metadataConflict.message,
|
|
1514
|
+
admission.workItemId,
|
|
1515
|
+
admissionRefs(admission, proposal),
|
|
1516
|
+
{
|
|
1517
|
+
field: metadataConflict.field,
|
|
1518
|
+
admissionValue: metadataConflict.admissionValue,
|
|
1519
|
+
proposalValue: metadataConflict.proposalValue
|
|
1520
|
+
}
|
|
1521
|
+
);
|
|
1522
|
+
emitApplication(ctx, state, admission, proposal, "rejected", [], {
|
|
1523
|
+
code: metadataConflict.code,
|
|
1524
|
+
message: metadataConflict.message
|
|
1525
|
+
});
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
const stale = staleRevision(proposal, admission, workItem);
|
|
1529
|
+
if (stale !== void 0) {
|
|
1530
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1531
|
+
emitApplicationIssue(
|
|
1532
|
+
ctx,
|
|
1533
|
+
state,
|
|
1534
|
+
`stale:${admission.admissionId}:${stale.kind}:${stale.expected}:${stale.current}`,
|
|
1535
|
+
stale.kind,
|
|
1536
|
+
`Admission '${admission.admissionId}' targets stale WorkItem revision`,
|
|
1537
|
+
admission.workItemId,
|
|
1538
|
+
admissionRefs(admission, proposal),
|
|
1539
|
+
{ expected: stale.expected, current: stale.current }
|
|
1540
|
+
);
|
|
1541
|
+
emitApplication(ctx, state, admission, proposal, "rejected", [], {
|
|
1542
|
+
code: stale.kind,
|
|
1543
|
+
message: "Stale WorkItem revision"
|
|
1544
|
+
});
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const policyResult = selectApplyPolicy(state, proposal, admission);
|
|
1548
|
+
if (typeof policyResult === "string") {
|
|
1549
|
+
emitApplicationIssue(
|
|
1550
|
+
ctx,
|
|
1551
|
+
state,
|
|
1552
|
+
`policy:${admission.admissionId}:${policyResult}`,
|
|
1553
|
+
policyResult,
|
|
1554
|
+
applyPolicyMessage(policyResult, proposal, admission),
|
|
1555
|
+
admission.workItemId,
|
|
1556
|
+
admissionRefs(admission, proposal)
|
|
1557
|
+
);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const output = lowerAction(ctx, state, workItem, proposal, admission, policyResult);
|
|
1561
|
+
if (output === void 0) {
|
|
1562
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1563
|
+
emitApplication(ctx, state, admission, proposal, "rejected", [], {
|
|
1564
|
+
code: "invalid-action",
|
|
1565
|
+
message: "WorkItem domain action application rejected; see emitted issues",
|
|
1566
|
+
policyId: policyResult.policyId
|
|
1567
|
+
});
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
const duplicateFactId = firstDuplicateFactId(state, output.facts);
|
|
1571
|
+
if (duplicateFactId !== void 0) {
|
|
1572
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1573
|
+
emitApplicationIssue(
|
|
1574
|
+
ctx,
|
|
1575
|
+
state,
|
|
1576
|
+
`duplicate-authoring-fact:${admission.admissionId}:${duplicateFactId}`,
|
|
1577
|
+
"duplicate-suppressed",
|
|
1578
|
+
`WorkItem domain action application '${admission.admissionId}' would duplicate authoring fact '${duplicateFactId}'`,
|
|
1579
|
+
admission.workItemId,
|
|
1580
|
+
admissionRefs(admission, proposal, policyResult),
|
|
1581
|
+
{ eventId: duplicateFactId }
|
|
1582
|
+
);
|
|
1583
|
+
emitApplication(ctx, state, admission, proposal, "duplicate", [], {
|
|
1584
|
+
code: "duplicate-suppressed",
|
|
1585
|
+
message: "Duplicate WorkItem authoring fact suppressed atomically",
|
|
1586
|
+
policyId: policyResult.policyId
|
|
1587
|
+
});
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
state.appliedAdmissions.add(admission.admissionId);
|
|
1591
|
+
for (const fact of output.facts) claimFactId(state, fact.eventId);
|
|
1592
|
+
for (const fact of output.facts) emitApplicationFact(ctx, "authoring-fact", fact);
|
|
1593
|
+
emitApplication(ctx, state, admission, proposal, output.state, output.facts, {
|
|
1594
|
+
code: output.code,
|
|
1595
|
+
message: output.message,
|
|
1596
|
+
policyId: policyResult.policyId
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
function lowerAction(ctx, state, workItem, proposal, admission, policy) {
|
|
1600
|
+
if (!policyAllowsAction(policy, proposal.actionKind)) {
|
|
1601
|
+
emitApplicationIssue(
|
|
1602
|
+
ctx,
|
|
1603
|
+
state,
|
|
1604
|
+
`policy-action:${admission.admissionId}:${policy.policyId}:${proposal.actionKind}`,
|
|
1605
|
+
"policy-mismatch",
|
|
1606
|
+
`Apply policy '${policy.policyId}' does not allow actionKind '${proposal.actionKind}'`,
|
|
1607
|
+
proposal.workItemId,
|
|
1608
|
+
admissionRefs(admission, proposal, policy)
|
|
1609
|
+
);
|
|
1610
|
+
return { state: "policy-rejected", facts: [], code: "policy-mismatch" };
|
|
1611
|
+
}
|
|
1612
|
+
if (proposal.actionKind === "patch") {
|
|
1613
|
+
const lowered = lowerPatchAction(ctx, state, workItem, proposal, admission, policy);
|
|
1614
|
+
if (lowered === void 0) return void 0;
|
|
1615
|
+
return { state: "applied", facts: lowered };
|
|
1616
|
+
}
|
|
1617
|
+
if (proposal.actionKind === "mark-verified" || proposal.actionKind === "require-review") {
|
|
1618
|
+
return {
|
|
1619
|
+
state: "proposal-only",
|
|
1620
|
+
facts: [],
|
|
1621
|
+
code: "domain-fact-unimplemented",
|
|
1622
|
+
message: `Action '${proposal.actionKind}' recorded as visible application status only; no WorkItem domain mutation fact was emitted`
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
if (proposal.actionKind === "spawn-proposed" || proposal.actionKind === "spawn-child") {
|
|
1626
|
+
return lowerSpawnAction(ctx, state, proposal, admission, policy);
|
|
1627
|
+
}
|
|
1628
|
+
emitApplicationIssue(
|
|
1629
|
+
ctx,
|
|
1630
|
+
state,
|
|
1631
|
+
`unsupported-action:${admission.admissionId}:${proposal.actionKind}`,
|
|
1632
|
+
"unsupported-effect-kind",
|
|
1633
|
+
`Unsupported WorkItem domain actionKind '${proposal.actionKind}'`,
|
|
1634
|
+
proposal.workItemId,
|
|
1635
|
+
admissionRefs(admission, proposal, policy)
|
|
1636
|
+
);
|
|
1637
|
+
return { state: "rejected", facts: [], code: "unsupported-effect-kind" };
|
|
1638
|
+
}
|
|
1639
|
+
function lowerPatchAction(ctx, state, workItem, proposal, admission, policy) {
|
|
1640
|
+
const payload = normalizePatchPayload(proposal.payload);
|
|
1641
|
+
if (typeof payload === "string") {
|
|
1642
|
+
emitApplicationIssue(
|
|
1643
|
+
ctx,
|
|
1644
|
+
state,
|
|
1645
|
+
`invalid-patch:${admission.admissionId}`,
|
|
1646
|
+
"invalid-patch",
|
|
1647
|
+
payload,
|
|
1648
|
+
proposal.workItemId,
|
|
1649
|
+
admissionRefs(admission, proposal, policy)
|
|
1650
|
+
);
|
|
1651
|
+
return void 0;
|
|
1652
|
+
}
|
|
1653
|
+
const splitPayload = splitDedicatedPatchFields(payload, workItem);
|
|
1654
|
+
const facts = [];
|
|
1655
|
+
if (splitPayload.patch !== void 0) {
|
|
1656
|
+
const invalid = invalidPatchKeys(splitPayload.patch, policy);
|
|
1657
|
+
if (invalid.length > 0) {
|
|
1658
|
+
emitApplicationIssue(
|
|
1659
|
+
ctx,
|
|
1660
|
+
state,
|
|
1661
|
+
`invalid-patch-fields:${admission.admissionId}:${invalid.join(",")}`,
|
|
1662
|
+
"invalid-patch",
|
|
1663
|
+
`Patch action uses unsupported fields: ${invalid.join(", ")}`,
|
|
1664
|
+
proposal.workItemId,
|
|
1665
|
+
admissionRefs(admission, proposal, policy),
|
|
1666
|
+
{ invalidFields: invalid }
|
|
1667
|
+
);
|
|
1668
|
+
return void 0;
|
|
1669
|
+
}
|
|
1670
|
+
facts.push({
|
|
1671
|
+
kind: "work-item-patched",
|
|
1672
|
+
eventId: splitPayload.eventId ?? `${admission.admissionId}:work-item-patched`,
|
|
1673
|
+
workItemId: workItem.workItemId,
|
|
1674
|
+
patch: splitPayload.patch,
|
|
1675
|
+
executionRelevantFields: splitPayload.executionRelevantFields ?? policy.patch?.executionRelevantFields,
|
|
1676
|
+
authorId: splitPayload.authorId,
|
|
1677
|
+
sourceRefs: actionSourceRefs(admission, proposal, policy),
|
|
1678
|
+
metadata: { ...splitPayload.metadata ?? {}, actionKind: proposal.actionKind }
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
if (splitPayload.acceptanceCriteria !== void 0) {
|
|
1682
|
+
if (!allowsAcceptanceCriteria(policy)) {
|
|
1683
|
+
emitApplicationIssue(
|
|
1684
|
+
ctx,
|
|
1685
|
+
state,
|
|
1686
|
+
`ac-policy:${admission.admissionId}`,
|
|
1687
|
+
"policy-mismatch",
|
|
1688
|
+
`Apply policy '${policy.policyId}' does not allow acceptance criteria changes`,
|
|
1689
|
+
proposal.workItemId,
|
|
1690
|
+
admissionRefs(admission, proposal, policy)
|
|
1691
|
+
);
|
|
1692
|
+
return void 0;
|
|
1693
|
+
}
|
|
1694
|
+
const issues = validateAcceptanceCriteria(splitPayload.acceptanceCriteria, {
|
|
1695
|
+
workItemId: workItem.workItemId
|
|
1696
|
+
});
|
|
1697
|
+
if (issues.length > 0) {
|
|
1698
|
+
for (const item of issues) emitApplicationFact(ctx, "issue", item);
|
|
1699
|
+
emitApplicationStatus(ctx, state, {
|
|
1700
|
+
state: "rejected",
|
|
1701
|
+
code: issues[0]?.code,
|
|
1702
|
+
message: issues[0]?.message,
|
|
1703
|
+
workItemId: workItem.workItemId,
|
|
1704
|
+
proposalId: proposal.proposalId,
|
|
1705
|
+
admissionId: admission.admissionId,
|
|
1706
|
+
actionKind: proposal.actionKind,
|
|
1707
|
+
sourceRefs: admissionRefs(admission, proposal, policy)
|
|
1708
|
+
});
|
|
1709
|
+
return void 0;
|
|
1710
|
+
}
|
|
1711
|
+
facts.push({
|
|
1712
|
+
kind: "acceptance-criteria-changed",
|
|
1713
|
+
eventId: `${admission.admissionId}:acceptance-criteria-changed`,
|
|
1714
|
+
workItemId: workItem.workItemId,
|
|
1715
|
+
acceptanceCriteria: splitPayload.acceptanceCriteria,
|
|
1716
|
+
authorId: splitPayload.authorId,
|
|
1717
|
+
sourceRefs: actionSourceRefs(admission, proposal, policy),
|
|
1718
|
+
metadata: { ...splitPayload.metadata ?? {}, actionKind: proposal.actionKind }
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
if (splitPayload.verificationPlan !== void 0) {
|
|
1722
|
+
if (!allowsVerificationPlan(policy)) {
|
|
1723
|
+
emitApplicationIssue(
|
|
1724
|
+
ctx,
|
|
1725
|
+
state,
|
|
1726
|
+
`plan-policy:${admission.admissionId}`,
|
|
1727
|
+
"policy-mismatch",
|
|
1728
|
+
`Apply policy '${policy.policyId}' does not allow verification plan changes`,
|
|
1729
|
+
proposal.workItemId,
|
|
1730
|
+
admissionRefs(admission, proposal, policy)
|
|
1731
|
+
);
|
|
1732
|
+
return void 0;
|
|
1733
|
+
}
|
|
1734
|
+
const issues = validateVerificationPlan(
|
|
1735
|
+
splitPayload.verificationPlan,
|
|
1736
|
+
splitPayload.acceptanceCriteria ?? workItem.acceptanceCriteria ?? [],
|
|
1737
|
+
{ workItemId: workItem.workItemId }
|
|
1738
|
+
);
|
|
1739
|
+
if (issues.length > 0) {
|
|
1740
|
+
for (const item of issues) emitApplicationFact(ctx, "issue", item);
|
|
1741
|
+
emitApplicationStatus(ctx, state, {
|
|
1742
|
+
state: "rejected",
|
|
1743
|
+
code: issues[0]?.code,
|
|
1744
|
+
message: issues[0]?.message,
|
|
1745
|
+
workItemId: workItem.workItemId,
|
|
1746
|
+
proposalId: proposal.proposalId,
|
|
1747
|
+
admissionId: admission.admissionId,
|
|
1748
|
+
actionKind: proposal.actionKind,
|
|
1749
|
+
sourceRefs: admissionRefs(admission, proposal, policy)
|
|
1750
|
+
});
|
|
1751
|
+
return void 0;
|
|
1752
|
+
}
|
|
1753
|
+
facts.push({
|
|
1754
|
+
kind: "verification-plan-changed",
|
|
1755
|
+
eventId: `${admission.admissionId}:verification-plan-changed`,
|
|
1756
|
+
workItemId: workItem.workItemId,
|
|
1757
|
+
verificationPlan: splitPayload.verificationPlan,
|
|
1758
|
+
authorId: splitPayload.authorId,
|
|
1759
|
+
sourceRefs: actionSourceRefs(admission, proposal, policy),
|
|
1760
|
+
metadata: { ...splitPayload.metadata ?? {}, actionKind: proposal.actionKind }
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
if (facts.length === 0) {
|
|
1764
|
+
emitApplicationIssue(
|
|
1765
|
+
ctx,
|
|
1766
|
+
state,
|
|
1767
|
+
`empty-patch:${admission.admissionId}`,
|
|
1768
|
+
"invalid-patch",
|
|
1769
|
+
"Patch action payload did not contain patch, acceptanceCriteria, or verificationPlan",
|
|
1770
|
+
proposal.workItemId,
|
|
1771
|
+
admissionRefs(admission, proposal, policy)
|
|
1772
|
+
);
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
return facts;
|
|
1776
|
+
}
|
|
1777
|
+
function lowerSpawnAction(ctx, state, proposal, admission, policy) {
|
|
1778
|
+
if (policy.spawn?.create !== true) {
|
|
1779
|
+
return {
|
|
1780
|
+
state: "proposal-only",
|
|
1781
|
+
facts: [],
|
|
1782
|
+
message: "Spawn action remains proposal/admission only without explicit create policy"
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
emitApplicationIssue(
|
|
1786
|
+
ctx,
|
|
1787
|
+
state,
|
|
1788
|
+
`spawn-create-policy:${admission.admissionId}:${policy.policyId}`,
|
|
1789
|
+
"policy-mismatch",
|
|
1790
|
+
`Spawn create policy '${policy.policyId}' is incomplete: child creation requires an explicit create/link/idempotency policy surface`,
|
|
1791
|
+
proposal.workItemId,
|
|
1792
|
+
admissionRefs(admission, proposal, policy),
|
|
1793
|
+
{
|
|
1794
|
+
requires: ["linkParent", "idempotencyKey", "maxChildrenPerAdmission", "link-fact-vocabulary"]
|
|
1795
|
+
}
|
|
1796
|
+
);
|
|
1797
|
+
return {
|
|
1798
|
+
state: "proposal-only",
|
|
1799
|
+
facts: [],
|
|
1800
|
+
code: "policy-mismatch",
|
|
1801
|
+
message: "Spawn action remains proposal/admission only until explicit create/link policy is available"
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function normalizePatchPayload(payload) {
|
|
1805
|
+
if (!isRecord2(payload)) return "Patch action payload must be an object";
|
|
1806
|
+
if ("patch" in payload || "acceptanceCriteria" in payload || "verificationPlan" in payload || "executionRelevantFields" in payload) {
|
|
1807
|
+
if ("patch" in payload && payload.patch !== void 0 && !isRecord2(payload.patch))
|
|
1808
|
+
return "Patch action payload.patch must be an object";
|
|
1809
|
+
if ("acceptanceCriteria" in payload && payload.acceptanceCriteria !== void 0 && !Array.isArray(payload.acceptanceCriteria))
|
|
1810
|
+
return "Patch action payload.acceptanceCriteria must be an array";
|
|
1811
|
+
if ("verificationPlan" in payload && payload.verificationPlan !== void 0 && !isRecord2(payload.verificationPlan))
|
|
1812
|
+
return "Patch action payload.verificationPlan must be an object";
|
|
1813
|
+
const inlinePatch = inlinePatchFields(payload);
|
|
1814
|
+
const explicitPatch = isRecord2(payload.patch) ? payload.patch : void 0;
|
|
1815
|
+
const patch = explicitPatch === void 0 ? inlinePatch : { ...inlinePatch, ...explicitPatch };
|
|
1816
|
+
return {
|
|
1817
|
+
...payload,
|
|
1818
|
+
patch: Object.keys(patch).length === 0 ? void 0 : patch
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
return { patch: payload };
|
|
1822
|
+
}
|
|
1823
|
+
function inlinePatchFields(payload) {
|
|
1824
|
+
const patch = {};
|
|
1825
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1826
|
+
if (key === "metadata") continue;
|
|
1827
|
+
if (patchFieldKeys.has(key)) patch[key] = value;
|
|
1828
|
+
}
|
|
1829
|
+
return patch;
|
|
1830
|
+
}
|
|
1831
|
+
function splitDedicatedPatchFields(payload, workItem) {
|
|
1832
|
+
if (!isRecord2(payload.patch)) return payload;
|
|
1833
|
+
const patchRecord = payload.patch;
|
|
1834
|
+
const patch = {};
|
|
1835
|
+
for (const [key, value] of Object.entries(patchRecord)) {
|
|
1836
|
+
if (key === "acceptanceCriteria" || key === "verificationPlan" || key === "verificationSteps")
|
|
1837
|
+
continue;
|
|
1838
|
+
patch[key] = value;
|
|
1839
|
+
}
|
|
1840
|
+
const acceptanceCriteria = payload.acceptanceCriteria ?? (Array.isArray(patchRecord.acceptanceCriteria) ? patchRecord.acceptanceCriteria : void 0);
|
|
1841
|
+
const verificationPlan = payload.verificationPlan ?? (isRecord2(patchRecord.verificationPlan) ? patchRecord.verificationPlan : Array.isArray(patchRecord.verificationSteps) ? {
|
|
1842
|
+
...workItem.verificationPlan ?? { planId: "default" },
|
|
1843
|
+
steps: patchRecord.verificationSteps
|
|
1844
|
+
} : void 0);
|
|
1845
|
+
return {
|
|
1846
|
+
...payload,
|
|
1847
|
+
patch: Object.keys(patch).length === 0 ? void 0 : patch,
|
|
1848
|
+
acceptanceCriteria,
|
|
1849
|
+
verificationPlan
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
function invalidPatchKeys(patch, policy) {
|
|
1853
|
+
const allowed = new Set(policy.patch?.allowedFields ?? Array.from(patchFieldKeys));
|
|
1854
|
+
return Object.keys(patch).filter((key) => !patchFieldKeys.has(key) || !allowed.has(key));
|
|
1855
|
+
}
|
|
1856
|
+
function allowsAcceptanceCriteria(policy) {
|
|
1857
|
+
if (policy.patch?.allowAcceptanceCriteria === false) return false;
|
|
1858
|
+
const allowed = policy.patch?.allowedFields;
|
|
1859
|
+
return allowed === void 0 || allowed.includes("acceptanceCriteria") || policy.patch?.allowAcceptanceCriteria === true;
|
|
1860
|
+
}
|
|
1861
|
+
function allowsVerificationPlan(policy) {
|
|
1862
|
+
if (policy.patch?.allowVerificationPlan === false) return false;
|
|
1863
|
+
const allowed = policy.patch?.allowedFields;
|
|
1864
|
+
return allowed === void 0 || allowed.includes("verificationPlan") || allowed.includes("verificationSteps") || policy.patch?.allowVerificationPlan === true;
|
|
1865
|
+
}
|
|
1866
|
+
function selectApplyPolicy(state, proposal, admission) {
|
|
1867
|
+
const explicitId = explicitApplyPolicyId(proposal, admission);
|
|
1868
|
+
if (explicitId !== void 0)
|
|
1869
|
+
return state.policies.get(explicitId) ?? "unknown-work-item-domain-action-apply-policy";
|
|
1870
|
+
const matching = Array.from(state.policies.values()).filter(
|
|
1871
|
+
(policy) => policyAllowsAction(policy, proposal.actionKind)
|
|
1872
|
+
);
|
|
1873
|
+
if (matching.length === 0) return "missing-policy";
|
|
1874
|
+
if (matching.length > 1) return "policy-mismatch";
|
|
1875
|
+
return matching[0];
|
|
1876
|
+
}
|
|
1877
|
+
function explicitApplyPolicyId(proposal, admission) {
|
|
1878
|
+
const metadataPolicy = stringMetadata(admission.metadata, "applyPolicyId") ?? stringMetadata(proposal.metadata, "applyPolicyId");
|
|
1879
|
+
if (metadataPolicy !== void 0) return metadataPolicy;
|
|
1880
|
+
return [...admission.sourceRefs ?? [], ...proposal.sourceRefs ?? []].find(
|
|
1881
|
+
(sourceRef) => sourceRef.kind === "work-item-domain-action-apply-policy"
|
|
1882
|
+
)?.id;
|
|
1883
|
+
}
|
|
1884
|
+
function policyAllowsAction(policy, actionKind) {
|
|
1885
|
+
return policy.actionKinds === void 0 || policy.actionKinds.includes(actionKind);
|
|
1886
|
+
}
|
|
1887
|
+
function admissionProposalMismatch(admission, proposal) {
|
|
1888
|
+
if (admission.workItemId !== proposal.workItemId)
|
|
1889
|
+
return {
|
|
1890
|
+
field: "workItemId",
|
|
1891
|
+
admissionValue: admission.workItemId,
|
|
1892
|
+
proposalValue: proposal.workItemId,
|
|
1893
|
+
message: `Admission '${admission.admissionId}' workItemId does not match proposal '${proposal.proposalId}'`
|
|
1894
|
+
};
|
|
1895
|
+
if (admission.actionKind !== proposal.actionKind)
|
|
1896
|
+
return {
|
|
1897
|
+
field: "actionKind",
|
|
1898
|
+
admissionValue: admission.actionKind,
|
|
1899
|
+
proposalValue: proposal.actionKind,
|
|
1900
|
+
message: `Admission '${admission.admissionId}' actionKind does not match proposal '${proposal.proposalId}'`
|
|
1901
|
+
};
|
|
1902
|
+
return void 0;
|
|
1903
|
+
}
|
|
1904
|
+
function conflictingApplicationMetadata(proposal, admission) {
|
|
1905
|
+
const authoringRevision = conflictingMetadataNumber(proposal, admission, "authoringRevision");
|
|
1906
|
+
if (authoringRevision !== void 0)
|
|
1907
|
+
return {
|
|
1908
|
+
field: "authoringRevision",
|
|
1909
|
+
code: "stale-revision",
|
|
1910
|
+
...authoringRevision,
|
|
1911
|
+
message: `Admission '${admission.admissionId}' carries conflicting authoringRevision metadata`
|
|
1912
|
+
};
|
|
1913
|
+
const executionInputRevision = conflictingMetadataNumber(
|
|
1914
|
+
proposal,
|
|
1915
|
+
admission,
|
|
1916
|
+
"executionInputRevision"
|
|
1917
|
+
);
|
|
1918
|
+
if (executionInputRevision !== void 0)
|
|
1919
|
+
return {
|
|
1920
|
+
field: "executionInputRevision",
|
|
1921
|
+
code: "stale-execution-input",
|
|
1922
|
+
...executionInputRevision,
|
|
1923
|
+
message: `Admission '${admission.admissionId}' carries conflicting executionInputRevision metadata`
|
|
1924
|
+
};
|
|
1925
|
+
const applyPolicyId = conflictingMetadataString(proposal, admission, "applyPolicyId");
|
|
1926
|
+
if (applyPolicyId !== void 0)
|
|
1927
|
+
return {
|
|
1928
|
+
field: "applyPolicyId",
|
|
1929
|
+
code: "policy-mismatch",
|
|
1930
|
+
...applyPolicyId,
|
|
1931
|
+
message: `Admission '${admission.admissionId}' carries conflicting applyPolicyId metadata`
|
|
1932
|
+
};
|
|
1933
|
+
return void 0;
|
|
1934
|
+
}
|
|
1935
|
+
function conflictingMetadataNumber(proposal, admission, key) {
|
|
1936
|
+
const proposalValue = numberMetadata(proposal.metadata, key);
|
|
1937
|
+
const admissionValue = numberMetadata(admission.metadata, key);
|
|
1938
|
+
if (proposalValue === void 0 || admissionValue === void 0 || proposalValue === admissionValue)
|
|
1939
|
+
return void 0;
|
|
1940
|
+
return { proposalValue, admissionValue };
|
|
1941
|
+
}
|
|
1942
|
+
function conflictingMetadataString(proposal, admission, key) {
|
|
1943
|
+
const proposalValue = stringMetadata(proposal.metadata, key);
|
|
1944
|
+
const admissionValue = stringMetadata(admission.metadata, key);
|
|
1945
|
+
if (proposalValue === void 0 || admissionValue === void 0 || proposalValue === admissionValue)
|
|
1946
|
+
return void 0;
|
|
1947
|
+
return { proposalValue, admissionValue };
|
|
1948
|
+
}
|
|
1949
|
+
function staleRevision(proposal, admission, workItem) {
|
|
1950
|
+
const authoringRevision = numberMetadata(admission.metadata, "authoringRevision") ?? numberMetadata(proposal.metadata, "authoringRevision");
|
|
1951
|
+
if (authoringRevision !== void 0 && authoringRevision !== workItem.authoringRevision)
|
|
1952
|
+
return {
|
|
1953
|
+
kind: "stale-revision",
|
|
1954
|
+
expected: authoringRevision,
|
|
1955
|
+
current: workItem.authoringRevision
|
|
1956
|
+
};
|
|
1957
|
+
const executionInputRevision = numberMetadata(admission.metadata, "executionInputRevision") ?? numberMetadata(proposal.metadata, "executionInputRevision");
|
|
1958
|
+
if (executionInputRevision !== void 0 && executionInputRevision !== workItem.executionInputRevision)
|
|
1959
|
+
return {
|
|
1960
|
+
kind: "stale-execution-input",
|
|
1961
|
+
expected: executionInputRevision,
|
|
1962
|
+
current: workItem.executionInputRevision
|
|
1963
|
+
};
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
function applyPolicyMessage(code, proposal, admission) {
|
|
1967
|
+
if (code === "missing-policy")
|
|
1968
|
+
return `Admitted action '${admission.admissionId}' requires an explicit apply policy`;
|
|
1969
|
+
if (code === "unknown-work-item-domain-action-apply-policy")
|
|
1970
|
+
return `Admitted action '${admission.admissionId}' references a missing apply policy`;
|
|
1971
|
+
return `Admitted action '${proposal.proposalId}' matches multiple apply policies`;
|
|
1972
|
+
}
|
|
1973
|
+
function emitApplication(ctx, state, admission, proposal, applicationState, facts, opts = {}) {
|
|
1974
|
+
const producedFactIds = facts.map((fact) => fact.eventId);
|
|
1975
|
+
const application = {
|
|
1976
|
+
kind: "work-item-domain-action-application",
|
|
1977
|
+
applicationId: `${admission.admissionId}:application`,
|
|
1978
|
+
admissionId: admission.admissionId,
|
|
1979
|
+
proposalId: proposal.proposalId,
|
|
1980
|
+
workItemId: proposal.workItemId,
|
|
1981
|
+
actionKind: proposal.actionKind,
|
|
1982
|
+
state: applicationState,
|
|
1983
|
+
producedFactIds,
|
|
1984
|
+
reason: admission.reason ?? proposal.reason,
|
|
1985
|
+
sourceRefs: admissionRefs(admission, proposal),
|
|
1986
|
+
metadata: { policyId: opts.policyId, code: opts.code }
|
|
1987
|
+
};
|
|
1988
|
+
emitApplicationFact(ctx, "application", application);
|
|
1989
|
+
emitApplicationStatus(ctx, state, {
|
|
1990
|
+
state: applicationState,
|
|
1991
|
+
code: opts.code,
|
|
1992
|
+
message: opts.message,
|
|
1993
|
+
workItemId: proposal.workItemId,
|
|
1994
|
+
proposalId: proposal.proposalId,
|
|
1995
|
+
admissionId: admission.admissionId,
|
|
1996
|
+
actionKind: proposal.actionKind,
|
|
1997
|
+
sourceRefs: application.sourceRefs,
|
|
1998
|
+
metadata: { producedFactIds, policyId: opts.policyId }
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
function emitApplicationIssue(ctx, state, key, code, message, workItemId, sourceRefs2, metadata) {
|
|
2002
|
+
if (state.issueKeys.has(key)) return;
|
|
2003
|
+
state.issueKeys.add(key);
|
|
2004
|
+
const item = dataIssue2(code, message, { subjectId: workItemId, refs: sourceRefs2, metadata });
|
|
2005
|
+
emitApplicationFact(ctx, "issue", item);
|
|
2006
|
+
emitApplicationStatus(ctx, state, {
|
|
2007
|
+
state: code === "missing-policy" ? "missing-policy" : "rejected",
|
|
2008
|
+
code,
|
|
2009
|
+
message,
|
|
2010
|
+
workItemId,
|
|
2011
|
+
sourceRefs: sourceRefs2,
|
|
2012
|
+
metadata
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function emitIntakeStatus(ctx, state, status) {
|
|
2016
|
+
state.statusSeq += 1;
|
|
2017
|
+
const statusFact = {
|
|
2018
|
+
kind: "work-item-domain-action-status",
|
|
2019
|
+
statusId: `work-item-domain-action-intake-status:${state.statusSeq}`,
|
|
2020
|
+
...status
|
|
2021
|
+
};
|
|
2022
|
+
emitIntake(ctx, "status", statusFact);
|
|
2023
|
+
emitIntakeAudit(ctx, state, "work-item-domain-action-intake-status", statusFact);
|
|
2024
|
+
}
|
|
2025
|
+
function emitApplicationStatus(ctx, state, status) {
|
|
2026
|
+
state.statusSeq += 1;
|
|
2027
|
+
const statusFact = {
|
|
2028
|
+
kind: "work-item-domain-action-status",
|
|
2029
|
+
statusId: `work-item-domain-action-application-status:${state.statusSeq}`,
|
|
2030
|
+
...status
|
|
2031
|
+
};
|
|
2032
|
+
emitApplicationFact(ctx, "status", statusFact);
|
|
2033
|
+
emitApplicationAudit(ctx, state, "work-item-domain-action-application-status", statusFact);
|
|
2034
|
+
}
|
|
2035
|
+
function emitIntakeAudit(ctx, state, kind, status) {
|
|
2036
|
+
state.auditSeq += 1;
|
|
2037
|
+
emitIntake(ctx, "audit", auditRecord(kind, state.auditSeq, status));
|
|
2038
|
+
}
|
|
2039
|
+
function emitApplicationAudit(ctx, state, kind, status) {
|
|
2040
|
+
state.auditSeq += 1;
|
|
2041
|
+
emitApplicationFact(ctx, "audit", auditRecord(kind, state.auditSeq, status));
|
|
2042
|
+
}
|
|
2043
|
+
function auditRecord(kind, seq, status) {
|
|
2044
|
+
return {
|
|
2045
|
+
id: `${kind}:${seq}`,
|
|
2046
|
+
kind,
|
|
2047
|
+
subjectId: status.workItemId,
|
|
2048
|
+
message: status.message,
|
|
2049
|
+
issueCode: status.code,
|
|
2050
|
+
sourceRefs: status.sourceRefs,
|
|
2051
|
+
metadata: {
|
|
2052
|
+
statusId: status.statusId,
|
|
2053
|
+
state: status.state,
|
|
2054
|
+
proposalId: status.proposalId,
|
|
2055
|
+
admissionId: status.admissionId,
|
|
2056
|
+
actionKind: status.actionKind,
|
|
2057
|
+
...status.metadata ?? {}
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function emitIntake(ctx, kind, value) {
|
|
2062
|
+
ctx.down([["DATA", { kind, value }]]);
|
|
2063
|
+
}
|
|
2064
|
+
function emitApplicationFact(ctx, kind, value) {
|
|
2065
|
+
ctx.down([["DATA", { kind, value }]]);
|
|
2066
|
+
}
|
|
2067
|
+
function project(graph, runtime, name, factory, select) {
|
|
2068
|
+
return graph.node(
|
|
2069
|
+
[runtime],
|
|
2070
|
+
(ctx) => {
|
|
2071
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
2072
|
+
const selected = select(raw);
|
|
2073
|
+
if (selected !== void 0) ctx.down([["DATA", selected]]);
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
{ name, factory, partial: true, completeWhenDepsComplete: false, errorWhenDepsError: false }
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
function firstDuplicateFactId(state, facts) {
|
|
2080
|
+
const pending = /* @__PURE__ */ new Set();
|
|
2081
|
+
for (const fact of facts) {
|
|
2082
|
+
if (state.emittedFactIds.has(fact.eventId) || pending.has(fact.eventId)) return fact.eventId;
|
|
2083
|
+
pending.add(fact.eventId);
|
|
2084
|
+
}
|
|
2085
|
+
return void 0;
|
|
2086
|
+
}
|
|
2087
|
+
function claimFactId(state, factId) {
|
|
2088
|
+
if (state.emittedFactIds.has(factId)) return false;
|
|
2089
|
+
state.emittedFactIds.add(factId);
|
|
2090
|
+
return true;
|
|
2091
|
+
}
|
|
2092
|
+
function admissionRefs(admission, proposal, policy) {
|
|
2093
|
+
return uniqueSourceRefs2([
|
|
2094
|
+
ref2("work-item-domain-action-admission", admission.admissionId),
|
|
2095
|
+
ref2("work-item-domain-action-proposal", admission.proposalId),
|
|
2096
|
+
...proposal === void 0 ? [] : [
|
|
2097
|
+
ref2("work-item", proposal.workItemId),
|
|
2098
|
+
ref2("work-item-evidence", proposal.evidenceId),
|
|
2099
|
+
...proposal.sourceRefs ?? []
|
|
2100
|
+
],
|
|
2101
|
+
...policy === void 0 ? [] : [ref2("work-item-domain-action-apply-policy", policy.policyId)],
|
|
2102
|
+
...admission.sourceRefs ?? []
|
|
2103
|
+
]);
|
|
2104
|
+
}
|
|
2105
|
+
function actionSourceRefs(admission, proposal, policy) {
|
|
2106
|
+
return admissionRefs(admission, proposal, policy);
|
|
2107
|
+
}
|
|
2108
|
+
function dataIssue2(code, message, opts = {}) {
|
|
2109
|
+
return {
|
|
2110
|
+
kind: "issue",
|
|
2111
|
+
code,
|
|
2112
|
+
message,
|
|
2113
|
+
severity: "error",
|
|
2114
|
+
source: "work-item-actions",
|
|
2115
|
+
subjectId: opts.subjectId,
|
|
2116
|
+
refs: opts.refs?.map((sourceRef) => `${sourceRef.kind}:${sourceRef.id}`),
|
|
2117
|
+
metadata: opts.metadata
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
function uniqueSourceRefs2(sourceRefs2) {
|
|
2121
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2122
|
+
const out = [];
|
|
2123
|
+
for (const sourceRef of sourceRefs2) {
|
|
2124
|
+
const key = `${sourceRef.kind}:${sourceRef.id}:${JSON.stringify(sourceRef.metadata ?? {})}`;
|
|
2125
|
+
if (seen.has(key)) continue;
|
|
2126
|
+
seen.add(key);
|
|
2127
|
+
out.push(sourceRef);
|
|
2128
|
+
}
|
|
2129
|
+
return out;
|
|
2130
|
+
}
|
|
2131
|
+
function sourceRefs(value) {
|
|
2132
|
+
if (!Array.isArray(value)) return void 0;
|
|
2133
|
+
return value.filter(
|
|
2134
|
+
(item) => isRecord2(item) && typeof item.kind === "string" && typeof item.id === "string"
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
function numberMetadata(metadata, key) {
|
|
2138
|
+
const value = metadata?.[key];
|
|
2139
|
+
return typeof value === "number" ? value : void 0;
|
|
2140
|
+
}
|
|
2141
|
+
function stringMetadata(metadata, key) {
|
|
2142
|
+
const value = metadata?.[key];
|
|
2143
|
+
return typeof value === "string" ? value : void 0;
|
|
2144
|
+
}
|
|
2145
|
+
function ref2(kind, id) {
|
|
2146
|
+
return { kind, id };
|
|
2147
|
+
}
|
|
2148
|
+
function recordString(value, key) {
|
|
2149
|
+
return isRecord2(value) && typeof value[key] === "string" ? value[key] : void 0;
|
|
2150
|
+
}
|
|
2151
|
+
function isRecord2(value) {
|
|
2152
|
+
return value !== null && typeof value === "object";
|
|
2153
|
+
}
|
|
2154
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2155
|
+
0 && (module.exports = {
|
|
2156
|
+
workItemDomainActionAdmissionProjector,
|
|
2157
|
+
workItemDomainActionApplicationProjector,
|
|
2158
|
+
workItemDomainActionApplyPolicy,
|
|
2159
|
+
workItemDomainActionCapabilityGuardProjector,
|
|
2160
|
+
workItemDomainActionProposal,
|
|
2161
|
+
workItemDomainActionProposalIntake,
|
|
2162
|
+
workItemDomainActionProposalIntakeProjector
|
|
2163
|
+
});
|
|
2164
|
+
//# sourceMappingURL=actions.cjs.map
|