@cleocode/core 2026.9.24 → 2026.9.25
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/dist/audit-prune.d.ts +32 -0
- package/dist/audit-prune.d.ts.map +1 -1
- package/dist/audit-prune.js +37 -3
- package/dist/audit-prune.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/crypto.d.ts +1 -2
- package/dist/cloud/crypto.d.ts.map +1 -1
- package/dist/cloud/crypto.js +1 -11
- package/dist/cloud/crypto.js.map +1 -1
- package/dist/cloud/uuidv7.d.ts +9 -0
- package/dist/cloud/uuidv7.d.ts.map +1 -0
- package/dist/cloud/uuidv7.js +19 -0
- package/dist/cloud/uuidv7.js.map +1 -0
- package/dist/decide/bench/index.d.ts +1 -1
- package/dist/decide/bench/index.d.ts.map +1 -1
- package/dist/decide/bench/index.js +1 -1
- package/dist/decide/bench/index.js.map +1 -1
- package/dist/decide/bench/operation.d.ts +1 -1
- package/dist/decide/bench/operation.d.ts.map +1 -1
- package/dist/decide/bench/operation.js +1 -1
- package/dist/decide/bench/operation.js.map +1 -1
- package/dist/decide/bench/profiles.d.ts +50 -22
- package/dist/decide/bench/profiles.d.ts.map +1 -1
- package/dist/decide/bench/profiles.js +103 -59
- package/dist/decide/bench/profiles.js.map +1 -1
- package/dist/decide/bench/report.d.ts.map +1 -1
- package/dist/decide/bench/report.js +6 -1
- package/dist/decide/bench/report.js.map +1 -1
- package/dist/decide/bench/runner.d.ts +6 -1
- package/dist/decide/bench/runner.d.ts.map +1 -1
- package/dist/decide/bench/runner.js +1 -0
- package/dist/decide/bench/runner.js.map +1 -1
- package/dist/decide/client.d.ts.map +1 -1
- package/dist/decide/client.js +11 -5
- package/dist/decide/client.js.map +1 -1
- package/dist/decide/jev-wire.d.ts +31 -1
- package/dist/decide/jev-wire.d.ts.map +1 -1
- package/dist/decide/jev-wire.js +61 -7
- package/dist/decide/jev-wire.js.map +1 -1
- package/dist/docs/export-document.js +17181 -15536
- package/dist/docs/export-document.js.map +3 -3
- package/dist/doctor/orphan-ac-bindings.d.ts +64 -0
- package/dist/doctor/orphan-ac-bindings.d.ts.map +1 -0
- package/dist/doctor/orphan-ac-bindings.js +59 -0
- package/dist/doctor/orphan-ac-bindings.js.map +1 -0
- package/dist/doctor/projects.d.ts.map +1 -1
- package/dist/doctor/projects.js +43 -29
- package/dist/doctor/projects.js.map +1 -1
- package/dist/doctor/row-identity.d.ts +31 -0
- package/dist/doctor/row-identity.d.ts.map +1 -0
- package/dist/doctor/row-identity.js +93 -0
- package/dist/doctor/row-identity.js.map +1 -0
- package/dist/doctor/twin-collapse.d.ts +58 -2
- package/dist/doctor/twin-collapse.d.ts.map +1 -1
- package/dist/doctor/twin-collapse.js +212 -14
- package/dist/doctor/twin-collapse.js.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.js +10 -0
- package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
- package/dist/llm/plugin-facade.js +17045 -15400
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +19 -10
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manifest-identity.d.ts +171 -0
- package/dist/memory/manifest-identity.d.ts.map +1 -0
- package/dist/memory/manifest-identity.js +345 -0
- package/dist/memory/manifest-identity.js.map +1 -0
- package/dist/memory/mental-model-queue.d.ts +14 -4
- package/dist/memory/mental-model-queue.d.ts.map +1 -1
- package/dist/memory/mental-model-queue.js +64 -35
- package/dist/memory/mental-model-queue.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +47 -5
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +130 -14
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +2 -0
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/transcript-scanner.js +1 -1
- package/dist/nexus/deps.d.ts.map +1 -1
- package/dist/nexus/deps.js +7 -14
- package/dist/nexus/deps.js.map +1 -1
- package/dist/nexus/discover.d.ts.map +1 -1
- package/dist/nexus/discover.js +6 -9
- package/dist/nexus/discover.js.map +1 -1
- package/dist/nexus/git-state.d.ts.map +1 -1
- package/dist/nexus/git-state.js +10 -1
- package/dist/nexus/git-state.js.map +1 -1
- package/dist/nexus/projects-clean.d.ts.map +1 -1
- package/dist/nexus/projects-clean.js +22 -11
- package/dist/nexus/projects-clean.js.map +1 -1
- package/dist/nexus/projects-scan.d.ts.map +1 -1
- package/dist/nexus/projects-scan.js +8 -13
- package/dist/nexus/projects-scan.js.map +1 -1
- package/dist/nexus/query.d.ts.map +1 -1
- package/dist/nexus/query.js +4 -12
- package/dist/nexus/query.js.map +1 -1
- package/dist/nexus/registry-roots.d.ts +5 -2
- package/dist/nexus/registry-roots.d.ts.map +1 -1
- package/dist/nexus/registry-roots.js +8 -10
- package/dist/nexus/registry-roots.js.map +1 -1
- package/dist/nexus/registry.d.ts +18 -0
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +37 -16
- package/dist/nexus/registry.js.map +1 -1
- package/dist/orchestrate/worktree-complete.d.ts.map +1 -1
- package/dist/orchestrate/worktree-complete.js +40 -2
- package/dist/orchestrate/worktree-complete.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts +26 -0
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +62 -91
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +190 -0
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +436 -4
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/sdk/validator-ac-pull.d.ts.map +1 -1
- package/dist/sdk/validator-ac-pull.js +2 -1
- package/dist/sdk/validator-ac-pull.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +17049 -15404
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/cross-project-hygiene.d.ts +5 -0
- package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
- package/dist/sentient/cross-project-hygiene.js +7 -1
- package/dist/sentient/cross-project-hygiene.js.map +1 -1
- package/dist/sentient/propose-tick.d.ts.map +1 -1
- package/dist/sentient/propose-tick.js +2 -3
- package/dist/sentient/propose-tick.js.map +1 -1
- package/dist/sentient/stage-drift-tick.d.ts.map +1 -1
- package/dist/sentient/stage-drift-tick.js +2 -3
- package/dist/sentient/stage-drift-tick.js.map +1 -1
- package/dist/skills/manifests/research.js +2 -0
- package/dist/skills/manifests/research.js.map +1 -1
- package/dist/skills/orchestrator/validator.d.ts.map +1 -1
- package/dist/skills/orchestrator/validator.js +9 -2
- package/dist/skills/orchestrator/validator.js.map +1 -1
- package/dist/skills/validation.d.ts +4 -1
- package/dist/skills/validation.d.ts.map +1 -1
- package/dist/skills/validation.js +11 -3
- package/dist/skills/validation.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts +111 -7
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +296 -8
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/store/ac-binding-prune.d.ts +144 -0
- package/dist/store/ac-binding-prune.d.ts.map +1 -0
- package/dist/store/ac-binding-prune.js +293 -0
- package/dist/store/ac-binding-prune.js.map +1 -0
- package/dist/store/agent-registry-store.d.ts.map +1 -1
- package/dist/store/agent-registry-store.js +3 -2
- package/dist/store/agent-registry-store.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +85 -2
- package/dist/store/backup-sidecar.d.ts.map +1 -1
- package/dist/store/backup-sidecar.js +173 -1
- package/dist/store/backup-sidecar.js.map +1 -1
- package/dist/store/conduit-sqlite.d.ts.map +1 -1
- package/dist/store/conduit-sqlite.js +3 -2
- package/dist/store/conduit-sqlite.js.map +1 -1
- package/dist/store/credential-transfer.d.ts.map +1 -1
- package/dist/store/credential-transfer.js +3 -0
- package/dist/store/credential-transfer.js.map +1 -1
- package/dist/store/db-helpers.d.ts +17 -11
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +61 -38
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/display-id-alias.d.ts +529 -0
- package/dist/store/display-id-alias.d.ts.map +1 -0
- package/dist/store/display-id-alias.js +1063 -0
- package/dist/store/display-id-alias.js.map +1 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +21 -0
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/reconcile.d.ts.map +1 -1
- package/dist/store/exodus/reconcile.js +7 -2
- package/dist/store/exodus/reconcile.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts.map +1 -1
- package/dist/store/exodus/recovery.js +20 -1
- package/dist/store/exodus/recovery.js.map +1 -1
- package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
- package/dist/store/legacy-tasks-lineage.js +19 -2
- package/dist/store/legacy-tasks-lineage.js.map +1 -1
- package/dist/store/row-identity-flag.d.ts +26 -0
- package/dist/store/row-identity-flag.d.ts.map +1 -0
- package/dist/store/row-identity-flag.js +31 -0
- package/dist/store/row-identity-flag.js.map +1 -0
- package/dist/store/row-identity-registry.d.ts +61 -0
- package/dist/store/row-identity-registry.d.ts.map +1 -0
- package/dist/store/row-identity-registry.js +198 -0
- package/dist/store/row-identity-registry.js.map +1 -0
- package/dist/store/row-identity.d.ts +390 -0
- package/dist/store/row-identity.d.ts.map +1 -0
- package/dist/store/row-identity.js +1240 -0
- package/dist/store/row-identity.js.map +1 -0
- package/dist/store/safety-data-accessor.d.ts +2 -0
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +4 -0
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/schema/attachments.d.ts +1 -1
- package/dist/store/schema/audit.d.ts +4 -4
- package/dist/store/schema/background-jobs.d.ts +1 -1
- package/dist/store/schema/cleo-global/nexus.d.ts +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +2 -2
- package/dist/store/schema/cleo-project/audit.d.ts +4 -4
- package/dist/store/schema/cleo-project/docs.d.ts +1 -1
- package/dist/store/schema/cleo-project/lifecycle.d.ts +4 -4
- package/dist/store/schema/cleo-project/nexus-graph.d.ts +3 -3
- package/dist/store/schema/cleo-project/provenance-commits.d.ts +2 -2
- package/dist/store/schema/cleo-project/provenance-rest.d.ts +3 -3
- package/dist/store/schema/cleo-project/runtime.d.ts +3 -3
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts +87 -1
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core-batch2.js +35 -0
- package/dist/store/schema/cleo-project/tasks-core-batch2.js.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +183 -7
- package/dist/store/schema/cleo-project/tasks-core.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.js +52 -1
- package/dist/store/schema/cleo-project/tasks-core.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +6 -6
- package/dist/store/schema/lifecycle.d.ts +4 -4
- package/dist/store/schema/manifest.d.ts +1 -1
- package/dist/store/schema/memory-schema.d.ts +5 -5
- package/dist/store/schema/nexus-schema.d.ts +3 -3
- package/dist/store/schema/provenance/releases.d.ts +1 -1
- package/dist/store/schema/skills-schema.d.ts +2 -2
- package/dist/store/schema/tasks.d.ts +6 -6
- package/dist/store/split-brain-import.d.ts +2 -14
- package/dist/store/split-brain-import.d.ts.map +1 -1
- package/dist/store/split-brain-import.js +4 -19
- package/dist/store/split-brain-import.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +279 -2
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +544 -44
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sync/clock-store.d.ts +67 -0
- package/dist/store/sync/clock-store.d.ts.map +1 -0
- package/dist/store/sync/clock-store.js +117 -0
- package/dist/store/sync/clock-store.js.map +1 -0
- package/dist/store/sync/flags.d.ts +51 -0
- package/dist/store/sync/flags.d.ts.map +1 -0
- package/dist/store/sync/flags.js +81 -0
- package/dist/store/sync/flags.js.map +1 -0
- package/dist/store/sync/hlc.d.ts +115 -0
- package/dist/store/sync/hlc.d.ts.map +1 -0
- package/dist/store/sync/hlc.js +172 -0
- package/dist/store/sync/hlc.js.map +1 -0
- package/dist/store/sync/index.d.ts +20 -0
- package/dist/store/sync/index.d.ts.map +1 -0
- package/dist/store/sync/index.js +20 -0
- package/dist/store/sync/index.js.map +1 -0
- package/dist/store/sync/replica-registry.d.ts +85 -0
- package/dist/store/sync/replica-registry.d.ts.map +1 -0
- package/dist/store/sync/replica-registry.js +147 -0
- package/dist/store/sync/replica-registry.js.map +1 -0
- package/dist/store/sync/replica.d.ts +181 -0
- package/dist/store/sync/replica.d.ts.map +1 -0
- package/dist/store/sync/replica.js +305 -0
- package/dist/store/sync/replica.js.map +1 -0
- package/dist/store/sync/schema.d.ts +68 -0
- package/dist/store/sync/schema.d.ts.map +1 -0
- package/dist/store/sync/schema.js +130 -0
- package/dist/store/sync/schema.js.map +1 -0
- package/dist/store/sync/skew-hold.d.ts +71 -0
- package/dist/store/sync/skew-hold.d.ts.map +1 -0
- package/dist/store/sync/skew-hold.js +89 -0
- package/dist/store/sync/skew-hold.js.map +1 -0
- package/dist/store/table-classification.d.ts +10 -8
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +93 -25
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +9 -1
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/twin-collapse.d.ts +113 -2
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +247 -19
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +2 -0
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +4 -0
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +241 -49
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +3 -0
- package/dist/system/health.js.map +1 -1
- package/dist/system/project-health.d.ts +9 -2
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +8 -3
- package/dist/system/project-health.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +3 -0
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/ac-table.d.ts +35 -7
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +119 -14
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +48 -2
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/backfill-child-projections.js +47 -6
- package/dist/tasks/backfill-child-projections.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +6 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/completion-evaluation.d.ts.map +1 -1
- package/dist/tasks/completion-evaluation.js +3 -0
- package/dist/tasks/completion-evaluation.js.map +1 -1
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +14 -3
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/validation/compliance.d.ts +5 -1
- package/dist/validation/compliance.d.ts.map +1 -1
- package/dist/validation/compliance.js +24 -3
- package/dist/validation/compliance.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/protocol-common.d.ts +47 -2
- package/dist/validation/protocol-common.d.ts.map +1 -1
- package/dist/validation/protocol-common.js +86 -21
- package/dist/validation/protocol-common.js.map +1 -1
- package/migrations/README.md +12 -0
- package/migrations/drizzle-cleo-project/20260928120000_t12341-row-uids/migration.sql +169 -0
- package/migrations/sync-journal/20260929140000_t12342-sync-clock/migration.sql +42 -0
- package/package.json +13 -13
- package/templates/CLEO-REFERENCE.md +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flags.js","sourceRoot":"","sources":["../../../src/store/sync/flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEzD,2CAA2C;AAC3C,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,cAAc;IACd,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;CACL,CAAC;AAQX,MAAM,OAAO,GAAkB,MAAM,CAAC,MAAM,CAC1C,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAA8B,CACnF,CAAC;AAEF,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,OAAO,aAAa,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;QAAE,OAAO,OAAO,CAAC;IAChD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN,mDAAmD,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC3F;SACA,GAAG,CAAC,GAAG,UAAU,CAA4C,CAAC;IACjE,MAAM,GAAG,GAA8B,EAAE,GAAG,OAAO,EAAE,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC;IACnD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,OAAO,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,EAAgB,EAChB,IAAc,EACd,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,EAAgB,EAChB,IAAc,EACd,EAAW,EACX,UAA+C,EAAE;IAEjD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;IAC9E,IAAI,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,aAAa,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACjD,gBAAgB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,EAAE,CAAC,OAAO,CACR,mEAAmE;QACjE,yFAAyF,CAC5F,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid logical clock (HLC) for the change journal.
|
|
3
|
+
*
|
|
4
|
+
* Wire format (fixed by the cloud contract, `Hlc` in `@cleocode/contracts`):
|
|
5
|
+
* `PPPPPPPPPPPPP-CCCCCC-<replica uuid>`, where `P` is physical milliseconds
|
|
6
|
+
* since the Unix epoch zero-padded to 13 digits, `C` is the logical counter
|
|
7
|
+
* zero-padded to 6 digits, and the replica id is the lowercase UUIDv7 of the
|
|
8
|
+
* store that issued the timestamp. Tuple order `(phys, ctr, replica)` is
|
|
9
|
+
* exactly the lexical order of the encoded string, so SQL `ORDER BY hlc` and
|
|
10
|
+
* `MAX(hlc)` are correct.
|
|
11
|
+
*
|
|
12
|
+
* This module is pure. The persisted clock row lives in `clock-store.ts`.
|
|
13
|
+
*
|
|
14
|
+
* It never reads time from a uid (journal spec R7, T12341 §5.2): a backfilled
|
|
15
|
+
* uid's timestamp is the row's birth, or 0, not the time of a change. The
|
|
16
|
+
* module therefore imports nothing, and a test pins that.
|
|
17
|
+
*
|
|
18
|
+
* @task T12342
|
|
19
|
+
* @module store/sync/hlc
|
|
20
|
+
*/
|
|
21
|
+
/** A decoded HLC. */
|
|
22
|
+
export interface Hlc {
|
|
23
|
+
/** Physical milliseconds since the Unix epoch. */
|
|
24
|
+
readonly phys: number;
|
|
25
|
+
/** Logical counter, 0 to {@link MAX_COUNTER}. */
|
|
26
|
+
readonly ctr: number;
|
|
27
|
+
/** Lowercase UUID of the issuing replica. */
|
|
28
|
+
readonly replica: string;
|
|
29
|
+
}
|
|
30
|
+
/** The largest counter the 6-digit field can hold. */
|
|
31
|
+
export declare const MAX_COUNTER = 999999;
|
|
32
|
+
/** The largest physical time the 13-digit field can hold (year 2286). */
|
|
33
|
+
export declare const MAX_PHYS = 9999999999999;
|
|
34
|
+
/**
|
|
35
|
+
* Default skew bound: a received HLC more than this far ahead of the local
|
|
36
|
+
* wall clock is held, never applied or dropped (journal spec §1.3, Q6).
|
|
37
|
+
*/
|
|
38
|
+
export declare const MAX_DRIFT_MS: number;
|
|
39
|
+
/** Thrown for a malformed HLC, replica id or physical time. */
|
|
40
|
+
export declare class HlcError extends Error {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Encode an HLC to its wire form.
|
|
45
|
+
*
|
|
46
|
+
* @param h - The clock value.
|
|
47
|
+
* @returns `PPPPPPPPPPPPP-CCCCCC-<replica>`.
|
|
48
|
+
* @throws {HlcError} When a part is out of range or the replica id is malformed.
|
|
49
|
+
*/
|
|
50
|
+
export declare function encodeHlc(h: Hlc): string;
|
|
51
|
+
/**
|
|
52
|
+
* Decode an HLC from its wire form.
|
|
53
|
+
*
|
|
54
|
+
* @param s - An encoded HLC.
|
|
55
|
+
* @returns The decoded parts.
|
|
56
|
+
* @throws {HlcError} When `s` is not an encoded HLC.
|
|
57
|
+
*/
|
|
58
|
+
export declare function parseHlc(s: string): Hlc;
|
|
59
|
+
/**
|
|
60
|
+
* Total order on HLCs: `(phys, ctr, replica)`. The replica tiebreak is
|
|
61
|
+
* arbitrary but deterministic, so every replica orders identically.
|
|
62
|
+
*
|
|
63
|
+
* @returns Negative, zero or positive, like a sort comparator.
|
|
64
|
+
*/
|
|
65
|
+
export declare function compareHlc(a: Hlc, b: Hlc): number;
|
|
66
|
+
/**
|
|
67
|
+
* The clock of a replica that has issued nothing yet.
|
|
68
|
+
*
|
|
69
|
+
* @param replica - The replica id.
|
|
70
|
+
*/
|
|
71
|
+
export declare function genesisHlc(replica: string): Hlc;
|
|
72
|
+
/**
|
|
73
|
+
* Issue the next local HLC.
|
|
74
|
+
*
|
|
75
|
+
* `phys' = max(last.phys, wall)`; the counter increments when the physical
|
|
76
|
+
* part did not move and resets to 0 when it did; a counter past
|
|
77
|
+
* {@link MAX_COUNTER} carries into `phys`. HLCs therefore increase strictly
|
|
78
|
+
* per replica, even when the wall clock steps backwards or a batch issues
|
|
79
|
+
* 10^6 timestamps in one millisecond.
|
|
80
|
+
*
|
|
81
|
+
* @param last - The replica's current clock.
|
|
82
|
+
* @param wallMs - The physical candidate (a capture's `at_ms`, or `Date.now()`).
|
|
83
|
+
* @returns The new clock value, which is also the issued timestamp.
|
|
84
|
+
*/
|
|
85
|
+
export declare function tick(last: Hlc, wallMs: number): Hlc;
|
|
86
|
+
/**
|
|
87
|
+
* Merge a received HLC into the local clock (standard HLC receive rule).
|
|
88
|
+
*
|
|
89
|
+
* `phys' = max(last.phys, remote.phys, now)`. The counter is
|
|
90
|
+
* `max(last.ctr, remote.ctr) + 1` when all three agree, `last.ctr + 1` or
|
|
91
|
+
* `remote.ctr + 1` when `phys'` equals only that one, and 0 otherwise. The
|
|
92
|
+
* result keeps the LOCAL replica id.
|
|
93
|
+
*
|
|
94
|
+
* Callers apply the skew bound first ({@link isWithinSkew}): a remote HLC too
|
|
95
|
+
* far ahead must be held, never merged.
|
|
96
|
+
*
|
|
97
|
+
* @param last - The local clock.
|
|
98
|
+
* @param remote - The received timestamp.
|
|
99
|
+
* @param nowMs - The local wall clock.
|
|
100
|
+
* @returns The new local clock value.
|
|
101
|
+
*/
|
|
102
|
+
export declare function receive(last: Hlc, remote: Hlc, nowMs: number): Hlc;
|
|
103
|
+
/**
|
|
104
|
+
* Whether a received HLC is inside the skew bound: `phys <= now + maxDrift`.
|
|
105
|
+
*
|
|
106
|
+
* @param remote - The received timestamp.
|
|
107
|
+
* @param nowMs - The local wall clock.
|
|
108
|
+
* @param maxDriftMs - The bound; defaults to {@link MAX_DRIFT_MS}.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isWithinSkew(remote: Hlc, nowMs: number, maxDriftMs?: number): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The larger of two HLCs (by {@link compareHlc}).
|
|
113
|
+
*/
|
|
114
|
+
export declare function maxHlc(a: Hlc, b: Hlc): Hlc;
|
|
115
|
+
//# sourceMappingURL=hlc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.d.ts","sourceRoot":"","sources":["../../../src/store/sync/hlc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qBAAqB;AACrB,MAAM,WAAW,GAAG;IAClB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,sDAAsD;AACtD,eAAO,MAAM,WAAW,SAAU,CAAC;AAEnC,yEAAyE;AACzE,eAAO,MAAM,QAAQ,gBAAoB,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAgB,CAAC;AAK1C,+DAA+D;AAC/D,qBAAa,QAAS,SAAQ,KAAK;gBACrB,OAAO,EAAE,MAAM;CAI5B;AAcD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAGxC;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAMvC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAG/C;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAMnD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CASlE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAe,GAAG,OAAO,CAE3F;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,GAAG,CAE1C"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid logical clock (HLC) for the change journal.
|
|
3
|
+
*
|
|
4
|
+
* Wire format (fixed by the cloud contract, `Hlc` in `@cleocode/contracts`):
|
|
5
|
+
* `PPPPPPPPPPPPP-CCCCCC-<replica uuid>`, where `P` is physical milliseconds
|
|
6
|
+
* since the Unix epoch zero-padded to 13 digits, `C` is the logical counter
|
|
7
|
+
* zero-padded to 6 digits, and the replica id is the lowercase UUIDv7 of the
|
|
8
|
+
* store that issued the timestamp. Tuple order `(phys, ctr, replica)` is
|
|
9
|
+
* exactly the lexical order of the encoded string, so SQL `ORDER BY hlc` and
|
|
10
|
+
* `MAX(hlc)` are correct.
|
|
11
|
+
*
|
|
12
|
+
* This module is pure. The persisted clock row lives in `clock-store.ts`.
|
|
13
|
+
*
|
|
14
|
+
* It never reads time from a uid (journal spec R7, T12341 §5.2): a backfilled
|
|
15
|
+
* uid's timestamp is the row's birth, or 0, not the time of a change. The
|
|
16
|
+
* module therefore imports nothing, and a test pins that.
|
|
17
|
+
*
|
|
18
|
+
* @task T12342
|
|
19
|
+
* @module store/sync/hlc
|
|
20
|
+
*/
|
|
21
|
+
/** The largest counter the 6-digit field can hold. */
|
|
22
|
+
export const MAX_COUNTER = 999_999;
|
|
23
|
+
/** The largest physical time the 13-digit field can hold (year 2286). */
|
|
24
|
+
export const MAX_PHYS = 9_999_999_999_999;
|
|
25
|
+
/**
|
|
26
|
+
* Default skew bound: a received HLC more than this far ahead of the local
|
|
27
|
+
* wall clock is held, never applied or dropped (journal spec §1.3, Q6).
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_DRIFT_MS = 5 * 60 * 1000;
|
|
30
|
+
const HLC_RE = /^(\d{13})-(\d{6})-([0-9a-f-]{36})$/;
|
|
31
|
+
const REPLICA_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
32
|
+
/** Thrown for a malformed HLC, replica id or physical time. */
|
|
33
|
+
export class HlcError extends Error {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'HlcError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function assertParts(phys, ctr, replica) {
|
|
40
|
+
if (!Number.isSafeInteger(phys) || phys < 0 || phys > MAX_PHYS) {
|
|
41
|
+
throw new HlcError(`HLC physical time out of range: ${phys}`);
|
|
42
|
+
}
|
|
43
|
+
if (!Number.isSafeInteger(ctr) || ctr < 0 || ctr > MAX_COUNTER) {
|
|
44
|
+
throw new HlcError(`HLC counter out of range: ${ctr}`);
|
|
45
|
+
}
|
|
46
|
+
if (!REPLICA_RE.test(replica)) {
|
|
47
|
+
throw new HlcError(`HLC replica id is not a lowercase UUID: ${replica}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Encode an HLC to its wire form.
|
|
52
|
+
*
|
|
53
|
+
* @param h - The clock value.
|
|
54
|
+
* @returns `PPPPPPPPPPPPP-CCCCCC-<replica>`.
|
|
55
|
+
* @throws {HlcError} When a part is out of range or the replica id is malformed.
|
|
56
|
+
*/
|
|
57
|
+
export function encodeHlc(h) {
|
|
58
|
+
assertParts(h.phys, h.ctr, h.replica);
|
|
59
|
+
return `${String(h.phys).padStart(13, '0')}-${String(h.ctr).padStart(6, '0')}-${h.replica}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decode an HLC from its wire form.
|
|
63
|
+
*
|
|
64
|
+
* @param s - An encoded HLC.
|
|
65
|
+
* @returns The decoded parts.
|
|
66
|
+
* @throws {HlcError} When `s` is not an encoded HLC.
|
|
67
|
+
*/
|
|
68
|
+
export function parseHlc(s) {
|
|
69
|
+
const m = HLC_RE.exec(s);
|
|
70
|
+
if (!m)
|
|
71
|
+
throw new HlcError(`not an encoded HLC: ${s}`);
|
|
72
|
+
const h = { phys: Number(m[1]), ctr: Number(m[2]), replica: m[3] };
|
|
73
|
+
assertParts(h.phys, h.ctr, h.replica);
|
|
74
|
+
return h;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Total order on HLCs: `(phys, ctr, replica)`. The replica tiebreak is
|
|
78
|
+
* arbitrary but deterministic, so every replica orders identically.
|
|
79
|
+
*
|
|
80
|
+
* @returns Negative, zero or positive, like a sort comparator.
|
|
81
|
+
*/
|
|
82
|
+
export function compareHlc(a, b) {
|
|
83
|
+
if (a.phys !== b.phys)
|
|
84
|
+
return a.phys < b.phys ? -1 : 1;
|
|
85
|
+
if (a.ctr !== b.ctr)
|
|
86
|
+
return a.ctr < b.ctr ? -1 : 1;
|
|
87
|
+
if (a.replica === b.replica)
|
|
88
|
+
return 0;
|
|
89
|
+
return a.replica < b.replica ? -1 : 1;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The clock of a replica that has issued nothing yet.
|
|
93
|
+
*
|
|
94
|
+
* @param replica - The replica id.
|
|
95
|
+
*/
|
|
96
|
+
export function genesisHlc(replica) {
|
|
97
|
+
assertParts(0, 0, replica);
|
|
98
|
+
return { phys: 0, ctr: 0, replica };
|
|
99
|
+
}
|
|
100
|
+
/** Carry a counter overflow into the physical part. */
|
|
101
|
+
function carry(phys, ctr, replica) {
|
|
102
|
+
if (ctr > MAX_COUNTER)
|
|
103
|
+
return { phys: phys + 1, ctr: 0, replica };
|
|
104
|
+
return { phys, ctr, replica };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Issue the next local HLC.
|
|
108
|
+
*
|
|
109
|
+
* `phys' = max(last.phys, wall)`; the counter increments when the physical
|
|
110
|
+
* part did not move and resets to 0 when it did; a counter past
|
|
111
|
+
* {@link MAX_COUNTER} carries into `phys`. HLCs therefore increase strictly
|
|
112
|
+
* per replica, even when the wall clock steps backwards or a batch issues
|
|
113
|
+
* 10^6 timestamps in one millisecond.
|
|
114
|
+
*
|
|
115
|
+
* @param last - The replica's current clock.
|
|
116
|
+
* @param wallMs - The physical candidate (a capture's `at_ms`, or `Date.now()`).
|
|
117
|
+
* @returns The new clock value, which is also the issued timestamp.
|
|
118
|
+
*/
|
|
119
|
+
export function tick(last, wallMs) {
|
|
120
|
+
const wall = Math.floor(wallMs);
|
|
121
|
+
assertParts(Math.max(0, wall), 0, last.replica);
|
|
122
|
+
const phys = Math.max(last.phys, wall);
|
|
123
|
+
const ctr = phys === last.phys ? last.ctr + 1 : 0;
|
|
124
|
+
return carry(phys, ctr, last.replica);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Merge a received HLC into the local clock (standard HLC receive rule).
|
|
128
|
+
*
|
|
129
|
+
* `phys' = max(last.phys, remote.phys, now)`. The counter is
|
|
130
|
+
* `max(last.ctr, remote.ctr) + 1` when all three agree, `last.ctr + 1` or
|
|
131
|
+
* `remote.ctr + 1` when `phys'` equals only that one, and 0 otherwise. The
|
|
132
|
+
* result keeps the LOCAL replica id.
|
|
133
|
+
*
|
|
134
|
+
* Callers apply the skew bound first ({@link isWithinSkew}): a remote HLC too
|
|
135
|
+
* far ahead must be held, never merged.
|
|
136
|
+
*
|
|
137
|
+
* @param last - The local clock.
|
|
138
|
+
* @param remote - The received timestamp.
|
|
139
|
+
* @param nowMs - The local wall clock.
|
|
140
|
+
* @returns The new local clock value.
|
|
141
|
+
*/
|
|
142
|
+
export function receive(last, remote, nowMs) {
|
|
143
|
+
const now = Math.floor(nowMs);
|
|
144
|
+
const phys = Math.max(last.phys, remote.phys, now);
|
|
145
|
+
let ctr;
|
|
146
|
+
if (phys === last.phys && phys === remote.phys)
|
|
147
|
+
ctr = Math.max(last.ctr, remote.ctr) + 1;
|
|
148
|
+
else if (phys === last.phys)
|
|
149
|
+
ctr = last.ctr + 1;
|
|
150
|
+
else if (phys === remote.phys)
|
|
151
|
+
ctr = remote.ctr + 1;
|
|
152
|
+
else
|
|
153
|
+
ctr = 0;
|
|
154
|
+
return carry(phys, ctr, last.replica);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether a received HLC is inside the skew bound: `phys <= now + maxDrift`.
|
|
158
|
+
*
|
|
159
|
+
* @param remote - The received timestamp.
|
|
160
|
+
* @param nowMs - The local wall clock.
|
|
161
|
+
* @param maxDriftMs - The bound; defaults to {@link MAX_DRIFT_MS}.
|
|
162
|
+
*/
|
|
163
|
+
export function isWithinSkew(remote, nowMs, maxDriftMs = MAX_DRIFT_MS) {
|
|
164
|
+
return remote.phys <= Math.floor(nowMs) + maxDriftMs;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The larger of two HLCs (by {@link compareHlc}).
|
|
168
|
+
*/
|
|
169
|
+
export function maxHlc(a, b) {
|
|
170
|
+
return compareHlc(a, b) >= 0 ? a : b;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=hlc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.js","sourceRoot":"","sources":["../../../src/store/sync/hlc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change journal's foundation (T12342, slice S1): the hybrid logical
|
|
3
|
+
* clock, its persisted row, the store-level `sync.*` flags, the lazily
|
|
4
|
+
* applied sync schema, replica binding and the device replica registry.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here runs on a hot path yet, and every write is behind a flag that
|
|
7
|
+
* is off by default. Capture (S2), sealing (S3), push (S4) and pull (S5)
|
|
8
|
+
* build on these modules.
|
|
9
|
+
*
|
|
10
|
+
* @task T12342
|
|
11
|
+
* @module store/sync
|
|
12
|
+
*/
|
|
13
|
+
export * from './clock-store.js';
|
|
14
|
+
export * from './flags.js';
|
|
15
|
+
export * from './hlc.js';
|
|
16
|
+
export * from './replica.js';
|
|
17
|
+
export * from './replica-registry.js';
|
|
18
|
+
export * from './schema.js';
|
|
19
|
+
export * from './skew-hold.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/store/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change journal's foundation (T12342, slice S1): the hybrid logical
|
|
3
|
+
* clock, its persisted row, the store-level `sync.*` flags, the lazily
|
|
4
|
+
* applied sync schema, replica binding and the device replica registry.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here runs on a hot path yet, and every write is behind a flag that
|
|
7
|
+
* is off by default. Capture (S2), sealing (S3), push (S4) and pull (S5)
|
|
8
|
+
* build on these modules.
|
|
9
|
+
*
|
|
10
|
+
* @task T12342
|
|
11
|
+
* @module store/sync
|
|
12
|
+
*/
|
|
13
|
+
export * from './clock-store.js';
|
|
14
|
+
export * from './flags.js';
|
|
15
|
+
export * from './hlc.js';
|
|
16
|
+
export * from './replica.js';
|
|
17
|
+
export * from './replica-registry.js';
|
|
18
|
+
export * from './schema.js';
|
|
19
|
+
export * from './skew-hold.js';
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/store/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The device's replica registry (journal spec §1.5, N6).
|
|
3
|
+
*
|
|
4
|
+
* A JSON file of device state, never inside a store and never synced: its
|
|
5
|
+
* path comes from `@cleocode/paths` ({@link resolveSyncReplicaRegistryPath}),
|
|
6
|
+
* in the machine-local state dir, keyed by the stable device id. Per replica
|
|
7
|
+
* id it records the store's nonce, where the store lives, and the device-side
|
|
8
|
+
* high-water mark (`hwm`): the last `replicaSeq` this device persisted per
|
|
9
|
+
* stream. A store whose own persisted sequence is BELOW the hwm was rolled
|
|
10
|
+
* back (restored into the same file), which the open pass turns into a
|
|
11
|
+
* rebind.
|
|
12
|
+
*
|
|
13
|
+
* Losing the file (a new device, a wiped state dir) or finding it unreadable
|
|
14
|
+
* is treated as an empty registry: the open pass re-registers the store and
|
|
15
|
+
* does not rebind for that alone. The server's sequence check (S4) is the
|
|
16
|
+
* authority that catches what a lost registry cannot.
|
|
17
|
+
*
|
|
18
|
+
* Writes use the file-then-rename pattern, so a crash leaves the old or the
|
|
19
|
+
* new file, never a torn one.
|
|
20
|
+
*
|
|
21
|
+
* @task T12342
|
|
22
|
+
* @module store/sync/replica-registry
|
|
23
|
+
*/
|
|
24
|
+
/** One replica this device has bound. */
|
|
25
|
+
export interface ReplicaRegistryEntry {
|
|
26
|
+
/** The nonce minted with the replica and stored in its `_sync_replica` row. */
|
|
27
|
+
readonly nonce: string;
|
|
28
|
+
/** `project` or `global`. */
|
|
29
|
+
readonly scope: string;
|
|
30
|
+
/** Real path of the store file when last opened here. */
|
|
31
|
+
readonly dbRealpath: string;
|
|
32
|
+
/** Last persisted `replicaSeq` per stream. */
|
|
33
|
+
readonly hwm: Readonly<Record<string, number>>;
|
|
34
|
+
/** When this replica was retired on this device, if it was. */
|
|
35
|
+
readonly retiredAt?: string;
|
|
36
|
+
/** The replica that replaced it. */
|
|
37
|
+
readonly successor?: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
}
|
|
40
|
+
/** The registry file. */
|
|
41
|
+
export interface ReplicaRegistryFile {
|
|
42
|
+
readonly version: 1;
|
|
43
|
+
readonly deviceId: string;
|
|
44
|
+
readonly replicas: Readonly<Record<string, ReplicaRegistryEntry>>;
|
|
45
|
+
}
|
|
46
|
+
/** Reads and writes one device's registry file. */
|
|
47
|
+
export declare class ReplicaRegistry {
|
|
48
|
+
readonly path: string;
|
|
49
|
+
readonly deviceId: string;
|
|
50
|
+
/**
|
|
51
|
+
* @param path - The registry file.
|
|
52
|
+
* @param deviceId - The device the file belongs to.
|
|
53
|
+
*/
|
|
54
|
+
constructor(path: string, deviceId: string);
|
|
55
|
+
/** The registry at its canonical path for `deviceId`. */
|
|
56
|
+
static forDevice(deviceId: string): ReplicaRegistry;
|
|
57
|
+
private empty;
|
|
58
|
+
/**
|
|
59
|
+
* The registry contents. A missing, unreadable or foreign file (another
|
|
60
|
+
* device id) reads as empty.
|
|
61
|
+
*/
|
|
62
|
+
read(): ReplicaRegistryFile;
|
|
63
|
+
/** One replica's entry, if registered. */
|
|
64
|
+
get(replicaId: string): ReplicaRegistryEntry | undefined;
|
|
65
|
+
/** Replace the whole file (file-then-rename). */
|
|
66
|
+
write(file: ReplicaRegistryFile): void;
|
|
67
|
+
/**
|
|
68
|
+
* Insert or update one replica's entry. The hwm only moves up.
|
|
69
|
+
*
|
|
70
|
+
* @returns Whether the file changed.
|
|
71
|
+
*/
|
|
72
|
+
upsert(replicaId: string, patch: Omit<ReplicaRegistryEntry, 'updatedAt' | 'hwm'> & {
|
|
73
|
+
hwm?: Readonly<Record<string, number>>;
|
|
74
|
+
}, now?: Date): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Advance a replica's hwm for one stream after a segment persist committed
|
|
77
|
+
* (S4 calls this right after the store transaction, §2.8). If the process
|
|
78
|
+
* dies in between, the store is ahead of the registry, which is the safe
|
|
79
|
+
* direction: only a store BEHIND the registry rebinds.
|
|
80
|
+
*
|
|
81
|
+
* @returns Whether the file changed.
|
|
82
|
+
*/
|
|
83
|
+
advanceHwm(replicaId: string, stream: string, seq: number, now?: Date): boolean;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=replica-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replica-registry.d.ts","sourceRoot":"","sources":["../../../src/store/sync/replica-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,yBAAyB;AACzB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CACnE;AAED,mDAAmD;AACnD,qBAAa,eAAe;IAMxB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAN3B;;;OAGG;gBAEQ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM;IAG3B,yDAAyD;IACzD,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe;IAInD,OAAO,CAAC,KAAK;IAIb;;;OAGG;IACH,IAAI,IAAI,mBAAmB;IAuB3B,0CAA0C;IAC1C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAKxD,iDAAiD;IACjD,KAAK,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI;IAYtC;;;;OAIG;IACH,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG;QACvD,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACxC,EACD,GAAG,GAAE,IAAiB,GACrB,OAAO;IAwBV;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO;CAO5F"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The device's replica registry (journal spec §1.5, N6).
|
|
3
|
+
*
|
|
4
|
+
* A JSON file of device state, never inside a store and never synced: its
|
|
5
|
+
* path comes from `@cleocode/paths` ({@link resolveSyncReplicaRegistryPath}),
|
|
6
|
+
* in the machine-local state dir, keyed by the stable device id. Per replica
|
|
7
|
+
* id it records the store's nonce, where the store lives, and the device-side
|
|
8
|
+
* high-water mark (`hwm`): the last `replicaSeq` this device persisted per
|
|
9
|
+
* stream. A store whose own persisted sequence is BELOW the hwm was rolled
|
|
10
|
+
* back (restored into the same file), which the open pass turns into a
|
|
11
|
+
* rebind.
|
|
12
|
+
*
|
|
13
|
+
* Losing the file (a new device, a wiped state dir) or finding it unreadable
|
|
14
|
+
* is treated as an empty registry: the open pass re-registers the store and
|
|
15
|
+
* does not rebind for that alone. The server's sequence check (S4) is the
|
|
16
|
+
* authority that catches what a lost registry cannot.
|
|
17
|
+
*
|
|
18
|
+
* Writes use the file-then-rename pattern, so a crash leaves the old or the
|
|
19
|
+
* new file, never a torn one.
|
|
20
|
+
*
|
|
21
|
+
* @task T12342
|
|
22
|
+
* @module store/sync/replica-registry
|
|
23
|
+
*/
|
|
24
|
+
import { randomBytes } from 'node:crypto';
|
|
25
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { dirname } from 'node:path';
|
|
27
|
+
import { resolveSyncReplicaRegistryPath } from '@cleocode/paths';
|
|
28
|
+
/** Reads and writes one device's registry file. */
|
|
29
|
+
export class ReplicaRegistry {
|
|
30
|
+
path;
|
|
31
|
+
deviceId;
|
|
32
|
+
/**
|
|
33
|
+
* @param path - The registry file.
|
|
34
|
+
* @param deviceId - The device the file belongs to.
|
|
35
|
+
*/
|
|
36
|
+
constructor(path, deviceId) {
|
|
37
|
+
this.path = path;
|
|
38
|
+
this.deviceId = deviceId;
|
|
39
|
+
}
|
|
40
|
+
/** The registry at its canonical path for `deviceId`. */
|
|
41
|
+
static forDevice(deviceId) {
|
|
42
|
+
return new ReplicaRegistry(resolveSyncReplicaRegistryPath(deviceId), deviceId);
|
|
43
|
+
}
|
|
44
|
+
empty() {
|
|
45
|
+
return { version: 1, deviceId: this.deviceId, replicas: {} };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The registry contents. A missing, unreadable or foreign file (another
|
|
49
|
+
* device id) reads as empty.
|
|
50
|
+
*/
|
|
51
|
+
read() {
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = readFileSync(this.path, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return this.empty();
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (parsed.version !== 1 ||
|
|
62
|
+
parsed.deviceId !== this.deviceId ||
|
|
63
|
+
typeof parsed.replicas !== 'object' ||
|
|
64
|
+
parsed.replicas === null) {
|
|
65
|
+
return this.empty();
|
|
66
|
+
}
|
|
67
|
+
return { version: 1, deviceId: this.deviceId, replicas: parsed.replicas };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return this.empty();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** One replica's entry, if registered. */
|
|
74
|
+
get(replicaId) {
|
|
75
|
+
const replicas = this.read().replicas;
|
|
76
|
+
return Object.hasOwn(replicas, replicaId) ? replicas[replicaId] : undefined;
|
|
77
|
+
}
|
|
78
|
+
/** Replace the whole file (file-then-rename). */
|
|
79
|
+
write(file) {
|
|
80
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
81
|
+
const tmp = `${this.path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
|
|
82
|
+
try {
|
|
83
|
+
writeFileSync(tmp, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
|
|
84
|
+
renameSync(tmp, this.path);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
rmSync(tmp, { force: true });
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Insert or update one replica's entry. The hwm only moves up.
|
|
93
|
+
*
|
|
94
|
+
* @returns Whether the file changed.
|
|
95
|
+
*/
|
|
96
|
+
upsert(replicaId, patch, now = new Date()) {
|
|
97
|
+
const file = this.read();
|
|
98
|
+
const prev = Object.hasOwn(file.replicas, replicaId) ? file.replicas[replicaId] : undefined;
|
|
99
|
+
const hwm = { ...(prev?.hwm ?? {}) };
|
|
100
|
+
for (const [stream, seq] of Object.entries(patch.hwm ?? {})) {
|
|
101
|
+
hwm[stream] = Math.max(hwm[stream] ?? 0, seq);
|
|
102
|
+
}
|
|
103
|
+
const next = {
|
|
104
|
+
nonce: patch.nonce,
|
|
105
|
+
scope: patch.scope,
|
|
106
|
+
dbRealpath: patch.dbRealpath,
|
|
107
|
+
hwm,
|
|
108
|
+
...(patch.retiredAt !== undefined ? { retiredAt: patch.retiredAt } : {}),
|
|
109
|
+
...(patch.successor !== undefined ? { successor: patch.successor } : {}),
|
|
110
|
+
updatedAt: prev?.updatedAt ?? now.toISOString(),
|
|
111
|
+
};
|
|
112
|
+
if (prev && sameEntry(prev, next))
|
|
113
|
+
return false;
|
|
114
|
+
this.write({
|
|
115
|
+
...file,
|
|
116
|
+
replicas: { ...file.replicas, [replicaId]: { ...next, updatedAt: now.toISOString() } },
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Advance a replica's hwm for one stream after a segment persist committed
|
|
122
|
+
* (S4 calls this right after the store transaction, §2.8). If the process
|
|
123
|
+
* dies in between, the store is ahead of the registry, which is the safe
|
|
124
|
+
* direction: only a store BEHIND the registry rebinds.
|
|
125
|
+
*
|
|
126
|
+
* @returns Whether the file changed.
|
|
127
|
+
*/
|
|
128
|
+
advanceHwm(replicaId, stream, seq, now = new Date()) {
|
|
129
|
+
const prev = this.get(replicaId);
|
|
130
|
+
if (!prev) {
|
|
131
|
+
throw new Error(`replica ${replicaId} is not registered on device ${this.deviceId}`);
|
|
132
|
+
}
|
|
133
|
+
return this.upsert(replicaId, { ...prev, hwm: { [stream]: seq } }, now);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function sameEntry(a, b) {
|
|
137
|
+
const keys = new Set([...Object.keys(a.hwm), ...Object.keys(b.hwm)]);
|
|
138
|
+
for (const k of keys)
|
|
139
|
+
if (a.hwm[k] !== b.hwm[k])
|
|
140
|
+
return false;
|
|
141
|
+
return (a.nonce === b.nonce &&
|
|
142
|
+
a.scope === b.scope &&
|
|
143
|
+
a.dbRealpath === b.dbRealpath &&
|
|
144
|
+
a.retiredAt === b.retiredAt &&
|
|
145
|
+
a.successor === b.successor);
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=replica-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replica-registry.js","sourceRoot":"","sources":["../../../src/store/sync/replica-registry.ts"],"names":[],"mappings":"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"}
|