@open-agent-toolkit/cli 0.2.65 → 0.2.66

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 (142) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +19 -1
  3. package/assets/docs/cli-utilities/index.md +3 -0
  4. package/assets/docs/cli-utilities/remote-project-management.md +191 -0
  5. package/assets/docs/reference/cli-reference.md +12 -1
  6. package/assets/docs/reference/file-locations.md +22 -0
  7. package/assets/docs/reference/oat-directory-structure.md +30 -17
  8. package/assets/docs/reference/troubleshooting.md +44 -0
  9. package/assets/docs/workflows/skills/index.md +33 -0
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-doctor/SKILL.md +4 -4
  12. package/assets/skills/oat-pjm-remote/SKILL.md +101 -0
  13. package/assets/skills/oat-pjm-remote/references/external-action-protocol.md +43 -0
  14. package/dist/commands/backlog/new.d.ts +1 -0
  15. package/dist/commands/backlog/new.d.ts.map +1 -1
  16. package/dist/commands/backlog/new.js +3 -1
  17. package/dist/commands/config/index.d.ts +1 -0
  18. package/dist/commands/config/index.d.ts.map +1 -1
  19. package/dist/commands/config/index.js +232 -1
  20. package/dist/commands/pjm/doctor.d.ts +2 -0
  21. package/dist/commands/pjm/doctor.d.ts.map +1 -1
  22. package/dist/commands/pjm/doctor.js +71 -1
  23. package/dist/commands/pjm/index.d.ts +2 -0
  24. package/dist/commands/pjm/index.d.ts.map +1 -1
  25. package/dist/commands/pjm/index.js +3 -0
  26. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts +60 -0
  27. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts.map +1 -0
  28. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.js +151 -0
  29. package/dist/commands/pjm/remote/association.d.ts +27 -0
  30. package/dist/commands/pjm/remote/association.d.ts.map +1 -0
  31. package/dist/commands/pjm/remote/association.js +68 -0
  32. package/dist/commands/pjm/remote/authority.d.ts +210 -0
  33. package/dist/commands/pjm/remote/authority.d.ts.map +1 -0
  34. package/dist/commands/pjm/remote/authority.js +341 -0
  35. package/dist/commands/pjm/remote/batch.d.ts +15 -0
  36. package/dist/commands/pjm/remote/batch.d.ts.map +1 -0
  37. package/dist/commands/pjm/remote/batch.js +126 -0
  38. package/dist/commands/pjm/remote/closeout.d.ts +69 -0
  39. package/dist/commands/pjm/remote/closeout.d.ts.map +1 -0
  40. package/dist/commands/pjm/remote/closeout.js +208 -0
  41. package/dist/commands/pjm/remote/create-binding.d.ts +62 -0
  42. package/dist/commands/pjm/remote/create-binding.d.ts.map +1 -0
  43. package/dist/commands/pjm/remote/create-binding.js +126 -0
  44. package/dist/commands/pjm/remote/credential-safety.d.ts +7 -0
  45. package/dist/commands/pjm/remote/credential-safety.d.ts.map +1 -0
  46. package/dist/commands/pjm/remote/credential-safety.js +21 -0
  47. package/dist/commands/pjm/remote/discussion.d.ts +49 -0
  48. package/dist/commands/pjm/remote/discussion.d.ts.map +1 -0
  49. package/dist/commands/pjm/remote/discussion.js +124 -0
  50. package/dist/commands/pjm/remote/doctor.d.ts +22 -0
  51. package/dist/commands/pjm/remote/doctor.d.ts.map +1 -0
  52. package/dist/commands/pjm/remote/doctor.js +380 -0
  53. package/dist/commands/pjm/remote/external-action.d.ts +208 -0
  54. package/dist/commands/pjm/remote/external-action.d.ts.map +1 -0
  55. package/dist/commands/pjm/remote/external-action.js +367 -0
  56. package/dist/commands/pjm/remote/host-execution.d.ts +28 -0
  57. package/dist/commands/pjm/remote/host-execution.d.ts.map +1 -0
  58. package/dist/commands/pjm/remote/host-execution.js +63 -0
  59. package/dist/commands/pjm/remote/index.d.ts +41 -0
  60. package/dist/commands/pjm/remote/index.d.ts.map +1 -0
  61. package/dist/commands/pjm/remote/index.js +280 -0
  62. package/dist/commands/pjm/remote/jira-adf.d.ts +35 -0
  63. package/dist/commands/pjm/remote/jira-adf.d.ts.map +1 -0
  64. package/dist/commands/pjm/remote/jira-adf.js +169 -0
  65. package/dist/commands/pjm/remote/lifecycle.d.ts +163 -0
  66. package/dist/commands/pjm/remote/lifecycle.d.ts.map +1 -0
  67. package/dist/commands/pjm/remote/lifecycle.js +224 -0
  68. package/dist/commands/pjm/remote/local-projection.d.ts +30 -0
  69. package/dist/commands/pjm/remote/local-projection.d.ts.map +1 -0
  70. package/dist/commands/pjm/remote/local-projection.js +95 -0
  71. package/dist/commands/pjm/remote/managed-markdown.d.ts +30 -0
  72. package/dist/commands/pjm/remote/managed-markdown.d.ts.map +1 -0
  73. package/dist/commands/pjm/remote/managed-markdown.js +128 -0
  74. package/dist/commands/pjm/remote/migrate.d.ts +14 -0
  75. package/dist/commands/pjm/remote/migrate.d.ts.map +1 -0
  76. package/dist/commands/pjm/remote/migrate.js +32 -0
  77. package/dist/commands/pjm/remote/operation-state.d.ts +15 -0
  78. package/dist/commands/pjm/remote/operation-state.d.ts.map +1 -0
  79. package/dist/commands/pjm/remote/operation-state.js +143 -0
  80. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts +25 -0
  81. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts.map +1 -0
  82. package/dist/commands/pjm/remote/outbound-projection-safety.js +99 -0
  83. package/dist/commands/pjm/remote/output.d.ts +60 -0
  84. package/dist/commands/pjm/remote/output.d.ts.map +1 -0
  85. package/dist/commands/pjm/remote/output.js +48 -0
  86. package/dist/commands/pjm/remote/preview.d.ts +88 -0
  87. package/dist/commands/pjm/remote/preview.d.ts.map +1 -0
  88. package/dist/commands/pjm/remote/preview.js +154 -0
  89. package/dist/commands/pjm/remote/provider-conformance.d.ts +13 -0
  90. package/dist/commands/pjm/remote/provider-conformance.d.ts.map +1 -0
  91. package/dist/commands/pjm/remote/provider-conformance.js +37 -0
  92. package/dist/commands/pjm/remote/provider.d.ts +57 -0
  93. package/dist/commands/pjm/remote/provider.d.ts.map +1 -0
  94. package/dist/commands/pjm/remote/provider.js +28 -0
  95. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts +46 -0
  96. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts.map +1 -0
  97. package/dist/commands/pjm/remote/providers/github-publication-safety.js +126 -0
  98. package/dist/commands/pjm/remote/providers/github.d.ts +207 -0
  99. package/dist/commands/pjm/remote/providers/github.d.ts.map +1 -0
  100. package/dist/commands/pjm/remote/providers/github.js +1456 -0
  101. package/dist/commands/pjm/remote/providers/jira.d.ts +245 -0
  102. package/dist/commands/pjm/remote/providers/jira.d.ts.map +1 -0
  103. package/dist/commands/pjm/remote/providers/jira.js +1650 -0
  104. package/dist/commands/pjm/remote/providers/linear.d.ts +182 -0
  105. package/dist/commands/pjm/remote/providers/linear.d.ts.map +1 -0
  106. package/dist/commands/pjm/remote/providers/linear.js +1289 -0
  107. package/dist/commands/pjm/remote/purpose-policy.d.ts +29 -0
  108. package/dist/commands/pjm/remote/purpose-policy.d.ts.map +1 -0
  109. package/dist/commands/pjm/remote/purpose-policy.js +118 -0
  110. package/dist/commands/pjm/remote/reconcile.d.ts +42 -0
  111. package/dist/commands/pjm/remote/reconcile.d.ts.map +1 -0
  112. package/dist/commands/pjm/remote/reconcile.js +101 -0
  113. package/dist/commands/pjm/remote/resolution.d.ts +122 -0
  114. package/dist/commands/pjm/remote/resolution.d.ts.map +1 -0
  115. package/dist/commands/pjm/remote/resolution.js +243 -0
  116. package/dist/commands/pjm/remote/schema.d.ts +9309 -0
  117. package/dist/commands/pjm/remote/schema.d.ts.map +1 -0
  118. package/dist/commands/pjm/remote/schema.js +1503 -0
  119. package/dist/commands/pjm/remote/service.d.ts +20 -0
  120. package/dist/commands/pjm/remote/service.d.ts.map +1 -0
  121. package/dist/commands/pjm/remote/service.js +5121 -0
  122. package/dist/commands/pjm/remote/shared-storage.d.ts +36 -0
  123. package/dist/commands/pjm/remote/shared-storage.d.ts.map +1 -0
  124. package/dist/commands/pjm/remote/shared-storage.js +110 -0
  125. package/dist/commands/pjm/remote/snapshot.d.ts +32 -0
  126. package/dist/commands/pjm/remote/snapshot.d.ts.map +1 -0
  127. package/dist/commands/pjm/remote/snapshot.js +140 -0
  128. package/dist/commands/pjm/remote/storage-locator.d.ts +29 -0
  129. package/dist/commands/pjm/remote/storage-locator.d.ts.map +1 -0
  130. package/dist/commands/pjm/remote/storage-locator.js +62 -0
  131. package/dist/commands/pjm/remote/store.d.ts +85 -0
  132. package/dist/commands/pjm/remote/store.d.ts.map +1 -0
  133. package/dist/commands/pjm/remote/store.js +553 -0
  134. package/dist/commands/pjm/remote/verification.d.ts +40 -0
  135. package/dist/commands/pjm/remote/verification.d.ts.map +1 -0
  136. package/dist/commands/pjm/remote/verification.js +100 -0
  137. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  138. package/dist/commands/tools/shared/pack-manifest.js +1 -0
  139. package/dist/config/oat-config.d.ts +26 -0
  140. package/dist/config/oat-config.d.ts.map +1 -1
  141. package/dist/config/oat-config.js +186 -2
  142. package/package.json +2 -2
