fix(seed): an untouched draft no longer holds a workflow back from the new template
jellyfin stayed on the first template because it had an unedited copy of v1 as a draft. A draft the owner changed still keeps the workflow where it is. No new dependencies. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
77a7027065
commit
3dfac076ee
3 files changed
+35
-6
No files matched your search
@@ -14,6 +14,7 @@ Aplikacja wygodna na co dzień: stan „tu i teraz”, historia wersji, cisza no
|
||||
|
||||
- Logowanie Claude'a z UI (`8ea9f88`, wdrożone 23.10): `#/settings` → „Anthropic account” (status konta, „Sign in” / „Sign in with another account” → link + wklejony kod, „Sign out”); sidecar `model-claude` ma `/auth/*`, serwer WWW proxuje `/api/model-auth*`.
|
||||
- Orcaslicer 22.10 (run `feeb48d6`): `verify_initial` nie doczekał się przebudowy kontenera (140 s, `image_mismatch`), a `repair` padł na wygasłym OAuth Claude'a (`auth_failed`, 0 USD), więc run czekał na właściciela mimo udanej aktualizacji. Poprawka: gdy `repair`/`repair_full` się poddaje, workflow robi jeszcze jeden `verify` (`verify_after_gave_up`; zdrowa appka → `terminal_updated`, inaczej bramka jak dotąd); czas `verify` bez zmian. Szablon ma nowy digest.
|
||||
- Jellyfin został na v1 (35 węzłów), bo miał nietknięty szkic (kopia v1), a seed pomijał workflow ze szkicem; teraz nietknięty szkic (digest = ostatnia wersja) nie blokuje przeniesienia na szablon, edytowany nadal tak.
|
||||
- Odczyt działającej appki: `tools/vm-api.sh /api/...` (GET przez SSH na VM103, bez Traefika i Keycloaka), zob. AGENTS.md.
|
||||
|
||||
- H1–H6 na `master` i na VM103: migawka hosta, historia wersji, raport i cisza nocna, decyzje z telefonu, PWA, `Ctrl+K`, logowanie Keycloak.
|
||||
|
||||
@@ -136,6 +136,27 @@ describe('ensureAppWorkflows', () => {
|
||||
expect(again.workflows).toEqual([]);
|
||||
});
|
||||
|
||||
it('moves a workflow whose draft is an untouched copy of its version, and leaves one with an edited draft', async () => {
|
||||
const { deps, workflowCatalog } = setup();
|
||||
await ensureAppWorkflows(deps, ['jackett', 'radarr']);
|
||||
for (const app of ['jackett', 'radarr']) {
|
||||
const cloned = workflowCatalog.clonePublishedVersion({ sourceWorkflowId: `update-${app}`, sourceVersion: 1, workflowId: `update-${app}`, name: `Update ${app}`, now });
|
||||
if (!cloned.ok) throw new Error(cloned.reason);
|
||||
}
|
||||
// radarr's draft is being edited by the owner.
|
||||
const edited = { ...COMPONENT_UPDATE_WORKFLOW_DOCUMENT_V2, nodes: [...COMPONENT_UPDATE_WORKFLOW_DOCUMENT_V2.nodes].reverse() };
|
||||
expect(workflowCatalog.saveDraft({ workflowId: 'update-radarr', expectedRevision: 1, document: edited }).ok).toBe(true);
|
||||
|
||||
const next = { ...COMPONENT_UPDATE_WORKFLOW_DOCUMENT_V2, nodes: [...COMPONENT_UPDATE_WORKFLOW_DOCUMENT_V2.nodes.slice(1), COMPONENT_UPDATE_WORKFLOW_DOCUMENT_V2.nodes[0]!] };
|
||||
const moved = await ensureAppWorkflows({ ...deps, template: { document: next, digests: [...COMPONENT_UPDATE_TEMPLATE_DIGESTS, workflowDocumentDigest(next)] } }, ['jackett', 'radarr']);
|
||||
|
||||
expect(moved).toEqual({ functions: [], workflows: ['update-jackett'], edited: [], failures: [] });
|
||||
expect(workflowCatalog.listPublishedVersions?.('update-jackett')).toHaveLength(2);
|
||||
expect(workflowCatalog.getDraft('update-jackett')).toBeNull();
|
||||
expect(workflowCatalog.listPublishedVersions?.('update-radarr')).toHaveLength(1);
|
||||
expect(workflowCatalog.getDraft('update-radarr')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('reports a function whose cases fail and skips the workflows that need it', async () => {
|
||||
const failingRuntime: FunctionTestExecutor = {
|
||||
execute: () => Promise.resolve({ event: 'error', summary: 'bwrap: sandbox unavailable', data: {} }),
|
||||
|
||||
@@ -114,8 +114,11 @@ export async function ensureAppWorkflows(deps: WorkflowSeedDeps, apps: readonly
|
||||
edited.push(workflowId);
|
||||
continue;
|
||||
}
|
||||
if (!functionsReady || deps.workflowCatalog.getDraft(workflowId) !== null) continue;
|
||||
const reason = upgradeWorkflow(deps, workflowId, app, latest!.version, template.document);
|
||||
if (!functionsReady) continue;
|
||||
// A draft the owner is editing stays; an untouched copy of the version (opened and left) must not hold the workflow back.
|
||||
const draft = deps.workflowCatalog.getDraft(workflowId);
|
||||
if (draft !== null && workflowDocumentDigest(draft.document) !== digest) continue;
|
||||
const reason = upgradeWorkflow(deps, workflowId, app, latest!.version, template.document, draft?.revision ?? null);
|
||||
if (reason === null) workflows.push(workflowId);
|
||||
else failures.push({ id: workflowId, reason });
|
||||
continue;
|
||||
@@ -132,14 +135,18 @@ export async function ensureAppWorkflows(deps: WorkflowSeedDeps, apps: readonly
|
||||
}
|
||||
|
||||
/** Publishes the current template as the next version of an app's workflow that is still on an older template. */
|
||||
function upgradeWorkflow(deps: WorkflowSeedDeps, workflowId: string, app: string, latestVersion: number, document: WorkflowDocumentV2): string | null {
|
||||
function upgradeWorkflow(deps: WorkflowSeedDeps, workflowId: string, app: string, latestVersion: number, document: WorkflowDocumentV2, draftRevision: number | null): string | null {
|
||||
const workflowApp = createWorkflowCatalogApp(
|
||||
deps.workflowCatalog,
|
||||
createWorkflowGraphValidator({ workflowId, version: latestVersion + 1 }, [], deps.extensions),
|
||||
);
|
||||
const cloned = workflowApp.clonePublishedVersion({ sourceWorkflowId: workflowId, sourceVersion: latestVersion, workflowId, name: `Update ${app}`, now: deps.now() });
|
||||
if (!cloned.ok) return `draft: ${cloned.reason}`;
|
||||
const saved = workflowApp.saveDraft({ workflowId, expectedRevision: cloned.draft.revision, document });
|
||||
let revision = draftRevision;
|
||||
if (revision === null) {
|
||||
const cloned = workflowApp.clonePublishedVersion({ sourceWorkflowId: workflowId, sourceVersion: latestVersion, workflowId, name: `Update ${app}`, now: deps.now() });
|
||||
if (!cloned.ok) return `draft: ${cloned.reason}`;
|
||||
revision = cloned.draft.revision;
|
||||
}
|
||||
const saved = workflowApp.saveDraft({ workflowId, expectedRevision: revision, document });
|
||||
if (!saved.ok) return `draft: ${saved.reason}`;
|
||||
return validateAndPublish(deps, workflowApp, workflowId, saved.draft.revision);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user