opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +318 -2
- package/index.js +1836 -0
- package/package.json +70 -4
- package/skills/codebase-design/SKILL.md +114 -0
- package/skills/design-contract/SKILL.md +27 -0
- package/skills/design-critique/SKILL.md +19 -0
- package/skills/design-system/SKILL.md +17 -0
- package/skills/design-system/references/image-analysis.md +14 -0
- package/skills/design-system/references/tokens.md +32 -0
- package/skills/design-system/references/ui-rules.md +14 -0
- package/skills/diagnosing-bugs/SKILL.md +138 -0
- package/skills/frontend-design/SKILL.md +36 -0
- package/skills/handoff/SKILL.md +16 -0
- package/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/skills/linter-negative/SKILL.md +74 -0
- package/skills/no-slop-ui/SKILL.md +26 -0
- package/skills/prototype/SKILL.md +26 -0
- package/skills/registry.json +44087 -0
- package/skills/tastemaker/SKILL.md +8 -0
- package/skills/tastemaker/references/tastemaker.md +14 -0
- package/skills/tdd/SKILL.md +38 -0
- package/skills/visual-review/SKILL.md +103 -0
- package/skills/visual-review/references/review-gates.md +42 -0
- package/skills/visual-review/references/surface-archetypes.md +37 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/writing-for-agents/SKILL.md +81 -0
- package/src/cognition/cognition-store.js +251 -0
- package/src/cognition/cognitive-state.js +126 -0
- package/src/cognition/hypothesis-manager.js +113 -0
- package/src/cognition/observation-engine.js +112 -0
- package/src/cognition/observation-provenance.js +268 -0
- package/src/context/active-context-boundary.js +68 -0
- package/src/context/assembly.js +494 -0
- package/src/context/assessment-rules.js +55 -0
- package/src/context/capsule-staleness.js +267 -0
- package/src/context/capsules.js +2 -0
- package/src/context/compare-array.js +98 -0
- package/src/context/compare-object.js +89 -0
- package/src/context/compare-values.js +42 -0
- package/src/context/context-assembly-contract.js +71 -0
- package/src/context/context-budget-manager.js +232 -0
- package/src/context/context-builder.js +49 -0
- package/src/context/context-cache.js +55 -0
- package/src/context/context-capture.js +243 -0
- package/src/context/context-classification.js +199 -0
- package/src/context/context-compaction.js +179 -0
- package/src/context/context-decision-audit.js +267 -0
- package/src/context/context-demotion.js +173 -0
- package/src/context/context-dictionary.js +106 -0
- package/src/context/context-drift-detection.js +225 -0
- package/src/context/context-evaluation.js +374 -0
- package/src/context/context-event-ingress.js +100 -0
- package/src/context/context-event-normalization.js +224 -0
- package/src/context/context-feedback-loop.js +216 -0
- package/src/context/context-freshness.js +21 -0
- package/src/context/context-graph-builder.js +71 -0
- package/src/context/context-graph.js +451 -0
- package/src/context/context-history-relevance.js +321 -0
- package/src/context/context-interception.js +145 -0
- package/src/context/context-invalidation-events.js +154 -0
- package/src/context/context-lifecycle.js +170 -0
- package/src/context/context-loop-prevention.js +347 -0
- package/src/context/context-manager.js +340 -0
- package/src/context/context-memory-layers.js +265 -0
- package/src/context/context-minimality.js +166 -0
- package/src/context/context-normalization.js +130 -0
- package/src/context/context-optimization-metrics.js +434 -0
- package/src/context/context-output-policy.js +135 -0
- package/src/context/context-pack-authority.js +157 -0
- package/src/context/context-promotion.js +172 -0
- package/src/context/context-query-contract.js +101 -0
- package/src/context/context-ranking.js +164 -0
- package/src/context/context-rate-distortion.js +261 -0
- package/src/context/context-recovery.js +209 -0
- package/src/context/context-retrieval-integration.js +60 -0
- package/src/context/context-retrieval.js +93 -0
- package/src/context/context-router.js +457 -0
- package/src/context/context-routing-evaluation.js +331 -0
- package/src/context/context-routing-observability.js +296 -0
- package/src/context/context-routing-shadow.js +237 -0
- package/src/context/context-scope.js +181 -0
- package/src/context/context-snapshot.js +226 -0
- package/src/context/context-source-registry.js +95 -0
- package/src/context/context-sufficiency-gate.js +120 -0
- package/src/context/context-sufficiency-oracle.js +167 -0
- package/src/context/context-telemetry.js +176 -0
- package/src/context/context.js +633 -0
- package/src/context/dependency-closure.js +246 -0
- package/src/context/page-fault-tracker.js +275 -0
- package/src/context/prompt-engine.js +74 -0
- package/src/context/runtime-context-graph.js +260 -0
- package/src/context/selection.js +2 -0
- package/src/context/session.js +2 -0
- package/src/context/sufficiency-contract.js +300 -0
- package/src/context/tokenizer.js +17 -0
- package/src/context-benchmark-router.mjs +125 -0
- package/src/evidence/evidence-engine.js +161 -0
- package/src/evidence/evidence-factory.js +3 -0
- package/src/evidence/evidence-freshness.js +463 -0
- package/src/evidence/evidence-gap.js +76 -0
- package/src/evidence/evidence-lineage.js +525 -0
- package/src/evidence/evidence.js +136 -0
- package/src/evidence/freshness-validation.js +187 -0
- package/src/evidence/lineage.js +4 -0
- package/src/execution/action-evaluation.js +179 -0
- package/src/execution/circuit-breaker.js +70 -0
- package/src/execution/claim-interception.js +63 -0
- package/src/execution/execution-engine.js +258 -0
- package/src/execution/execution-intent.js +69 -0
- package/src/execution/execution-state.js +89 -0
- package/src/execution/formatting.js +60 -0
- package/src/execution/l1-sanitize.js +16 -0
- package/src/execution/l2-resource-guards.js +19 -0
- package/src/execution/l3-invariant-checker.js +5 -0
- package/src/execution/l4-containment.js +6 -0
- package/src/execution/runtime-guards.js +106 -0
- package/src/execution/strategy-identity.js +162 -0
- package/src/execution/tool-interceptor.js +7 -0
- package/src/execution/tool-result-classifier.js +63 -0
- package/src/integration/canary-deploy.js +143 -0
- package/src/integration/collector.js +59 -0
- package/src/integration/error-recovery.js +6 -0
- package/src/integration/event-bus.js +66 -0
- package/src/integration/health-checker.js +57 -0
- package/src/integration/logger.js +39 -0
- package/src/integration/message-handler.js +611 -0
- package/src/integration/opencode-db.js +153 -0
- package/src/integration/orchestration.js +419 -0
- package/src/integration/part-provenance.js +65 -0
- package/src/integration/rate-limiter.js +55 -0
- package/src/integration/release-gate.js +336 -0
- package/src/integration/rollback-manager.js +164 -0
- package/src/integration/router-adapter.js +269 -0
- package/src/integration/telemetry.js +45 -0
- package/src/integration/update-checker.js +10 -0
- package/src/persistence/event-sourcing.js +75 -0
- package/src/persistence/memory.js +501 -0
- package/src/persistence/migration-runner.js +16 -0
- package/src/persistence/persistence-manager.js +39 -0
- package/src/persistence/transaction-log.js +157 -0
- package/src/policy/action-capabilities.js +116 -0
- package/src/policy/action-policy.js +4 -0
- package/src/policy/assessment-engine.js +104 -0
- package/src/policy/assumption-tracking.js +66 -0
- package/src/policy/budget-enforcer.js +7 -0
- package/src/policy/config-validator.js +102 -0
- package/src/policy/execution-assessment.js +142 -0
- package/src/policy/failure-classification.js +5 -0
- package/src/policy/feature-flags.js +65 -0
- package/src/policy/governance-enforcement.js +118 -0
- package/src/policy/locale.js +15 -0
- package/src/policy/multi-signer.js +151 -0
- package/src/policy/policy-state-machine.js +97 -0
- package/src/policy/ponytail-constraint.js +38 -0
- package/src/policy/project-inspector.js +93 -0
- package/src/policy/query-builder.js +14 -0
- package/src/policy/request-classifier.js +79 -0
- package/src/policy/risk-engine.js +266 -0
- package/src/policy/scope-enforcement.js +140 -0
- package/src/policy/secret-scanner.js +68 -0
- package/src/policy/skill-routing.js +104 -0
- package/src/policy/strategy-capabilities.js +211 -0
- package/src/policy/strategy.js +6 -0
- package/src/policy/uncertainty.js +117 -0
- package/src/shared/container.js +44 -0
- package/src/shared/decision-types.js +24 -0
- package/src/shared/simple-test.js +160 -0
- package/src/shared/wam-events.js +52 -0
- package/src/skills/engine.js +1705 -0
- package/src/skills/skill-registry.js +19 -0
- package/src/skills/skill-routing.js +340 -0
- package/src/state/lifecycle-manager.js +113 -0
- package/src/state/operational-state.js +198 -0
- package/src/state/projections.js +47 -0
- package/src/state/replay-engine.js +5 -0
- package/src/state/requirement-ownership.js +20 -0
- package/src/state/requirement-state.js +152 -0
- package/src/state/run-event-identity.js +172 -0
- package/src/state/state-engine.js +19 -0
- package/src/state/state-machine.js +119 -0
- package/src/state/state-schema.js +318 -0
- package/src/state/state-store.js +335 -0
- package/src/state/state-transition.js +84 -0
- package/src/state/task-dependencies.js +287 -0
- package/src/state/task-execution.js +217 -0
- package/src/state/task-lifecycle.js +59 -0
- package/src/state/task-runs.js +361 -0
- package/src/state/task-state-file.js +86 -0
- package/src/state/task-state.js +41 -0
- package/src/state/task-status.js +54 -0
- package/src/state/task-store.js +48 -0
- package/src/state/wam-state.js +148 -0
- package/src/verification/completion-gate-simple.js +8 -0
- package/src/verification/completion-gate.js +183 -0
- package/src/verification/contract.js +9 -0
- package/src/verification/contradiction-resolution.js +255 -0
- package/src/verification/diagnostic-engine.js +659 -0
- package/src/verification/false-completion-prevention.js +100 -0
- package/src/verification/hook-validator.js +6 -0
- package/src/verification/requirements.js +6 -0
- package/src/verification/verification-context.js +162 -0
- package/src/verification/verification-lifecycle.js +66 -0
- package/src/verification/verification-model.js +89 -0
- package/src/verification/verification-persistence.js +129 -0
- package/src/verification/verification-policy.js +127 -0
- package/src/verification/verification.js +415 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release Gate
|
|
3
|
+
*
|
|
4
|
+
* Aggregates the guarantees from all prior phases into a single green/red
|
|
5
|
+
* signal before any production rollout. Seven sub-gates:
|
|
6
|
+
*
|
|
7
|
+
* 1. assessmentContract — every task has a valid, non-stale assessment
|
|
8
|
+
* 2. completionIntegrity — every task has a VERIFIED completion report
|
|
9
|
+
* 3. executionDecoupling — no unpredicted file changes; no out-of-scope
|
|
10
|
+
* 4. durableState — durable state is consistent and recoverable
|
|
11
|
+
* 5. contextProduction — canonical context artifacts are fresh/valid
|
|
12
|
+
* 6. runtimeHardening — no unresolved BLOCKED/ESCALATE events
|
|
13
|
+
* 7. observability — release manifest exposes required fields
|
|
14
|
+
*
|
|
15
|
+
* API:
|
|
16
|
+
* evaluateRelease({ tasks, manifest, hardeningLog, durableState, context })
|
|
17
|
+
* -> {
|
|
18
|
+
* allowed: boolean,
|
|
19
|
+
* reason: string,
|
|
20
|
+
* gates: Array<{ id, allowed, reason, blockers }>,
|
|
21
|
+
* summary: { total, passed, failed }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* gateSubGate(name, payload) -> { allowed, reason, blockers }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const SUB_GATES = [
|
|
28
|
+
"assessmentContract",
|
|
29
|
+
"completionIntegrity",
|
|
30
|
+
"executionDecoupling",
|
|
31
|
+
"durableState",
|
|
32
|
+
"contextProduction",
|
|
33
|
+
"runtimeHardening",
|
|
34
|
+
"observability",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/* ---------- helpers ---------- */
|
|
38
|
+
|
|
39
|
+
function safe(fn, fallback) {
|
|
40
|
+
try {
|
|
41
|
+
const r = fn();
|
|
42
|
+
return Array.isArray(r) ? r : r === undefined ? fallback : r;
|
|
43
|
+
} catch {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasField(obj, field) {
|
|
49
|
+
return obj && typeof obj === "object" && field in obj && obj[field] !== undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ---------- gate: assessmentContract ---------- */
|
|
53
|
+
|
|
54
|
+
function gateAssessmentContract(tasks) {
|
|
55
|
+
const blockers = [];
|
|
56
|
+
if (!Array.isArray(tasks)) {
|
|
57
|
+
return { allowed: false, reason: "tasks array missing", blockers: [] };
|
|
58
|
+
}
|
|
59
|
+
for (const t of tasks) {
|
|
60
|
+
if (!t || typeof t !== "object") {
|
|
61
|
+
blockers.push({ taskId: null, reason: "non-object task entry" });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!hasField(t, "assessment") || !t.assessment) {
|
|
65
|
+
blockers.push({ taskId: t.id ?? null, reason: "missing assessment" });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const a = t.assessment;
|
|
69
|
+
if (a.stale === true) {
|
|
70
|
+
blockers.push({ taskId: t.id ?? null, reason: "assessment marked stale" });
|
|
71
|
+
}
|
|
72
|
+
if (typeof a.valid === "boolean" && a.valid === false) {
|
|
73
|
+
blockers.push({ taskId: t.id ?? null, reason: "assessment explicitly invalid" });
|
|
74
|
+
}
|
|
75
|
+
if (!hasField(a, "objective") || !a.objective) {
|
|
76
|
+
blockers.push({ taskId: t.id ?? null, reason: "assessment missing objective" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
allowed: blockers.length === 0,
|
|
81
|
+
reason: blockers.length === 0 ? "all assessments valid" : "assessment defects",
|
|
82
|
+
blockers,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ---------- gate: completionIntegrity ---------- */
|
|
87
|
+
|
|
88
|
+
function gateCompletionIntegrity(tasks) {
|
|
89
|
+
const blockers = [];
|
|
90
|
+
if (!Array.isArray(tasks)) {
|
|
91
|
+
return { allowed: false, reason: "tasks array missing", blockers: [] };
|
|
92
|
+
}
|
|
93
|
+
for (const t of tasks) {
|
|
94
|
+
if (!t || typeof t !== "object") {
|
|
95
|
+
blockers.push({ taskId: null, reason: "non-object task entry" });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const c = t.completion;
|
|
99
|
+
if (!c) {
|
|
100
|
+
blockers.push({ taskId: t.id ?? null, reason: "missing completion report" });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (c.status !== "VERIFIED") {
|
|
104
|
+
blockers.push({
|
|
105
|
+
taskId: t.id ?? null,
|
|
106
|
+
reason: `completion status=${c.status ?? "undefined"} (expected VERIFIED)`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
allowed: blockers.length === 0,
|
|
112
|
+
reason: blockers.length === 0 ? "all completions VERIFIED" : "completion defects",
|
|
113
|
+
blockers,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ---------- gate: executionDecoupling ---------- */
|
|
118
|
+
|
|
119
|
+
function gateExecutionDecoupling(tasks) {
|
|
120
|
+
const blockers = [];
|
|
121
|
+
if (!Array.isArray(tasks)) {
|
|
122
|
+
return { allowed: false, reason: "tasks array missing", blockers: [] };
|
|
123
|
+
}
|
|
124
|
+
for (const t of tasks) {
|
|
125
|
+
if (!t || typeof t !== "object") continue;
|
|
126
|
+
const predicted = Array.isArray(t.predictedFiles) ? t.predictedFiles : [];
|
|
127
|
+
const actual = Array.isArray(t.actualFiles) ? t.actualFiles : [];
|
|
128
|
+
const outOfScope = actual.filter((f) => !predicted.includes(f));
|
|
129
|
+
for (const f of outOfScope) {
|
|
130
|
+
blockers.push({
|
|
131
|
+
taskId: t.id ?? null,
|
|
132
|
+
reason: `out-of-scope mutation: ${f}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(t.scope) && t.scope.length > 0 && Array.isArray(t.violations)) {
|
|
136
|
+
for (const v of t.violations) {
|
|
137
|
+
blockers.push({ taskId: t.id ?? null, reason: v });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
allowed: blockers.length === 0,
|
|
143
|
+
reason: blockers.length === 0 ? "no out-of-scope mutations" : "scope drift detected",
|
|
144
|
+
blockers,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ---------- gate: durableState ---------- */
|
|
149
|
+
|
|
150
|
+
function gateDurableState(durableState) {
|
|
151
|
+
const blockers = [];
|
|
152
|
+
if (!durableState || typeof durableState !== "object") {
|
|
153
|
+
return {
|
|
154
|
+
allowed: false,
|
|
155
|
+
reason: "durable state payload missing",
|
|
156
|
+
blockers: [{ reason: "no durable state provided" }],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (durableState.corrupted === true) {
|
|
160
|
+
blockers.push({ reason: "durable state flagged corrupted" });
|
|
161
|
+
}
|
|
162
|
+
if (typeof durableState.recoverable === "boolean" && durableState.recoverable === false) {
|
|
163
|
+
blockers.push({ reason: "durable state not recoverable" });
|
|
164
|
+
}
|
|
165
|
+
if (!Array.isArray(durableState.snapshots) || durableState.snapshots.length === 0) {
|
|
166
|
+
blockers.push({ reason: "no pre-release snapshot available for rollback" });
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
allowed: blockers.length === 0,
|
|
170
|
+
reason: blockers.length === 0 ? "durable state consistent" : "durable state defects",
|
|
171
|
+
blockers,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ---------- gate: contextProduction ---------- */
|
|
176
|
+
|
|
177
|
+
function gateContextProduction(context) {
|
|
178
|
+
const blockers = [];
|
|
179
|
+
if (!context || typeof context !== "object") {
|
|
180
|
+
return {
|
|
181
|
+
allowed: false,
|
|
182
|
+
reason: "context payload missing",
|
|
183
|
+
blockers: [{ reason: "no canonical context provided" }],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const artifacts = Array.isArray(context.artifacts) ? context.artifacts : [];
|
|
187
|
+
if (artifacts.length === 0) {
|
|
188
|
+
blockers.push({ reason: "no canonical context artifacts" });
|
|
189
|
+
}
|
|
190
|
+
for (const a of artifacts) {
|
|
191
|
+
if (!a || typeof a !== "object") {
|
|
192
|
+
blockers.push({ reason: "non-object artifact entry" });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (a.stale === true) {
|
|
196
|
+
blockers.push({ artifactId: a.id ?? null, reason: `artifact ${a.id ?? "?"} stale` });
|
|
197
|
+
}
|
|
198
|
+
if (typeof a.valid === "boolean" && a.valid === false) {
|
|
199
|
+
blockers.push({ artifactId: a.id ?? null, reason: `artifact ${a.id ?? "?"} invalid` });
|
|
200
|
+
}
|
|
201
|
+
if (!hasField(a, "producedAt")) {
|
|
202
|
+
blockers.push({ artifactId: a.id ?? null, reason: "artifact missing producedAt" });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
allowed: blockers.length === 0,
|
|
207
|
+
reason: blockers.length === 0 ? "context fresh" : "context defects",
|
|
208
|
+
blockers,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ---------- gate: runtimeHardening ---------- */
|
|
213
|
+
|
|
214
|
+
function gateRuntimeHardening(hardeningLog) {
|
|
215
|
+
const blockers = [];
|
|
216
|
+
const events = Array.isArray(hardeningLog?.events) ? hardeningLog.events : [];
|
|
217
|
+
for (const e of events) {
|
|
218
|
+
if (!e || typeof e !== "object") continue;
|
|
219
|
+
if (e.status === "BLOCKED" || e.status === "ESCALATE") {
|
|
220
|
+
if (e.resolved !== true) {
|
|
221
|
+
blockers.push({
|
|
222
|
+
eventId: e.id ?? null,
|
|
223
|
+
reason: `unresolved hardening event status=${e.status}`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
allowed: blockers.length === 0,
|
|
230
|
+
reason: blockers.length === 0 ? "no unresolved hardening events" : "hardening events open",
|
|
231
|
+
blockers,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ---------- gate: observability ---------- */
|
|
236
|
+
|
|
237
|
+
const REQUIRED_MANIFEST_FIELDS = [
|
|
238
|
+
"releaseId",
|
|
239
|
+
"changeSetId",
|
|
240
|
+
"version",
|
|
241
|
+
"producedAt",
|
|
242
|
+
"signers",
|
|
243
|
+
"gateResults",
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
function gateObservability(manifest) {
|
|
247
|
+
const blockers = [];
|
|
248
|
+
if (!manifest || typeof manifest !== "object") {
|
|
249
|
+
return {
|
|
250
|
+
allowed: false,
|
|
251
|
+
reason: "manifest missing",
|
|
252
|
+
blockers: [{ reason: "no release manifest" }],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
for (const f of REQUIRED_MANIFEST_FIELDS) {
|
|
256
|
+
if (!hasField(manifest, f)) {
|
|
257
|
+
blockers.push({ field: f, reason: `manifest missing field ${f}` });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (manifest.signers !== undefined && !Array.isArray(manifest.signers)) {
|
|
261
|
+
blockers.push({ reason: "manifest.signers must be an array when provided" });
|
|
262
|
+
}
|
|
263
|
+
if (manifest.gateResults !== undefined && !Array.isArray(manifest.gateResults)) {
|
|
264
|
+
blockers.push({ reason: "manifest.gateResults must be an array when provided" });
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
allowed: blockers.length === 0,
|
|
268
|
+
reason: blockers.length === 0 ? "manifest schema valid" : "manifest schema invalid",
|
|
269
|
+
blockers,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* ---------- dispatcher ---------- */
|
|
274
|
+
|
|
275
|
+
const GATE_FNS = {
|
|
276
|
+
assessmentContract: gateAssessmentContract,
|
|
277
|
+
completionIntegrity: gateCompletionIntegrity,
|
|
278
|
+
executionDecoupling: gateExecutionDecoupling,
|
|
279
|
+
durableState: gateDurableState,
|
|
280
|
+
contextProduction: gateContextProduction,
|
|
281
|
+
runtimeHardening: gateRuntimeHardening,
|
|
282
|
+
observability: gateObservability,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Evaluate a single named sub-gate.
|
|
287
|
+
* Accepts a payload tailored to that gate. See source for expected shapes.
|
|
288
|
+
*/
|
|
289
|
+
export function evaluateSubGate(name, payload) {
|
|
290
|
+
const fn = GATE_FNS[name];
|
|
291
|
+
if (!fn) {
|
|
292
|
+
return { allowed: false, reason: `unknown sub-gate: ${name}`, blockers: [] };
|
|
293
|
+
}
|
|
294
|
+
return safe(() => fn(payload), { allowed: false, reason: "gate error", blockers: [] });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Evaluate the full release gate. `payload` is an object with named fields,
|
|
299
|
+
* each forwarded to its sub-gate. Missing fields default to permissive
|
|
300
|
+
* (allowed) so partial evaluations remain testable.
|
|
301
|
+
*/
|
|
302
|
+
export function evaluateRelease(payload = {}) {
|
|
303
|
+
const gates = [];
|
|
304
|
+
for (const name of SUB_GATES) {
|
|
305
|
+
const result = evaluateSubGate(name, payload[mapGateToPayload(name)]);
|
|
306
|
+
gates.push({ id: name, ...result });
|
|
307
|
+
}
|
|
308
|
+
const passed = gates.filter((g) => g.allowed).length;
|
|
309
|
+
const failed = gates.length - passed;
|
|
310
|
+
const allowed = failed === 0;
|
|
311
|
+
return {
|
|
312
|
+
allowed,
|
|
313
|
+
reason: allowed ? "all sub-gates passed" : `${failed} sub-gate(s) failed`,
|
|
314
|
+
gates,
|
|
315
|
+
summary: { total: gates.length, passed, failed },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function mapGateToPayload(name) {
|
|
320
|
+
switch (name) {
|
|
321
|
+
case "assessmentContract":
|
|
322
|
+
case "completionIntegrity":
|
|
323
|
+
case "executionDecoupling":
|
|
324
|
+
return "tasks";
|
|
325
|
+
case "durableState":
|
|
326
|
+
return "durableState";
|
|
327
|
+
case "contextProduction":
|
|
328
|
+
return "context";
|
|
329
|
+
case "runtimeHardening":
|
|
330
|
+
return "hardeningLog";
|
|
331
|
+
case "observability":
|
|
332
|
+
return "manifest";
|
|
333
|
+
default:
|
|
334
|
+
return name;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Manager
|
|
3
|
+
*
|
|
4
|
+
* Atomic rollback driven by a durable state snapshot. The contract:
|
|
5
|
+
*
|
|
6
|
+
* 1. Caller supplies a list of rollback "steps" plus a snapshot handle.
|
|
7
|
+
* 2. Steps execute strictly sequentially.
|
|
8
|
+
* 3. If ANY step fails the entire rollback aborts, all prior side effects
|
|
9
|
+
* are reverted via the snapshot, and `rolledBack: true` is returned
|
|
10
|
+
* with the failing step + cause.
|
|
11
|
+
* 4. If all steps succeed, the snapshot is committed (overwritten with
|
|
12
|
+
* the new pre-release state) and `rolledBack: true` is returned
|
|
13
|
+
* with `cause: "succeeded"`.
|
|
14
|
+
*
|
|
15
|
+
* Snapshot handling is dependency-injected so tests can use an in-memory
|
|
16
|
+
* store while production code can persist to disk / remote.
|
|
17
|
+
*
|
|
18
|
+
* API:
|
|
19
|
+
* createRollbackManager({ store, now })
|
|
20
|
+
* rollback({ releaseId, steps, snapshot })
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
function defaultNow() {
|
|
24
|
+
return new Date().toISOString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a rollback manager.
|
|
29
|
+
* store: {
|
|
30
|
+
* load(snapshotId) -> snapshot | null,
|
|
31
|
+
* save(snapshotId, snapshot) -> void,
|
|
32
|
+
* commit(snapshotId, snapshot) -> void,
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export function createRollbackManager({ store, now = defaultNow } = {}) {
|
|
36
|
+
if (!store || typeof store.load !== "function" || typeof store.save !== "function") {
|
|
37
|
+
throw new Error("rollback manager requires a store with load/save");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function rollback({ releaseId, steps, snapshot } = {}) {
|
|
41
|
+
const log = [];
|
|
42
|
+
const stamp = () => ({ at: now(), releaseId });
|
|
43
|
+
|
|
44
|
+
if (typeof releaseId !== "string" || !releaseId) {
|
|
45
|
+
return {
|
|
46
|
+
rolledBack: false,
|
|
47
|
+
cause: "invalid releaseId",
|
|
48
|
+
failedStep: null,
|
|
49
|
+
log,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(steps)) {
|
|
53
|
+
return {
|
|
54
|
+
rolledBack: false,
|
|
55
|
+
cause: "steps must be an array",
|
|
56
|
+
failedStep: null,
|
|
57
|
+
log,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
61
|
+
return {
|
|
62
|
+
rolledBack: false,
|
|
63
|
+
cause: "snapshot required",
|
|
64
|
+
failedStep: null,
|
|
65
|
+
log,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Pre-flight: persist the snapshot so partial side effects can be undone.
|
|
70
|
+
try {
|
|
71
|
+
store.save(snapshot.id, snapshot);
|
|
72
|
+
log.push({ ...stamp(), event: "snapshot-saved", snapshotId: snapshot.id });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return {
|
|
75
|
+
rolledBack: false,
|
|
76
|
+
cause: `snapshot save failed: ${err && err.message ? err.message : String(err)}`,
|
|
77
|
+
failedStep: null,
|
|
78
|
+
log,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const executed = [];
|
|
83
|
+
for (const step of steps) {
|
|
84
|
+
if (!step || typeof step !== "object" || typeof step.run !== "function") {
|
|
85
|
+
// Step is malformed — abort and restore snapshot.
|
|
86
|
+
return await restore(snapshot, executed, "malformed step", step && step.id, log, store, stamp);
|
|
87
|
+
}
|
|
88
|
+
log.push({ ...stamp(), event: "step-start", stepId: step.id });
|
|
89
|
+
try {
|
|
90
|
+
const result = await step.run();
|
|
91
|
+
executed.push({ id: step.id, result });
|
|
92
|
+
log.push({ ...stamp(), event: "step-ok", stepId: step.id, result });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return await restore(
|
|
95
|
+
snapshot,
|
|
96
|
+
executed,
|
|
97
|
+
`step ${step.id} failed: ${err && err.message ? err.message : String(err)}`,
|
|
98
|
+
step.id,
|
|
99
|
+
log,
|
|
100
|
+
store,
|
|
101
|
+
stamp
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// All steps succeeded — commit (replace) the snapshot.
|
|
107
|
+
try {
|
|
108
|
+
store.commit(snapshot.id, snapshot);
|
|
109
|
+
log.push({ ...stamp(), event: "snapshot-committed", snapshotId: snapshot.id });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return await restore(
|
|
112
|
+
snapshot,
|
|
113
|
+
executed,
|
|
114
|
+
`commit failed: ${err && err.message ? err.message : String(err)}`,
|
|
115
|
+
"commit",
|
|
116
|
+
log,
|
|
117
|
+
store,
|
|
118
|
+
stamp
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { rolledBack: true, cause: "succeeded", failedStep: null, log };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { rollback };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Restore by replaying each executed step's `undo` (if any). The snapshot
|
|
130
|
+
* itself is re-saved so any in-flight state mutation is reverted.
|
|
131
|
+
*/
|
|
132
|
+
async function restore(snapshot, executed, cause, failedStep, log, store, stamp) {
|
|
133
|
+
for (const ex of executed) {
|
|
134
|
+
if (ex && typeof ex.result?.undo === "function") {
|
|
135
|
+
try {
|
|
136
|
+
await ex.result.undo();
|
|
137
|
+
log.push({ ...stamp(), event: "step-undone", stepId: ex.id });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
log.push({
|
|
140
|
+
...stamp(),
|
|
141
|
+
event: "step-undo-failed",
|
|
142
|
+
stepId: ex.id,
|
|
143
|
+
error: err && err.message ? err.message : String(err),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
store.save(snapshot.id, snapshot);
|
|
150
|
+
log.push({ ...stamp(), event: "snapshot-restored", snapshotId: snapshot.id });
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log.push({
|
|
153
|
+
...stamp(),
|
|
154
|
+
event: "snapshot-restore-failed",
|
|
155
|
+
error: err && err.message ? err.message : String(err),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
rolledBack: true,
|
|
160
|
+
cause,
|
|
161
|
+
failedStep,
|
|
162
|
+
log,
|
|
163
|
+
};
|
|
164
|
+
}
|