@@ -0,0 +1,1503 @@
1
+ import { basename } from 'node:path';
2
+ import { z } from 'zod';
3
+ import { containsSensitiveContentSignal, containsSensitiveContentSignalInValue, } from './credential-safety.js';
4
+ export const MAX_REMOTE_DESCRIPTION_BYTES = 1_048_576;
5
+ export const MAX_PROVIDER_EXTENSION_BYTES = 16_384;
6
+ export const MAX_SNAPSHOT_SUPPRESSION_EVIDENCE = 16;
7
+ export const WHOLE_FIELD_SUPPRESSION_MARKER = '[SUPPRESSED:SENSITIVE-CONTENT]';
8
+ const StableIdSchema = z
9
+ .string()
10
+ .min(8)
11
+ .max(128)
12
+ .regex(/^[a-z][a-z0-9]*(?:_[A-Za-z0-9][A-Za-z0-9_-]*)+$/);
13
+ const TimestampSchema = z.string().datetime({ offset: true });
14
+ const ProviderSchema = z.enum(['github', 'linear', 'jira']);
15
+ const PurposeSchema = z.enum(['source', 'planning', 'delivery', 'reference']);
16
+ const OperationClassSchema = z.enum([
17
+ 'create',
18
+ 'update-fields',
19
+ 'transition',
20
+ 'annotate',
21
+ 'delete',
22
+ 'relink',
23
+ 'detach',
24
+ 'recreate',
25
+ ]);
26
+ const OperationRecordClassSchema = z.union([
27
+ OperationClassSchema,
28
+ z.literal('composite'),
29
+ z.null(),
30
+ ]);
31
+ const DescriptionModeSchema = z.enum(['none', 'managed-section', 'replace']);
32
+ const MutationAuthoritySchema = z.enum([
33
+ 'read-only',
34
+ 'user-approved',
35
+ 'user-authorized',
36
+ 'autonomous',
37
+ ]);
38
+ const OperationStateSchema = z.enum([
39
+ 'planned',
40
+ 'pending',
41
+ 'authorized',
42
+ 'attempt-started',
43
+ 'verification-pending',
44
+ 'blocked',
45
+ 'verified',
46
+ 'partial',
47
+ 'uncertain',
48
+ 'failed',
49
+ 'rejected',
50
+ ]);
51
+ const LifecycleConditionSchema = z.enum([
52
+ 'active',
53
+ 'archived',
54
+ 'moved',
55
+ 'missing-or-invisible',
56
+ 'deleted-confirmed',
57
+ 'temporarily-unavailable',
58
+ ]);
59
+ const READ_ONLY_LIFECYCLE_OPERATIONS = new Set([
60
+ 'intake',
61
+ 'refresh',
62
+ 'discussion',
63
+ ]);
64
+ const ALLOWED_MUTATION_CLASSES_BY_LIFECYCLE = {
65
+ publish: new Set(['create', 'update-fields']),
66
+ reconcile: new Set(['update-fields', 'transition', 'delete']),
67
+ relink: new Set(['relink']),
68
+ detach: new Set(['detach']),
69
+ recreate: new Set(['recreate']),
70
+ };
71
+ export const RemoteAliasSchema = z
72
+ .object({
73
+ kind: z.enum(['url', 'display', 'key']),
74
+ value: z.string().min(1).max(2_048),
75
+ })
76
+ .strict();
77
+ export const RemoteAccountContextSchema = z
78
+ .object({
79
+ host: z.string().min(1).max(255).optional(),
80
+ owner: z.string().min(1).max(255).optional(),
81
+ repositoryId: z.string().min(1).max(255).optional(),
82
+ workspaceId: z.string().min(1).max(255).optional(),
83
+ teamId: z.string().min(1).max(255).optional(),
84
+ cloudId: z.string().min(1).max(255).optional(),
85
+ siteId: z.string().min(1).max(255).optional(),
86
+ projectId: z.string().min(1).max(255).optional(),
87
+ })
88
+ .strict();
89
+ export const RemoteIdentitySchema = z
90
+ .object({
91
+ stableId: z.string().min(1).max(512),
92
+ context: RemoteAccountContextSchema,
93
+ aliases: z.array(RemoteAliasSchema).max(64),
94
+ })
95
+ .strict();
96
+ export const HistoricalRemoteIdentitySchema = z
97
+ .object({
98
+ provider: ProviderSchema,
99
+ identity: RemoteIdentitySchema,
100
+ replacedAt: TimestampSchema,
101
+ replacedByOperationId: StableIdSchema,
102
+ })
103
+ .strict();
104
+ export const VerifiedDurableRemoteIdentitySchema = z
105
+ .object({
106
+ provider: ProviderSchema,
107
+ stableId: z.string().min(1).max(512),
108
+ verifiedAt: TimestampSchema,
109
+ evidenceDigest: z.string().min(1).max(512),
110
+ })
111
+ .strict();
112
+ export const RemoteLocalTargetSchema = z
113
+ .object({
114
+ kind: z.enum(['backlog', 'project']),
115
+ scope: z.enum(['shared', 'synced', 'local']),
116
+ id: z.string().min(1).max(255),
117
+ path: z.string().min(1).max(4_096),
118
+ })
119
+ .strict();
120
+ export const BindingPolicyRestrictionSchema = z
121
+ .object({
122
+ description: DescriptionModeSchema.optional(),
123
+ authority: z
124
+ .object({
125
+ default: MutationAuthoritySchema.optional(),
126
+ operations: z
127
+ .record(OperationClassSchema, MutationAuthoritySchema)
128
+ .optional(),
129
+ })
130
+ .strict()
131
+ .optional(),
132
+ })
133
+ .strict();
134
+ export const PublicationProjectionSchema = z
135
+ .object({
136
+ title: z.enum(['frontmatter', 'plan', 'none']),
137
+ description: z.enum(['description-section', 'summary', 'none']),
138
+ priority: z.enum(['frontmatter', 'plan', 'none']),
139
+ })
140
+ .strict();
141
+ export const ProviderMappingEvidenceSchema = z
142
+ .object({
143
+ priority: z
144
+ .object({
145
+ status: z.enum(['safe', 'unavailable']),
146
+ evidenceDigest: z.string().min(1).max(512),
147
+ observedAt: TimestampSchema,
148
+ })
149
+ .strict(),
150
+ })
151
+ .strict();
152
+ const LocalIssueProjectionSchema = z
153
+ .object({
154
+ title: z.string().max(8_192),
155
+ description: z.string().nullable(),
156
+ priority: z.string().max(255).nullable(),
157
+ source: z.enum(['backlog-description', 'explicit-project-publication']),
158
+ sourceRevision: z.string().min(1).max(512),
159
+ observedAt: TimestampSchema,
160
+ })
161
+ .strict();
162
+ const CurrentPlannedBindingCreateSchema = z
163
+ .object({
164
+ schemaVersion: z.literal(1),
165
+ bindingId: StableIdSchema,
166
+ operationId: StableIdSchema,
167
+ provider: ProviderSchema,
168
+ target: RemoteLocalTargetSchema,
169
+ publicationProjection: PublicationProjectionSchema,
170
+ providerContext: RemoteAccountContextSchema,
171
+ purposes: z.array(PurposeSchema).min(1).max(4),
172
+ policyRestrictions: BindingPolicyRestrictionSchema,
173
+ provenanceToken: z.string().min(1).max(512),
174
+ localProjection: LocalIssueProjectionSchema.optional(),
175
+ projectionStatus: z.enum(['complete', 'reconcile-required']).optional(),
176
+ createdAt: TimestampSchema,
177
+ })
178
+ .strict()
179
+ .superRefine((intent, context) => {
180
+ if (intent.target.kind !== 'project')
181
+ return;
182
+ if (intent.projectionStatus !== 'complete' ||
183
+ intent.localProjection?.source !== 'explicit-project-publication') {
184
+ if (intent.projectionStatus !== 'reconcile-required' ||
185
+ intent.localProjection !== undefined) {
186
+ context.addIssue({
187
+ code: z.ZodIssueCode.custom,
188
+ path: ['localProjection'],
189
+ message: 'Project create requires a complete explicit local publication projection.',
190
+ });
191
+ }
192
+ }
193
+ });
194
+ export const PlannedBindingCreateSchema = z.preprocess(markIncompleteProjectCreate, CurrentPlannedBindingCreateSchema);
195
+ export const RemoteBindingMetadataSchema = z
196
+ .object({
197
+ recordType: z.literal('binding-metadata'),
198
+ schemaVersion: z.literal(1),
199
+ bindingId: StableIdSchema,
200
+ provider: ProviderSchema,
201
+ target: RemoteLocalTargetSchema,
202
+ remoteIdentity: RemoteIdentitySchema,
203
+ identityHistory: z.array(HistoricalRemoteIdentitySchema).max(64),
204
+ purposes: z
205
+ .array(PurposeSchema)
206
+ .min(1)
207
+ .max(4)
208
+ .refine((values) => new Set(values).size === values.length, {
209
+ message: 'Binding purposes must be unique.',
210
+ }),
211
+ policyRestrictions: BindingPolicyRestrictionSchema,
212
+ publicationProjection: PublicationProjectionSchema,
213
+ providerMappingEvidence: ProviderMappingEvidenceSchema.optional(),
214
+ provenanceToken: z.string().min(1).max(512),
215
+ lifecycle: z.enum(['active', 'blocked', 'tombstoned']),
216
+ createdAt: TimestampSchema,
217
+ updatedAt: TimestampSchema,
218
+ })
219
+ .strict();
220
+ const ExtensionPayloadSchema = z
221
+ .record(z.unknown())
222
+ .superRefine((value, context) => {
223
+ const bytes = Buffer.byteLength(JSON.stringify(value), 'utf8');
224
+ if (bytes > MAX_PROVIDER_EXTENSION_BYTES) {
225
+ context.addIssue({
226
+ code: z.ZodIssueCode.custom,
227
+ message: `Provider extension exceeds ${MAX_PROVIDER_EXTENSION_BYTES} byte limit.`,
228
+ });
229
+ }
230
+ });
231
+ const ProviderExtensionsSchema = z
232
+ .object({
233
+ github: ExtensionPayloadSchema.optional(),
234
+ linear: ExtensionPayloadSchema.optional(),
235
+ jira: ExtensionPayloadSchema.optional(),
236
+ })
237
+ .strict();
238
+ const SnapshotExtensionKeySchema = z
239
+ .string()
240
+ .min(1)
241
+ .max(64)
242
+ .regex(/^[A-Za-z][A-Za-z0-9_-]*$/);
243
+ const SnapshotSuppressedFieldSchema = z.discriminatedUnion('kind', [
244
+ z
245
+ .object({
246
+ kind: z.literal('core'),
247
+ name: z.enum(['title', 'description', 'priority', 'status']),
248
+ })
249
+ .strict(),
250
+ z
251
+ .object({
252
+ kind: z.literal('extension'),
253
+ key: SnapshotExtensionKeySchema,
254
+ })
255
+ .strict(),
256
+ ]);
257
+ const CoreIssueSchema = z
258
+ .object({
259
+ title: z.string().max(8_192),
260
+ description: z
261
+ .string()
262
+ .refine((value) => Buffer.byteLength(value, 'utf8') <= MAX_REMOTE_DESCRIPTION_BYTES, `Remote description exceeds ${MAX_REMOTE_DESCRIPTION_BYTES} byte limit.`),
263
+ priority: z.string().max(255).nullable(),
264
+ status: z.string().min(1).max(255),
265
+ })
266
+ .strict();
267
+ const CurrentCapabilityReferenceSchema = z
268
+ .object({
269
+ provider: ProviderSchema,
270
+ surfaceKind: z.enum(['connector', 'configured-cli', 'legacy-observation']),
271
+ context: RemoteAccountContextSchema,
272
+ evidenceDigest: z.string().min(1).max(512),
273
+ semanticCapabilities: z.array(z.string().min(1).max(128)).max(32),
274
+ })
275
+ .strict();
276
+ const CurrentCapabilitySnapshotSchema = z
277
+ .object({
278
+ schemaVersion: z.literal(2),
279
+ provider: ProviderSchema,
280
+ surfaceKind: z.enum(['connector', 'configured-cli', 'legacy-observation']),
281
+ context: RemoteAccountContextSchema,
282
+ availability: z.enum([
283
+ 'available',
284
+ 'authorization-required',
285
+ 'unsupported-or-unresolved',
286
+ ]),
287
+ permissions: z.enum(['known', 'unknown']),
288
+ semanticCapabilities: z.array(z.string().min(1).max(128)).max(32),
289
+ observedAt: TimestampSchema,
290
+ evidenceDigest: z.string().min(1).max(512),
291
+ })
292
+ .strict();
293
+ const CapabilitySnapshotSchema = z.preprocess(migrateLegacyCapabilitySnapshot, CurrentCapabilitySnapshotSchema);
294
+ const RemoteRevisionSchema = z
295
+ .object({
296
+ strength: z.enum(['token', 'updated-at-and-hash', 'hash-only', 'unknown']),
297
+ token: z.string().max(4_096).nullable(),
298
+ updatedAt: TimestampSchema.nullable(),
299
+ contentHash: z.string().min(1).max(512),
300
+ })
301
+ .strict();
302
+ const RemoteSnapshotCommonShape = {
303
+ recordType: z.literal('snapshot'),
304
+ snapshotId: StableIdSchema,
305
+ bindingId: StableIdSchema,
306
+ provider: ProviderSchema,
307
+ observedAt: TimestampSchema,
308
+ observedBy: z.preprocess(migrateLegacyCapabilityReference, CurrentCapabilityReferenceSchema),
309
+ identity: RemoteIdentitySchema,
310
+ revision: RemoteRevisionSchema,
311
+ issue: CoreIssueSchema,
312
+ lifecycle: LifecycleConditionSchema,
313
+ contentRedacted: z.boolean(),
314
+ redactionCount: z.number().int().min(0),
315
+ extensions: ProviderExtensionsSchema.optional(),
316
+ };
317
+ const LegacyRemoteSnapshotRecordBaseSchema = z
318
+ .object({
319
+ ...RemoteSnapshotCommonShape,
320
+ schemaVersion: z.literal(1),
321
+ redactions: z
322
+ .array(z
323
+ .object({
324
+ field: z.enum(['title', 'description', 'priority', 'status']),
325
+ reason: z.enum(['credential', 'policy']),
326
+ })
327
+ .strict())
328
+ .max(MAX_SNAPSHOT_SUPPRESSION_EVIDENCE),
329
+ })
330
+ .strict();
331
+ const LegacyRemoteSnapshotRecordSchema = LegacyRemoteSnapshotRecordBaseSchema.superRefine((record, context) => {
332
+ if (record.redactionCount !== record.redactions.length) {
333
+ context.addIssue({
334
+ code: z.ZodIssueCode.custom,
335
+ path: ['redactionCount'],
336
+ message: 'Legacy snapshot redactionCount must match redactions.',
337
+ });
338
+ }
339
+ if (!record.contentRedacted && record.redactions.length > 0) {
340
+ context.addIssue({
341
+ code: z.ZodIssueCode.custom,
342
+ path: ['contentRedacted'],
343
+ message: 'Legacy snapshot redaction evidence must mark content redacted.',
344
+ });
345
+ }
346
+ if (record.contentRedacted &&
347
+ record.redactions.length === 0 &&
348
+ !snapshotContainsSensitiveSignal(record)) {
349
+ context.addIssue({
350
+ code: z.ZodIssueCode.custom,
351
+ path: ['contentRedacted'],
352
+ message: 'Legacy snapshot has incomplete redaction evidence and requires refresh.',
353
+ });
354
+ }
355
+ });
356
+ const CurrentRemoteSnapshotRecordBaseSchema = z
357
+ .object({
358
+ ...RemoteSnapshotCommonShape,
359
+ schemaVersion: z.literal(2),
360
+ redactions: z
361
+ .array(z
362
+ .object({
363
+ field: SnapshotSuppressedFieldSchema,
364
+ reason: z.enum(['sensitive-content', 'policy']),
365
+ representation: z.literal('whole-field-marker'),
366
+ })
367
+ .strict())
368
+ .max(MAX_SNAPSHOT_SUPPRESSION_EVIDENCE),
369
+ })
370
+ .strict();
371
+ const CurrentRemoteSnapshotRecordSchema = CurrentRemoteSnapshotRecordBaseSchema.superRefine(validateCurrentRemoteSnapshot);
372
+ const RemoteSnapshotRecordCompatibilitySchema = z.union([
373
+ CurrentRemoteSnapshotRecordSchema,
374
+ LegacyRemoteSnapshotRecordSchema.transform(migrateLegacyRemoteSnapshot).pipe(CurrentRemoteSnapshotRecordSchema),
375
+ ]);
376
+ // Runtime reads accept legacy v1 records, but callers and writes use only the
377
+ // canonical v2 output shape produced by the compatibility parser.
378
+ export const RemoteSnapshotRecordSchema = RemoteSnapshotRecordCompatibilitySchema;
379
+ const CORE_SNAPSHOT_FIELDS = [
380
+ 'title',
381
+ 'description',
382
+ 'priority',
383
+ 'status',
384
+ ];
385
+ function validateCurrentRemoteSnapshot(record, context) {
386
+ if (record.observedBy.provider !== record.provider) {
387
+ context.addIssue({
388
+ code: z.ZodIssueCode.custom,
389
+ path: ['observedBy', 'provider'],
390
+ message: 'Snapshot capability provider must match snapshot provider.',
391
+ });
392
+ }
393
+ if (JSON.stringify(record.observedBy.context) !==
394
+ JSON.stringify(record.identity.context)) {
395
+ context.addIssue({
396
+ code: z.ZodIssueCode.custom,
397
+ path: ['observedBy', 'context'],
398
+ message: 'Snapshot capability context must match remote identity context.',
399
+ });
400
+ }
401
+ if (record.redactionCount !== record.redactions.length) {
402
+ context.addIssue({
403
+ code: z.ZodIssueCode.custom,
404
+ path: ['redactionCount'],
405
+ message: 'Snapshot redactionCount must match retained redactions.',
406
+ });
407
+ }
408
+ if (record.contentRedacted !== record.redactions.length > 0) {
409
+ context.addIssue({
410
+ code: z.ZodIssueCode.custom,
411
+ path: ['contentRedacted'],
412
+ message: 'Snapshot contentRedacted must match field suppression evidence.',
413
+ });
414
+ }
415
+ const evidenceByField = new Map();
416
+ for (const [index, redaction] of record.redactions.entries()) {
417
+ const fieldId = suppressionFieldId(redaction.field);
418
+ if (evidenceByField.has(fieldId)) {
419
+ context.addIssue({
420
+ code: z.ZodIssueCode.custom,
421
+ path: ['redactions', index, 'field'],
422
+ message: 'Snapshot suppression evidence fields must be unique.',
423
+ });
424
+ }
425
+ evidenceByField.set(fieldId, index);
426
+ const retainedValue = redaction.field.kind === 'core'
427
+ ? record.issue[redaction.field.name]
428
+ : record.extensions?.[record.provider]?.[redaction.field.key];
429
+ if (retainedValue !== WHOLE_FIELD_SUPPRESSION_MARKER) {
430
+ context.addIssue({
431
+ code: z.ZodIssueCode.custom,
432
+ path: ['redactions', index, 'field'],
433
+ message: 'Snapshot suppression evidence requires the whole-field suppression marker.',
434
+ });
435
+ }
436
+ }
437
+ for (const field of CORE_SNAPSHOT_FIELDS) {
438
+ validateRetainedSnapshotField(record.issue[field], `core:${field}`, ['issue', field], evidenceByField, context);
439
+ }
440
+ for (const [provider, payload] of Object.entries(record.extensions ?? {})) {
441
+ if (provider !== record.provider) {
442
+ context.addIssue({
443
+ code: z.ZodIssueCode.custom,
444
+ path: ['extensions', provider],
445
+ message: 'Snapshot extensions must match the snapshot provider.',
446
+ });
447
+ continue;
448
+ }
449
+ for (const [key, value] of Object.entries(payload ?? {})) {
450
+ validateRetainedSnapshotField(value, `extension:${key}`, ['extensions', provider, key], evidenceByField, context);
451
+ }
452
+ }
453
+ }
454
+ function validateRetainedSnapshotField(value, fieldId, path, evidenceByField, context) {
455
+ const hasEvidence = evidenceByField.has(fieldId);
456
+ if (value === WHOLE_FIELD_SUPPRESSION_MARKER) {
457
+ if (!hasEvidence) {
458
+ context.addIssue({
459
+ code: z.ZodIssueCode.custom,
460
+ path,
461
+ message: 'Snapshot contains an unpaired whole-field suppression marker.',
462
+ });
463
+ }
464
+ return;
465
+ }
466
+ if (containsSensitiveContentSignalInValue(value)) {
467
+ context.addIssue({
468
+ code: z.ZodIssueCode.custom,
469
+ path,
470
+ message: 'Snapshot retained field contains a sensitive-content signal without whole-field suppression.',
471
+ });
472
+ }
473
+ }
474
+ function migrateLegacyRemoteSnapshot(record) {
475
+ const coreReasons = new Map();
476
+ for (const redaction of record.redactions) {
477
+ coreReasons.set(redaction.field, redaction.reason === 'policy' ? 'policy' : 'sensitive-content');
478
+ }
479
+ for (const field of CORE_SNAPSHOT_FIELDS) {
480
+ const value = record.issue[field];
481
+ if (typeof value === 'string' &&
482
+ containsSensitiveContentSignal(value) &&
483
+ !coreReasons.has(field)) {
484
+ coreReasons.set(field, 'sensitive-content');
485
+ }
486
+ }
487
+ const issue = { ...record.issue };
488
+ const redactions = [];
489
+ for (const field of CORE_SNAPSHOT_FIELDS) {
490
+ const reason = coreReasons.get(field);
491
+ if (!reason)
492
+ continue;
493
+ issue[field] = WHOLE_FIELD_SUPPRESSION_MARKER;
494
+ redactions.push({
495
+ field: { kind: 'core', name: field },
496
+ reason,
497
+ representation: 'whole-field-marker',
498
+ });
499
+ }
500
+ const extensions = record.extensions ? { ...record.extensions } : undefined;
501
+ const providerExtensions = record.extensions?.[record.provider];
502
+ if (extensions && providerExtensions) {
503
+ const migratedExtensions = { ...providerExtensions };
504
+ extensions[record.provider] = migratedExtensions;
505
+ for (const [key, value] of Object.entries(providerExtensions)) {
506
+ if (!containsSensitiveContentSignalInValue(value))
507
+ continue;
508
+ migratedExtensions[key] = WHOLE_FIELD_SUPPRESSION_MARKER;
509
+ redactions.push({
510
+ field: { kind: 'extension', key },
511
+ reason: 'sensitive-content',
512
+ representation: 'whole-field-marker',
513
+ });
514
+ }
515
+ }
516
+ return {
517
+ ...record,
518
+ schemaVersion: 2,
519
+ issue,
520
+ extensions,
521
+ contentRedacted: redactions.length > 0,
522
+ redactionCount: redactions.length,
523
+ redactions,
524
+ };
525
+ }
526
+ function snapshotContainsSensitiveSignal(record) {
527
+ return (CORE_SNAPSHOT_FIELDS.some((field) => {
528
+ const value = record.issue[field];
529
+ return typeof value === 'string' && containsSensitiveContentSignal(value);
530
+ }) ||
531
+ Object.values(record.extensions?.[record.provider] ?? {}).some((value) => containsSensitiveContentSignalInValue(value)));
532
+ }
533
+ function suppressionFieldId(field) {
534
+ return field.kind === 'core'
535
+ ? `core:${field.name}`
536
+ : `extension:${field.key}`;
537
+ }
538
+ const BaselineFieldSchema = z
539
+ .object({
540
+ value: z.string().nullable(),
541
+ hash: z.string().min(1).max(512),
542
+ })
543
+ .strict();
544
+ export const RemoteBaselineRecordSchema = z
545
+ .object({
546
+ recordType: z.literal('baseline'),
547
+ schemaVersion: z.literal(1),
548
+ baselineId: StableIdSchema,
549
+ bindingId: StableIdSchema,
550
+ agreedAt: TimestampSchema,
551
+ acceptedByOperationId: StableIdSchema,
552
+ localProjectionRevision: z.string().min(1).max(512),
553
+ remoteRevision: RemoteRevisionSchema,
554
+ fields: z
555
+ .object({
556
+ title: BaselineFieldSchema,
557
+ description: BaselineFieldSchema,
558
+ priority: BaselineFieldSchema,
559
+ })
560
+ .strict(),
561
+ })
562
+ .strict();
563
+ const CurrentRemoteBindingStateSchema = z
564
+ .object({
565
+ recordType: z.literal('binding-state'),
566
+ schemaVersion: z.literal(2),
567
+ bindingId: StableIdSchema,
568
+ provider: ProviderSchema,
569
+ metadataUpdatedAt: TimestampSchema,
570
+ localProjection: LocalIssueProjectionSchema,
571
+ snapshot: RemoteSnapshotRecordSchema.nullable(),
572
+ baseline: RemoteBaselineRecordSchema.nullable(),
573
+ capability: CapabilitySnapshotSchema.nullable(),
574
+ contentRedacted: z.boolean(),
575
+ lifecycle: z.enum(['active', 'blocked', 'tombstoned']),
576
+ lifecycleCondition: LifecycleConditionSchema,
577
+ activeOperationIds: z.array(StableIdSchema).max(256),
578
+ createdAt: TimestampSchema,
579
+ updatedAt: TimestampSchema,
580
+ })
581
+ .strict()
582
+ .superRefine((record, context) => {
583
+ if (record.snapshot && record.snapshot.bindingId !== record.bindingId) {
584
+ context.addIssue({
585
+ code: z.ZodIssueCode.custom,
586
+ path: ['snapshot', 'bindingId'],
587
+ message: 'Snapshot bindingId must match binding state.',
588
+ });
589
+ }
590
+ if (record.snapshot && record.snapshot.provider !== record.provider) {
591
+ context.addIssue({
592
+ code: z.ZodIssueCode.custom,
593
+ path: ['snapshot', 'provider'],
594
+ message: 'Snapshot provider must match binding state provider.',
595
+ });
596
+ }
597
+ if (record.baseline && record.baseline.bindingId !== record.bindingId) {
598
+ context.addIssue({
599
+ code: z.ZodIssueCode.custom,
600
+ path: ['baseline', 'bindingId'],
601
+ message: 'Baseline bindingId must match binding state.',
602
+ });
603
+ }
604
+ if (record.capability && record.capability.provider !== record.provider) {
605
+ context.addIssue({
606
+ code: z.ZodIssueCode.custom,
607
+ path: ['capability', 'provider'],
608
+ message: 'Capability provider must match binding state provider.',
609
+ });
610
+ }
611
+ });
612
+ export const RemoteBindingStateSchema = z.preprocess(migrateLegacyBindingState, CurrentRemoteBindingStateSchema);
613
+ export const RemoteOperationOutcomeSchema = z
614
+ .object({
615
+ classification: z.enum([
616
+ 'pending',
617
+ 'verified',
618
+ 'partial',
619
+ 'uncertain',
620
+ 'rejected',
621
+ 'blocked',
622
+ ]),
623
+ message: z.string().max(8_192).nullable(),
624
+ verifiedAt: TimestampSchema.nullable(),
625
+ })
626
+ .strict();
627
+ export const RemoteOperationStepSchema = z
628
+ .object({
629
+ stepId: StableIdSchema,
630
+ semanticOperation: OperationClassSchema,
631
+ state: OperationStateSchema,
632
+ actionDigest: z.string().min(1).max(512),
633
+ previewDigest: z.string().min(1).max(512),
634
+ authority: z
635
+ .object({
636
+ effective: MutationAuthoritySchema,
637
+ sourceDigest: z.string().min(1).max(512),
638
+ })
639
+ .strict(),
640
+ approvalRequirement: z.enum([
641
+ 'none',
642
+ 'explicit-instruction',
643
+ 'fresh-approval',
644
+ ]),
645
+ approval: z
646
+ .object({
647
+ previewDigest: z.string().min(1).max(512),
648
+ approvedAt: TimestampSchema,
649
+ source: z.string().min(1).max(255),
650
+ })
651
+ .strict()
652
+ .nullable(),
653
+ approvalPreview: z.lazy(() => ApprovalPreviewSchema).optional(),
654
+ attempts: z.array(z.string().min(1).max(512)).max(32),
655
+ verification: z.array(z.string().min(1).max(512)).max(64),
656
+ retryDisposition: z.enum([
657
+ 'not-applicable',
658
+ 'safe-before-attempt',
659
+ 'reconcile-required',
660
+ ]),
661
+ })
662
+ .strict();
663
+ const AuthorityDecisionSchema = z
664
+ .object({
665
+ effective: MutationAuthoritySchema,
666
+ sourceDigest: z.string().min(1).max(512),
667
+ })
668
+ .strict();
669
+ const ApprovalEvidenceSchema = z
670
+ .object({
671
+ previewDigest: z.string().min(1).max(512),
672
+ operationClass: OperationClassSchema.optional(),
673
+ approvedAt: TimestampSchema,
674
+ actor: z.string().min(1).max(255).optional(),
675
+ source: z.string().min(1).max(255),
676
+ })
677
+ .strict();
678
+ const OperationAttemptSchema = z
679
+ .object({
680
+ attemptId: StableIdSchema,
681
+ startedAt: TimestampSchema,
682
+ completedAt: TimestampSchema.nullable(),
683
+ execution: CurrentCapabilityReferenceSchema,
684
+ requestDigest: z.string().min(1).max(512),
685
+ receiptDigest: z.string().min(1).max(512).nullable(),
686
+ })
687
+ .strict();
688
+ const ExternalObservationSchema = z
689
+ .object({
690
+ observedAt: TimestampSchema,
691
+ classification: z.enum(['none', 'committed', 'not-committed', 'unknown']),
692
+ evidenceDigest: z.string().min(1).max(512),
693
+ actionDigest: z.string().min(1).max(512).optional(),
694
+ })
695
+ .strict();
696
+ const DurableVerificationReadActionSchema = z
697
+ .object({
698
+ schemaVersion: z.literal(1),
699
+ operationId: StableIdSchema,
700
+ stepId: StableIdSchema,
701
+ actionDigest: z.string().min(1).max(512),
702
+ provider: ProviderSchema,
703
+ semanticOperation: z.literal('read'),
704
+ context: RemoteAccountContextSchema,
705
+ intent: z.object({ stableId: z.string().min(1).max(512) }).strict(),
706
+ expectedObservation: z
707
+ .object({
708
+ fields: z
709
+ .array(z
710
+ .string()
711
+ .min(1)
712
+ .max(64)
713
+ .regex(/^[A-Za-z][A-Za-z0-9_-]*$/))
714
+ .max(64),
715
+ extensionFields: z
716
+ .array(z
717
+ .string()
718
+ .min(1)
719
+ .max(64)
720
+ .regex(/^[A-Za-z][A-Za-z0-9_-]*$/))
721
+ .max(32)
722
+ .optional(),
723
+ requireIdentity: z.literal(true),
724
+ stableId: z.string().min(1).max(512),
725
+ capabilityEvidenceDigest: z.string().min(1).max(512),
726
+ })
727
+ .strict(),
728
+ outboundSafety: z.null(),
729
+ })
730
+ .strict();
731
+ const DurableVerificationHandoffSchema = z
732
+ .object({
733
+ acceptedMutation: z
734
+ .object({
735
+ actionDigest: z.string().min(1).max(512),
736
+ observedAt: TimestampSchema,
737
+ evidenceDigest: z.string().min(1).max(512),
738
+ stableId: z.string().min(1).max(512),
739
+ })
740
+ .strict(),
741
+ verificationAction: DurableVerificationReadActionSchema,
742
+ })
743
+ .strict()
744
+ .superRefine((handoff, context) => {
745
+ if (handoff.verificationAction.intent.stableId !==
746
+ handoff.acceptedMutation.stableId ||
747
+ handoff.verificationAction.expectedObservation.stableId !==
748
+ handoff.acceptedMutation.stableId) {
749
+ context.addIssue({
750
+ code: z.ZodIssueCode.custom,
751
+ path: ['verificationAction', 'intent', 'stableId'],
752
+ message: 'Verification action identity must match accepted mutation evidence.',
753
+ });
754
+ }
755
+ });
756
+ const FieldVerificationSchema = z
757
+ .object({
758
+ field: z.string().min(1).max(255),
759
+ expectedHash: z.string().min(1).max(512),
760
+ observedHash: z.string().min(1).max(512).nullable(),
761
+ status: z.enum(['verified', 'mismatch', 'unavailable']),
762
+ })
763
+ .strict();
764
+ const MaterializationStepSchema = z
765
+ .object({
766
+ step: z.enum([
767
+ 'journal',
768
+ 'action-pointer',
769
+ 'target',
770
+ 'metadata',
771
+ 'state',
772
+ 'snapshot',
773
+ 'baseline',
774
+ 'association',
775
+ ]),
776
+ completedAt: TimestampSchema,
777
+ evidenceDigest: z.string().min(1).max(512),
778
+ })
779
+ .strict();
780
+ const MaterializationPlanSchema = z
781
+ .object({
782
+ kind: z.enum(['create', 'intake-create', 'intake-enrich', 'update']),
783
+ metadata: RemoteBindingMetadataSchema,
784
+ finalState: RemoteBindingStateSchema,
785
+ association: z
786
+ .object({
787
+ provider: ProviderSchema,
788
+ ref: z.string().min(1).max(2_048),
789
+ bindingId: StableIdSchema,
790
+ target: RemoteLocalTargetSchema,
791
+ seedContent: z.string().max(1_048_576).nullable(),
792
+ resolutionBindingId: StableIdSchema.nullable().optional(),
793
+ resolutionReferenceRef: z
794
+ .string()
795
+ .min(1)
796
+ .max(2_048)
797
+ .nullable()
798
+ .optional(),
799
+ })
800
+ .strict()
801
+ .nullable(),
802
+ resolutionEvidence: z
803
+ .object({
804
+ schemaVersion: z.literal(1),
805
+ formerSnapshot: RemoteSnapshotRecordSchema,
806
+ replacementSnapshot: RemoteSnapshotRecordSchema,
807
+ journalDigest: z.string().min(1).max(512),
808
+ })
809
+ .strict()
810
+ .optional(),
811
+ retireAction: z
812
+ .object({
813
+ stepId: StableIdSchema,
814
+ actionDigest: z.string().min(1).max(512),
815
+ })
816
+ .strict()
817
+ .optional(),
818
+ terminal: z
819
+ .object({
820
+ state: z.enum(['verified', 'uncertain', 'rejected', 'blocked']),
821
+ message: z.string().max(8_192).nullable(),
822
+ verifiedAt: TimestampSchema.nullable(),
823
+ })
824
+ .strict()
825
+ .optional(),
826
+ })
827
+ .strict();
828
+ const ApprovalRevisionEvidenceSchema = z
829
+ .object({
830
+ source: z.enum(['remote', 'remote-unobserved', 'local-source-unbound']),
831
+ strength: z.enum(['token', 'updated-at-and-hash', 'hash-only', 'unknown']),
832
+ updatedAt: TimestampSchema.nullable(),
833
+ observedAt: TimestampSchema.nullable(),
834
+ })
835
+ .strict();
836
+ const OperationPreviewSchema = z
837
+ .object({
838
+ digest: z.string().min(1).max(512),
839
+ bindingId: StableIdSchema,
840
+ provider: ProviderSchema,
841
+ providerContext: RemoteAccountContextSchema,
842
+ capabilityEvidenceDigest: z.string().min(1).max(512),
843
+ revisionDigest: z.string().min(1).max(512),
844
+ revisionEvidence: ApprovalRevisionEvidenceSchema.optional(),
845
+ policyDigest: z.string().min(1).max(512),
846
+ projectionDigest: z.string().min(1).max(512).nullable().optional(),
847
+ safetyResultDigest: z.string().min(1).max(512).nullable().optional(),
848
+ })
849
+ .strict();
850
+ const ApprovalPreviewSchema = z
851
+ .object({
852
+ schemaVersion: z.literal(1),
853
+ digest: z.string().min(1).max(512),
854
+ bindingId: StableIdSchema,
855
+ provider: ProviderSchema,
856
+ operationClass: OperationClassSchema,
857
+ fieldMask: z
858
+ .array(z.enum(['title', 'description', 'priority']))
859
+ .min(1)
860
+ .max(3),
861
+ createdAt: TimestampSchema,
862
+ componentDigests: z
863
+ .object({
864
+ target: z.string().min(1).max(512),
865
+ baseline: z.string().min(1).max(512),
866
+ revision: z.string().min(1).max(512),
867
+ capability: z.string().min(1).max(512),
868
+ policy: z.string().min(1).max(512),
869
+ projection: z.string().min(1).max(512),
870
+ outboundSafety: z.string().min(1).max(512),
871
+ })
872
+ .strict(),
873
+ revisionEvidence: ApprovalRevisionEvidenceSchema.optional(),
874
+ renderedFields: z.record(z.enum(['title', 'description', 'priority']), z.union([
875
+ z
876
+ .object({
877
+ kind: z.literal('value'),
878
+ value: z.string().nullable(),
879
+ })
880
+ .strict(),
881
+ z
882
+ .object({
883
+ kind: z.literal('hash'),
884
+ digest: z.string().min(1).max(512),
885
+ bytes: z.number().int().nonnegative().max(1_048_576),
886
+ })
887
+ .strict(),
888
+ ])),
889
+ })
890
+ .strict();
891
+ const CurrentRemoteOperationRecordSchema = z
892
+ .object({
893
+ recordType: z.literal('operation'),
894
+ schemaVersion: z.literal(2),
895
+ operationId: StableIdSchema,
896
+ correlationId: StableIdSchema,
897
+ bindingId: StableIdSchema,
898
+ provider: ProviderSchema,
899
+ providerContext: RemoteAccountContextSchema,
900
+ lifecycleOperation: z.enum([
901
+ 'intake',
902
+ 'publish',
903
+ 'refresh',
904
+ 'reconcile',
905
+ 'closeout',
906
+ 'discussion',
907
+ 'relink',
908
+ 'detach',
909
+ 'recreate',
910
+ ]),
911
+ operationClass: OperationRecordClassSchema,
912
+ state: OperationStateSchema,
913
+ reason: z
914
+ .object({
915
+ code: z.string().min(1).max(255),
916
+ message: z.string().min(1).max(8_192),
917
+ })
918
+ .strict()
919
+ .nullable(),
920
+ lastSafeStep: z.enum([
921
+ 'planned',
922
+ 'authorized',
923
+ 'attempt-started',
924
+ 'verification-pending',
925
+ 'complete',
926
+ ]),
927
+ preview: OperationPreviewSchema,
928
+ approvalPreview: ApprovalPreviewSchema.optional(),
929
+ descriptionMode: DescriptionModeSchema.optional(),
930
+ authority: AuthorityDecisionSchema.nullable(),
931
+ approval: ApprovalEvidenceSchema.nullable(),
932
+ createdAt: TimestampSchema,
933
+ updatedAt: TimestampSchema,
934
+ selectedExecution: CurrentCapabilityReferenceSchema.nullable(),
935
+ attempts: z.array(OperationAttemptSchema).max(32),
936
+ observations: z.array(ExternalObservationSchema).max(64),
937
+ verification: z.array(FieldVerificationSchema).max(64),
938
+ retryDisposition: z.enum([
939
+ 'not-applicable',
940
+ 'safe-before-attempt',
941
+ 'reconcile-required',
942
+ ]),
943
+ steps: z.array(RemoteOperationStepSchema).max(64),
944
+ materializationSteps: z.array(MaterializationStepSchema).max(8).optional(),
945
+ materializationPlan: MaterializationPlanSchema.optional(),
946
+ currentAction: DurableVerificationReadActionSchema.optional(),
947
+ verificationHandoff: DurableVerificationHandoffSchema.optional(),
948
+ outcome: RemoteOperationOutcomeSchema,
949
+ createIntent: PlannedBindingCreateSchema.optional(),
950
+ })
951
+ .strict()
952
+ .superRefine((record, context) => {
953
+ const isReadOnlyLifecycle = READ_ONLY_LIFECYCLE_OPERATIONS.has(record.lifecycleOperation);
954
+ if (isReadOnlyLifecycle) {
955
+ if (record.operationClass !== null) {
956
+ context.addIssue({
957
+ code: z.ZodIssueCode.custom,
958
+ path: ['operationClass'],
959
+ message: 'Read-only lifecycle operations require operationClass null.',
960
+ });
961
+ }
962
+ if (record.operationClass === null && record.authority !== null) {
963
+ context.addIssue({
964
+ code: z.ZodIssueCode.custom,
965
+ path: ['authority'],
966
+ message: 'Read-only operations must not retain mutation authority.',
967
+ });
968
+ }
969
+ if (record.operationClass === null && record.approval !== null) {
970
+ context.addIssue({
971
+ code: z.ZodIssueCode.custom,
972
+ path: ['approval'],
973
+ message: 'Read-only operations must not retain mutation approval.',
974
+ });
975
+ }
976
+ if (record.operationClass === null &&
977
+ (record.attempts.length > 0 || record.steps.length > 0)) {
978
+ context.addIssue({
979
+ code: z.ZodIssueCode.custom,
980
+ path: ['attempts'],
981
+ message: 'Read-only operations must not retain mutation attempts or substeps.',
982
+ });
983
+ }
984
+ }
985
+ else if (record.lifecycleOperation === 'closeout') {
986
+ if (record.operationClass !== 'composite') {
987
+ context.addIssue({
988
+ code: z.ZodIssueCode.custom,
989
+ path: ['operationClass'],
990
+ message: 'Closeout lifecycle operations require operationClass composite.',
991
+ });
992
+ }
993
+ if (record.operationClass === 'composite' && record.authority !== null) {
994
+ context.addIssue({
995
+ code: z.ZodIssueCode.custom,
996
+ path: ['authority'],
997
+ message: 'Composite parent authority must be null.',
998
+ });
999
+ }
1000
+ if (record.operationClass === 'composite' && record.approval !== null) {
1001
+ context.addIssue({
1002
+ code: z.ZodIssueCode.custom,
1003
+ path: ['approval'],
1004
+ message: 'Composite parent approval must be null.',
1005
+ });
1006
+ }
1007
+ if (record.operationClass === 'composite' && record.steps.length === 0) {
1008
+ context.addIssue({
1009
+ code: z.ZodIssueCode.custom,
1010
+ path: ['steps'],
1011
+ message: 'Composite operations require authoritative substeps.',
1012
+ });
1013
+ }
1014
+ if (record.operationClass === 'composite') {
1015
+ const semanticOperations = new Set();
1016
+ for (const [index, step] of record.steps.entries()) {
1017
+ if (!['annotate', 'transition'].includes(step.semanticOperation)) {
1018
+ context.addIssue({
1019
+ code: z.ZodIssueCode.custom,
1020
+ path: ['steps', index, 'semanticOperation'],
1021
+ message: 'Composite closeout substeps must be annotate or transition.',
1022
+ });
1023
+ }
1024
+ if (semanticOperations.has(step.semanticOperation)) {
1025
+ context.addIssue({
1026
+ code: z.ZodIssueCode.custom,
1027
+ path: ['steps', index, 'semanticOperation'],
1028
+ message: `Duplicate composite ${step.semanticOperation} substep.`,
1029
+ });
1030
+ }
1031
+ semanticOperations.add(step.semanticOperation);
1032
+ }
1033
+ if (record.steps[0]?.semanticOperation === 'transition' &&
1034
+ record.steps[1]?.semanticOperation === 'annotate') {
1035
+ context.addIssue({
1036
+ code: z.ZodIssueCode.custom,
1037
+ path: ['steps'],
1038
+ message: 'Composite closeout annotation must precede transition.',
1039
+ });
1040
+ }
1041
+ }
1042
+ }
1043
+ else {
1044
+ if (record.operationClass === null) {
1045
+ context.addIssue({
1046
+ code: z.ZodIssueCode.custom,
1047
+ path: ['lifecycleOperation'],
1048
+ message: 'Read-only operation class is valid only for intake, refresh, or discussion lifecycle.',
1049
+ });
1050
+ }
1051
+ else if (record.operationClass === 'composite') {
1052
+ context.addIssue({
1053
+ code: z.ZodIssueCode.custom,
1054
+ path: ['lifecycleOperation'],
1055
+ message: 'Composite operation lifecycle must be closeout.',
1056
+ });
1057
+ }
1058
+ else if (!ALLOWED_MUTATION_CLASSES_BY_LIFECYCLE[record.lifecycleOperation]?.has(record.operationClass)) {
1059
+ context.addIssue({
1060
+ code: z.ZodIssueCode.custom,
1061
+ path: ['operationClass'],
1062
+ message: `Lifecycle '${record.lifecycleOperation}' does not allow mutation class '${record.operationClass}'.`,
1063
+ });
1064
+ }
1065
+ if (record.operationClass !== null && record.authority === null) {
1066
+ context.addIssue({
1067
+ code: z.ZodIssueCode.custom,
1068
+ path: ['authority'],
1069
+ message: 'Mutation operations require an authority decision.',
1070
+ });
1071
+ }
1072
+ if (record.steps.length > 0) {
1073
+ context.addIssue({
1074
+ code: z.ZodIssueCode.custom,
1075
+ path: ['steps'],
1076
+ message: 'Non-composite operations must not contain substeps.',
1077
+ });
1078
+ }
1079
+ }
1080
+ if (record.approvalPreview &&
1081
+ (record.approvalPreview.digest !== record.preview.digest ||
1082
+ record.approvalPreview.bindingId !== record.bindingId ||
1083
+ record.approvalPreview.provider !== record.provider ||
1084
+ record.approvalPreview.operationClass !== record.operationClass)) {
1085
+ context.addIssue({
1086
+ code: z.ZodIssueCode.custom,
1087
+ path: ['approvalPreview'],
1088
+ message: 'Approval preview must match its persisted operation.',
1089
+ });
1090
+ }
1091
+ if (record.approvalPreview &&
1092
+ ((record.approvalPreview.revisionEvidence === undefined) !==
1093
+ (record.preview.revisionEvidence === undefined) ||
1094
+ (record.approvalPreview.revisionEvidence !== undefined &&
1095
+ JSON.stringify(record.approvalPreview.revisionEvidence) !==
1096
+ JSON.stringify(record.preview.revisionEvidence)))) {
1097
+ context.addIssue({
1098
+ code: z.ZodIssueCode.custom,
1099
+ path: ['approvalPreview', 'revisionEvidence'],
1100
+ message: 'Approval revision freshness evidence must match its digest-bound operation preview.',
1101
+ });
1102
+ }
1103
+ if (record.approvalPreview &&
1104
+ ['relink', 'detach', 'recreate'].includes(record.lifecycleOperation) &&
1105
+ !record.approvalPreview.revisionEvidence) {
1106
+ context.addIssue({
1107
+ code: z.ZodIssueCode.custom,
1108
+ path: ['approvalPreview', 'revisionEvidence'],
1109
+ message: 'Resolution approval preview requires revision freshness evidence.',
1110
+ });
1111
+ }
1112
+ if (record.approval &&
1113
+ record.approval.previewDigest !== record.preview.digest) {
1114
+ context.addIssue({
1115
+ code: z.ZodIssueCode.custom,
1116
+ path: ['approval', 'previewDigest'],
1117
+ message: 'Operation approval preview digest must match the preview.',
1118
+ });
1119
+ }
1120
+ if ((record.preview.projectionDigest == null) !==
1121
+ (record.preview.safetyResultDigest == null)) {
1122
+ context.addIssue({
1123
+ code: z.ZodIssueCode.custom,
1124
+ path: ['preview'],
1125
+ message: 'Operation preview projection and safety-result digests must be persisted together.',
1126
+ });
1127
+ }
1128
+ for (const [index, step] of record.steps.entries()) {
1129
+ if (step.approval && step.approval.previewDigest !== step.previewDigest) {
1130
+ context.addIssue({
1131
+ code: z.ZodIssueCode.custom,
1132
+ path: ['steps', index, 'approval', 'previewDigest'],
1133
+ message: 'Substep approval preview digest must match its preview.',
1134
+ });
1135
+ }
1136
+ if (step.approvalPreview &&
1137
+ (step.approvalPreview.digest !== step.previewDigest ||
1138
+ step.approvalPreview.bindingId !== record.bindingId ||
1139
+ step.approvalPreview.provider !== record.provider ||
1140
+ step.approvalPreview.operationClass !== step.semanticOperation)) {
1141
+ context.addIssue({
1142
+ code: z.ZodIssueCode.custom,
1143
+ path: ['steps', index, 'approvalPreview'],
1144
+ message: 'Substep approval preview must match its persisted step.',
1145
+ });
1146
+ }
1147
+ }
1148
+ const seen = new Set();
1149
+ for (const [index, step] of record.steps.entries()) {
1150
+ if (seen.has(step.stepId)) {
1151
+ context.addIssue({
1152
+ code: z.ZodIssueCode.custom,
1153
+ path: ['steps', index, 'stepId'],
1154
+ message: `Duplicate stepId '${step.stepId}'.`,
1155
+ });
1156
+ }
1157
+ seen.add(step.stepId);
1158
+ }
1159
+ const materializationSeen = new Set();
1160
+ for (const [index, step] of (record.materializationSteps ?? []).entries()) {
1161
+ if (materializationSeen.has(step.step)) {
1162
+ context.addIssue({
1163
+ code: z.ZodIssueCode.custom,
1164
+ path: ['materializationSteps', index, 'step'],
1165
+ message: `Duplicate materialization step '${step.step}'.`,
1166
+ });
1167
+ }
1168
+ materializationSeen.add(step.step);
1169
+ }
1170
+ if (record.materializationPlan) {
1171
+ if (record.materializationPlan.metadata.bindingId !== record.bindingId ||
1172
+ record.materializationPlan.finalState.bindingId !== record.bindingId) {
1173
+ context.addIssue({
1174
+ code: z.ZodIssueCode.custom,
1175
+ path: ['materializationPlan'],
1176
+ message: 'Materialization plan binding must match its operation.',
1177
+ });
1178
+ }
1179
+ const evidence = record.materializationPlan.resolutionEvidence;
1180
+ if (evidence &&
1181
+ (evidence.formerSnapshot.bindingId !== record.bindingId ||
1182
+ evidence.replacementSnapshot.bindingId !== record.bindingId ||
1183
+ evidence.formerSnapshot.provider !== record.provider ||
1184
+ evidence.replacementSnapshot.provider !== record.provider)) {
1185
+ context.addIssue({
1186
+ code: z.ZodIssueCode.custom,
1187
+ path: ['materializationPlan', 'resolutionEvidence'],
1188
+ message: 'Resolution snapshot evidence must match its persisted operation.',
1189
+ });
1190
+ }
1191
+ if (record.state === 'verified') {
1192
+ const required = new Set([
1193
+ 'journal',
1194
+ ...(record.materializationPlan.retireAction
1195
+ ? ['action-pointer']
1196
+ : []),
1197
+ ...(record.materializationPlan.association?.seedContent
1198
+ ? ['target']
1199
+ : []),
1200
+ ...(record.materializationPlan.kind === 'update' ? [] : ['metadata']),
1201
+ 'state',
1202
+ 'snapshot',
1203
+ 'baseline',
1204
+ ...(record.materializationPlan.association ? ['association'] : []),
1205
+ ]);
1206
+ const completed = new Set((record.materializationSteps ?? []).map((step) => step.step));
1207
+ if ([...required].some((step) => !completed.has(step))) {
1208
+ context.addIssue({
1209
+ code: z.ZodIssueCode.custom,
1210
+ path: ['materializationSteps'],
1211
+ message: 'Verified materialization requires every planned local step.',
1212
+ });
1213
+ }
1214
+ }
1215
+ }
1216
+ if (record.preview.bindingId !== record.bindingId) {
1217
+ context.addIssue({
1218
+ code: z.ZodIssueCode.custom,
1219
+ path: ['preview', 'bindingId'],
1220
+ message: 'Operation preview bindingId must match operation bindingId.',
1221
+ });
1222
+ }
1223
+ if (record.preview.provider !== record.provider) {
1224
+ context.addIssue({
1225
+ code: z.ZodIssueCode.custom,
1226
+ path: ['preview', 'provider'],
1227
+ message: 'Operation preview provider must match operation provider.',
1228
+ });
1229
+ }
1230
+ if (JSON.stringify(record.preview.providerContext) !==
1231
+ JSON.stringify(record.providerContext)) {
1232
+ context.addIssue({
1233
+ code: z.ZodIssueCode.custom,
1234
+ path: ['preview', 'providerContext'],
1235
+ message: 'Operation preview provider context must match the operation.',
1236
+ });
1237
+ }
1238
+ if (record.selectedExecution &&
1239
+ record.selectedExecution.provider !== record.provider) {
1240
+ context.addIssue({
1241
+ code: z.ZodIssueCode.custom,
1242
+ path: ['selectedExecution', 'provider'],
1243
+ message: 'Selected execution provider must match operation provider.',
1244
+ });
1245
+ }
1246
+ if (record.selectedExecution &&
1247
+ JSON.stringify(record.selectedExecution.context) !==
1248
+ JSON.stringify(record.providerContext)) {
1249
+ context.addIssue({
1250
+ code: z.ZodIssueCode.custom,
1251
+ path: ['selectedExecution', 'context'],
1252
+ message: 'Selected execution context must match operation provider context.',
1253
+ });
1254
+ }
1255
+ if (record.createIntent) {
1256
+ if (record.operationClass !== 'create') {
1257
+ context.addIssue({
1258
+ code: z.ZodIssueCode.custom,
1259
+ path: ['createIntent'],
1260
+ message: 'A pre-create intent requires a create operation.',
1261
+ });
1262
+ }
1263
+ if (record.createIntent.operationId !== record.operationId) {
1264
+ context.addIssue({
1265
+ code: z.ZodIssueCode.custom,
1266
+ path: ['createIntent', 'operationId'],
1267
+ message: 'Pre-create intent operationId must match its journal.',
1268
+ });
1269
+ }
1270
+ if (record.createIntent.bindingId !== record.bindingId) {
1271
+ context.addIssue({
1272
+ code: z.ZodIssueCode.custom,
1273
+ path: ['createIntent', 'bindingId'],
1274
+ message: 'Pre-create intent bindingId must match its journal.',
1275
+ });
1276
+ }
1277
+ if (record.createIntent.provider !== record.provider) {
1278
+ context.addIssue({
1279
+ code: z.ZodIssueCode.custom,
1280
+ path: ['createIntent', 'provider'],
1281
+ message: 'Pre-create intent provider must match its journal.',
1282
+ });
1283
+ }
1284
+ if (JSON.stringify(record.createIntent.providerContext) !==
1285
+ JSON.stringify(record.providerContext)) {
1286
+ context.addIssue({
1287
+ code: z.ZodIssueCode.custom,
1288
+ path: ['createIntent', 'providerContext'],
1289
+ message: 'Pre-create provider context must match its journal.',
1290
+ });
1291
+ }
1292
+ }
1293
+ if (record.verificationHandoff) {
1294
+ const handoff = record.verificationHandoff;
1295
+ if (!record.currentAction ||
1296
+ JSON.stringify(record.currentAction) !==
1297
+ JSON.stringify(handoff.verificationAction)) {
1298
+ context.addIssue({
1299
+ code: z.ZodIssueCode.custom,
1300
+ path: ['currentAction'],
1301
+ message: 'Verification-pending evidence requires the exact canonical current action.',
1302
+ });
1303
+ }
1304
+ if (handoff.verificationAction.operationId !== record.operationId ||
1305
+ handoff.verificationAction.provider !== record.provider ||
1306
+ JSON.stringify(handoff.verificationAction.context) !==
1307
+ JSON.stringify(record.providerContext)) {
1308
+ context.addIssue({
1309
+ code: z.ZodIssueCode.custom,
1310
+ path: ['verificationHandoff', 'verificationAction'],
1311
+ message: 'Verification handoff action must match its persisted operation.',
1312
+ });
1313
+ }
1314
+ const acceptedObservation = record.observations.find((item) => item.actionDigest === handoff.acceptedMutation.actionDigest &&
1315
+ item.evidenceDigest === handoff.acceptedMutation.evidenceDigest &&
1316
+ item.observedAt === handoff.acceptedMutation.observedAt);
1317
+ if (!acceptedObservation ||
1318
+ acceptedObservation.classification !== 'committed') {
1319
+ context.addIssue({
1320
+ code: z.ZodIssueCode.custom,
1321
+ path: ['verificationHandoff', 'acceptedMutation'],
1322
+ message: 'Verification handoff requires matching committed mutation evidence.',
1323
+ });
1324
+ }
1325
+ const acceptedAttempt = record.attempts.find((attempt) => attempt.requestDigest === handoff.acceptedMutation.actionDigest &&
1326
+ attempt.completedAt !== null &&
1327
+ attempt.receiptDigest === handoff.acceptedMutation.evidenceDigest);
1328
+ if (!acceptedAttempt) {
1329
+ context.addIssue({
1330
+ code: z.ZodIssueCode.custom,
1331
+ path: ['verificationHandoff', 'acceptedMutation'],
1332
+ message: 'Verification handoff requires matching completed attempt evidence.',
1333
+ });
1334
+ }
1335
+ if (!['verification-pending', 'verified', 'partial', 'uncertain'].includes(record.state)) {
1336
+ context.addIssue({
1337
+ code: z.ZodIssueCode.custom,
1338
+ path: ['verificationHandoff'],
1339
+ message: 'Verification handoff is valid only after the mutation attempt.',
1340
+ });
1341
+ }
1342
+ }
1343
+ if (record.currentAction && !record.verificationHandoff) {
1344
+ context.addIssue({
1345
+ code: z.ZodIssueCode.custom,
1346
+ path: ['currentAction'],
1347
+ message: 'Canonical current action requires its matching verification handoff.',
1348
+ });
1349
+ }
1350
+ });
1351
+ export const RemoteOperationRecordSchema = z.preprocess(migrateLegacyOperationRecord, CurrentRemoteOperationRecordSchema);
1352
+ const RemoteBatchMemberSchema = z
1353
+ .object({
1354
+ bindingId: StableIdSchema,
1355
+ operationId: StableIdSchema,
1356
+ bindingPreviewDigest: z.string().min(1).max(512),
1357
+ })
1358
+ .strict();
1359
+ export const RemoteBatchRecordSchema = z
1360
+ .object({
1361
+ recordType: z.literal('batch'),
1362
+ schemaVersion: z.literal(1),
1363
+ batchId: StableIdSchema,
1364
+ lifecycleOperation: z.enum(['closeout', 'refresh', 'reconcile']),
1365
+ state: z.enum([
1366
+ 'planned',
1367
+ 'pending',
1368
+ 'authorized',
1369
+ 'in-progress',
1370
+ 'complete',
1371
+ 'partial',
1372
+ 'uncertain',
1373
+ 'blocked',
1374
+ ]),
1375
+ membershipDigest: z.string().min(1).max(512),
1376
+ previewDigest: z.string().min(1).max(512),
1377
+ authority: AuthorityDecisionSchema,
1378
+ approval: ApprovalEvidenceSchema.nullable(),
1379
+ createdAt: TimestampSchema,
1380
+ updatedAt: TimestampSchema,
1381
+ members: z.array(RemoteBatchMemberSchema).min(1).max(512),
1382
+ outcomes: z.record(StableIdSchema, OperationStateSchema),
1383
+ })
1384
+ .strict()
1385
+ .superRefine((record, context) => {
1386
+ const operationIds = record.members.map((member) => member.operationId);
1387
+ if (new Set(operationIds).size !== operationIds.length) {
1388
+ context.addIssue({
1389
+ code: z.ZodIssueCode.custom,
1390
+ path: ['members'],
1391
+ message: 'Batch operation membership must be unique and immutable.',
1392
+ });
1393
+ }
1394
+ const outcomes = new Set(Object.keys(record.outcomes));
1395
+ if (operationIds.some((operationId) => !outcomes.has(operationId)) ||
1396
+ outcomes.size !== operationIds.length) {
1397
+ context.addIssue({
1398
+ code: z.ZodIssueCode.custom,
1399
+ path: ['outcomes'],
1400
+ message: 'Batch outcomes must match immutable operation membership.',
1401
+ });
1402
+ }
1403
+ });
1404
+ export function assertRecordIdMatchesFilename(filePath, recordId) {
1405
+ const filenameId = basename(filePath, '.json');
1406
+ if (filenameId !== recordId) {
1407
+ throw new Error(`Remote record filename '${filenameId}' does not match stable ID '${recordId}'.`);
1408
+ }
1409
+ }
1410
+ function migrateLegacyCapabilityReference(value) {
1411
+ if (!isPlainRecord(value) || !('transport' in value))
1412
+ return value;
1413
+ return {
1414
+ provider: value.provider,
1415
+ surfaceKind: 'legacy-observation',
1416
+ context: value.context,
1417
+ evidenceDigest: value.capabilityDigest,
1418
+ semanticCapabilities: [],
1419
+ };
1420
+ }
1421
+ function migrateLegacyCapabilitySnapshot(value) {
1422
+ if (!isPlainRecord(value) || !('transport' in value))
1423
+ return value;
1424
+ return {
1425
+ schemaVersion: 2,
1426
+ provider: value.provider,
1427
+ surfaceKind: 'legacy-observation',
1428
+ context: value.context,
1429
+ availability: 'unsupported-or-unresolved',
1430
+ permissions: 'unknown',
1431
+ semanticCapabilities: [],
1432
+ observedAt: value.observedAt,
1433
+ evidenceDigest: value.evidenceDigest,
1434
+ };
1435
+ }
1436
+ function migrateLegacyBindingState(value) {
1437
+ if (!isPlainRecord(value) || value.recordType !== 'binding-state')
1438
+ return value;
1439
+ if (value.schemaVersion !== 1)
1440
+ return value;
1441
+ return {
1442
+ ...value,
1443
+ schemaVersion: 2,
1444
+ capability: migrateLegacyCapabilitySnapshot(value.capability),
1445
+ };
1446
+ }
1447
+ function migrateLegacyOperationRecord(value) {
1448
+ if (!isPlainRecord(value) || value.recordType !== 'operation')
1449
+ return value;
1450
+ if (value.schemaVersion !== 1)
1451
+ return value;
1452
+ const preview = isPlainRecord(value.preview)
1453
+ ? {
1454
+ ...value.preview,
1455
+ capabilityEvidenceDigest: value.preview.capabilityEvidenceDigest ??
1456
+ value.preview.capabilityDigest,
1457
+ projectionDigest: value.preview.projectionDigest ?? null,
1458
+ safetyResultDigest: value.preview.safetyResultDigest ?? null,
1459
+ }
1460
+ : value.preview;
1461
+ if (isPlainRecord(preview))
1462
+ delete preview.capabilityDigest;
1463
+ const selectedExecution = migrateLegacyCapabilityReference(value.selectedExecution ?? value.selectedTransport);
1464
+ const attempts = Array.isArray(value.attempts)
1465
+ ? value.attempts.map((attempt) => {
1466
+ if (!isPlainRecord(attempt))
1467
+ return attempt;
1468
+ const execution = migrateLegacyCapabilityReference(attempt.execution ?? attempt.transport);
1469
+ const migrated = { ...attempt, execution };
1470
+ delete migrated.transport;
1471
+ return migrated;
1472
+ })
1473
+ : value.attempts;
1474
+ const migrated = {
1475
+ ...value,
1476
+ schemaVersion: 2,
1477
+ preview,
1478
+ selectedExecution,
1479
+ attempts,
1480
+ };
1481
+ delete migrated.transport;
1482
+ delete migrated.selectedTransport;
1483
+ return migrated;
1484
+ }
1485
+ function markIncompleteProjectCreate(value) {
1486
+ if (!isPlainRecord(value) || value.schemaVersion !== 1)
1487
+ return value;
1488
+ if (value.projectionStatus !== undefined)
1489
+ return value;
1490
+ const target = isPlainRecord(value.target) ? value.target : null;
1491
+ const localProjection = isPlainRecord(value.localProjection)
1492
+ ? value.localProjection
1493
+ : null;
1494
+ return {
1495
+ ...value,
1496
+ projectionStatus: target?.kind === 'project' && localProjection === null
1497
+ ? 'reconcile-required'
1498
+ : 'complete',
1499
+ };
1500
+ }
1501
+ function isPlainRecord(value) {
1502
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1503
+ }