refactor(storage): remove deployment modes — sqlite|http client seam, sqlite|postgres server backend - #149
Open
andrei-hasna wants to merge 1 commit into
Open
refactor(storage): remove deployment modes — sqlite|http client seam, sqlite|postgres server backend#149andrei-hasna wants to merge 1 commit into
andrei-hasna wants to merge 1 commit into
Conversation
…te|postgres backend switch Collapse the three-way local|self_hosted|cloud deployment-mode axis to the single data-backend switch (owner directive 2026-07-29, modes-simplify): - Client store seam is sqlite|http: LoopStore keeps exactly two impls (LocalStore sqlite file, ApiStore /v1 API); the client never opens Postgres directly. StoreTransport renames local|cloud-http -> sqlite|http; isCloudStore -> isApiStore; CloudUnsupportedError -> ApiUnsupportedError. - Server data backend is sqlite|postgres: HASNA_LOOPS_DATABASE_URL alone selects postgres (loops-serve); the daemon schedules from sqlite. - HASNA_LOOPS_STORAGE_MODE takes sqlite|http; retired values local, self_hosted, cloud stay accepted as LEGACY-DEPLOYMENT-MODE-ALIAS lines because deployed fleets and the installed contracts harness still set them. They never appear in any output. - LoopDeploymentMode, resolveLoopDeploymentMode, buildDeploymentStatus (and its per-mode perspective arms), deploymentStatusLine, and the hosted_control_plane third arm are deleted; buildStorageStatus / storageStatusLine report dataBackend, clientTransport, and authority. - CLI: loops server status|migrate|push|pull replaces the self-hosted and cloud groups; the retired group names keep dispatching via an argv rewrite shim without appearing in help. Migration surface renames SelfHosted* -> Server*, operation ids self-hosted-* -> server-*, and the push-manifest schema id to open-loops.server-push-manifest/v1. - Dockerfile/docker-compose drop the vestigial mode env; docs move to docs/STORAGE.md; openapi prose loses mode wording. - New src/lib/mode-vocabulary.test.ts guards the whole src tree against retired-mode vocabulary with a scanner positive control. Deliberately untouched (contracts seam; concurrent hasna/contracts fix, no @hasna/contracts bump here): hasna.contract.json structured mode fields, the hash-locked src/generated/storage-kit, the local|cloud wire mode field on /health//ready//version, and the conformance harness env.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Removes the three-way
local | self_hosted | clouddeployment-mode axis from open-loops and collapses it to the single data-backend switch (owner directive 2026-07-29, knowledgek_ms3e6v41_zbe7m8, todos324c982a/ LOO3-00126, tagmodes-simplify):loopsCLI / MCP / SDK):sqlite | http. One abstraction (LoopStore) with exactly two implementations —LocalStore(on-box SQLite) andApiStore(server/v1API). The client never opens Postgres directly; there is no client-side PostgresStore (owner ruling).sqlite | postgres.HASNA_LOOPS_DATABASE_URL— and only it — selects postgres (loops-serve); the on-box daemon schedules from SQLite.HASNA_LOOPS_STORAGE_MODEnow takessqlite | http. The retired values (local,self_hosted,cloud) remain accepted as marked aliases (LEGACY-DEPLOYMENT-MODE-ALIAS, 6 lines) because deployed fleet machines and the installed@hasna/contractsconformance harness still set them — they map onto the backend they always selected and never appear in any output.Deleted / renamed
LoopDeploymentMode,LOOP_DEPLOYMENT_MODES,resolveLoopDeploymentMode,normalizeLoopDeploymentMode,buildDeploymentStatus(+ itsperspectiveparameter — the gratuitous per-mode status arms),deploymentStatusLine,LoopSourceOfTruth(self_hosted_control_plane/cloud_control_plane→server_api),hosted_control_plane_contract(dead third arm).buildStorageStatus/storageStatusLine(dataBackend,clientTransport,authority,server.*) — no mode words in any output, enforced by test.StoreTransport"local" | "cloud-http"→"sqlite" | "http";isCloudStore→isApiStore;CloudUnsupportedError→ApiUnsupportedError(typed refusal semantics unchanged).loops server status|migrate|push|pullreplaces theself-hostedandcloudgroups; both old group names remain as hidden aliases and print the same status (verified by test).loops modekeeps its name and prints the storage status.buildSelfHostedMigrationPlan→buildServerMigrationPlan, SDKplanSelfHostedMigration→planServerMigration, operation idsself-hosted-push|pull|migrate→server-push|pull|migrate, manifest schema idopen-loops.self-hosted-push-manifest/v1→open-loops.server-push-manifest/v1(wire-string change, see Behavior deltas).HASNA_LOOPS_STORAGE_MODE=self_hostedcontainer env (serve keys off the DSNs; wire behavior identical).docs/DEPLOYMENT_MODES.md→docs/STORAGE.md(rewritten); README / USAGE / RUNTIME_BOUNDARY / CUTOVER-RUNBOOK updated. Historical records (CHANGELOG, SHARED_KIT_EXTRACTION_INVENTORY, UNIFIED_PRODUCT_CONTRACT) left intact.Enumerated surfaces (deliverable)
Before: 28 src files (excl. generated kit) + 14 non-src surfaces carried mode vocabulary —
self_hosted68 lines / 17 files,self-hosted76/16,deploymentMode53/8,cloud-http26/7,hosted_control_plane10/3,DEPLOYMENT_MODE3/2.After: 0 outside the marked alias lines and the contracts seam (below), enforced by
src/lib/mode-vocabulary.test.ts— a repo-wide guard with a positive control on its own scanner, exempting onlysrc/generated/**and marked alias lines.The contracts seam (deliberately NOT touched — do not "fix" in review)
Per the coordination rule (contracts is being fixed concurrently; no
@hasna/contractsbump in this PR):hasna.contract.jsonstill declaresdeploymentModes,serviceSurfaces[].deploymentModes,storage.mode,metadata.service.deploymentModeMapping— the INSTALLED contracts schema requires them;bun run check:contractspasses against them today and the manifest is rewritten when the mode-free contracts release ships.src/generated/storage-kit/**untouched (hash-locked byscripts/check-storage-kit.mjs; regenerated from contracts)./health,/ready,/versionkeep the contract-requiredmode: "local" | "cloud"wire field, computed from storage authority exactly as before (commented as seam insrc/api/index.ts).scripts/check-contract-conformance.mjskeeps pinningHASNA_LOOPS_STORAGE_MODE=self_hosted(the installed harness's value), with a seam comment.Behavior deltas (diagnostic/wire surfaces only — flagged, not silent)
deploymentModefromloops mode --json,apiStatus(), orloops-runner status --json(deploymentkey →storage) must move to the new fields. The@hasna/loopspackage exports changed accordingly (0.x surface).loops-serve version(CLI subcommand) printedmode: "self_hosted"unconditionally; it now prints the same{status, version, mode}envelope asGET /health(local | cloud), aligning the two./version+/readyenvelopemodenow uses the samelocal | cloudmapping as/health(previously could sayself_hosted).schema: open-loops.server-push-manifest/v1andoperation: server-*. Old manifests are not re-read by the CLI (they are review artifacts), but any external tooling matching the old strings must update.STORAGE_MODE=cloudpin with API URL but no key previously producedcontrolPlane.configured=false; the collapsed rule reportsserver.configured=trueplus an explicit missing-key warning (the deleted third arm's only observable divergence).Tests
src/lib/mode.test.tsrewritten first and proven failing before implementation (rc=1; import + vocabulary-guard failures logged).src/lib/mode-vocabulary.test.ts— repo-wide retired-vocabulary guard with scanner positive control.local/self_hosted/cloud) still select the same backend — asserted in mode, store, runner, api, and cli tests; legacy CLI groups asserted to print byte-identical status.tsc --noEmitrc=0,bun run check:contractsrc=0,check:brandingrc=0,test:boundaryrc=0,check-packed-boundaryrc=0,bun pm pack --dry-runrc=0,bun run buildrc=0.bun auditfails with 6 pre-existing upstream advisories (@modelcontextprotocol/sdktransitives) — byte-identical on origin/main, not introduced here.pkill -f 'bun test'on this shared, load-76 box): 1013 pass / 11 fail, and every failure is reconciled:pr-handofffailures (templates/workflow-runnertests) — pre-existing: the identical 6 fail on a pristine origin/main worktree (evidence in task LOO3-00126).route-eventconcurrency timeout — load flake; the file passes 25/25 in isolation (rc=0).HASNA_LOOPS_API_URL) — fixed; those files re-run green (doctor 6/6, api 57/57).🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.