@zq-silk/yui 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only runtime identity facts for `controller status` and `execution audit`.
|
|
3
|
+
*
|
|
4
|
+
* Issue 11 boundary: this module only collects facts (manifest, process
|
|
5
|
+
* realpath/env, on-disk store evidence, Git). It never mutates state, never
|
|
6
|
+
* starts or stops a Controller, and never asks a producer to change. A fact
|
|
7
|
+
* that cannot be resolved is the literal string `"unsupported"` (the Issue 11
|
|
8
|
+
* fallback rule), and contradictory facts fail closed as findings with exact
|
|
9
|
+
* remediation actions.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import Database from "better-sqlite3";
|
|
16
|
+
import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storage/storageVersions.js";
|
|
17
|
+
import { resolveTaskStoreBackendForHome } from "../storage/sqliteStore.js";
|
|
18
|
+
import { resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
|
|
19
|
+
export const UNSUPPORTED = "unsupported";
|
|
20
|
+
/**
|
|
21
|
+
* Feature flag for the Issue 11 read-only status/audit presentation.
|
|
22
|
+
*
|
|
23
|
+
* Everything behind this flag is observe-only: identity fields, storage
|
|
24
|
+
* evidence, and metrics snapshots. The flag defaults ON because the fields
|
|
25
|
+
* change no behavior; set `YUI_STATUS_IDENTITY=0` (or `false`) to hide the new
|
|
26
|
+
* sections and the fail-closed health exit. `execution audit` is an explicit
|
|
27
|
+
* command and is not gated by this flag.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveStatusIdentityEnabled(env = process.env) {
|
|
30
|
+
const value = env.YUI_STATUS_IDENTITY;
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return true;
|
|
33
|
+
const normalized = value.trim().toLowerCase();
|
|
34
|
+
return normalized !== "0" && normalized !== "false" && normalized !== "off";
|
|
35
|
+
}
|
|
36
|
+
export function evaluateStorageHealth(identity) {
|
|
37
|
+
const contradictions = identity.findings.filter(({ severity }) => severity === "contradiction");
|
|
38
|
+
const needsRepair = identity.findings.filter(({ severity }) => severity === "needs-repair");
|
|
39
|
+
const warnings = identity.findings.filter(({ severity }) => severity === "warning");
|
|
40
|
+
const status = contradictions.length > 0
|
|
41
|
+
? "fail"
|
|
42
|
+
: needsRepair.length > 0
|
|
43
|
+
? "degraded"
|
|
44
|
+
: "ok";
|
|
45
|
+
return { status, healthy: status === "ok", contradictions, needsRepair, warnings };
|
|
46
|
+
}
|
|
47
|
+
export function createProductionRuntimeIdentityPorts(packageRoot, entryPath, env = process.env) {
|
|
48
|
+
return {
|
|
49
|
+
env,
|
|
50
|
+
packageRoot,
|
|
51
|
+
entryPath,
|
|
52
|
+
readText: (path) => {
|
|
53
|
+
try {
|
|
54
|
+
return readFileSync(path, "utf8");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
fileSize: (path) => {
|
|
61
|
+
try {
|
|
62
|
+
return statSync(path).size;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
exists: (path) => existsSync(path),
|
|
69
|
+
realpath: (path) => {
|
|
70
|
+
try {
|
|
71
|
+
return realpathSync(path);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
gitHead: (root) => readGitHead(root)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function readGitHead(packageRoot) {
|
|
81
|
+
try {
|
|
82
|
+
const result = spawnSync("git", ["-C", packageRoot, "rev-parse", "HEAD"], { encoding: "utf8", timeout: 1_500, stdio: ["ignore", "pipe", "ignore"] });
|
|
83
|
+
if (result.status !== 0)
|
|
84
|
+
return null;
|
|
85
|
+
const head = result.stdout.trim();
|
|
86
|
+
return /^[a-f0-9]{7,40}$/u.test(head) ? head : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function sha256Hex(content) {
|
|
93
|
+
return createHash("sha256").update(content).digest("hex");
|
|
94
|
+
}
|
|
95
|
+
export function collectRuntimeBuildIdentity(ports) {
|
|
96
|
+
const packageJsonText = ports.readText(join(ports.packageRoot, "package.json"));
|
|
97
|
+
let packageName = "unknown";
|
|
98
|
+
let packageVersion = "0.0.0";
|
|
99
|
+
let packageDigest = UNSUPPORTED;
|
|
100
|
+
if (packageJsonText !== null) {
|
|
101
|
+
packageDigest = `sha256:${sha256Hex(packageJsonText)}`;
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(packageJsonText);
|
|
104
|
+
if (typeof parsed.name === "string" && parsed.name.length > 0) {
|
|
105
|
+
packageName = parsed.name;
|
|
106
|
+
}
|
|
107
|
+
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
108
|
+
packageVersion = parsed.version;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Damaged package metadata still leaves the digest as evidence.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const entryRealpath = ports.realpath(ports.entryPath);
|
|
116
|
+
const entryText = entryRealpath === null ? null : ports.readText(entryRealpath);
|
|
117
|
+
return {
|
|
118
|
+
packageName,
|
|
119
|
+
packageVersion,
|
|
120
|
+
packageDigest,
|
|
121
|
+
entryPath: entryRealpath ?? UNSUPPORTED,
|
|
122
|
+
entryDigest: entryText === null
|
|
123
|
+
? UNSUPPORTED
|
|
124
|
+
: `sha256:${sha256Hex(entryText)}`,
|
|
125
|
+
sourceCommit: ports.gitHead(ports.packageRoot) ?? UNSUPPORTED,
|
|
126
|
+
nodeVersion: process.version,
|
|
127
|
+
platform: `${process.platform}/${process.arch}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function createProductionStorageIdentityPorts(env = process.env) {
|
|
131
|
+
return {
|
|
132
|
+
env,
|
|
133
|
+
readText: (path) => {
|
|
134
|
+
try {
|
|
135
|
+
return readFileSync(path, "utf8");
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
fileSize: (path) => {
|
|
142
|
+
try {
|
|
143
|
+
return statSync(path).size;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
exists: (path) => existsSync(path),
|
|
150
|
+
probeDatabaseHealth: (path) => {
|
|
151
|
+
if (!existsSync(path))
|
|
152
|
+
return null;
|
|
153
|
+
try {
|
|
154
|
+
const db = new Database(path, { readonly: true });
|
|
155
|
+
try {
|
|
156
|
+
const integrity = db.pragma("quick_check", { simple: true });
|
|
157
|
+
return integrity === "ok" ? "ok" : "corrupt";
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
db.close();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return "unopenable";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Collect durable-vs-physical storage evidence for one Home. The manifest
|
|
171
|
+
* declares a logical layout; the directory carries physical evidence
|
|
172
|
+
* (state.json vs yui.db + WAL). Contradictions between the two are the
|
|
173
|
+
* pseudo-layout-7 class of failure and fail closed.
|
|
174
|
+
*/
|
|
175
|
+
export function collectStorageIdentity(home, ports = createProductionStorageIdentityPorts()) {
|
|
176
|
+
const manifestPath = join(home, "schema.json");
|
|
177
|
+
const manifestText = ports.readText(manifestPath);
|
|
178
|
+
let manifestStatus = "uninitialized";
|
|
179
|
+
let logicalLayout = UNSUPPORTED;
|
|
180
|
+
let aggregateSchemaVersion = UNSUPPORTED;
|
|
181
|
+
if (manifestText !== null) {
|
|
182
|
+
manifestStatus = "current";
|
|
183
|
+
try {
|
|
184
|
+
const manifest = JSON.parse(manifestText);
|
|
185
|
+
if (typeof manifest.storageVersion === "number"
|
|
186
|
+
&& Number.isFinite(manifest.storageVersion)) {
|
|
187
|
+
logicalLayout = manifest.storageVersion;
|
|
188
|
+
}
|
|
189
|
+
if (typeof manifest.aggregateSchemaVersion === "number"
|
|
190
|
+
&& Number.isFinite(manifest.aggregateSchemaVersion)) {
|
|
191
|
+
aggregateSchemaVersion = manifest.aggregateSchemaVersion;
|
|
192
|
+
}
|
|
193
|
+
if (typeof logicalLayout === "number"
|
|
194
|
+
&& (logicalLayout > CURRENT_STORAGE_LAYOUT_VERSION
|
|
195
|
+
|| (typeof aggregateSchemaVersion === "number"
|
|
196
|
+
&& aggregateSchemaVersion > CURRENT_AGGREGATE_SCHEMA_VERSION))) {
|
|
197
|
+
manifestStatus = "unsupported";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
manifestStatus = "invalid";
|
|
202
|
+
logicalLayout = UNSUPPORTED;
|
|
203
|
+
aggregateSchemaVersion = UNSUPPORTED;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const statePath = join(home, "state.json");
|
|
207
|
+
const statePresent = ports.exists(statePath);
|
|
208
|
+
const stateBytes = ports.fileSize(statePath);
|
|
209
|
+
const dbPath = join(home, "yui.db");
|
|
210
|
+
const dbPresent = ports.exists(dbPath);
|
|
211
|
+
const dbBytes = ports.fileSize(dbPath);
|
|
212
|
+
const walPresent = ports.exists(`${dbPath}-wal`);
|
|
213
|
+
const shmPresent = ports.exists(`${dbPath}-shm`);
|
|
214
|
+
const configuredBackend = resolveTaskStoreBackendForHome(home, ports.env);
|
|
215
|
+
const workerEnabled = resolveStoreWorkerEnabledForHome(home, ports.env);
|
|
216
|
+
const dbHealth = dbPresent ? ports.probeDatabaseHealth(dbPath) : null;
|
|
217
|
+
const hasMigrationReceipt = ports.exists(join(home, "migration-receipt.json"));
|
|
218
|
+
const findings = [];
|
|
219
|
+
// A readable state.json is a JSON object (the file store's record container).
|
|
220
|
+
// Pseudo-layout-7 is only repairable when state.json is a valid fallback.
|
|
221
|
+
const stateText = statePresent ? ports.readText(statePath) : null;
|
|
222
|
+
let stateReadable = false;
|
|
223
|
+
if (stateText !== null) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = JSON.parse(stateText);
|
|
226
|
+
stateReadable = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
stateReadable = false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const layout7 = manifestStatus === "current"
|
|
233
|
+
&& typeof logicalLayout === "number"
|
|
234
|
+
&& logicalLayout >= CURRENT_STORAGE_LAYOUT_VERSION;
|
|
235
|
+
if (layout7 && !dbPresent) {
|
|
236
|
+
if (stateReadable) {
|
|
237
|
+
// Pseudo-layout-7: manifest claims SQLite WAL but yui.db was never
|
|
238
|
+
// materialized. state.json is the readable fallback; `yui upgrade`
|
|
239
|
+
// rebuilds yui.db from it. This is the recognized NEEDS_STORAGE_REPAIR
|
|
240
|
+
// state, not a hard contradiction — the file store is legitimately
|
|
241
|
+
// serving until repair runs.
|
|
242
|
+
findings.push({
|
|
243
|
+
code: "pseudo-layout-7",
|
|
244
|
+
severity: "needs-repair",
|
|
245
|
+
message: `Manifest declares layout ${logicalLayout} (SQLite WAL) but `
|
|
246
|
+
+ `${dbPath} does not exist; state.json is the only authoritative `
|
|
247
|
+
+ "copy (pseudo-layout-7).",
|
|
248
|
+
remediation: "Run `yui upgrade` to rebuild yui.db from state.json."
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
findings.push({
|
|
253
|
+
code: "no-authoritative-backend",
|
|
254
|
+
severity: "contradiction",
|
|
255
|
+
message: `Manifest declares layout ${logicalLayout} but neither `
|
|
256
|
+
+ "yui.db nor a readable state.json exists; there is no "
|
|
257
|
+
+ "authoritative backend.",
|
|
258
|
+
remediation: "Restore one copy from a backup; do not guess which is newer."
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
|
|
263
|
+
findings.push({
|
|
264
|
+
code: "database-unhealthy",
|
|
265
|
+
severity: "contradiction",
|
|
266
|
+
message: `yui.db exists but failed integrity check (${dbHealth}).`,
|
|
267
|
+
remediation: "Restore yui.db from backup, or run `yui upgrade` to rebuild "
|
|
268
|
+
+ "it from state.json."
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (dbPresent && statePresent && !hasMigrationReceipt && layout7) {
|
|
272
|
+
findings.push({
|
|
273
|
+
code: "dual-copy-conflict",
|
|
274
|
+
severity: "contradiction",
|
|
275
|
+
message: "Both state.json and yui.db exist without a migration receipt; "
|
|
276
|
+
+ "the authoritative copy is ambiguous.",
|
|
277
|
+
remediation: "Restore one copy from a backup; do not guess which is newer."
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (configuredBackend === "sqlite" && !dbPresent) {
|
|
281
|
+
findings.push({
|
|
282
|
+
code: "backend-sqlite-without-database",
|
|
283
|
+
severity: "contradiction",
|
|
284
|
+
message: "YUI_STORE_BACKEND=sqlite is set but no yui.db exists; the "
|
|
285
|
+
+ "SQLite store cannot open this Home.",
|
|
286
|
+
remediation: "Run `yui upgrade` to create yui.db, or unset "
|
|
287
|
+
+ "YUI_STORE_BACKEND to keep using the file store explicitly."
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (configuredBackend === "file"
|
|
291
|
+
&& dbPresent
|
|
292
|
+
&& manifestStatus === "current") {
|
|
293
|
+
findings.push({
|
|
294
|
+
code: "database-present-but-file-backend",
|
|
295
|
+
severity: "warning",
|
|
296
|
+
message: "yui.db exists but the file backend is selected; this process "
|
|
297
|
+
+ "is serving state.json, not the database.",
|
|
298
|
+
remediation: "Unset YUI_STORE_BACKEND to let the Home manifest select "
|
|
299
|
+
+ "SQLite, or remove the stale yui.db."
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const rawWorkerFlag = ports.env.YUI_STORE_WORKER;
|
|
303
|
+
if (rawWorkerFlag !== undefined
|
|
304
|
+
&& !workerEnabled
|
|
305
|
+
&& configuredBackend !== "sqlite") {
|
|
306
|
+
findings.push({
|
|
307
|
+
code: "worker-flag-without-sqlite",
|
|
308
|
+
severity: "warning",
|
|
309
|
+
message: `YUI_STORE_WORKER=${rawWorkerFlag} is set but the worker `
|
|
310
|
+
+ "requires YUI_STORE_BACKEND=sqlite; the worker is off.",
|
|
311
|
+
remediation: "Set YUI_STORE_BACKEND=sqlite to activate the worker, or "
|
|
312
|
+
+ "unset YUI_STORE_WORKER."
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// Only fire for pre-layout-7 Homes where the file store is the legitimate
|
|
316
|
+
// backend. A layout-7 Home without state.json is already covered by
|
|
317
|
+
// no-authoritative-backend above (the file store is just a fallback there).
|
|
318
|
+
if (configuredBackend === "file"
|
|
319
|
+
&& !statePresent
|
|
320
|
+
&& manifestStatus === "current"
|
|
321
|
+
&& !(typeof logicalLayout === "number" && logicalLayout >= CURRENT_STORAGE_LAYOUT_VERSION)) {
|
|
322
|
+
findings.push({
|
|
323
|
+
code: "file-store-missing-state",
|
|
324
|
+
severity: "contradiction",
|
|
325
|
+
message: "The file backend is selected but state.json is missing.",
|
|
326
|
+
remediation: "Restore state.json from backup, or run the storage "
|
|
327
|
+
+ "migration to create yui.db."
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
home,
|
|
332
|
+
manifestStatus,
|
|
333
|
+
logicalLayout,
|
|
334
|
+
aggregateSchemaVersion,
|
|
335
|
+
configuredBackend,
|
|
336
|
+
workerEnabled,
|
|
337
|
+
physicalStateJson: {
|
|
338
|
+
present: statePresent,
|
|
339
|
+
bytes: stateBytes ?? UNSUPPORTED
|
|
340
|
+
},
|
|
341
|
+
physicalDatabase: {
|
|
342
|
+
present: dbPresent,
|
|
343
|
+
bytes: dbBytes ?? UNSUPPORTED,
|
|
344
|
+
wal: walPresent,
|
|
345
|
+
shm: shmPresent,
|
|
346
|
+
health: dbHealth ?? UNSUPPORTED
|
|
347
|
+
},
|
|
348
|
+
hasMigrationReceipt,
|
|
349
|
+
findings
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Count telemetry events the runtime inbox rejected into
|
|
354
|
+
* `runtime/inbox-invalid/`. Dropped telemetry must stay countable and must
|
|
355
|
+
* never affect the semantic lane (Issue 11 §4). A missing directory means no
|
|
356
|
+
* drops (the inbox was never used); an unreadable directory is `unsupported`.
|
|
357
|
+
*/
|
|
358
|
+
export function countDroppedInboxEvents(home, listDirectory = (path) => readdirSync(path)) {
|
|
359
|
+
if (!existsSync(join(home, "runtime", "inbox-invalid")))
|
|
360
|
+
return 0;
|
|
361
|
+
try {
|
|
362
|
+
return listDirectory(join(home, "runtime", "inbox-invalid")).length;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return UNSUPPORTED;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deterministic double for every external system (GitHub, npm, Home,
|
|
3
|
+
* Controller). No real network, process, or filesystem side effect: the
|
|
4
|
+
* engine's at-most-once and recovery guarantees are proven against this.
|
|
5
|
+
*/
|
|
6
|
+
export function createFakeReleasePorts(scripts) {
|
|
7
|
+
const counters = new Map();
|
|
8
|
+
for (const stepId of Object.keys(scripts)) {
|
|
9
|
+
counters.set(stepId, { execute: 0, query: 0, keys: [] });
|
|
10
|
+
}
|
|
11
|
+
const tally = (stepId) => {
|
|
12
|
+
let calls = counters.get(stepId);
|
|
13
|
+
if (calls === undefined) {
|
|
14
|
+
calls = { execute: 0, query: 0, keys: [] };
|
|
15
|
+
counters.set(stepId, calls);
|
|
16
|
+
}
|
|
17
|
+
return calls;
|
|
18
|
+
};
|
|
19
|
+
const next = (queue, stepId, kind, consumed) => {
|
|
20
|
+
if (queue === undefined || consumed >= queue.length) {
|
|
21
|
+
throw new Error(`Fake release ports: no more scripted ${kind} outcomes for step ${stepId}.`);
|
|
22
|
+
}
|
|
23
|
+
const entry = queue[consumed];
|
|
24
|
+
return typeof entry === "function" ? entry() : entry;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
async executeStep(input) {
|
|
28
|
+
const calls = tally(input.step.id);
|
|
29
|
+
const effect = next(scripts[input.step.id]?.execute, input.step.id, "execute", calls.execute);
|
|
30
|
+
calls.execute += 1;
|
|
31
|
+
calls.keys.push(input.idempotencyKey);
|
|
32
|
+
return effect;
|
|
33
|
+
},
|
|
34
|
+
async queryStepEffect(input) {
|
|
35
|
+
const calls = tally(input.step.id);
|
|
36
|
+
const query = next(scripts[input.step.id]?.query, input.step.id, "query", calls.query);
|
|
37
|
+
calls.query += 1;
|
|
38
|
+
return query;
|
|
39
|
+
},
|
|
40
|
+
calls(stepId) {
|
|
41
|
+
const calls = tally(stepId);
|
|
42
|
+
return { execute: calls.execute, query: calls.query, keys: Object.freeze([...calls.keys]) };
|
|
43
|
+
},
|
|
44
|
+
exhausted() {
|
|
45
|
+
for (const [stepId, script] of Object.entries(scripts)) {
|
|
46
|
+
const calls = counters.get(stepId);
|
|
47
|
+
if ((script.execute?.length ?? 0) !== calls.execute)
|
|
48
|
+
return false;
|
|
49
|
+
if ((script.query?.length ?? 0) !== calls.query)
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|