@synoi/gap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +195 -0
  2. package/README.md +223 -0
  3. package/dist/canonicalize.d.ts +19 -0
  4. package/dist/canonicalize.d.ts.map +1 -0
  5. package/dist/canonicalize.js +36 -0
  6. package/dist/canonicalize.js.map +1 -0
  7. package/dist/capabilities.d.ts +605 -0
  8. package/dist/capabilities.d.ts.map +1 -0
  9. package/dist/capabilities.js +53 -0
  10. package/dist/capabilities.js.map +1 -0
  11. package/dist/cdro.d.ts +63 -0
  12. package/dist/cdro.d.ts.map +1 -0
  13. package/dist/cdro.js +16 -0
  14. package/dist/cdro.js.map +1 -0
  15. package/dist/channels.d.ts +107 -0
  16. package/dist/channels.d.ts.map +1 -0
  17. package/dist/channels.js +29 -0
  18. package/dist/channels.js.map +1 -0
  19. package/dist/constants.d.ts +32 -0
  20. package/dist/constants.d.ts.map +1 -0
  21. package/dist/constants.js +36 -0
  22. package/dist/constants.js.map +1 -0
  23. package/dist/index.d.ts +28 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +35 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/oid.d.ts +28 -0
  28. package/dist/oid.d.ts.map +1 -0
  29. package/dist/oid.js +68 -0
  30. package/dist/oid.js.map +1 -0
  31. package/dist/receipts.d.ts +128 -0
  32. package/dist/receipts.d.ts.map +1 -0
  33. package/dist/receipts.js +14 -0
  34. package/dist/receipts.js.map +1 -0
  35. package/dist/revocations.d.ts +65 -0
  36. package/dist/revocations.d.ts.map +1 -0
  37. package/dist/revocations.js +22 -0
  38. package/dist/revocations.js.map +1 -0
  39. package/dist/validate.d.ts +59 -0
  40. package/dist/validate.d.ts.map +1 -0
  41. package/dist/validate.js +835 -0
  42. package/dist/validate.js.map +1 -0
  43. package/dist/workflows.d.ts +186 -0
  44. package/dist/workflows.d.ts.map +1 -0
  45. package/dist/workflows.js +14 -0
  46. package/dist/workflows.js.map +1 -0
  47. package/package.json +55 -0
  48. package/src/canonicalize.ts +38 -0
  49. package/src/capabilities.ts +711 -0
  50. package/src/cdro.ts +92 -0
  51. package/src/channels.ts +183 -0
  52. package/src/constants.ts +46 -0
  53. package/src/index.ts +180 -0
  54. package/src/oid.ts +71 -0
  55. package/src/receipts.ts +169 -0
  56. package/src/revocations.ts +90 -0
  57. package/src/validate.ts +1008 -0
  58. package/src/workflows.ts +241 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * workflows.ts -- WorkflowDefinition / WorkflowInstance / StageTransition.
