@oscharko-dev/keiko-contracts 0.2.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 (227) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/bff-wire.d.ts +661 -0
  3. package/dist/bff-wire.d.ts.map +1 -0
  4. package/dist/bff-wire.js +102 -0
  5. package/dist/bug-investigation-events.d.ts +92 -0
  6. package/dist/bug-investigation-events.d.ts.map +1 -0
  7. package/dist/bug-investigation-events.js +18 -0
  8. package/dist/coding-context.d.ts +76 -0
  9. package/dist/coding-context.d.ts.map +1 -0
  10. package/dist/coding-context.js +158 -0
  11. package/dist/connected-context.d.ts +174 -0
  12. package/dist/connected-context.d.ts.map +1 -0
  13. package/dist/connected-context.js +636 -0
  14. package/dist/conversation-budget.d.ts +37 -0
  15. package/dist/conversation-budget.d.ts.map +1 -0
  16. package/dist/conversation-budget.js +97 -0
  17. package/dist/editor-agent.d.ts +131 -0
  18. package/dist/editor-agent.d.ts.map +1 -0
  19. package/dist/editor-agent.js +197 -0
  20. package/dist/editor-completion.d.ts +62 -0
  21. package/dist/editor-completion.d.ts.map +1 -0
  22. package/dist/editor-completion.js +147 -0
  23. package/dist/editor-dirty-close.d.ts +17 -0
  24. package/dist/editor-dirty-close.d.ts.map +1 -0
  25. package/dist/editor-dirty-close.js +8 -0
  26. package/dist/editor-hot-exit.d.ts +18 -0
  27. package/dist/editor-hot-exit.d.ts.map +1 -0
  28. package/dist/editor-hot-exit.js +42 -0
  29. package/dist/editor-inline-completion.d.ts +70 -0
  30. package/dist/editor-inline-completion.d.ts.map +1 -0
  31. package/dist/editor-inline-completion.js +215 -0
  32. package/dist/editor-layout.d.ts +105 -0
  33. package/dist/editor-layout.d.ts.map +1 -0
  34. package/dist/editor-layout.js +479 -0
  35. package/dist/editor-patch-apply.d.ts +77 -0
  36. package/dist/editor-patch-apply.d.ts.map +1 -0
  37. package/dist/editor-patch-apply.js +122 -0
  38. package/dist/editor-session.d.ts +31 -0
  39. package/dist/editor-session.d.ts.map +1 -0
  40. package/dist/editor-session.js +75 -0
  41. package/dist/editor-test-generation.d.ts +104 -0
  42. package/dist/editor-test-generation.d.ts.map +1 -0
  43. package/dist/editor-test-generation.js +211 -0
  44. package/dist/evaluations.d.ts +75 -0
  45. package/dist/evaluations.d.ts.map +1 -0
  46. package/dist/evaluations.js +16 -0
  47. package/dist/evidence.d.ts +297 -0
  48. package/dist/evidence.d.ts.map +1 -0
  49. package/dist/evidence.js +9 -0
  50. package/dist/gateway.d.ts +129 -0
  51. package/dist/gateway.d.ts.map +1 -0
  52. package/dist/gateway.js +66 -0
  53. package/dist/harness.d.ts +274 -0
  54. package/dist/harness.d.ts.map +1 -0
  55. package/dist/harness.js +38 -0
  56. package/dist/index.d.ts +101 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +83 -0
  59. package/dist/language-service.d.ts +145 -0
  60. package/dist/language-service.d.ts.map +1 -0
  61. package/dist/language-service.js +161 -0
  62. package/dist/local-knowledge-large-document-validation.d.ts +7 -0
  63. package/dist/local-knowledge-large-document-validation.d.ts.map +1 -0
  64. package/dist/local-knowledge-large-document-validation.js +161 -0
  65. package/dist/local-knowledge-large-document.d.ts +113 -0
  66. package/dist/local-knowledge-large-document.d.ts.map +1 -0
  67. package/dist/local-knowledge-large-document.js +142 -0
  68. package/dist/local-knowledge-paths.d.ts +3 -0
  69. package/dist/local-knowledge-paths.d.ts.map +1 -0
  70. package/dist/local-knowledge-paths.js +65 -0
  71. package/dist/local-knowledge-records.d.ts +190 -0
  72. package/dist/local-knowledge-records.d.ts.map +1 -0
  73. package/dist/local-knowledge-records.js +36 -0
  74. package/dist/local-knowledge-schema-validation.d.ts +19 -0
  75. package/dist/local-knowledge-schema-validation.d.ts.map +1 -0
  76. package/dist/local-knowledge-schema-validation.js +115 -0
  77. package/dist/local-knowledge-schema.d.ts +14 -0
  78. package/dist/local-knowledge-schema.d.ts.map +1 -0
  79. package/dist/local-knowledge-schema.js +715 -0
  80. package/dist/local-knowledge-validation.d.ts +20 -0
  81. package/dist/local-knowledge-validation.d.ts.map +1 -0
  82. package/dist/local-knowledge-validation.js +487 -0
  83. package/dist/local-knowledge.d.ts +158 -0
  84. package/dist/local-knowledge.d.ts.map +1 -0
  85. package/dist/local-knowledge.js +63 -0
  86. package/dist/memory-audit-events.d.ts +73 -0
  87. package/dist/memory-audit-events.d.ts.map +1 -0
  88. package/dist/memory-audit-events.js +44 -0
  89. package/dist/memory-audit-validation.d.ts +4 -0
  90. package/dist/memory-audit-validation.d.ts.map +1 -0
  91. package/dist/memory-audit-validation.js +151 -0
  92. package/dist/memory-barrel.d.ts +15 -0
  93. package/dist/memory-barrel.d.ts.map +1 -0
  94. package/dist/memory-barrel.js +20 -0
  95. package/dist/memory-internal.d.ts +26 -0
  96. package/dist/memory-internal.d.ts.map +1 -0
  97. package/dist/memory-internal.js +104 -0
  98. package/dist/memory-operations-validation.d.ts +12 -0
  99. package/dist/memory-operations-validation.d.ts.map +1 -0
  100. package/dist/memory-operations-validation.js +267 -0
  101. package/dist/memory-operations.d.ts +156 -0
  102. package/dist/memory-operations.d.ts.map +1 -0
  103. package/dist/memory-operations.js +29 -0
  104. package/dist/memory-record-validation.d.ts +10 -0
  105. package/dist/memory-record-validation.d.ts.map +1 -0
  106. package/dist/memory-record-validation.js +101 -0
  107. package/dist/memory-records.d.ts +66 -0
  108. package/dist/memory-records.d.ts.map +1 -0
  109. package/dist/memory-records.js +22 -0
  110. package/dist/memory-retrieval-validation.d.ts +6 -0
  111. package/dist/memory-retrieval-validation.d.ts.map +1 -0
  112. package/dist/memory-retrieval-validation.js +108 -0
  113. package/dist/memory-validation.d.ts +31 -0
  114. package/dist/memory-validation.d.ts.map +1 -0
  115. package/dist/memory-validation.js +318 -0
  116. package/dist/memory-workflow-port.d.ts +26 -0
  117. package/dist/memory-workflow-port.d.ts.map +1 -0
  118. package/dist/memory-workflow-port.js +13 -0
  119. package/dist/memory.d.ts +81 -0
  120. package/dist/memory.d.ts.map +1 -0
  121. package/dist/memory.js +104 -0
  122. package/dist/prompt-enhancer-analyzer.d.ts +7 -0
  123. package/dist/prompt-enhancer-analyzer.d.ts.map +1 -0
  124. package/dist/prompt-enhancer-analyzer.js +745 -0
  125. package/dist/prompt-enhancer-bff.d.ts +67 -0
  126. package/dist/prompt-enhancer-bff.d.ts.map +1 -0
  127. package/dist/prompt-enhancer-bff.js +156 -0
  128. package/dist/prompt-enhancer-critic.d.ts +46 -0
  129. package/dist/prompt-enhancer-critic.d.ts.map +1 -0
  130. package/dist/prompt-enhancer-critic.js +35 -0
  131. package/dist/prompt-enhancer-grounding.d.ts +19 -0
  132. package/dist/prompt-enhancer-grounding.d.ts.map +1 -0
  133. package/dist/prompt-enhancer-grounding.js +235 -0
  134. package/dist/prompt-enhancer-safety.d.ts +66 -0
  135. package/dist/prompt-enhancer-safety.d.ts.map +1 -0
  136. package/dist/prompt-enhancer-safety.js +446 -0
  137. package/dist/prompt-enhancer-validation.d.ts +28 -0
  138. package/dist/prompt-enhancer-validation.d.ts.map +1 -0
  139. package/dist/prompt-enhancer-validation.js +931 -0
  140. package/dist/prompt-enhancer.d.ts +184 -0
  141. package/dist/prompt-enhancer.d.ts.map +1 -0
  142. package/dist/prompt-enhancer.js +350 -0
  143. package/dist/qualityIntelligence/assertNever.d.ts +2 -0
  144. package/dist/qualityIntelligence/assertNever.d.ts.map +1 -0
  145. package/dist/qualityIntelligence/assertNever.js +7 -0
  146. package/dist/qualityIntelligence/auditSummary.d.ts +25 -0
  147. package/dist/qualityIntelligence/auditSummary.d.ts.map +1 -0
  148. package/dist/qualityIntelligence/auditSummary.js +7 -0
  149. package/dist/qualityIntelligence/bffWire.d.ts +356 -0
  150. package/dist/qualityIntelligence/bffWire.d.ts.map +1 -0
  151. package/dist/qualityIntelligence/bffWire.js +22 -0
  152. package/dist/qualityIntelligence/coverageMap.d.ts +21 -0
  153. package/dist/qualityIntelligence/coverageMap.d.ts.map +1 -0
  154. package/dist/qualityIntelligence/coverageMap.js +29 -0
  155. package/dist/qualityIntelligence/editableRevision.d.ts +21 -0
  156. package/dist/qualityIntelligence/editableRevision.d.ts.map +1 -0
  157. package/dist/qualityIntelligence/editableRevision.js +8 -0
  158. package/dist/qualityIntelligence/evidenceAtom.d.ts +35 -0
  159. package/dist/qualityIntelligence/evidenceAtom.d.ts.map +1 -0
  160. package/dist/qualityIntelligence/evidenceAtom.js +29 -0
  161. package/dist/qualityIntelligence/exportBundle.d.ts +28 -0
  162. package/dist/qualityIntelligence/exportBundle.d.ts.map +1 -0
  163. package/dist/qualityIntelligence/exportBundle.js +46 -0
  164. package/dist/qualityIntelligence/handoffEnvelope.d.ts +23 -0
  165. package/dist/qualityIntelligence/handoffEnvelope.d.ts.map +1 -0
  166. package/dist/qualityIntelligence/handoffEnvelope.js +8 -0
  167. package/dist/qualityIntelligence/ids.d.ts +58 -0
  168. package/dist/qualityIntelligence/ids.d.ts.map +1 -0
  169. package/dist/qualityIntelligence/ids.js +93 -0
  170. package/dist/qualityIntelligence/index.d.ts +29 -0
  171. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  172. package/dist/qualityIntelligence/index.js +20 -0
  173. package/dist/qualityIntelligence/reviewRecord.d.ts +19 -0
  174. package/dist/qualityIntelligence/reviewRecord.d.ts.map +1 -0
  175. package/dist/qualityIntelligence/reviewRecord.js +20 -0
  176. package/dist/qualityIntelligence/runPlanAndEvents.d.ts +84 -0
  177. package/dist/qualityIntelligence/runPlanAndEvents.d.ts.map +1 -0
  178. package/dist/qualityIntelligence/runPlanAndEvents.js +51 -0
  179. package/dist/qualityIntelligence/sourceEnvelope.d.ts +77 -0
  180. package/dist/qualityIntelligence/sourceEnvelope.d.ts.map +1 -0
  181. package/dist/qualityIntelligence/sourceEnvelope.js +118 -0
  182. package/dist/qualityIntelligence/testCaseCandidate.d.ts +21 -0
  183. package/dist/qualityIntelligence/testCaseCandidate.d.ts.map +1 -0
  184. package/dist/qualityIntelligence/testCaseCandidate.js +21 -0
  185. package/dist/qualityIntelligence/testQualityRubric.d.ts +17 -0
  186. package/dist/qualityIntelligence/testQualityRubric.d.ts.map +1 -0
  187. package/dist/qualityIntelligence/testQualityRubric.js +32 -0
  188. package/dist/qualityIntelligence/validationFinding.d.ts +48 -0
  189. package/dist/qualityIntelligence/validationFinding.d.ts.map +1 -0
  190. package/dist/qualityIntelligence/validationFinding.js +36 -0
  191. package/dist/relationships-validation.d.ts +13 -0
  192. package/dist/relationships-validation.d.ts.map +1 -0
  193. package/dist/relationships-validation.js +422 -0
  194. package/dist/relationships.d.ts +79 -0
  195. package/dist/relationships.d.ts.map +1 -0
  196. package/dist/relationships.js +307 -0
  197. package/dist/text-safety.d.ts +7 -0
  198. package/dist/text-safety.d.ts.map +1 -0
  199. package/dist/text-safety.js +58 -0
  200. package/dist/tools.d.ts +153 -0
  201. package/dist/tools.d.ts.map +1 -0
  202. package/dist/tools.js +118 -0
  203. package/dist/unit-test-events.d.ts +87 -0
  204. package/dist/unit-test-events.d.ts.map +1 -0
  205. package/dist/unit-test-events.js +14 -0
  206. package/dist/verification-summary.d.ts +38 -0
  207. package/dist/verification-summary.d.ts.map +1 -0
  208. package/dist/verification-summary.js +5 -0
  209. package/dist/verification.d.ts +64 -0
  210. package/dist/verification.d.ts.map +1 -0
  211. package/dist/verification.js +13 -0
  212. package/dist/workflow-descriptor.d.ts +21 -0
  213. package/dist/workflow-descriptor.d.ts.map +1 -0
  214. package/dist/workflow-descriptor.js +8 -0
  215. package/dist/workflow-handoff.d.ts +69 -0
  216. package/dist/workflow-handoff.d.ts.map +1 -0
  217. package/dist/workflow-handoff.js +381 -0
  218. package/dist/workspace-descriptors.d.ts +21 -0
  219. package/dist/workspace-descriptors.d.ts.map +1 -0
  220. package/dist/workspace-descriptors.js +180 -0
  221. package/dist/workspace-ui.d.ts +119 -0
  222. package/dist/workspace-ui.d.ts.map +1 -0
  223. package/dist/workspace-ui.js +105 -0
  224. package/dist/workspace.d.ts +104 -0
  225. package/dist/workspace.d.ts.map +1 -0
  226. package/dist/workspace.js +27 -0
  227. package/package.json +71 -0
