fix: bootstrap registry on first workspace list
This commit is contained in:
@@ -155,6 +155,12 @@ export class WorkspaceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async list(): Promise<WorkspaceRevision[]> {
|
async list(): Promise<WorkspaceRevision[]> {
|
||||||
|
const active = await this.tryActiveState();
|
||||||
|
if (active) return active.revisions;
|
||||||
|
// A clean installation has no active snapshot until the first registry operation. Keep
|
||||||
|
// this lazy so health/startup remain available when Git is temporarily unreachable, while
|
||||||
|
// still refusing corrupted existing state (tryActiveState throws instead of returning none).
|
||||||
|
await this.bootstrap();
|
||||||
return (await this.activeState()).revisions;
|
return (await this.activeState()).revisions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,21 @@ test("bootstraps a checkout and activates a validated immutable snapshot", async
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("first list lazily bootstraps a clean registry", async () => {
|
||||||
|
const remote = await fixture();
|
||||||
|
const root = join(remote.root, "registry");
|
||||||
|
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||||
|
|
||||||
|
await expect(registry.list()).resolves.toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "psd-clinical",
|
||||||
|
commit: remote.initialCommit,
|
||||||
|
state: "operational",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
expect(existsSync(join(root, "state", "active.json"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test("publishes create, update, and delete with the configured Git author identity", async () => {
|
test("publishes create, update, and delete with the configured Git author identity", async () => {
|
||||||
const remote = await fixture();
|
const remote = await fixture();
|
||||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote, {
|
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote, {
|
||||||
|
|||||||
@@ -630,6 +630,27 @@ task13_report_server_workspace_failure() {
|
|||||||
else
|
else
|
||||||
printf '%s\n' '(empty response)' >&2
|
printf '%s\n' '(empty response)' >&2
|
||||||
fi
|
fi
|
||||||
|
printf '%s\n' '--- sanitized registry integrity probe ---' >&2
|
||||||
|
{
|
||||||
|
task13_compose exec -T core node --input-type=module -e '
|
||||||
|
const { loadConfig } = await import("/app/backend/dist/config.js");
|
||||||
|
const { WorkspaceRegistry } = await import("/app/backend/dist/workspaces/registry.js");
|
||||||
|
const registry = new WorkspaceRegistry(loadConfig(process.env).workspaceRegistry);
|
||||||
|
try {
|
||||||
|
const revisions = await registry.list();
|
||||||
|
for (const revision of revisions) await registry.read(revision.id);
|
||||||
|
console.log(JSON.stringify({ ok: true, revisions: revisions.length }));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: false,
|
||||||
|
name: error instanceof Error ? error.name : "UnknownError",
|
||||||
|
code: error && typeof error === "object" && "code" in error ? error.code : "unknown",
|
||||||
|
message: error instanceof Error ? error.message : "Unknown registry failure",
|
||||||
|
}));
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
' 2>&1 || printf '%s\n' '(registry integrity probe unavailable)'
|
||||||
|
} | tail -n 20 | task13_sanitize >&2
|
||||||
printf '%s\n' '--- sanitized core logs (last 100 lines) ---' >&2
|
printf '%s\n' '--- sanitized core logs (last 100 lines) ---' >&2
|
||||||
{
|
{
|
||||||
task13_compose logs --no-color --tail 100 core 2>&1 \
|
task13_compose logs --no-color --tail 100 core 2>&1 \
|
||||||
@@ -1063,6 +1084,11 @@ task13_self_test_server_workspace_diagnostics() {
|
|||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'{"error":"workspace_invalid","detail":"password=fixture-known-secret"}' >"$response"
|
'{"error":"workspace_invalid","detail":"password=fixture-known-secret"}' >"$response"
|
||||||
task13_compose() {
|
task13_compose() {
|
||||||
|
if [[ "$*" == "exec -T core node --input-type=module -e "* ]]; then
|
||||||
|
printf '%s\n' \
|
||||||
|
'{"name":"WorkspaceRegistryError","code":"workspace_invalid","message":"Workspace snapshot integrity check failed"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
[[ "$*" == "logs --no-color --tail 100 core" ]] \
|
[[ "$*" == "logs --no-color --tail 100 core" ]] \
|
||||||
|| task13_fail "server diagnostics requested an unexpected Compose command"
|
|| task13_fail "server diagnostics requested an unexpected Compose command"
|
||||||
while [[ "$i" -le 150 ]]; do
|
while [[ "$i" -le 150 ]]; do
|
||||||
@@ -1083,6 +1109,8 @@ task13_self_test_server_workspace_diagnostics() {
|
|||||||
|| task13_fail "server diagnostics omit the unexpected HTTP status"
|
|| task13_fail "server diagnostics omit the unexpected HTTP status"
|
||||||
[[ "$output" == *'workspace_invalid'* ]] \
|
[[ "$output" == *'workspace_invalid'* ]] \
|
||||||
|| task13_fail "server diagnostics omit the generic response"
|
|| task13_fail "server diagnostics omit the generic response"
|
||||||
|
[[ "$output" == *'Workspace snapshot integrity check failed'* ]] \
|
||||||
|
|| task13_fail "server diagnostics omit the bounded internal registry reason"
|
||||||
[[ "$output" == *'core-log-051'* && "$output" != *'core-log-050'* ]] \
|
[[ "$output" == *'core-log-051'* && "$output" != *'core-log-050'* ]] \
|
||||||
|| task13_fail "server diagnostics do not bound core logs to the last 100 lines"
|
|| task13_fail "server diagnostics do not bound core logs to the last 100 lines"
|
||||||
count="$(grep -Ec '^core-log-[0-9]{3}' <<<"$output")"
|
count="$(grep -Ec '^core-log-[0-9]{3}' <<<"$output")"
|
||||||
|
|||||||
Reference in New Issue
Block a user