3
+ *
4
+ * Workflows choreograph the HITL flow when a capability invocation requires
5
+ * human approval (or any orchestrated multi-stage interaction). Shapes
6
+ * mirror the GAP wire types.
7
+ *
8
+ * State model:
9
+ * - WorkflowDefinition is a static template.
10
+ * - WorkflowInstance is a single live run.
11
+ * - StageTransition is the audited edge between two stages of an instance.
12
+ */
13
+
14
+ import type { GapCdroEnvelope } from './cdro.js'
15
+ import type { CapabilityPredicate } from './capabilities.js'
16
+ import type {
17
+ ChannelKind,
18
+ StageAction,
19
+ StageListen,
20
+ StageTransitionTarget,
21
+ } from './channels.js'
22
+
23
+ // -- Optional ambient effect -------------------------------------------------
24
+
25
+ /**
26
+ * Optional ambient effect -- fires if the operator's environment has a
27
+ * matching declared+granted capability, silently skipped if not. See
28
+ * OPTIONAL_CAPABILITIES_SPEC.md for the full design.
29
+ *
30
+ * The match algorithm:
31
+ * 1. Resolve `requires_capability` against gap:capability_declaration
32
+ * records that are (a) not revoked, (b) granted to this workflow's
33
+ * invoking actor, (c) visible per the operator's sharing policy.
34
+ * 2. If zero matches -> skip silently.
35
+ * 3. If >=1 match -> fire the action through the named channel.
36
+ * 4. Failures of optional_effects DO NOT propagate to stage outcome.
37
+ * They're best-effort by definition.
38
+ */
39
+ export interface OptionalEffect {
40
+ /** Capability the effect needs in the environment to fire. Supports
41
+ dotted-taxonomy wildcards: 'home.lighting.*' matches any lighting
42
+ capability the operator's smart home has declared. */
43
+ requires_capability: string
44
+ /** The action to perform when the capability is matched. */
45
+ action: StageAction
46
+ /** Optional human-readable label for audit logs + portal UI. */
47
+ label?: string
48
+ }
49
+
50
+ // -- Stage safety wrapper (CI/CD governance net) -----------------------------
51
+
52
+ /**
53
+ * CI/CD safety net (see SYNOI_CICD_GOVERNANCE_SPEC.md §10.5). When set,
54
+ * the engine routes the stage's invocation through runSafetyPipeline:
55
+ * snapshot -> two-person -> cooldown -> invoke -> record.
56
+ * All four sub-pieces are independently optional. Absent => default
57
+ * behaviour (no safety wrapping; existing GAP semantics).
58
+ */
59
+ export interface StageSafety {
60
+ /** Marker -- set when this stage performs a destructive operation.
61
+ * Required for the safety wrapper to engage. */
62
+ destructive: true
63
+ /** Resource kind for snapshot routing. Engine picks the adapter by kind. */
64
+ resource_kind?:
65
+ | 'postgres_db'
66
+ | 'mysql_db'
67
+ | 'aws_rds_instance'
68
+ | 'aws_ebs_volume'
69
+ | 'aws_s3_object_versioned'
70
+ | 'k8s_namespace_velero'
71
+ | 'terraform_state'
72
+ | 'git_branch'
73
+ | 'filesystem_path'
74
+ /** Override adapter name. If unset, engine picks the first registered
75
+ * adapter for resource_kind. */
76
+ snapshot_adapter?: string
77
+ /** Resource id passed to adapter.capture() -- usually interpolated. */
78
+ snapshot_resource_id?: string
79
+ /** Adapter config (interpolated). */
80
+ snapshot_config?: Record<string, string>
81
+ /** Two-person rule config. Pass null/undefined to skip. */
82
+ two_person?: {
83
+ required: number
84
+ require_disjoint_groups?: boolean
85
+ eligible_groups?: string[]
86
+ }
87
+ /** Cooldown window in milliseconds. 0/undefined => skip. */
88
+ cooldown_ms?: number
89
+ /** Approver-groups that can cancel the cooldown. */
90
+ cooldown_eligible_groups?: string[]
91
+ /** Optional OID of the paired rollback workflow. */
92
+ rollback_workflow_oid?: string
93
+ }
94
+
95
+ // -- Stage shape -------------------------------------------------------------
96
+
97
+ export interface WorkflowStage {
98
+ stage_id: string
99
+ duration_seconds?: number
100
+ actions?: StageAction[]
101
+ /** Optional ambient effects -- fire if matching capabilities exist; skip
102
+ silently otherwise. Independent of the mandatory `actions` list. */
103
+ optional_effects?: OptionalEffect[]
104
+ /**
105
+ * Actor OIDs permitted to supply valid approval signals for this stage.
106
+ * When set, the gateway MUST verify that the approver's authenticated identity
107
+ * resolves to one of these OIDs before advancing the stage on a YES signal.
108
+ * The gateway MUST enforce actor_oid disjointness: the same actor_oid cannot
109
+ * count as more than one approval (prevents one person approving twice).
110
+ *
111
+ * MUST be set for stages governing physical_safety=true or safety_class C
112
+ * capabilities. Absent means any tenant-authenticated signal is accepted
113
+ * (dangerous; use only for class A/B capabilities).
114
+ */
115
+ authorized_approvers?: string[]
116
+ listen?: StageListen[]
117
+ /**
118
+ * Stage to transition to when the stage timer expires.
119
+ *
120
+ * WARNING: For workflow definitions whose trigger matches a
121
+ * physical_safety=true or safety_class C capability, `on_timeout` MUST NOT
122
+ * lead to a terminal stage with `terminal_outcome='approved'`. The gateway
123
+ * MUST reject such definitions at registration time (PC-11).
124
+ */
125
+ on_timeout?: StageTransitionTarget
126
+ on_action_failure?: StageTransitionTarget
127
+ terminal?: boolean
128
+ terminal_outcome?: 'approved' | 'denied' | 'timed_out' | 'withdrawn' | 'error'
129
+ invocation?: {
130
+ capability: string
131
+ args: Record<string, unknown>
132
+ on_success?: StageTransitionTarget
133
+ on_failure?: StageTransitionTarget
134
+ }
135
+ precondition?: CapabilityPredicate
136
+ safety?: StageSafety
137
+ }
138
+
139
+ /** Alias used in the spec docs + spec discussion. WorkflowStage is the
140
+ * authoritative shape; this alias makes the engine API readable. */
141
+ export type WorkflowStageDefinition = WorkflowStage
142
+
143
+ // -- Triggers ----------------------------------------------------------------
144
+
145
+ export type WorkflowTriggerKind =
146
+ | 'risk_policy'
147
+ | 'capability_invocation'
148
+ | 'explicit'
149
+ | 'schedule'
150
+
151
+ export interface WorkflowTrigger {
152
+ kind: WorkflowTriggerKind
153
+ risk_class?: 'A' | 'B' | 'C'
154
+ action_class?: string
155
+ action_type_pattern?: string
156
+ capability_pattern?: string
157
+ cron?: string
158
+ }
159
+
160
+ // -- Definition CDRO ---------------------------------------------------------
161
+
162
+ export interface WorkflowDefinitionBody {
163
+ workflow_id: string
164
+ workflow_name: string
165
+ workflow_version: string
166
+ description?: string
167
+ trigger: WorkflowTrigger
168
+ stages: WorkflowStage[]
169
+ initial_stage_id: string
170
+ cleanup_stage_id?: string
171
+ required_channels: ChannelKind[]
172
+ optional_channels?: ChannelKind[]
173
+ max_total_duration_seconds: number
174
+ /**
175
+ * When true, this workflow definition (and any supersession of it) requires
176
+ * operator-level authorization before it becomes active. The registration
177
+ * request must carry the operator's signed attestation. MUST be true for
178
+ * definitions whose `trigger.capability_pattern` matches any
179
+ * physical_safety=true capability. The gateway MUST reject registration of
180
+ * unsafe (on_timeout->approved) paths for physical safety patterns.
181
+ */
182
+ requires_operator_approval?: boolean
183
+ }
184
+
185
+ export type WorkflowDefinition = GapCdroEnvelope<WorkflowDefinitionBody>
186
+
187
+ // -- Instance CDRO -----------------------------------------------------------
188
+
189
+ export interface WorkflowInstanceBody {
190
+ workflow_definition_oid: string
191
+ workflow_id: string
192
+ trigger_event: {
193
+ kind: WorkflowTriggerKind
194
+ source_invocation_oid?: string
195
+ source_risk_policy_id?: string
196
+ source_actor_oid: string
197
+ }
198
+ current_stage_id: string
199
+ scope_variables: Record<string, unknown>
200
+ started_at_ms: number
201
+ last_transition_at_ms: number
202
+ terminated_at_ms: number | null
203
+ terminal_outcome: WorkflowStage['terminal_outcome'] | null
204
+ active_channel_listeners: Array<{
205
+ channel: ChannelKind
206
+ listen_spec: StageListen
207
+ started_at_ms: number
208
+ }>
209
+ transition_oids: string[]
210
+ final_receipt_oid?: string
211
+ }
212
+
213
+ export type WorkflowInstance = GapCdroEnvelope<WorkflowInstanceBody>
214
+
215
+ // -- Stage transition CDRO ---------------------------------------------------
216
+
217
+ export type StageTransitionReason =
218
+ | 'listen_matched'
219
+ | 'timeout'
220
+ | 'action_completed'
221
+ | 'action_failed'
222
+ | 'precondition_passed'
223
+ | 'precondition_failed'
224
+ | 'invocation_succeeded'
225
+ | 'invocation_failed'
226
+ | 'external_signal'
227
+ | 'cleanup'
228
+
229
+ export interface StageTransitionBody {
230
+ workflow_instance_oid: string
231
+ previous_transition_oid: string | null
232
+ from_stage_id: string
233
+ to_stage_id: string
234
+ trigger_reason: StageTransitionReason
235
+ bind_outputs: Record<string, unknown>
236
+ triggering_event_oid?: string
237
+ triggering_invocation_oid?: string
238
+ transitioned_at_ms: number
239
+ }
240
+
241
+ export type StageTransition = GapCdroEnvelope<StageTransitionBody>