@@ -0,0 +1,381 @@
1
+ // Public type contracts for the governed workflow-handoff surface (Epic #177, Issue #186).
2
+ // All string content here is already redacted upstream; nothing performs IO, crypto, clock
3
+ // reads, or randomness. Pure data + pure validators only. The schemaVersion discriminant
4
+ // follows the same evolution rule as CONNECTED_CONTEXT_SCHEMA_VERSION (ADR-0010 D2): a
5
+ // breaking change introduces a NEW literal member rather than mutating "1". Leaf-package
6
+ // rule (ADR-0019 direction 1) means no `@oscharko-dev/keiko-*` imports may appear in this
7
+ // module — including this package's own siblings; intra-package imports use relative paths.
8
+ import { isValidScopePath } from "./connected-context.js";
9
+ // ─── Schema version ───────────────────────────────────────────────────────────
10
+ export const WORKFLOW_HANDOFF_SCHEMA_VERSION = "1";
11
+ export const DEFAULT_PATCH_SCOPE_LIMITS = {
12
+ maxFileCount: 16,
13
+ maxPatchBytes: 65_536,
14
+ maxNewFiles: 4,
15
+ elapsedMsMax: 60_000,
16
+ };
17
+ export const EXPECTED_CHECKS = [
18
+ "verify",
19
+ "lint",
20
+ "typecheck",
21
+ "tests",
22
+ "manual",
23
+ ];
24
+ export const WORKFLOW_KINDS = [
25
+ "unit-test-generation",
26
+ "bug-investigation",
27
+ "verification",
28
+ ];
29
+ // ─── Internal helpers ─────────────────────────────────────────────────────────
30
+ const APPROVAL_TOKEN_RE = /^[0-9a-f]{64}$/;
31
+ const CONTEXT_PACK_ID_RE = /^(pl-[0-9a-f]{16}|p-[0-9a-f]{64})$/;
32
+ // The schema discriminant comparisons below are statically true at the type level (the
33
+ // constant equals the literal field type), so we widen to `string` before comparing. This
34
+ // keeps the validator honest against runtime inputs that bypass the type system — e.g.,
35
+ // objects materialized from JSON.parse or cross-version manifests. Same posture as
36
+ // connected-context.ts schemaMismatch.
37
+ function schemaMismatch(actual, expected) {
38
+ return actual !== expected;
39
+ }
40
+ function isFiniteNonNegativeInteger(value) {
41
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
42
+ }
43
+ function isNonEmptyTrimmed(value) {
44
+ return value.trim().length > 0;
45
+ }
46
+ function hasDuplicates(values) {
47
+ return new Set(values).size !== values.length;
48
+ }
49
+ function hasIntersection(a, b) {
50
+ if (a.length === 0 || b.length === 0) {
51
+ return false;
52
+ }
53
+ const set = new Set(a);
54
+ for (const value of b) {
55
+ if (set.has(value)) {
56
+ return true;
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ function pushIf(reasons, condition, reason) {
62
+ if (condition) {
63
+ reasons.push(reason);
64
+ }
65
+ }
66
+ function buildResult(reasons) {
67
+ return reasons.length === 0 ? { ok: true } : { ok: false, reasons };
68
+ }
69
+ function isRecord(value) {
70
+ return typeof value === "object" && value !== null && !Array.isArray(value);
71
+ }
72
+ function readArrayField(source, field, reasons) {
73
+ const value = source[field];
74
+ if (!Array.isArray(value)) {
75
+ reasons.push(`patchScope.${field} must be an array`);
76
+ return undefined;
77
+ }
78
+ return value;
79
+ }
80
+ function isExpectedCheck(value) {
81
+ return typeof value === "string" && EXPECTED_CHECKS.includes(value);
82
+ }
83
+ function isWorkflowKind(value) {
84
+ return typeof value === "string" && WORKFLOW_KINDS.includes(value);
85
+ }
86
+ // ─── Approval-token shape ─────────────────────────────────────────────────────
87
+ export function isApprovalTokenShape(token) {
88
+ return APPROVAL_TOKEN_RE.test(token);
89
+ }
90
+ // ─── PatchScope validation ────────────────────────────────────────────────────
91
+ function validateScopePathArray(values, field, reasons) {
92
+ const parsed = [];
93
+ for (const value of values) {
94
+ if (typeof value !== "string" || !isNonEmptyTrimmed(value)) {
95
+ reasons.push(`patchScope.${field} contains empty entry`);
96
+ return undefined;
97
+ }
98
+ if (!isValidScopePath(value, { mustBeRelative: true })) {
99
+ reasons.push(`patchScope.${field} contains invalid path`);
100
+ return undefined;
101
+ }
102
+ parsed.push(value);
103
+ }
104
+ return parsed;
105
+ }
106
+ function validatePatchScopeLimits(limits, reasons) {
107
+ if (!isRecord(limits)) {
108
+ reasons.push("patchScope.limits must be an object");
109
+ return;
110
+ }
111
+ pushIf(reasons, !isFiniteNonNegativeInteger(limits.maxFileCount), "patchScope.limits.maxFileCount invalid");
112
+ pushIf(reasons, !isFiniteNonNegativeInteger(limits.maxPatchBytes), "patchScope.limits.maxPatchBytes invalid");
113
+ pushIf(reasons, !isFiniteNonNegativeInteger(limits.maxNewFiles), "patchScope.limits.maxNewFiles invalid");
114
+ pushIf(reasons, !isFiniteNonNegativeInteger(limits.elapsedMsMax), "patchScope.limits.elapsedMsMax invalid");
115
+ }
116
+ function validateExpectedChecks(checks, reasons) {
117
+ if (checks.length === 0) {
118
+ reasons.push("patchScope.expectedChecks empty");
119
+ return;
120
+ }
121
+ for (const check of checks) {
122
+ if (!isExpectedCheck(check)) {
123
+ reasons.push("patchScope.expectedChecks contains invalid value");
124
+ return;
125
+ }
126
+ }
127
+ }
128
+ function validateEvidenceAtomIds(ids, reasons) {
129
+ if (ids.length === 0) {
130
+ reasons.push("patchScope.evidenceAtomIds empty");
131
+ return undefined;
132
+ }
133
+ const parsed = [];
134
+ for (const id of ids) {
135
+ if (typeof id !== "string" || !isNonEmptyTrimmed(id)) {
136
+ reasons.push("patchScope.evidenceAtomIds contains empty entry");
137
+ return undefined;
138
+ }
139
+ parsed.push(id);
140
+ }
141
+ if (hasDuplicates(parsed)) {
142
+ reasons.push("patchScope.evidenceAtomIds contains duplicates");
143
+ }
144
+ return parsed;
145
+ }
146
+ function validateUnknowns(unknowns, reasons) {
147
+ for (const entry of unknowns) {
148
+ if (typeof entry !== "string") {
149
+ reasons.push("patchScope.unknowns contains non-string entry");
150
+ return;
151
+ }
152
+ if (!isNonEmptyTrimmed(entry)) {
153
+ reasons.push("patchScope.unknowns contains empty entry");
154
+ return;
155
+ }
156
+ }
157
+ }
158
+ function readPatchScopeArrays(scope, reasons) {
159
+ return {
160
+ editablePaths: readArrayField(scope, "editablePaths", reasons),
161
+ readOnlyPaths: readArrayField(scope, "readOnlyPaths", reasons),
162
+ evidenceAtomIds: readArrayField(scope, "evidenceAtomIds", reasons),
163
+ expectedChecks: readArrayField(scope, "expectedChecks", reasons),
164
+ unknowns: readArrayField(scope, "unknowns", reasons),
165
+ };
166
+ }
167
+ function validatePatchScopePaths(arrays, reasons) {
168
+ const editablePaths = arrays.editablePaths
169
+ ? validateScopePathArray(arrays.editablePaths, "editablePaths", reasons)
170
+ : undefined;
171
+ const readOnlyPaths = arrays.readOnlyPaths
172
+ ? validateScopePathArray(arrays.readOnlyPaths, "readOnlyPaths", reasons)
173
+ : undefined;
174
+ if (editablePaths) {
175
+ pushIf(reasons, hasDuplicates(editablePaths), "patchScope.editablePaths contains duplicates");
176
+ }
177
+ if (readOnlyPaths) {
178
+ pushIf(reasons, hasDuplicates(readOnlyPaths), "patchScope.readOnlyPaths contains duplicates");
179
+ }
180
+ if (editablePaths && readOnlyPaths) {
181
+ pushIf(reasons, hasIntersection(editablePaths, readOnlyPaths), "patchScope.editablePaths overlaps readOnlyPaths");
182
+ }
183
+ }
184
+ function validatePatchScopeArrays(arrays, reasons) {
185
+ validatePatchScopePaths(arrays, reasons);
186
+ if (arrays.evidenceAtomIds) {
187
+ validateEvidenceAtomIds(arrays.evidenceAtomIds, reasons);
188
+ }
189
+ if (arrays.expectedChecks) {
190
+ validateExpectedChecks(arrays.expectedChecks, reasons);
191
+ }
192
+ if (arrays.unknowns) {
193
+ validateUnknowns(arrays.unknowns, reasons);
194
+ }
195
+ }
196
+ export function validatePatchScope(scope) {
197
+ const reasons = [];
198
+ if (!isRecord(scope)) {
199
+ return { ok: false, reasons: ["patchScope must be an object"] };
200
+ }
201
+ pushIf(reasons, schemaMismatch(scope.schemaVersion, WORKFLOW_HANDOFF_SCHEMA_VERSION), "patchScope.schemaVersion mismatch");
202
+ validatePatchScopeArrays(readPatchScopeArrays(scope, reasons), reasons);
203
+ validatePatchScopeLimits(scope.limits, reasons);
204
+ return buildResult(reasons);
205
+ }
206
+ // ─── WorkflowHandoffRequest validation ────────────────────────────────────────
207
+ export function validateWorkflowHandoffRequest(request) {
208
+ const reasons = [];
209
+ if (!isRecord(request)) {
210
+ return { ok: false, reasons: ["request must be an object"] };
211
+ }
212
+ pushIf(reasons, schemaMismatch(request.schemaVersion, WORKFLOW_HANDOFF_SCHEMA_VERSION), "request.schemaVersion mismatch");
213
+ if (typeof request.contextPackStableId !== "string" ||
214
+ !isNonEmptyTrimmed(request.contextPackStableId)) {
215
+ reasons.push("request.contextPackStableId empty");
216
+ }
217
+ else if (!CONTEXT_PACK_ID_RE.test(request.contextPackStableId)) {
218
+ reasons.push("request.contextPackStableId malformed");
219
+ }
220
+ pushIf(reasons, !isWorkflowKind(request.workflowKind), "request.workflowKind invalid");
221
+ pushIf(reasons, !isFiniteNonNegativeInteger(request.requestedAtMs), "request.requestedAtMs invalid");
222
+ pushIf(reasons, typeof request.userApprovalToken !== "string" ||
223
+ !isApprovalTokenShape(request.userApprovalToken), "request.userApprovalToken malformed");
224
+ const scopeResult = validatePatchScope(request.patchScope);
225
+ if (!scopeResult.ok) {
226
+ for (const reason of scopeResult.reasons) {
227
+ reasons.push(`request.${reason}`);
228
+ }
229
+ }
230
+ return buildResult(reasons);
231
+ }
232
+ // ─── checkPatchAgainstScope ───────────────────────────────────────────────────
233
+ function violationOutsideSet(path) {
234
+ return {
235
+ kind: "outside-editable-set",
236
+ path,
237
+ observed: undefined,
238
+ limit: undefined,
239
+ message: `path "${path}" is not in patchScope.editablePaths`,
240
+ };
241
+ }
242
+ function violationBound(kind, observed, limit, field) {
243
+ return {
244
+ kind,
245
+ path: undefined,
246
+ observed,
247
+ limit,
248
+ message: `${field}: observed ${observed.toString()} exceeds limit ${limit.toString()}`,
249
+ };
250
+ }
251
+ function violationNoChecks() {
252
+ return {
253
+ kind: "no-expected-checks",
254
+ path: undefined,
255
+ observed: undefined,
256
+ limit: undefined,
257
+ message: "patchScope.expectedChecks is empty",
258
+ };
259
+ }
260
+ function violationInvalidScope(reason) {
261
+ return {
262
+ kind: "invalid-patch-scope",
263
+ path: undefined,
264
+ observed: undefined,
265
+ limit: undefined,
266
+ message: `patchScope invalid: ${reason}`,
267
+ };
268
+ }
269
+ function violationInvalidEntry(path, reason) {
270
+ return {
271
+ kind: "invalid-patch-entry",
272
+ path,
273
+ observed: undefined,
274
+ limit: undefined,
275
+ message: path === undefined ? `patch entry invalid: ${reason}` : `path "${path}" invalid: ${reason}`,
276
+ };
277
+ }
278
+ function readProposedPath(entry, violations) {
279
+ const path = typeof entry.path === "string" ? entry.path : undefined;
280
+ const valid = path !== undefined &&
281
+ isNonEmptyTrimmed(path) &&
282
+ isValidScopePath(path, { mustBeRelative: true });
283
+ if (!valid) {
284
+ violations.push(violationInvalidEntry(path, "path must be a valid relative scope path"));
285
+ return undefined;
286
+ }
287
+ return path;
288
+ }
289
+ function readProposedPatchBytes(entry, path, violations) {
290
+ const patchBytes = entry.patchBytes;
291
+ if (typeof patchBytes !== "number" || !Number.isFinite(patchBytes) || patchBytes < 0) {
292
+ violations.push(violationInvalidEntry(path, "patchBytes must be a finite non-negative number"));
293
+ return undefined;
294
+ }
295
+ return patchBytes;
296
+ }
297
+ function readProposedNewFile(entry, path, violations) {
298
+ const newFile = entry.newFile;
299
+ if (typeof newFile !== "boolean") {
300
+ violations.push(violationInvalidEntry(path, "newFile must be a boolean"));
301
+ return undefined;
302
+ }
303
+ return newFile;
304
+ }
305
+ function normalizeProposedEntry(entry, violations) {
306
+ if (!isRecord(entry)) {
307
+ violations.push(violationInvalidEntry(undefined, "entry must be an object"));
308
+ return undefined;
309
+ }
310
+ const path = readProposedPath(entry, violations);
311
+ const patchBytes = readProposedPatchBytes(entry, path, violations);
312
+ const newFile = readProposedNewFile(entry, path, violations);
313
+ if (path === undefined || patchBytes === undefined || newFile === undefined) {
314
+ return undefined;
315
+ }
316
+ return { path, patchBytes, newFile };
317
+ }
318
+ function collectProposedEntries(proposed, violations) {
319
+ const valid = [];
320
+ for (const entry of proposed) {
321
+ const normalized = normalizeProposedEntry(entry, violations);
322
+ if (normalized) {
323
+ valid.push(normalized);
324
+ }
325
+ }
326
+ return valid;
327
+ }
328
+ function collectOutsideSet(scope, proposed, violations) {
329
+ const editable = new Set(scope.editablePaths);
330
+ for (const entry of proposed) {
331
+ if (!editable.has(entry.path)) {
332
+ violations.push(violationOutsideSet(entry.path));
333
+ }
334
+ }
335
+ }
336
+ function collectAggregateBounds(scope, proposed, violations) {
337
+ if (proposed.length > scope.limits.maxFileCount) {
338
+ violations.push(violationBound("exceeds-max-file-count", proposed.length, scope.limits.maxFileCount, "fileCount"));
339
+ }
340
+ let totalBytes = 0;
341
+ let newFiles = 0;
342
+ for (const entry of proposed) {
343
+ totalBytes += entry.patchBytes;
344
+ if (entry.newFile) {
345
+ newFiles += 1;
346
+ }
347
+ }
348
+ if (totalBytes > scope.limits.maxPatchBytes) {
349
+ violations.push(violationBound("exceeds-max-patch-bytes", totalBytes, scope.limits.maxPatchBytes, "patchBytes"));
350
+ }
351
+ if (newFiles > scope.limits.maxNewFiles) {
352
+ violations.push(violationBound("exceeds-max-new-files", newFiles, scope.limits.maxNewFiles, "newFiles"));
353
+ }
354
+ }
355
+ export function checkPatchAgainstScope(scope, proposed) {
356
+ const violations = [];
357
+ const scopeResult = validatePatchScope(scope);
358
+ let hasBlockingScopeViolation = false;
359
+ if (!scopeResult.ok) {
360
+ for (const reason of scopeResult.reasons) {
361
+ if (reason === "patchScope.expectedChecks empty") {
362
+ violations.push(violationNoChecks());
363
+ }
364
+ else {
365
+ hasBlockingScopeViolation = true;
366
+ violations.push(violationInvalidScope(reason));
367
+ }
368
+ }
369
+ }
370
+ if (hasBlockingScopeViolation) {
371
+ return { ok: false, violations };
372
+ }
373
+ if (!Array.isArray(proposed)) {
374
+ violations.push(violationInvalidEntry(undefined, "proposed patch list must be an array"));
375
+ return { ok: false, violations };
376
+ }
377
+ const validProposed = collectProposedEntries(proposed, violations);
378
+ collectOutsideSet(scope, validProposed, violations);
379
+ collectAggregateBounds(scope, validProposed, violations);
380
+ return violations.length === 0 ? { ok: true } : { ok: false, violations };
381
+ }
@@ -0,0 +1,21 @@
1
+ export type WorkspaceObjectLifecycleState = "idle" | "live" | "error" | "empty" | "focused" | "none" | "connecting" | "connected" | "degraded" | "disconnected" | "draft" | "streaming" | "final" | "archived" | "proposed" | "running" | "blocked" | "needs-review" | "verified" | "cancelled" | "applied" | "reverted" | "passed" | "failed" | "viewing" | "editing" | "unsaved" | "saved" | "unread" | "read" | "dismissed" | "paired" | "unpaired" | "searching" | "results" | "installed" | "disabled" | "enabled";
2
+ export type WorkspaceObjectTrustBoundary = "ui" | "fs" | "tool" | "model" | "evidence" | "memory" | "network";
3
+ export type WorkspaceObjectAuthority = "ui-only" | "user" | "user-confirm" | "read-only";
4
+ export type WorkspaceObjectPersistence = "transient" | "durable.ui" | "durable.config" | "evidence-reference" | "fs-reference" | "memory-reference";
5
+ export interface WorkspaceDescriptorMeta {
6
+ readonly lifecycle: readonly WorkspaceObjectLifecycleState[];
7
+ readonly trustBoundary: readonly WorkspaceObjectTrustBoundary[];
8
+ readonly authority: WorkspaceObjectAuthority;
9
+ readonly persistence: WorkspaceObjectPersistence;
10
+ }
11
+ export declare const WORKSPACE_LIFECYCLE_STATES: readonly WorkspaceObjectLifecycleState[];
12
+ export declare const WORKSPACE_TRUST_BOUNDARIES: readonly WorkspaceObjectTrustBoundary[];
13
+ export declare const WORKSPACE_AUTHORITY_REQUIREMENTS: readonly WorkspaceObjectAuthority[];
14
+ export declare const WORKSPACE_PERSISTENCE_EXPECTATIONS: readonly WorkspaceObjectPersistence[];
15
+ export interface WorkspaceDescriptorValidationError {
16
+ readonly objectType: string;
17
+ readonly field: "lifecycle" | "trustBoundary" | "authority" | "persistence" | "consistency";
18
+ readonly message: string;
19
+ }
20
+ export declare function validateWorkspaceDescriptorMeta(objectType: string, meta: WorkspaceDescriptorMeta): readonly WorkspaceDescriptorValidationError[];
21
+ //# sourceMappingURL=workspace-descriptors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-descriptors.d.ts","sourceRoot":"","sources":["../src/workspace-descriptors.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,6BAA6B,GAErC,MAAM,GACN,MAAM,GACN,OAAO,GACP,OAAO,GACP,SAAS,GAET,MAAM,GACN,YAAY,GACZ,WAAW,GACX,UAAU,GACV,cAAc,GAEd,OAAO,GACP,WAAW,GACX,OAAO,GACP,UAAU,GAEV,UAAU,GACV,SAAS,GACT,SAAS,GACT,cAAc,GACd,UAAU,GACV,WAAW,GAEX,SAAS,GACT,UAAU,GAEV,QAAQ,GACR,QAAQ,GAER,SAAS,GACT,SAAS,GACT,SAAS,GACT,OAAO,GAEP,QAAQ,GACR,MAAM,GACN,WAAW,GAEX,QAAQ,GACR,UAAU,GAEV,WAAW,GACX,SAAS,GAET,WAAW,GACX,UAAU,GACV,SAAS,CAAC;AAId,MAAM,MAAM,4BAA4B,GACpC,IAAI,GACJ,IAAI,GACJ,MAAM,GACN,OAAO,GACP,UAAU,GACV,QAAQ,GACR,SAAS,CAAC;AAId,MAAM,MAAM,wBAAwB,GAChC,SAAS,GACT,MAAM,GACN,cAAc,GACd,WAAW,CAAC;AAIhB,MAAM,MAAM,0BAA0B,GAClC,WAAW,GACX,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,cAAc,GACd,kBAAkB,CAAC;AAIvB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC7D,QAAQ,CAAC,aAAa,EAAE,SAAS,4BAA4B,EAAE,CAAC;IAChE,QAAQ,CAAC,SAAS,EAAE,wBAAwB,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CAClD;AAID,eAAO,MAAM,0BAA0B,EAAE,SAAS,6BAA6B,EAuC9E,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,SAAS,4BAA4B,EAQ7E,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,SAAS,wBAAwB,EAK/E,CAAC;AAEF,eAAO,MAAM,kCAAkC,EAAE,SAAS,0BAA0B,EAOnF,CAAC;AAIF,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC;IAC5F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AA4BD,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,uBAAuB,GAC5B,SAAS,kCAAkC,EAAE,CAsF/C"}
@@ -0,0 +1,180 @@
1
+ // Epic #518 / Issue #528 — Workspace object descriptor metadata contracts.
2
+ //
3
+ // ADR-0029 extends the workspace object registry with four declarative
4
+ // metadata fields per object type: lifecycle, trustBoundary, authority,
5
+ // persistence. The enums below are the closed sets the registration-time
6
+ // validator (in `@oscharko-dev/keiko-ui`) checks against.
7
+ //
8
+ // These types are kept side-table-friendly so the existing
9
+ // WindowsRegistry.ts in keiko-ui does not need invasive edits. The UI
10
+ // package exposes a parallel `WIN_META: Record<WindowType, WorkspaceDescriptorMeta>`
11
+ // table consumed by the validator and any object-aware UI surface.
12
+ // ─── Closed-set membership (for the validator) ────────────────────────────
13
+ export const WORKSPACE_LIFECYCLE_STATES = [
14
+ "idle",
15
+ "live",
16
+ "error",
17
+ "empty",
18
+ "focused",
19
+ "none",
20
+ "connecting",
21
+ "connected",
22
+ "degraded",
23
+ "disconnected",
24
+ "draft",
25
+ "streaming",
26
+ "final",
27
+ "archived",
28
+ "proposed",
29
+ "running",
30
+ "blocked",
31
+ "needs-review",
32
+ "verified",
33
+ "cancelled",
34
+ "applied",
35
+ "reverted",
36
+ "passed",
37
+ "failed",
38
+ "viewing",
39
+ "editing",
40
+ "unsaved",
41
+ "saved",
42
+ "unread",
43
+ "read",
44
+ "dismissed",
45
+ "paired",
46
+ "unpaired",
47
+ "searching",
48
+ "results",
49
+ "installed",
50
+ "disabled",
51
+ "enabled",
52
+ ];
53
+ export const WORKSPACE_TRUST_BOUNDARIES = [
54
+ "ui",
55
+ "fs",
56
+ "tool",
57
+ "model",
58
+ "evidence",
59
+ "memory",
60
+ "network",
61
+ ];
62
+ export const WORKSPACE_AUTHORITY_REQUIREMENTS = [
63
+ "ui-only",
64
+ "user",
65
+ "user-confirm",
66
+ "read-only",
67
+ ];
68
+ export const WORKSPACE_PERSISTENCE_EXPECTATIONS = [
69
+ "transient",
70
+ "durable.ui",
71
+ "durable.config",
72
+ "evidence-reference",
73
+ "fs-reference",
74
+ "memory-reference",
75
+ ];
76
+ // ─── Pure validator ──────────────────────────────────────────────────────
77
+ //
78
+ // Validates a single descriptor meta record against the closed sets and
79
+ // the ADR-0029 / ADR-0030 consistency rules:
80
+ //
81
+ // R1 unknown enum: every member of lifecycle / trustBoundary, and the
82
+ // authority / persistence value, must belong to its closed set.
83
+ // R2 trust-vs-authority: authority="ui-only" requires trustBoundary be
84
+ // ["ui"] only; any other trust class implies the user originates the
85
+ // action.
86
+ // R3 evidence-persistence: persistence="evidence-reference" requires the
87
+ // trustBoundary set to include "evidence".
88
+ // R4 fs-persistence: persistence="fs-reference" requires the
89
+ // trustBoundary set to include "fs".
90
+ // R5 memory-persistence: persistence="memory-reference" requires the
91
+ // trustBoundary set to include "memory".
92
+ // R6 durable.ui-readonly: persistence="durable.ui" with authority="read-only"
93
+ // is allowed but trustBoundary must include "ui" (the durable record
94
+ // itself is a UI surface).
95
+ //
96
+ // The validator is pure; the failing entries are returned as a list so the
97
+ // caller decides how to surface them (dev: throw, prod: assert in test).
98
+ // The rules above are individually simple; keeping them in one function
99
+ // keeps the entire contract reviewable in one place.
100
+ // eslint-disable-next-line complexity, max-lines-per-function
101
+ export function validateWorkspaceDescriptorMeta(objectType, meta) {
102
+ const errors = [];
103
+ // R1 — closed-set membership
104
+ for (const state of meta.lifecycle) {
105
+ if (!WORKSPACE_LIFECYCLE_STATES.includes(state)) {
106
+ errors.push({
107
+ objectType,
108
+ field: "lifecycle",
109
+ message: `unknown lifecycle state '${state}'`,
110
+ });
111
+ }
112
+ }
113
+ for (const tb of meta.trustBoundary) {
114
+ if (!WORKSPACE_TRUST_BOUNDARIES.includes(tb)) {
115
+ errors.push({
116
+ objectType,
117
+ field: "trustBoundary",
118
+ message: `unknown trust boundary '${tb}'`,
119
+ });
120
+ }
121
+ }
122
+ if (!WORKSPACE_AUTHORITY_REQUIREMENTS.includes(meta.authority)) {
123
+ errors.push({
124
+ objectType,
125
+ field: "authority",
126
+ message: `unknown authority requirement '${meta.authority}'`,
127
+ });
128
+ }
129
+ if (!WORKSPACE_PERSISTENCE_EXPECTATIONS.includes(meta.persistence)) {
130
+ errors.push({
131
+ objectType,
132
+ field: "persistence",
133
+ message: `unknown persistence expectation '${meta.persistence}'`,
134
+ });
135
+ }
136
+ // R2 — ui-only authority must not cross any boundary other than ui
137
+ if (meta.authority === "ui-only") {
138
+ const nonUi = meta.trustBoundary.filter((tb) => tb !== "ui");
139
+ if (nonUi.length > 0) {
140
+ errors.push({
141
+ objectType,
142
+ field: "consistency",
143
+ message: `authority 'ui-only' is inconsistent with trust boundary [${nonUi.join(", ")}]`,
144
+ });
145
+ }
146
+ }
147
+ // R3 — evidence-reference requires the evidence trust boundary
148
+ if (meta.persistence === "evidence-reference" && !meta.trustBoundary.includes("evidence")) {
149
+ errors.push({
150
+ objectType,
151
+ field: "consistency",
152
+ message: `persistence 'evidence-reference' requires trustBoundary to include 'evidence'`,
153
+ });
154
+ }
155
+ // R4 — fs-reference requires the fs trust boundary
156
+ if (meta.persistence === "fs-reference" && !meta.trustBoundary.includes("fs")) {
157
+ errors.push({
158
+ objectType,
159
+ field: "consistency",
160
+ message: `persistence 'fs-reference' requires trustBoundary to include 'fs'`,
161
+ });
162
+ }
163
+ // R5 — memory-reference requires the memory trust boundary
164
+ if (meta.persistence === "memory-reference" && !meta.trustBoundary.includes("memory")) {
165
+ errors.push({
166
+ objectType,
167
+ field: "consistency",
168
+ message: `persistence 'memory-reference' requires trustBoundary to include 'memory'`,
169
+ });
170
+ }
171
+ // R6 — durable.ui requires the ui trust boundary
172
+ if (meta.persistence === "durable.ui" && !meta.trustBoundary.includes("ui")) {
173
+ errors.push({
174
+ objectType,
175
+ field: "consistency",
176
+ message: `persistence 'durable.ui' requires trustBoundary to include 'ui'`,
177
+ });
178
+ }
179
+ return errors;
180
+ }