@amalgm/shell 0.1.164-preview.36072132286 → 0.1.164-preview.36809057014
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/PURPOSE.md +642 -128
- package/README.md +10 -8
- package/dist/accepted-tail.d.ts +26 -0
- package/dist/accepted-tail.js +37 -0
- package/dist/accepted-tail.js.map +1 -0
- package/dist/agent-execution.d.ts +16 -1
- package/dist/agent-execution.js +13 -5
- package/dist/agent-execution.js.map +1 -1
- package/dist/apply-cleanup.d.ts +28 -0
- package/dist/apply-cleanup.js +166 -0
- package/dist/apply-cleanup.js.map +1 -0
- package/dist/apply-recovery.d.ts +47 -0
- package/dist/apply-recovery.js +99 -0
- package/dist/apply-recovery.js.map +1 -0
- package/dist/apply-rows.d.ts +72 -0
- package/dist/apply-rows.js +150 -0
- package/dist/apply-rows.js.map +1 -0
- package/dist/apply-saves.d.ts +85 -0
- package/dist/apply-saves.js +238 -0
- package/dist/apply-saves.js.map +1 -0
- package/dist/boot.js +2 -1
- package/dist/boot.js.map +1 -1
- package/dist/capture-work.d.ts +8 -0
- package/dist/capture-work.js +67 -0
- package/dist/capture-work.js.map +1 -0
- package/dist/cargo-group.d.ts +45 -0
- package/dist/cargo-group.js +88 -0
- package/dist/cargo-group.js.map +1 -0
- package/dist/channel-session.d.ts +4 -2
- package/dist/channel-session.js +4 -3
- package/dist/channel-session.js.map +1 -1
- package/dist/chat-composition.d.ts +2 -0
- package/dist/chat-composition.js +1 -0
- package/dist/chat-composition.js.map +1 -1
- package/dist/cli.js +20 -1
- package/dist/cli.js.map +1 -1
- package/dist/construct-apply-host.d.ts +4 -2
- package/dist/construct-apply-host.js +8 -3
- package/dist/construct-apply-host.js.map +1 -1
- package/dist/construct-door.js +1 -1
- package/dist/construct-door.js.map +1 -1
- package/dist/construct-ledger.d.ts +6 -1
- package/dist/construct-ledger.js +21 -33
- package/dist/construct-ledger.js.map +1 -1
- package/dist/content-cache-collection.d.ts +18 -0
- package/dist/content-cache-collection.js +99 -0
- package/dist/content-cache-collection.js.map +1 -0
- package/dist/content-cache-host.d.ts +83 -17
- package/dist/content-cache-host.js +335 -95
- package/dist/content-cache-host.js.map +1 -1
- package/dist/content-cache-namespace.d.ts +59 -0
- package/dist/content-cache-namespace.js +210 -0
- package/dist/content-cache-namespace.js.map +1 -0
- package/dist/content-cache-ownership.d.ts +13 -0
- package/dist/content-cache-ownership.js +75 -0
- package/dist/content-cache-ownership.js.map +1 -0
- package/dist/content-cache-retention.d.ts +23 -0
- package/dist/content-cache-retention.js +195 -0
- package/dist/content-cache-retention.js.map +1 -0
- package/dist/content-capture-pool.d.ts +287 -0
- package/dist/content-capture-pool.js +461 -0
- package/dist/content-capture-pool.js.map +1 -0
- package/dist/content-capture-worker.d.ts +8 -0
- package/dist/content-capture-worker.js +109 -0
- package/dist/content-capture-worker.js.map +1 -0
- package/dist/content-clock.d.ts +42 -0
- package/dist/content-clock.js +166 -0
- package/dist/content-clock.js.map +1 -0
- package/dist/content-direct.d.ts +2 -0
- package/dist/content-direct.js +12 -0
- package/dist/content-direct.js.map +1 -1
- package/dist/content-failure.d.ts +18 -0
- package/dist/content-failure.js +38 -0
- package/dist/content-failure.js.map +1 -0
- package/dist/content-stream-thread.d.ts +60 -0
- package/dist/content-stream-thread.js +95 -0
- package/dist/content-stream-thread.js.map +1 -0
- package/dist/content-stream-worker.d.ts +7 -0
- package/dist/content-stream-worker.js +52 -0
- package/dist/content-stream-worker.js.map +1 -0
- package/dist/declaration-absorption.d.ts +37 -0
- package/dist/declaration-absorption.js +119 -0
- package/dist/declaration-absorption.js.map +1 -0
- package/dist/declaration-chain.d.ts +36 -0
- package/dist/declaration-chain.js +96 -0
- package/dist/declaration-chain.js.map +1 -0
- package/dist/declaration-commit.d.ts +31 -0
- package/dist/declaration-commit.js +49 -0
- package/dist/declaration-commit.js.map +1 -0
- package/dist/declaration-hold.d.ts +22 -0
- package/dist/declaration-hold.js +24 -0
- package/dist/declaration-hold.js.map +1 -0
- package/dist/declaration-journal.d.ts +62 -0
- package/dist/declaration-journal.js +99 -0
- package/dist/declaration-journal.js.map +1 -0
- package/dist/declaration-save.d.ts +31 -0
- package/dist/declaration-save.js +134 -0
- package/dist/declaration-save.js.map +1 -0
- package/dist/detection/accepted-result.d.ts +19 -1
- package/dist/detection/accepted-result.js +36 -5
- package/dist/detection/accepted-result.js.map +1 -1
- package/dist/detection/admission-save.d.ts +55 -0
- package/dist/detection/admission-save.js +48 -0
- package/dist/detection/admission-save.js.map +1 -0
- package/dist/detection/admission-units.d.ts +66 -0
- package/dist/detection/admission-units.js +322 -0
- package/dist/detection/admission-units.js.map +1 -0
- package/dist/detection/alias-discovery.d.ts +12 -0
- package/dist/detection/alias-discovery.js +80 -0
- package/dist/detection/alias-discovery.js.map +1 -0
- package/dist/detection/alias-examination.d.ts +30 -0
- package/dist/detection/alias-examination.js +49 -0
- package/dist/detection/alias-examination.js.map +1 -0
- package/dist/detection/apply-ownership.d.ts +81 -0
- package/dist/detection/apply-ownership.js +232 -0
- package/dist/detection/apply-ownership.js.map +1 -0
- package/dist/detection/basis-advanced.d.ts +11 -0
- package/dist/detection/basis-advanced.js +26 -0
- package/dist/detection/basis-advanced.js.map +1 -0
- package/dist/detection/capture-outcome.d.ts +16 -0
- package/dist/detection/capture-outcome.js +92 -0
- package/dist/detection/capture-outcome.js.map +1 -0
- package/dist/detection/container-membership.d.ts +32 -0
- package/dist/detection/container-membership.js +49 -0
- package/dist/detection/container-membership.js.map +1 -0
- package/dist/detection/detect-cancelled.d.ts +6 -0
- package/dist/detection/detect-cancelled.js +10 -0
- package/dist/detection/detect-cancelled.js.map +1 -0
- package/dist/detection/directory-proof.d.ts +47 -0
- package/dist/detection/directory-proof.js +77 -0
- package/dist/detection/directory-proof.js.map +1 -0
- package/dist/detection/disposition-rows.d.ts +18 -0
- package/dist/detection/disposition-rows.js +29 -0
- package/dist/detection/disposition-rows.js.map +1 -0
- package/dist/detection/dispositions.d.ts +130 -0
- package/dist/detection/dispositions.js +248 -0
- package/dist/detection/dispositions.js.map +1 -0
- package/dist/detection/git-notice.d.ts +88 -0
- package/dist/detection/git-notice.js +444 -0
- package/dist/detection/git-notice.js.map +1 -0
- package/dist/detection/ground-proof.d.ts +130 -0
- package/dist/detection/ground-proof.js +274 -0
- package/dist/detection/ground-proof.js.map +1 -0
- package/dist/detection/ground-reader.d.ts +29 -0
- package/dist/detection/ground-reader.js +102 -0
- package/dist/detection/ground-reader.js.map +1 -0
- package/dist/detection/held-territory.d.ts +51 -0
- package/dist/detection/held-territory.js +205 -0
- package/dist/detection/held-territory.js.map +1 -0
- package/dist/detection/identity-discovery.d.ts +45 -0
- package/dist/detection/identity-discovery.js +287 -0
- package/dist/detection/identity-discovery.js.map +1 -0
- package/dist/detection/maintenance-sweep.d.ts +108 -0
- package/dist/detection/maintenance-sweep.js +325 -0
- package/dist/detection/maintenance-sweep.js.map +1 -0
- package/dist/detection/name-reach.d.ts +12 -0
- package/dist/detection/name-reach.js +28 -0
- package/dist/detection/name-reach.js.map +1 -0
- package/dist/detection/notebook-basis.d.ts +5 -7
- package/dist/detection/notebook-basis.js +6 -7
- package/dist/detection/notebook-basis.js.map +1 -1
- package/dist/detection/owed-selection.d.ts +51 -0
- package/dist/detection/owed-selection.js +169 -0
- package/dist/detection/owed-selection.js.map +1 -0
- package/dist/detection/portable.d.ts +34 -34
- package/dist/detection/portable.js +67 -93
- package/dist/detection/portable.js.map +1 -1
- package/dist/detection/reconciliation-coverage.d.ts +7 -0
- package/dist/detection/reconciliation-coverage.js +16 -0
- package/dist/detection/reconciliation-coverage.js.map +1 -0
- package/dist/detection/repository-publication.d.ts +20 -0
- package/dist/detection/repository-publication.js +79 -0
- package/dist/detection/repository-publication.js.map +1 -0
- package/dist/detection/repository-routes.d.ts +43 -0
- package/dist/detection/repository-routes.js +97 -0
- package/dist/detection/repository-routes.js.map +1 -0
- package/dist/detection/root-examination.d.ts +94 -0
- package/dist/detection/root-examination.js +523 -0
- package/dist/detection/root-examination.js.map +1 -0
- package/dist/detection/runtime.d.ts +131 -64
- package/dist/detection/runtime.js +808 -1103
- package/dist/detection/runtime.js.map +1 -1
- package/dist/detection/scoped-identity-port.d.ts +34 -0
- package/dist/detection/scoped-identity-port.js +81 -0
- package/dist/detection/scoped-identity-port.js.map +1 -0
- package/dist/detection/seal-law.d.ts +66 -0
- package/dist/detection/seal-law.js +116 -0
- package/dist/detection/seal-law.js.map +1 -0
- package/dist/detection/sharing-policy.d.ts +11 -0
- package/dist/detection/sharing-policy.js +32 -0
- package/dist/detection/sharing-policy.js.map +1 -0
- package/dist/detection/thread-database.d.ts +4 -0
- package/dist/detection/thread-database.js +30 -0
- package/dist/detection/thread-database.js.map +1 -0
- package/dist/detection/unreadable.d.ts +11 -0
- package/dist/detection/unreadable.js +9 -0
- package/dist/detection/unreadable.js.map +1 -0
- package/dist/detection/verified-content.d.ts +6 -0
- package/dist/detection/verified-content.js +12 -0
- package/dist/detection/verified-content.js.map +1 -0
- package/dist/detection/walk-law.d.ts +34 -0
- package/dist/detection/walk-law.js +55 -0
- package/dist/detection/walk-law.js.map +1 -0
- package/dist/development/fly-guest.js +2 -1
- package/dist/development/fly-guest.js.map +1 -1
- package/dist/entity-apply-host.d.ts +29 -13
- package/dist/entity-apply-host.js +1426 -461
- package/dist/entity-apply-host.js.map +1 -1
- package/dist/entity-apply-store.d.ts +77 -30
- package/dist/entity-apply-store.js +221 -516
- package/dist/entity-apply-store.js.map +1 -1
- package/dist/entity-record-observability.js +2 -0
- package/dist/entity-record-observability.js.map +1 -1
- package/dist/entity-record-saves.d.ts +20 -0
- package/dist/entity-record-saves.js +91 -0
- package/dist/entity-record-saves.js.map +1 -0
- package/dist/entity-record-store.d.ts +15 -7
- package/dist/entity-record-store.js +57 -117
- package/dist/entity-record-store.js.map +1 -1
- package/dist/files-register-host.d.ts +22 -3
- package/dist/files-register-host.js +70 -31
- package/dist/files-register-host.js.map +1 -1
- package/dist/filesystem.js +3 -2
- package/dist/filesystem.js.map +1 -1
- package/dist/git-binding.d.ts +65 -0
- package/dist/git-binding.js +220 -0
- package/dist/git-binding.js.map +1 -0
- package/dist/git-capture.d.ts +39 -0
- package/dist/git-capture.js +86 -0
- package/dist/git-capture.js.map +1 -0
- package/dist/git-card.d.ts +12 -0
- package/dist/git-card.js +123 -0
- package/dist/git-card.js.map +1 -0
- package/dist/git-checkpoint-construction.d.ts +41 -0
- package/dist/git-checkpoint-construction.js +96 -0
- package/dist/git-checkpoint-construction.js.map +1 -0
- package/dist/git-checkpoint-preparation.d.ts +37 -0
- package/dist/git-checkpoint-preparation.js +145 -0
- package/dist/git-checkpoint-preparation.js.map +1 -0
- package/dist/git-command.d.ts +61 -8
- package/dist/git-command.js +217 -63
- package/dist/git-command.js.map +1 -1
- package/dist/git-configuration.d.ts +40 -0
- package/dist/git-configuration.js +217 -0
- package/dist/git-configuration.js.map +1 -0
- package/dist/git-evidence.d.ts +61 -0
- package/dist/git-evidence.js +181 -0
- package/dist/git-evidence.js.map +1 -0
- package/dist/git-index-entries.d.ts +15 -0
- package/dist/git-index-entries.js +44 -0
- package/dist/git-index-entries.js.map +1 -0
- package/dist/git-index-format.d.ts +35 -0
- package/dist/git-index-format.js +293 -0
- package/dist/git-index-format.js.map +1 -0
- package/dist/git-index-witness.d.ts +33 -0
- package/dist/git-index-witness.js +69 -0
- package/dist/git-index-witness.js.map +1 -0
- package/dist/git-literal-host.d.ts +82 -0
- package/dist/git-literal-host.js +172 -0
- package/dist/git-literal-host.js.map +1 -0
- package/dist/git-literal-read.d.ts +88 -0
- package/dist/git-literal-read.js +170 -0
- package/dist/git-literal-read.js.map +1 -0
- package/dist/git-literal-replay.d.ts +33 -0
- package/dist/git-literal-replay.js +268 -0
- package/dist/git-literal-replay.js.map +1 -0
- package/dist/git-native-file.d.ts +42 -0
- package/dist/git-native-file.js +145 -0
- package/dist/git-native-file.js.map +1 -0
- package/dist/git-native-index.d.ts +21 -0
- package/dist/git-native-index.js +44 -0
- package/dist/git-native-index.js.map +1 -0
- package/dist/git-operation-host.d.ts +48 -0
- package/dist/git-operation-host.js +127 -0
- package/dist/git-operation-host.js.map +1 -0
- package/dist/git-private-index.d.ts +11 -0
- package/dist/git-private-index.js +42 -0
- package/dist/git-private-index.js.map +1 -0
- package/dist/git-private-repository.d.ts +4 -0
- package/dist/git-private-repository.js +69 -0
- package/dist/git-private-repository.js.map +1 -0
- package/dist/git-private-view.d.ts +39 -0
- package/dist/git-private-view.js +162 -0
- package/dist/git-private-view.js.map +1 -0
- package/dist/git-ref-store.d.ts +12 -0
- package/dist/git-ref-store.js +68 -0
- package/dist/git-ref-store.js.map +1 -0
- package/dist/git-registration-host.d.ts +20 -30
- package/dist/git-registration-host.js +86 -249
- package/dist/git-registration-host.js.map +1 -1
- package/dist/git-repository-host.d.ts +148 -22
- package/dist/git-repository-host.js +633 -650
- package/dist/git-repository-host.js.map +1 -1
- package/dist/git-worktree-host.d.ts +2 -0
- package/dist/git-worktree-host.js +16 -0
- package/dist/git-worktree-host.js.map +1 -0
- package/dist/graph-publication.d.ts +75 -0
- package/dist/graph-publication.js +203 -0
- package/dist/graph-publication.js.map +1 -0
- package/dist/ground-basis.d.ts +242 -0
- package/dist/ground-basis.js +1023 -0
- package/dist/ground-basis.js.map +1 -0
- package/dist/ground-checkpoint-worker.d.ts +1 -0
- package/dist/ground-checkpoint-worker.js +84 -0
- package/dist/ground-checkpoint-worker.js.map +1 -0
- package/dist/ground-checkpoints.d.ts +16 -0
- package/dist/ground-checkpoints.js +45 -0
- package/dist/ground-checkpoints.js.map +1 -0
- package/dist/ground-coordination.d.ts +107 -3
- package/dist/ground-coordination.js +249 -24
- package/dist/ground-coordination.js.map +1 -1
- package/dist/ground-database.d.ts +42 -0
- package/dist/ground-database.js +290 -0
- package/dist/ground-database.js.map +1 -0
- package/dist/ground-differences.d.ts +23 -0
- package/dist/ground-differences.js +47 -0
- package/dist/ground-differences.js.map +1 -0
- package/dist/ground-holders.d.ts +4 -0
- package/dist/ground-holders.js +15 -0
- package/dist/ground-holders.js.map +1 -0
- package/dist/ground-initialization-worker.d.ts +1 -0
- package/dist/ground-initialization-worker.js +20 -0
- package/dist/ground-initialization-worker.js.map +1 -0
- package/dist/ground-initialization.d.ts +35 -0
- package/dist/ground-initialization.js +102 -0
- package/dist/ground-initialization.js.map +1 -0
- package/dist/ground-install-saves.d.ts +63 -0
- package/dist/ground-install-saves.js +139 -0
- package/dist/ground-install-saves.js.map +1 -0
- package/dist/ground-inventory-schema.d.ts +18 -0
- package/dist/ground-inventory-schema.js +168 -0
- package/dist/ground-inventory-schema.js.map +1 -0
- package/dist/ground-profile.d.ts +7 -0
- package/dist/ground-profile.js +52 -0
- package/dist/ground-profile.js.map +1 -0
- package/dist/ground-root-index.d.ts +2 -1
- package/dist/ground-root-index.js +16 -14
- package/dist/ground-root-index.js.map +1 -1
- package/dist/ground-row.d.ts +62 -0
- package/dist/ground-row.js +70 -0
- package/dist/ground-row.js.map +1 -0
- package/dist/ground-save-ops.d.ts +44 -0
- package/dist/ground-save-ops.js +41 -0
- package/dist/ground-save-ops.js.map +1 -0
- package/dist/ground-saver-worker.d.ts +1 -0
- package/dist/ground-saver-worker.js +156 -0
- package/dist/ground-saver-worker.js.map +1 -0
- package/dist/ground-saver.d.ts +75 -0
- package/dist/ground-saver.js +216 -0
- package/dist/ground-saver.js.map +1 -0
- package/dist/ground-store.d.ts +84 -0
- package/dist/ground-store.js +473 -0
- package/dist/ground-store.js.map +1 -0
- package/dist/ground-territory.d.ts +130 -0
- package/dist/ground-territory.js +511 -0
- package/dist/ground-territory.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installation/ownership.d.ts +0 -2
- package/dist/installation/ownership.js +3 -15
- package/dist/installation/ownership.js.map +1 -1
- package/dist/machine-host.d.ts +3 -2
- package/dist/machine-host.js +28 -16
- package/dist/machine-host.js.map +1 -1
- package/dist/materialized-graph.d.ts +7 -7
- package/dist/materialized-graph.js +11 -23
- package/dist/materialized-graph.js.map +1 -1
- package/dist/module-thread.d.ts +9 -0
- package/dist/module-thread.js +10 -0
- package/dist/module-thread.js.map +1 -0
- package/dist/native/fsevents-stream.node +0 -0
- package/dist/physical-path-witness.d.ts +64 -0
- package/dist/physical-path-witness.js +168 -0
- package/dist/physical-path-witness.js.map +1 -0
- package/dist/register-upload-fusion.d.ts +5 -4
- package/dist/register-upload-fusion.js +9 -6
- package/dist/register-upload-fusion.js.map +1 -1
- package/dist/registry-graph.d.ts +3 -2
- package/dist/registry-graph.js +9 -6
- package/dist/registry-graph.js.map +1 -1
- package/dist/registry-page-cache.d.ts +5 -2
- package/dist/registry-page-cache.js.map +1 -1
- package/dist/repository-identity-steps.d.ts +5 -0
- package/dist/repository-identity-steps.js +33 -0
- package/dist/repository-identity-steps.js.map +1 -0
- package/dist/runtime-node-flags.d.ts +10 -0
- package/dist/runtime-node-flags.js +11 -0
- package/dist/runtime-node-flags.js.map +1 -0
- package/dist/service/adapter.d.ts +1 -1
- package/dist/service/adapter.js +1 -1
- package/dist/service/adapter.js.map +1 -1
- package/dist/service/cli.js +16 -20
- package/dist/service/cli.js.map +1 -1
- package/dist/service/control.d.ts +2 -2
- package/dist/service/keepalive.d.ts +4 -11
- package/dist/service/keepalive.js +65 -118
- package/dist/service/keepalive.js.map +1 -1
- package/dist/service/policy.d.ts +36 -0
- package/dist/service/policy.js +25 -0
- package/dist/service/policy.js.map +1 -0
- package/dist/service/portable.d.ts +1 -1
- package/dist/service/portable.js +1 -1
- package/dist/service/portable.js.map +1 -1
- package/dist/service/recovery.d.ts +10 -9
- package/dist/service/recovery.js +29 -19
- package/dist/service/recovery.js.map +1 -1
- package/dist/service/windows-action.d.ts +5 -3
- package/dist/service/windows-action.js +6 -9
- package/dist/service/windows-action.js.map +1 -1
- package/dist/service/windows-task.js +5 -5
- package/dist/service/windows-task.js.map +1 -1
- package/dist/sharing-admission.d.ts +17 -0
- package/dist/sharing-admission.js +128 -0
- package/dist/sharing-admission.js.map +1 -0
- package/dist/snapshot-delivery-baseline.d.ts +12 -3
- package/dist/snapshot-delivery-baseline.js +41 -42
- package/dist/snapshot-delivery-baseline.js.map +1 -1
- package/dist/source-proof.d.ts +36 -0
- package/dist/source-proof.js +70 -0
- package/dist/source-proof.js.map +1 -0
- package/dist/source-signature.d.ts +56 -0
- package/dist/source-signature.js +56 -0
- package/dist/source-signature.js.map +1 -0
- package/dist/source-witness.d.ts +15 -0
- package/dist/source-witness.js +29 -0
- package/dist/source-witness.js.map +1 -0
- package/dist/sqlite-durability.d.ts +31 -0
- package/dist/sqlite-durability.js +28 -0
- package/dist/sqlite-durability.js.map +1 -0
- package/dist/sqlite.d.ts +8 -2
- package/dist/sqlite.js +27 -4
- package/dist/sqlite.js.map +1 -1
- package/dist/sync-address.d.ts +19 -0
- package/dist/sync-address.js +52 -0
- package/dist/sync-address.js.map +1 -0
- package/dist/sync-setting.d.ts +15 -0
- package/dist/sync-setting.js +13 -0
- package/dist/sync-setting.js.map +1 -1
- package/dist/turns.d.ts +42 -0
- package/dist/turns.js +112 -0
- package/dist/turns.js.map +1 -0
- package/dist/updates/bootstrap.d.ts +53 -7
- package/dist/updates/bootstrap.js +224 -137
- package/dist/updates/bootstrap.js.map +1 -1
- package/dist/updates/controller.d.ts +8 -0
- package/dist/updates/controller.js +27 -3
- package/dist/updates/controller.js.map +1 -1
- package/dist/updates/store.d.ts +3 -0
- package/dist/updates/store.js +12 -0
- package/dist/updates/store.js.map +1 -1
- package/dist/user-ground-host.d.ts +364 -69
- package/dist/user-ground-host.js +5231 -3366
- package/dist/user-ground-host.js.map +1 -1
- package/dist/watching/fsevents.d.ts +52 -1
- package/dist/watching/fsevents.js +60 -25
- package/dist/watching/fsevents.js.map +1 -1
- package/dist/watching/host.d.ts +129 -5
- package/dist/watching/host.js +433 -80
- package/dist/watching/host.js.map +1 -1
- package/dist/watching/index.d.ts +1 -1
- package/dist/wire-binary.d.ts +3 -2
- package/dist/wire-binary.js +10 -8
- package/dist/wire-binary.js.map +1 -1
- package/dist/wire-client.d.ts +3 -4
- package/dist/wire-client.js +18 -44
- package/dist/wire-client.js.map +1 -1
- package/dist/wire-outbox.d.ts +34 -0
- package/dist/wire-outbox.js +127 -0
- package/dist/wire-outbox.js.map +1 -0
- package/dist/workspace-host.d.ts +4 -0
- package/dist/workspace-host.js +12 -1
- package/dist/workspace-host.js.map +1 -1
- package/examinations/fixtures/private-wire-authority.js +133 -10
- package/npm-shrinkwrap.json +43 -56
- package/package.json +26 -13
|
@@ -1,809 +1,719 @@
|
|
|
1
|
+
import { makeRecordContentDurable } from "../content-cache-host.js";
|
|
1
2
|
/**
|
|
2
3
|
* The named Detect lane.
|
|
3
4
|
*
|
|
4
5
|
* A watcher path that still names enrolled, non-repository ground does not
|
|
5
6
|
* justify a workspace walk. This runtime holds one SQLite connection,
|
|
6
|
-
* resolves indexed paths
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
7
|
+
* resolves indexed paths and commits exact Live proposals plus local truth
|
|
8
|
+
* in one transaction. Every examination ends in one typed outcome. Evidence it
|
|
9
|
+
* cannot prove is declined before mutation as a `widen` naming the missing
|
|
10
|
+
* proof and the scope that can supply it; a moved source is `retry`; a fault
|
|
11
|
+
* is thrown to the owning host, never converted into a wider scan.
|
|
10
12
|
*/
|
|
11
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
-
import {
|
|
13
|
-
import { join, posix
|
|
14
|
-
import { canonicalVersion,
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
14
|
+
import { lstatSync, readlinkSync } from "node:fs";
|
|
15
|
+
import { join, posix } from "node:path";
|
|
16
|
+
import { AMALGM_IGNORE_FILE, artifactForRecord, canonicalVersion, childGroupOwed, classifyFile, createLocalEntityRecord, detectedChangeProposal, encodeEntityRecord, observedPortableName, portableFileMode, resolveScopedLeaves, selectPublication, PublicationScopeExceeded, } from "@amalgm/live";
|
|
17
|
+
import { openGroundConnection } from "../ground-database.js";
|
|
18
|
+
import { save } from "../ground-saver.js";
|
|
19
|
+
import { activeUnder, effectivelyActive, GROUND_ROW_PROJECTION, localParentOf, portableGroundRecord as portable } from "../ground-row.js";
|
|
20
|
+
import { groundStore } from "../ground-store.js";
|
|
21
|
+
import { PhysicalPathWitnesses } from "../physical-path-witness.js";
|
|
22
|
+
import { GroundBasisChangedError } from "../ground-basis.js";
|
|
23
|
+
import { GroundEffectCrossedError, GroundFootprintExceededError, PhysicalKeySet } from "../ground-coordination.js";
|
|
24
|
+
import { physicalKey, sourceFingerprint, sourceReusable } from "../source-signature.js";
|
|
25
|
+
import { closePhysically } from "../source-proof.js";
|
|
26
|
+
import { leafBoundary, stampContentClock } from "../content-clock.js";
|
|
27
|
+
import { repositoryGroundCones } from "../git-binding.js";
|
|
28
|
+
import { eachInTurns, settleInTurns, turnBoundary } from "../turns.js";
|
|
29
|
+
import { CargoGroup } from "../cargo-group.js";
|
|
30
|
+
import { CaptureSourceChangedError } from "../content-cache-host.js";
|
|
31
|
+
import { captureContent, contentManifest, readOwedAddresses, replayCachedContent, sealContent } from "../content-capture-pool.js";
|
|
32
|
+
import { hasGitMarker } from "../git-repository-host.js";
|
|
33
|
+
import { referenceWorkspaceId } from "../files-register-host.js";
|
|
19
34
|
import { AcceptedInboxResultIndex } from "./accepted-result.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
]
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
];
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
resource_id = excluded.resource_id,
|
|
50
|
-
root_uuid = excluded.root_uuid,
|
|
51
|
-
type = excluded.type,
|
|
52
|
-
parent_uuid = excluded.parent_uuid,
|
|
53
|
-
name = excluded.name,
|
|
54
|
-
status = excluded.status,
|
|
55
|
-
version = excluded.version,
|
|
56
|
-
payload_version = excluded.payload_version,
|
|
57
|
-
transport_version = excluded.transport_version,
|
|
58
|
-
relative_path = excluded.relative_path,
|
|
59
|
-
absolute_path = excluded.absolute_path,
|
|
60
|
-
device_number = excluded.device_number,
|
|
61
|
-
inode = excluded.inode,
|
|
62
|
-
byte_size = excluded.byte_size,
|
|
63
|
-
modified_time_ms = excluded.modified_time_ms,
|
|
64
|
-
changed_time_ms = excluded.changed_time_ms,
|
|
65
|
-
filesystem_mode = excluded.filesystem_mode,
|
|
66
|
-
content_verified_at_ms = excluded.content_verified_at_ms
|
|
67
|
-
`);
|
|
68
|
-
const portable = (row) => {
|
|
69
|
-
const derived = row.type === "workspace" || row.type === "folder";
|
|
70
|
-
return {
|
|
71
|
-
uuid: row.uuid,
|
|
72
|
-
type: row.type,
|
|
73
|
-
parentUUID: row.parentUUID,
|
|
74
|
-
name: row.name,
|
|
75
|
-
status: row.status,
|
|
76
|
-
version: row.version,
|
|
77
|
-
payloadVersion: derived ? null : row.payloadVersion,
|
|
78
|
-
fileMode: portableFileMode(row.type, row.filesystemMode),
|
|
79
|
-
transportVersion: derived ? null : row.transportVersion,
|
|
80
|
-
};
|
|
35
|
+
import { aliasExaminations } from "./alias-examination.js";
|
|
36
|
+
import { NamedDirectoryProof } from "./directory-proof.js";
|
|
37
|
+
import { finalizeContainers, membershipParents } from "./container-membership.js";
|
|
38
|
+
import { scopedIdentityPort } from "./scoped-identity-port.js";
|
|
39
|
+
import { retainContentVerification } from "./verified-content.js";
|
|
40
|
+
import { NamedGroundReader } from "./ground-reader.js";
|
|
41
|
+
import { readSharingPolicy } from "./sharing-policy.js";
|
|
42
|
+
import { GitNoticeBindings, GitNoticeChanged } from "./git-notice.js";
|
|
43
|
+
import { repositoryRoutes } from "./repository-routes.js";
|
|
44
|
+
import { ApplyAttributionDeferred, ApplyOwnershipSnapshot, ownedObject, sqliteApplyOwnership } from "./apply-ownership.js";
|
|
45
|
+
import { admissionRejection } from "./dispositions.js";
|
|
46
|
+
import { denied } from "./unreadable.js";
|
|
47
|
+
/** One named attempt's phases, in order: reader and ownership snapshot,
|
|
48
|
+
* repository membership, Git routing and directory proofs, address checks
|
|
49
|
+
* and byte reads, cache durability, the semantic close, the in-process ready
|
|
50
|
+
* registration, the admission transaction, and what a changed policy newly
|
|
51
|
+
* includes. */
|
|
52
|
+
const NAMED_PHASES = ["open", "members", "route", "collect", "durable", "close", "ready", "admit", "include"];
|
|
53
|
+
const nothing = { evidence: [], observedEntityIds: [] };
|
|
54
|
+
const widen = (proof, roots, detail) => ({ ...nothing, kind: "widen", proof, roots, ...(detail ? { detail } : {}) });
|
|
55
|
+
const retry = (reason, after = "timer") => ({ ...nothing, kind: "retry", reason, after });
|
|
56
|
+
const LEAF_TYPES = new Set(["file.text", "file.binary", "link"]);
|
|
57
|
+
const parentPath = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
58
|
+
/** Where an escaped context member lives, for the widen receipt. */
|
|
59
|
+
const contextPath = (context, uuid) => [...context.observed, ...context.before].find(row => row.uuid === uuid)?.relativePath ?? uuid;
|
|
60
|
+
const required = (row, uuid) => {
|
|
61
|
+
if (!row)
|
|
62
|
+
throw new Error(`named Detect has no row for proved parent ${uuid}`);
|
|
63
|
+
return row;
|
|
81
64
|
};
|
|
65
|
+
/** Evidence from one committed examination decides its settled kind. */
|
|
66
|
+
function settled(evidence, observedEntityIds, excluded) {
|
|
67
|
+
const kind = evidence.some(item => item.records > 0) ? "recorded" : excluded ? "excluded" : "unchanged";
|
|
68
|
+
return { kind, evidence, observedEntityIds };
|
|
69
|
+
}
|
|
70
|
+
const ROW_SELECT = `SELECT ${GROUND_ROW_PROJECTION} FROM entities`;
|
|
82
71
|
const sha256Hex = (input) => createHash("sha256").update(input).digest("hex");
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
&& row.byteSize === stats.size
|
|
86
|
-
&& row.modifiedTimeMs === stats.mtimeMs
|
|
87
|
-
&& row.changedTimeMs === stats.ctimeMs
|
|
88
|
-
&& row.filesystemMode === stats.mode;
|
|
89
|
-
/** One persistent, prepared Detect store. It owns no Watch or Send behavior. */
|
|
72
|
+
/** One persistent, prepared Detect store. It owns no Watch or Send behavior;
|
|
73
|
+
* attempts on different roots run concurrently, each with its own state. */
|
|
90
74
|
export class NamedDetectRuntime {
|
|
75
|
+
store;
|
|
76
|
+
constructor(databaseFile, cacheDir, bindingDir, onRecordCommit, measureGround = () => false, onDetectCommit,
|
|
77
|
+
/** Rings a checkout whose Git binding was refreshed after its witness moved. */
|
|
78
|
+
rebound) {
|
|
79
|
+
const database = openGroundConnection(databaseFile);
|
|
80
|
+
this.store = {
|
|
81
|
+
database,
|
|
82
|
+
cacheDir,
|
|
83
|
+
bindingDir,
|
|
84
|
+
rowAtAbsolutePath: database.prepare(`${ROW_SELECT} WHERE absolute_path = ? AND status = 'active'`),
|
|
85
|
+
receivedThrough: database.prepare(`
|
|
86
|
+
SELECT received_through AS receivedThrough
|
|
87
|
+
FROM entity_receive_cursors
|
|
88
|
+
WHERE authority_id = ? AND entity_id = ?
|
|
89
|
+
`),
|
|
90
|
+
acceptedInbox: new AcceptedInboxResultIndex(database),
|
|
91
|
+
measureGround,
|
|
92
|
+
...(onRecordCommit ? { onRecordCommit } : {}),
|
|
93
|
+
...(onDetectCommit ? { onDetectCommit } : {}),
|
|
94
|
+
gitBindings: new GitNoticeBindings(rebound),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Startup discovery binds every enrolled repository in the background, so
|
|
98
|
+
* no first notice pays for all of them. */
|
|
99
|
+
discover() {
|
|
100
|
+
const { value } = groundStore(this.store.database).readSnapshot(reader => reader.repositoryMembers());
|
|
101
|
+
this.store.gitBindings.discover(value);
|
|
102
|
+
}
|
|
103
|
+
close() {
|
|
104
|
+
this.store.database.close();
|
|
105
|
+
}
|
|
106
|
+
/** The repositories escalated names reach, by this runtime's Git bindings. */
|
|
107
|
+
repositoryRoutes(directory, paths) {
|
|
108
|
+
return repositoryRoutes(this.store.database, this.store.gitBindings, directory, paths);
|
|
109
|
+
}
|
|
110
|
+
/** Named names split by the lane that proves them. A name in Git storage or
|
|
111
|
+
* inside an indexed repository belongs to its repository's examination;
|
|
112
|
+
* every other name is proved as a named leaf. The index is only a routing
|
|
113
|
+
* hint: each lane re-reads the real routes, and a wrong hint widens. */
|
|
114
|
+
repositoryNames(directory, paths) {
|
|
115
|
+
const leaves = [], repositories = [];
|
|
116
|
+
const nearest = indexedRepositories(this.store.rowAtAbsolutePath, directory);
|
|
117
|
+
for (const path of paths) {
|
|
118
|
+
(nearest(path) !== null || path.split("/").includes(".git") ? repositories : leaves).push(path);
|
|
119
|
+
}
|
|
120
|
+
return { leaves, repositories };
|
|
121
|
+
}
|
|
122
|
+
/** A blocked batch does not own unrelated named leaves. Retry its independent
|
|
123
|
+
* names without settling the original Watch generation. */
|
|
124
|
+
detectAvailable(observation, register, lease) {
|
|
125
|
+
return new NamedDetectAttempt(this.store, register, lease).detectGroups(observation);
|
|
126
|
+
}
|
|
127
|
+
/** The physical footprint a lane's proof of these paths reads: each path's
|
|
128
|
+
* subtree, or its nearest indexed repository's whole root and any Git
|
|
129
|
+
* storage kept outside it, plus the root's enrollment policy. Ancestors
|
|
130
|
+
* need no lease of their own: an effect on any ancestor overlaps these
|
|
131
|
+
* cones. An index that changes before admission fails the ground basis
|
|
132
|
+
* there; storage the observed binding names must lie inside the lease. */
|
|
133
|
+
footprint(directory, paths) {
|
|
134
|
+
const cones = new Set([join(directory, AMALGM_IGNORE_FILE)]);
|
|
135
|
+
const nearest = indexedRepositories(this.store.rowAtAbsolutePath, directory);
|
|
136
|
+
const repositories = new Set();
|
|
137
|
+
for (const path of paths) {
|
|
138
|
+
const repository = nearest(path);
|
|
139
|
+
if (repository === null)
|
|
140
|
+
cones.add(join(directory, path));
|
|
141
|
+
else
|
|
142
|
+
repositories.add(repository.path);
|
|
143
|
+
}
|
|
144
|
+
// Every name in one repository shares its cones: they are read once.
|
|
145
|
+
for (const repository of repositories) {
|
|
146
|
+
for (const cone of repositoryGroundCones(join(directory, repository)))
|
|
147
|
+
cones.add(cone);
|
|
148
|
+
}
|
|
149
|
+
return [...cones];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** The nearest indexed repository enclosing each named path, root-relative,
|
|
153
|
+
* each ancestor looked up once for all the names. It is a lane and footprint
|
|
154
|
+
* hint; each proof re-reads the real routes. */
|
|
155
|
+
function indexedRepositories(rowAtAbsolutePath, directory) {
|
|
156
|
+
const known = new Map();
|
|
157
|
+
const nearest = (at) => {
|
|
158
|
+
let found = known.get(at);
|
|
159
|
+
if (found !== undefined)
|
|
160
|
+
return found;
|
|
161
|
+
const row = rowAtAbsolutePath.get(join(directory, at));
|
|
162
|
+
found = row?.type === "repo.git" ? { path: at, uuid: row.uuid }
|
|
163
|
+
: at === "" ? null : nearest(at.includes("/") ? at.slice(0, at.lastIndexOf("/")) : "");
|
|
164
|
+
known.set(at, found);
|
|
165
|
+
return found;
|
|
166
|
+
};
|
|
167
|
+
return nearest;
|
|
168
|
+
}
|
|
169
|
+
/** One root's named proof: its reader, ownership snapshot, Git proof,
|
|
170
|
+
* collection registration and ground lease belong to this attempt alone. */
|
|
171
|
+
class NamedDetectAttempt {
|
|
172
|
+
register;
|
|
173
|
+
lease;
|
|
91
174
|
database;
|
|
92
175
|
cacheDir;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
rowAtPath;
|
|
96
|
-
rowAtUuid;
|
|
97
|
-
rowsUnderPath;
|
|
98
|
-
rowsAtPhysicalIdentity;
|
|
99
|
-
notebookChildren;
|
|
100
|
-
materializedSiblings;
|
|
101
|
-
/** One frozen sibling snapshot per Detect pass: collision-derived names
|
|
102
|
-
* are computed once per parent, never once per captured file. */
|
|
103
|
-
slotNamesByParent = new Map();
|
|
104
|
-
materializedByUuid;
|
|
105
|
-
writeEntity;
|
|
106
|
-
writeNotebook;
|
|
107
|
-
appendRecord;
|
|
176
|
+
bindingDir;
|
|
177
|
+
rowAtAbsolutePath;
|
|
108
178
|
receivedThrough;
|
|
109
179
|
acceptedInbox;
|
|
180
|
+
measureGround;
|
|
110
181
|
onRecordCommit;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
ORDER BY relative_path`);
|
|
129
|
-
this.rowsAtPhysicalIdentity = this.database.prepare(`${ROW_SELECT} WHERE device_number = ? AND inode = ? AND status = 'active' ORDER BY uuid`);
|
|
130
|
-
this.notebookChildren = this.database.prepare(`
|
|
131
|
-
SELECT uuid, name FROM detection_notebook
|
|
132
|
-
WHERE parent_uuid = ? AND status = 'active' ORDER BY name, uuid
|
|
133
|
-
`);
|
|
134
|
-
this.materializedSiblings = this.database.prepare(`${ROW_SELECT} WHERE parent_uuid = ? AND status = 'active' ORDER BY name, uuid`);
|
|
135
|
-
this.materializedByUuid = this.database.prepare("SELECT 1 AS present FROM entities WHERE uuid = ?");
|
|
136
|
-
this.writeEntity = upsert(this.database, "entities");
|
|
137
|
-
this.writeNotebook = upsert(this.database, "detection_notebook");
|
|
138
|
-
this.appendRecord = this.database.prepare(`
|
|
139
|
-
INSERT INTO record_outbox(
|
|
140
|
-
mutation_id, authority_id, entity_id, record_json, status,
|
|
141
|
-
global_sequence, created_at, accepted_at
|
|
142
|
-
) VALUES (?, ?, ?, ?, 'pending', NULL, ?, NULL)
|
|
143
|
-
`);
|
|
144
|
-
this.receivedThrough = this.database.prepare(`
|
|
145
|
-
SELECT received_through AS receivedThrough
|
|
146
|
-
FROM entity_receive_cursors
|
|
147
|
-
WHERE authority_id = ? AND entity_id = ?
|
|
148
|
-
`);
|
|
149
|
-
this.acceptedInbox = new AcceptedInboxResultIndex(this.database);
|
|
150
|
-
this.onRecordCommit = onRecordCommit;
|
|
182
|
+
onDetectCommit;
|
|
183
|
+
gitBindings;
|
|
184
|
+
observation = null;
|
|
185
|
+
ownership = null;
|
|
186
|
+
gitProof = null;
|
|
187
|
+
/** The root's content-clock stamp, taken before this observation's first
|
|
188
|
+
* byte: every leaf it reads carries it. */
|
|
189
|
+
clock = null;
|
|
190
|
+
/** What this observation's reads staged, named together before its
|
|
191
|
+
* Record needs them (cargo-group.ts). */
|
|
192
|
+
cargo = null;
|
|
193
|
+
phaseStarted = 0;
|
|
194
|
+
phases = new Map();
|
|
195
|
+
get ground() {
|
|
196
|
+
if (!this.observation)
|
|
197
|
+
throw new Error("named ground read outside its observation");
|
|
198
|
+
return this.observation;
|
|
151
199
|
}
|
|
152
|
-
|
|
153
|
-
|
|
200
|
+
constructor(store,
|
|
201
|
+
/** Registers each collection this attempt brings to admission. */
|
|
202
|
+
register, lease) {
|
|
203
|
+
this.register = register;
|
|
204
|
+
this.lease = lease;
|
|
205
|
+
this.database = store.database;
|
|
206
|
+
this.cacheDir = store.cacheDir;
|
|
207
|
+
this.bindingDir = store.bindingDir;
|
|
208
|
+
this.rowAtAbsolutePath = store.rowAtAbsolutePath;
|
|
209
|
+
this.receivedThrough = store.receivedThrough;
|
|
210
|
+
this.acceptedInbox = store.acceptedInbox;
|
|
211
|
+
this.measureGround = store.measureGround;
|
|
212
|
+
if (store.onRecordCommit)
|
|
213
|
+
this.onRecordCommit = store.onRecordCommit;
|
|
214
|
+
if (store.onDetectCommit)
|
|
215
|
+
this.onDetectCommit = store.onDetectCommit;
|
|
216
|
+
this.gitBindings = store.gitBindings;
|
|
217
|
+
}
|
|
218
|
+
/** Ends a phase; a phase reached more than once accumulates. */
|
|
219
|
+
mark(phase) {
|
|
220
|
+
const now = performance.now();
|
|
221
|
+
this.phases.set(phase, (this.phases.get(phase) ?? 0) + now - this.phaseStarted);
|
|
222
|
+
this.phaseStarted = now;
|
|
223
|
+
}
|
|
224
|
+
timings() {
|
|
225
|
+
const git = this.gitProof;
|
|
226
|
+
return {
|
|
227
|
+
phases: Object.fromEntries(NAMED_PHASES.flatMap(phase => this.phases.has(phase) ? [[phase, this.phases.get(phase)]] : [])),
|
|
228
|
+
git: git ? { discoveryWaitMs: git.discoveryWaitMs, routeMs: git.routeMs(), semanticMs: git.semanticMs() } : null,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async detect(observation) {
|
|
232
|
+
if (observation.suspicion.paths === null) {
|
|
233
|
+
throw new Error("named Detect requires named suspicion; complete roots belong to reconciliation");
|
|
234
|
+
}
|
|
235
|
+
this.phaseStarted = performance.now();
|
|
236
|
+
this.phases = new Map();
|
|
237
|
+
this.observation = new NamedGroundReader(this.database);
|
|
238
|
+
this.cargo = CargoGroup.open();
|
|
239
|
+
try {
|
|
240
|
+
return { ...await this.attempt(observation), timings: this.timings() };
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
await this.cargo.abandon();
|
|
244
|
+
this.observation = null;
|
|
245
|
+
this.ownership = null;
|
|
246
|
+
this.gitProof = null;
|
|
247
|
+
this.clock = null;
|
|
248
|
+
this.cargo = null;
|
|
249
|
+
}
|
|
154
250
|
}
|
|
155
|
-
|
|
156
|
-
|
|
251
|
+
async attempt(observation) {
|
|
252
|
+
try {
|
|
253
|
+
({ boundary: this.clock } = await stampContentClock(this.database.name, observation.directory));
|
|
254
|
+
this.ownership = new ApplyOwnershipSnapshot(sqliteApplyOwnership(this.database));
|
|
255
|
+
this.mark("open");
|
|
256
|
+
return await this.observe([observation]);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (error instanceof GitNoticeChanged)
|
|
260
|
+
return retry("git-notice-changed");
|
|
261
|
+
if (error instanceof GroundBasisChangedError)
|
|
262
|
+
return retry("ground-basis-changed");
|
|
263
|
+
if (error instanceof GroundEffectCrossedError)
|
|
264
|
+
return retry("ground-effect-crossed", "ground-release");
|
|
265
|
+
if (error instanceof GroundFootprintExceededError)
|
|
266
|
+
return retry("ground-footprint-exceeded", "ground-release");
|
|
267
|
+
if (error instanceof CaptureSourceChangedError)
|
|
268
|
+
return retry(`source-changed: ${error.message}`);
|
|
269
|
+
if (error instanceof ApplyAttributionDeferred)
|
|
270
|
+
return { ...nothing, kind: "deferred", owners: error.owners };
|
|
271
|
+
if (denied(error))
|
|
272
|
+
return widen("unreadable", [observation.directory]);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
157
275
|
}
|
|
158
|
-
async
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
276
|
+
async detectGroups(observation) {
|
|
277
|
+
const first = await this.detect(observation);
|
|
278
|
+
const paths = observation.suspicion.paths;
|
|
279
|
+
if (first.kind !== "deferred" || !paths || paths.length < 2)
|
|
280
|
+
return first;
|
|
281
|
+
const groups = new Map();
|
|
282
|
+
for (const path of paths) {
|
|
283
|
+
const absolute = join(observation.directory, path);
|
|
284
|
+
if (first.owners.some(owner => owner.cones.some(cone => absolute === cone || absolute.startsWith(`${cone}/`))))
|
|
285
|
+
continue;
|
|
286
|
+
// The real named proof below still re-reads ancestors, identities and ownership.
|
|
287
|
+
const row = this.rowAtAbsolutePath.get(absolute);
|
|
288
|
+
const ordinaryLeaf = row !== undefined && ["file.text", "file.binary", "link"].includes(row.type);
|
|
289
|
+
const key = ordinaryLeaf ? "ordinary-leaves" : `entry:${path}`;
|
|
290
|
+
const group = groups.get(key) ?? [];
|
|
291
|
+
group.push(path);
|
|
292
|
+
groups.set(key, group);
|
|
162
293
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
294
|
+
const evidence = [], observedEntityIds = [], examine = [];
|
|
295
|
+
// A committed group's owed examinations outlive this batch's answer.
|
|
296
|
+
const progress = () => ({ evidence, observedEntityIds, ...(examine.length ? { examine } : {}) });
|
|
297
|
+
const owners = [...first.owners];
|
|
298
|
+
let widened = null, moved = null;
|
|
299
|
+
for (const paths of groups.values()) {
|
|
300
|
+
const result = await this.detect({ ...observation, suspicion: { ...observation.suspicion, paths } });
|
|
301
|
+
for (const item of result.evidence)
|
|
302
|
+
evidence.push(item);
|
|
303
|
+
for (const uuid of result.observedEntityIds)
|
|
304
|
+
observedEntityIds.push(uuid);
|
|
305
|
+
for (const path of result.examine ?? [])
|
|
306
|
+
examine.push(path);
|
|
307
|
+
if (result.kind === "widen")
|
|
308
|
+
widened ??= result.proof;
|
|
309
|
+
// A racy source waits for its timer; an immediate rerun would read it again.
|
|
310
|
+
if (result.kind === "retry" && (!moved || result.after === "timer"))
|
|
311
|
+
moved = result;
|
|
312
|
+
if (result.kind === "deferred")
|
|
313
|
+
owners.push(...result.owners);
|
|
314
|
+
}
|
|
315
|
+
// Deferred names still hold this root, so it cannot be reconciled alone
|
|
316
|
+
// now: remaining unproved names need the domain continuation.
|
|
317
|
+
if (widened)
|
|
318
|
+
return { kind: "widen", proof: widened, roots: "domain", ...progress() };
|
|
319
|
+
if (moved)
|
|
320
|
+
return { kind: "retry", reason: moved.reason, after: moved.after, ...progress() };
|
|
321
|
+
return { kind: "deferred", owners, ...progress() };
|
|
322
|
+
}
|
|
323
|
+
requireAttribution(scope) {
|
|
324
|
+
const owners = this.ownership.use(scope);
|
|
325
|
+
if (owners.length)
|
|
326
|
+
throw new ApplyAttributionDeferred(owners);
|
|
327
|
+
}
|
|
328
|
+
async observe(observations) {
|
|
329
|
+
if (observations.every(({ suspicion }) => suspicion.paths?.length === 0)) {
|
|
330
|
+
return { ...nothing, kind: "unchanged" };
|
|
331
|
+
}
|
|
332
|
+
const members = this.ground.repositoryMembers();
|
|
333
|
+
this.mark("members");
|
|
334
|
+
this.gitProof = await this.gitBindings.observe(members, uuid => this.ground.physical(uuid));
|
|
335
|
+
const wider = new Set();
|
|
336
|
+
for (const observation of observations)
|
|
337
|
+
for (const path of observation.suspicion.paths) {
|
|
338
|
+
const route = this.gitProof.route(join(observation.directory, path));
|
|
339
|
+
if (route.kind !== "reconcile")
|
|
340
|
+
continue;
|
|
341
|
+
wider.add(observation.directory);
|
|
342
|
+
for (const uuid of route.rootUUIDs) {
|
|
343
|
+
const root = this.ground.physical(uuid);
|
|
344
|
+
if (!root || root.relativePath !== "")
|
|
345
|
+
throw new Error("Git notice consumer has no materialized root");
|
|
346
|
+
wider.add(root.absolutePath);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (wider.size)
|
|
350
|
+
return widen("git-notice-consumer", [...wider]);
|
|
351
|
+
const contexts = new Map();
|
|
352
|
+
for (const observation of observations) {
|
|
353
|
+
if (observation.suspicion.paths?.length === 0)
|
|
354
|
+
continue;
|
|
355
|
+
const root = this.ground.root(observation.directory);
|
|
356
|
+
if (!root)
|
|
357
|
+
return widen("unindexed-root", [observation.directory]);
|
|
358
|
+
const context = new NamedDirectoryProof({
|
|
359
|
+
lookup: (path, parent) => {
|
|
360
|
+
const row = parent ? this.ground.atPath(root.rootUUID, path).find(row => activeUnder(row, parent))
|
|
361
|
+
: this.activeRowAtPath(root.rootUUID, path);
|
|
362
|
+
this.requireAttribution({ entries: [join(observation.directory, path)], ...(row ? { uuids: new Set([row.uuid]) } : {}) });
|
|
363
|
+
return row;
|
|
364
|
+
},
|
|
365
|
+
read: path => {
|
|
366
|
+
const absolutePath = join(observation.directory, ...path.split("/"));
|
|
367
|
+
const stats = tryLstat(absolutePath);
|
|
368
|
+
return stats?.isDirectory() && !stats.isSymbolicLink()
|
|
369
|
+
? { physicalKey: physicalKey(stats), repository: hasGitMarker(absolutePath) }
|
|
370
|
+
: null;
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
contexts.set(observation.directory, context);
|
|
374
|
+
for (const path of observation.suspicion.paths) {
|
|
375
|
+
// Each name's directory proof is its own reads: names are boundaries.
|
|
376
|
+
await turnBoundary();
|
|
377
|
+
this.requireAttribution({ entries: [join(observation.directory, path)] });
|
|
378
|
+
if (!context.named(path))
|
|
379
|
+
return widen("named-directory", [observation.directory]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
this.mark("route");
|
|
383
|
+
const result = await this.detectWithContext(observations, contexts, new PhysicalPathWitnesses());
|
|
384
|
+
return { ...result, evidence: result.evidence.map(evidence => ({
|
|
385
|
+
...evidence,
|
|
386
|
+
metadataReads: evidence.metadataReads + (contexts.get(evidence.directory)?.metadataReads ?? 0),
|
|
387
|
+
})) };
|
|
388
|
+
}
|
|
389
|
+
async detectWithContext(observations, contexts, sources) {
|
|
166
390
|
const captured = [];
|
|
167
|
-
const
|
|
168
|
-
const membershipParents = new Set();
|
|
391
|
+
const acceptedBasis = this.acceptedInbox.snapshot();
|
|
169
392
|
const evidence = [];
|
|
170
|
-
const
|
|
171
|
-
|
|
393
|
+
const policies = new Map();
|
|
394
|
+
const examine = new Set();
|
|
395
|
+
// Roots whose policy this attempt reads as a named leaf.
|
|
396
|
+
const policyChanges = [];
|
|
397
|
+
const widened = (proof, detail) => widen(proof, observations.filter(item => item.suspicion.paths?.length).map(item => item.directory), detail);
|
|
398
|
+
let excluded = 0;
|
|
172
399
|
for (const observation of observations) {
|
|
173
400
|
const paths = observation.suspicion.paths;
|
|
174
|
-
if (paths.length
|
|
401
|
+
if (!paths.length)
|
|
175
402
|
continue;
|
|
176
|
-
|
|
177
|
-
|| path === ".git" || path.startsWith(".git/") || path.includes("/.git/")
|
|
178
|
-
|| path.endsWith("/.git"))) {
|
|
179
|
-
return { handled: false, evidence: [] };
|
|
180
|
-
}
|
|
181
|
-
const root = this.rootAtDirectory.get(observation.directory);
|
|
403
|
+
const root = this.ground.root(observation.directory);
|
|
182
404
|
if (!root)
|
|
183
|
-
return
|
|
405
|
+
return widened("unindexed-root");
|
|
406
|
+
const policy = readSharingPolicy(observation.directory);
|
|
407
|
+
policies.set(root.uuid, policy);
|
|
408
|
+
if (paths.includes(AMALGM_IGNORE_FILE)) {
|
|
409
|
+
policyChanges.push({ directory: observation.directory, rootUUID: root.rootUUID, text: policy.text });
|
|
410
|
+
}
|
|
184
411
|
const started = performance.now();
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
412
|
+
const first = captured.length;
|
|
413
|
+
// 1. Classify every name from metadata alone. Structure beyond ordinary
|
|
414
|
+
// leaves, and every control or repository consumer, needs wider proof.
|
|
415
|
+
const leaves = [];
|
|
416
|
+
const absent = [];
|
|
417
|
+
for (const path of paths) {
|
|
418
|
+
await turnBoundary();
|
|
419
|
+
if (path.split("/").includes(".git"))
|
|
420
|
+
return widened("structural-path", path);
|
|
421
|
+
if (path === "")
|
|
422
|
+
return widened("root-path");
|
|
423
|
+
const exact = this.activeRowAtPath(root.rootUUID, path);
|
|
191
424
|
const absolutePath = join(observation.directory, ...path.split("/"));
|
|
425
|
+
this.requireAttribution({ entries: [absolutePath], ...(exact ? { uuids: new Set([exact.uuid]) } : {}) });
|
|
192
426
|
const stats = tryLstat(absolutePath);
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
return { path, absolutePath, exact, stats };
|
|
199
|
-
});
|
|
200
|
-
if (entries.some(({ exact, stats }) => exact !== undefined && stats === null)) {
|
|
201
|
-
// An absent named address is not enough to distinguish deletion from
|
|
202
|
-
// a move whose destination doorbell arrives later (or never arrives
|
|
203
|
-
// on this platform). Reconcile the complete materialized root while
|
|
204
|
-
// the old row still supplies physical identity evidence.
|
|
205
|
-
return {
|
|
206
|
-
handled: false,
|
|
207
|
-
evidence: [],
|
|
208
|
-
widenedRoots: [observation.directory],
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
const moveIntoRepository = entries.some(({ path, absolutePath, exact, stats }) => {
|
|
212
|
-
if (exact !== undefined || stats === null)
|
|
213
|
-
return false;
|
|
214
|
-
const portableParent = posix.dirname(path);
|
|
215
|
-
const parentPath = portableParent === "." ? "" : portableParent;
|
|
216
|
-
const parent = this.rowAtPath.get(root.rootUUID, parentPath);
|
|
217
|
-
if (!parent || (parent.type !== "repo.git" && !this.enclosedByRepository(parent))) {
|
|
218
|
-
return false;
|
|
427
|
+
if (stats) {
|
|
428
|
+
const owners = this.ownership.physical(ownedObject(observation.directory, physicalKey(stats)));
|
|
429
|
+
if (owners.length)
|
|
430
|
+
throw new ApplyAttributionDeferred(owners);
|
|
219
431
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// repository territory. The complete root is the smallest proof that
|
|
225
|
-
// can remove the old address and add the new repo-owned address in one
|
|
226
|
-
// identity reconciliation.
|
|
227
|
-
return {
|
|
228
|
-
handled: false,
|
|
229
|
-
evidence: [],
|
|
230
|
-
widenedRoots: [observation.directory],
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (repositoryBoundary)
|
|
234
|
-
return { handled: false, evidence: [] };
|
|
235
|
-
prepared.push({ observation, root, paths, policy, started, entries });
|
|
236
|
-
}
|
|
237
|
-
for (const item of prepared) {
|
|
238
|
-
const { observation, root, paths, policy, started, entries } = item;
|
|
239
|
-
let metadataReads = entries.length;
|
|
240
|
-
let contentReads = 0;
|
|
241
|
-
let recordBytes = 0;
|
|
242
|
-
const beforeCount = captured.length;
|
|
243
|
-
for (const { path, absolutePath, exact, stats } of entries) {
|
|
432
|
+
sources.remember(absolutePath, stats);
|
|
433
|
+
if (exact ? this.enclosedByRepository(exact)
|
|
434
|
+
: this.repositoryAtPath(root.rootUUID, path, observation.directory))
|
|
435
|
+
return widened("repository-enclosed", path);
|
|
244
436
|
if (!stats) {
|
|
437
|
+
absent.push(path);
|
|
245
438
|
continue;
|
|
246
439
|
}
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (!parent || !["workspace", "folder"].includes(parent.type)
|
|
251
|
-
|| this.enclosedByRepository(parent)) {
|
|
252
|
-
return { handled: false, evidence: [] };
|
|
253
|
-
}
|
|
254
|
-
let scope;
|
|
255
|
-
if (stats.isFile()) {
|
|
256
|
-
if (exact && !["file.text", "file.binary"].includes(exact.type)) {
|
|
257
|
-
return { handled: false, evidence: [] };
|
|
258
|
-
}
|
|
259
|
-
const file = await this.captureFile({
|
|
260
|
-
root, path, absolutePath, stats, parent, exact,
|
|
261
|
-
});
|
|
262
|
-
scope = {
|
|
263
|
-
items: file.item ? [file.item] : [],
|
|
264
|
-
metadataReads: 1,
|
|
265
|
-
contentReads: file.contentReads,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
else if (stats.isSymbolicLink()) {
|
|
269
|
-
if (exact?.type === "reference"
|
|
270
|
-
|| (resolve(observation.directory) === this.userRoot
|
|
271
|
-
&& (path === "workspaces" || path.startsWith("workspaces/")))) {
|
|
272
|
-
return { handled: false, evidence: [] };
|
|
273
|
-
}
|
|
274
|
-
const link = this.captureLink({
|
|
275
|
-
root, path, absolutePath, stats, parent, exact,
|
|
276
|
-
});
|
|
277
|
-
if (!link)
|
|
278
|
-
return { handled: false, evidence: [] };
|
|
279
|
-
scope = {
|
|
280
|
-
items: link.item ? [link.item] : [],
|
|
281
|
-
metadataReads: 1,
|
|
282
|
-
contentReads: link.contentReads,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
else if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
286
|
-
const directory = await this.captureDirectory({
|
|
287
|
-
root,
|
|
288
|
-
path,
|
|
289
|
-
absolutePath,
|
|
290
|
-
stats,
|
|
291
|
-
parent,
|
|
292
|
-
policy,
|
|
293
|
-
});
|
|
294
|
-
if (!directory)
|
|
295
|
-
return { handled: false, evidence: [] };
|
|
296
|
-
scope = directory;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
return { handled: false, evidence: [] };
|
|
300
|
-
}
|
|
301
|
-
metadataReads += scope.metadataReads - 1;
|
|
302
|
-
contentReads += scope.contentReads;
|
|
303
|
-
for (const item of scope.items) {
|
|
304
|
-
if (item.proposal
|
|
305
|
-
&& !this.acceptedInbox.covers(item.result.resourceId, item.proposal)) {
|
|
306
|
-
outgoing.add(item.proposal);
|
|
307
|
-
recordBytes += Buffer.byteLength(this.previewRecordJson(item.result.resourceId, item.proposal));
|
|
308
|
-
}
|
|
309
|
-
captured.push(item);
|
|
310
|
-
if (item.base === null
|
|
311
|
-
|| item.base.parentUUID !== item.result.parentUUID
|
|
312
|
-
|| item.base.name !== item.result.name) {
|
|
313
|
-
if (item.base?.parentUUID)
|
|
314
|
-
membershipParents.add(item.base.parentUUID);
|
|
315
|
-
if (item.result.parentUUID)
|
|
316
|
-
membershipParents.add(item.result.parentUUID);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
evidence.push({
|
|
321
|
-
directory: observation.directory,
|
|
322
|
-
paths: paths.length,
|
|
323
|
-
metadataReads,
|
|
324
|
-
contentReads,
|
|
325
|
-
gitInspections: 0,
|
|
326
|
-
repositoryCaptures: 0,
|
|
327
|
-
plannedRows: captured.length - beforeCount,
|
|
328
|
-
records: captured.slice(beforeCount).filter(({ proposal }) => proposal !== null && outgoing.has(proposal)).length,
|
|
329
|
-
recordBytes,
|
|
330
|
-
durationMs: performance.now() - started,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
const committedRecords = [];
|
|
334
|
-
const commitStarted = performance.now();
|
|
335
|
-
const committed = this.database.transaction(() => {
|
|
336
|
-
if (captured.some((item) => {
|
|
337
|
-
const current = this.rowAtUuid.get(item.result.uuid);
|
|
338
|
-
return item.base ? !sameNotebookBasisRow(item.base, current) : current !== undefined;
|
|
339
|
-
}))
|
|
340
|
-
return false;
|
|
341
|
-
for (const item of captured) {
|
|
342
|
-
this.writeEntity.run(...rowValues(item.result));
|
|
343
|
-
this.writeNotebook.run(...rowValues(item.result));
|
|
344
|
-
if (!item.proposal || !outgoing.has(item.proposal))
|
|
345
|
-
continue;
|
|
346
|
-
if (this.acceptedInbox.covers(item.result.resourceId, item.proposal))
|
|
347
|
-
continue;
|
|
348
|
-
committedRecords.push(this.appendOutboxRecord(item.result.resourceId, item.proposal));
|
|
349
|
-
}
|
|
350
|
-
for (const parentUUID of membershipParents) {
|
|
351
|
-
const base = this.rowAtUuid.get(parentUUID);
|
|
352
|
-
if (!base || !["workspace", "folder"].includes(base.type))
|
|
353
|
-
continue;
|
|
354
|
-
const children = this.notebookChildren.all(parentUUID);
|
|
355
|
-
const payloadVersion = membershipHash(children, sha256Hex);
|
|
356
|
-
const version = canonicalVersion({
|
|
357
|
-
type: base.type,
|
|
358
|
-
parentUUID: base.parentUUID,
|
|
359
|
-
name: base.name,
|
|
360
|
-
status: base.status,
|
|
361
|
-
payloadVersion,
|
|
362
|
-
}, sha256Hex);
|
|
363
|
-
if (version === base.version)
|
|
364
|
-
continue;
|
|
365
|
-
const result = { ...base, payloadVersion: null, transportVersion: null, version };
|
|
366
|
-
this.writeEntity.run(...rowValues(result));
|
|
367
|
-
this.writeNotebook.run(...rowValues(result));
|
|
368
|
-
const proposal = detectedChangeProposal({
|
|
369
|
-
base: portable(base),
|
|
370
|
-
result: portable(result),
|
|
371
|
-
replay: { kind: "structure" },
|
|
372
|
-
});
|
|
373
|
-
if (!proposal)
|
|
374
|
-
continue;
|
|
375
|
-
if (!this.acceptedInbox.covers(base.resourceId, proposal)) {
|
|
376
|
-
committedRecords.push(this.appendOutboxRecord(base.resourceId, proposal));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return true;
|
|
380
|
-
})();
|
|
381
|
-
if (!committed)
|
|
382
|
-
return { handled: false, evidence: [] };
|
|
383
|
-
this.onRecordCommit?.({
|
|
384
|
-
durationMs: performance.now() - commitStarted,
|
|
385
|
-
records: committedRecords,
|
|
386
|
-
});
|
|
387
|
-
return { handled: true, evidence };
|
|
388
|
-
}
|
|
389
|
-
/** A known leaf edit inside one repository is still named evidence. It
|
|
390
|
-
* updates that leaf's local identity proof, then records one nearest-repo
|
|
391
|
-
* Card + Checkpoint transition. Ambiguous structure and rail changes widen
|
|
392
|
-
* to reconciliation before this method mutates SQLite. */
|
|
393
|
-
async detectRepository(observations) {
|
|
394
|
-
const active = observations.filter(({ suspicion }) => suspicion.paths.length > 0);
|
|
395
|
-
if (active.length === 0)
|
|
396
|
-
return null;
|
|
397
|
-
let repository = null;
|
|
398
|
-
let directory = "";
|
|
399
|
-
const leafRows = new Map();
|
|
400
|
-
let metadataReads = 0;
|
|
401
|
-
let contentReads = 0;
|
|
402
|
-
let observedPaths = 0;
|
|
403
|
-
let stateEvidence = false;
|
|
404
|
-
const gitLockPaths = [];
|
|
405
|
-
const started = performance.now();
|
|
406
|
-
for (const observation of active) {
|
|
407
|
-
const root = this.rootAtDirectory.get(observation.directory);
|
|
408
|
-
if (!root)
|
|
409
|
-
return null;
|
|
410
|
-
const paths = observation.suspicion.paths
|
|
411
|
-
.filter((path) => !observation.suspicion.paths?.some((candidate) => candidate !== path && path.startsWith(`${candidate}/`)));
|
|
412
|
-
observedPaths += paths.length;
|
|
413
|
-
for (const path of paths) {
|
|
414
|
-
const owner = this.repositoryAtPath(root.rootUUID, path);
|
|
415
|
-
if (!owner || (repository && owner.uuid !== repository.uuid))
|
|
416
|
-
return null;
|
|
417
|
-
repository = owner;
|
|
418
|
-
directory = observation.directory;
|
|
419
|
-
const segments = path.split("/");
|
|
420
|
-
const marker = segments.indexOf(".git");
|
|
421
|
-
if (marker >= 0) {
|
|
422
|
-
// An established repository may report Card or index metadata.
|
|
423
|
-
// Any named worktree path in the same generation is captured above;
|
|
424
|
-
// the Git Checkpoint itself carries staged bytes and modes exactly.
|
|
425
|
-
if (!hasGitMarker(owner.absolutePath))
|
|
426
|
-
return null;
|
|
427
|
-
metadataReads += 1;
|
|
428
|
-
if (isGitLockPath(segments.slice(marker + 1)))
|
|
429
|
-
gitLockPaths.push(path);
|
|
430
|
-
else
|
|
431
|
-
stateEvidence = true;
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
stateEvidence = true;
|
|
435
|
-
const exact = this.rowAtPath.get(root.rootUUID, path);
|
|
436
|
-
const stats = tryLstat(join(observation.directory, ...path.split("/")));
|
|
437
|
-
if (!exact || !stats || exact.uuid === owner.uuid
|
|
438
|
-
|| this.repositoryAtPath(root.rootUUID, exact.relativePath)?.uuid !== owner.uuid)
|
|
439
|
-
return null;
|
|
440
|
-
metadataReads += 1;
|
|
441
|
-
if (stats.isFile() && ["file.text", "file.binary"].includes(exact.type)) {
|
|
442
|
-
const captured = await captureContentFile(exact.absolutePath, stats, null);
|
|
443
|
-
const type = classifyFile({ binary: captured.binary });
|
|
444
|
-
const result = {
|
|
445
|
-
...exact,
|
|
446
|
-
type,
|
|
447
|
-
version: canonicalVersion({
|
|
448
|
-
type,
|
|
449
|
-
parentUUID: exact.parentUUID,
|
|
450
|
-
name: exact.name,
|
|
451
|
-
status: exact.status,
|
|
452
|
-
payloadVersion: captured.contentHash,
|
|
453
|
-
fileMode: portableFileMode(type, stats.mode),
|
|
454
|
-
}, sha256Hex),
|
|
455
|
-
payloadVersion: captured.contentHash,
|
|
456
|
-
transportVersion: null,
|
|
457
|
-
deviceNumber: stats.dev,
|
|
458
|
-
inode: stats.ino,
|
|
459
|
-
byteSize: stats.size,
|
|
460
|
-
modifiedTimeMs: stats.mtimeMs,
|
|
461
|
-
changedTimeMs: stats.ctimeMs,
|
|
462
|
-
filesystemMode: stats.mode,
|
|
463
|
-
contentVerifiedAtMs: Date.now(),
|
|
464
|
-
};
|
|
465
|
-
leafRows.set(result.uuid, result);
|
|
466
|
-
contentReads += 1;
|
|
467
|
-
continue;
|
|
440
|
+
const kind = stats.isFile() ? "file" : stats.isSymbolicLink() ? "link" : null;
|
|
441
|
+
if (kind === "link" && referenceWorkspaceId(absolutePath, this.bindingDir) !== null) {
|
|
442
|
+
return widened("managed-reference", path);
|
|
468
443
|
}
|
|
469
|
-
if (
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
const payloadVersion = sha256Hex(targetBytes);
|
|
473
|
-
// Record may become sendable in the same SQLite transaction below;
|
|
474
|
-
// immutable cargo therefore has to be sealed during this truth read,
|
|
475
|
-
// exactly like ordinary file capture.
|
|
476
|
-
sealCapturedArtifact(this.cacheDir, {
|
|
477
|
-
entityId: exact.uuid,
|
|
478
|
-
entityType: "link",
|
|
479
|
-
kind: "link",
|
|
480
|
-
contentHash: payloadVersion,
|
|
481
|
-
}, targetBytes);
|
|
482
|
-
const result = {
|
|
483
|
-
...exact,
|
|
484
|
-
version: canonicalVersion({
|
|
485
|
-
type: "link",
|
|
486
|
-
parentUUID: exact.parentUUID,
|
|
487
|
-
name: exact.name,
|
|
488
|
-
status: exact.status,
|
|
489
|
-
payloadVersion,
|
|
490
|
-
}, sha256Hex),
|
|
491
|
-
payloadVersion,
|
|
492
|
-
deviceNumber: stats.dev,
|
|
493
|
-
inode: stats.ino,
|
|
494
|
-
byteSize: stats.size,
|
|
495
|
-
modifiedTimeMs: stats.mtimeMs,
|
|
496
|
-
changedTimeMs: stats.ctimeMs,
|
|
497
|
-
filesystemMode: stats.mode,
|
|
498
|
-
contentVerifiedAtMs: Date.now(),
|
|
499
|
-
};
|
|
500
|
-
leafRows.set(result.uuid, result);
|
|
501
|
-
contentReads += 1;
|
|
502
|
-
continue;
|
|
444
|
+
if (kind === null || (exact && !LEAF_TYPES.has(exact.type))) {
|
|
445
|
+
const found = kind ?? (stats.isDirectory() ? "directory" : "special");
|
|
446
|
+
return widened(exact ? "kind-changed" : "unindexed-path", `${exact?.type ?? "none"} to ${found}: ${path}`);
|
|
503
447
|
}
|
|
504
|
-
|
|
448
|
+
leaves.push({ path, absolutePath, stats, kind });
|
|
505
449
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const registration = inspectGitRegistration(repository.absolutePath);
|
|
528
|
-
for (const [path, clean] of registration.cleanLeaves) {
|
|
529
|
-
const uuid = priorByOwnedPath.get(path);
|
|
530
|
-
const base = uuid ? (leafRows.get(uuid) ?? priorByUuid.get(uuid)) : undefined;
|
|
531
|
-
if (!base || (clean.type !== "link"
|
|
532
|
-
&& clean.type !== "file.text" && clean.type !== "file.binary"))
|
|
533
|
-
continue;
|
|
534
|
-
const version = canonicalVersion({
|
|
535
|
-
type: clean.type,
|
|
536
|
-
parentUUID: base.parentUUID,
|
|
537
|
-
name: base.name,
|
|
538
|
-
status: base.status,
|
|
539
|
-
payloadVersion: clean.payloadVersion,
|
|
540
|
-
fileMode: portableFileMode(clean.type, clean.fileMode),
|
|
541
|
-
}, sha256Hex);
|
|
542
|
-
if (base.type === clean.type
|
|
543
|
-
&& base.payloadVersion === clean.payloadVersion
|
|
544
|
-
&& base.version === version)
|
|
545
|
-
continue;
|
|
546
|
-
leafRows.set(base.uuid, {
|
|
547
|
-
...base,
|
|
548
|
-
type: clean.type,
|
|
549
|
-
version,
|
|
550
|
-
payloadVersion: clean.payloadVersion,
|
|
551
|
-
transportVersion: null,
|
|
552
|
-
contentVerifiedAtMs: Date.now(),
|
|
553
|
-
});
|
|
450
|
+
// A link to any file read may appear while it is read.
|
|
451
|
+
this.lease.physical(new PhysicalKeySet(leaves.filter(leaf => leaf.kind === "file").map(leaf => physicalKey(leaf.stats))));
|
|
452
|
+
// Collection is complete; identity preparation starts at a boundary.
|
|
453
|
+
await turnBoundary();
|
|
454
|
+
// 2. One identity decision for every leaf, from the closure of facts
|
|
455
|
+
// the complete resolver reads for them.
|
|
456
|
+
const identity = await resolveScopedLeaves(root.rootUUID, leaves.map(leaf => ({ relativePath: leaf.path,
|
|
457
|
+
kind: leaf.kind, physicalKey: physicalKey(leaf.stats), links: Number(leaf.stats.nlink) })), scopedIdentityPort({ root, directory: observation.directory, ground: this.ground,
|
|
458
|
+
proof: contexts.get(observation.directory), sources, eligible: row => this.isEffectivelyActive(row),
|
|
459
|
+
attribute: scope => this.requireAttribution(scope), require: address => this.lease.require([address]),
|
|
460
|
+
lstat: tryLstat }));
|
|
461
|
+
if (identity.kind === "examine")
|
|
462
|
+
return widened("identity-unproved", `${identity.reason}: ${identity.path}`);
|
|
463
|
+
for (const path of identity.occupied)
|
|
464
|
+
examine.add(join(observation.directory, ...path.split("/")));
|
|
465
|
+
const moved = new Map();
|
|
466
|
+
const { identities } = identity;
|
|
467
|
+
for (const leaf of leaves) {
|
|
468
|
+
const row = identities.get(leaf.path) ?? null;
|
|
469
|
+
if (row && row.relativePath !== leaf.path)
|
|
470
|
+
moved.set(row.relativePath, row.uuid);
|
|
554
471
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
catch {
|
|
565
|
-
// A dirty deletion or a newly discovered path changes repository
|
|
566
|
-
// membership. The ordinary reconciliation lane owns that graph
|
|
567
|
-
// change and will rebuild the exact identity map once.
|
|
568
|
-
return null;
|
|
569
|
-
}
|
|
570
|
-
const observed = uuid ? leafRows.get(uuid) : undefined;
|
|
571
|
-
if (observed
|
|
572
|
-
&& observed.deviceNumber === stats.dev
|
|
573
|
-
&& observed.inode === stats.ino
|
|
574
|
-
&& observed.byteSize === stats.size
|
|
575
|
-
&& observed.modifiedTimeMs === stats.mtimeMs
|
|
576
|
-
&& observed.changedTimeMs === stats.ctimeMs
|
|
577
|
-
&& observed.filesystemMode === stats.mode) {
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
let type;
|
|
581
|
-
let payloadVersion;
|
|
582
|
-
if (stats.isSymbolicLink()) {
|
|
583
|
-
type = "link";
|
|
584
|
-
payloadVersion = sha256Hex(Buffer.from(readlinkSync(base.absolutePath), "utf8"));
|
|
585
|
-
}
|
|
586
|
-
else if (stats.isFile()) {
|
|
587
|
-
const captured = await captureContentFile(base.absolutePath, stats, null);
|
|
588
|
-
type = classifyFile({ binary: captured.binary });
|
|
589
|
-
payloadVersion = captured.contentHash;
|
|
590
|
-
}
|
|
591
|
-
else {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
leafRows.set(base.uuid, {
|
|
595
|
-
...base,
|
|
596
|
-
type,
|
|
597
|
-
version: canonicalVersion({
|
|
598
|
-
type,
|
|
599
|
-
parentUUID: base.parentUUID,
|
|
600
|
-
name: base.name,
|
|
601
|
-
status: base.status,
|
|
602
|
-
payloadVersion,
|
|
603
|
-
fileMode: portableFileMode(type, stats.mode),
|
|
604
|
-
}, sha256Hex),
|
|
605
|
-
payloadVersion,
|
|
606
|
-
transportVersion: null,
|
|
607
|
-
deviceNumber: stats.dev,
|
|
608
|
-
inode: stats.ino,
|
|
609
|
-
byteSize: stats.size,
|
|
610
|
-
modifiedTimeMs: stats.mtimeMs,
|
|
611
|
-
changedTimeMs: stats.ctimeMs,
|
|
612
|
-
filesystemMode: stats.mode,
|
|
613
|
-
contentVerifiedAtMs: Date.now(),
|
|
614
|
-
});
|
|
615
|
-
contentReads += 1;
|
|
472
|
+
// 3. An identity moved in this unit covers its old address. A name no
|
|
473
|
+
// active identity holds, observed or shared, settles as
|
|
474
|
+
// examined-no-change: a trashed row there is an absence already
|
|
475
|
+
// concluded. Any other absence needs the complete membership.
|
|
476
|
+
for (const path of absent) {
|
|
477
|
+
await turnBoundary();
|
|
478
|
+
const holders = ["entities", "detection_notebook"].flatMap(inventory => this.ground.atPath(root.rootUUID, path, inventory).filter(row => this.isEffectivelyActive(row, inventory)));
|
|
479
|
+
if (holders.some(row => moved.get(path) !== row.uuid))
|
|
480
|
+
return widened("absent-path", path);
|
|
616
481
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
identityHash: layout.identityHash,
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
catch {
|
|
641
|
-
prior = null;
|
|
642
|
-
priorLayout = null;
|
|
643
|
-
}
|
|
482
|
+
// Every address is checked before any byte is read; the reads then run
|
|
483
|
+
// together on the capture threads instead of one file at a time.
|
|
484
|
+
const reads = [];
|
|
485
|
+
await eachInTurns(leaves, leaf => {
|
|
486
|
+
const base = identities.get(leaf.path) ?? null;
|
|
487
|
+
const parent = this.activeRowAtPath(root.rootUUID, parentPath(leaf.path));
|
|
488
|
+
if (!parent)
|
|
489
|
+
throw new Error(`named leaf ${leaf.path} has no proved parent`);
|
|
490
|
+
reads.push({ root, path: leaf.path, absolutePath: leaf.absolutePath, stats: leaf.stats, kind: leaf.kind,
|
|
491
|
+
parent, base, uuid: base?.uuid ?? randomUUID(), sources });
|
|
492
|
+
});
|
|
493
|
+
// Each read's reuse proof and dispatch are ground reads of its own.
|
|
494
|
+
const results = await settleInTurns(reads, input => input.kind === "link" ? this.captureLink(input) : this.captureFile(input));
|
|
495
|
+
let contentReads = 0;
|
|
496
|
+
for (const [index, result] of results.entries()) {
|
|
497
|
+
captured.push(result.item);
|
|
498
|
+
if (!policy.includes(reads[index].path))
|
|
499
|
+
excluded++;
|
|
500
|
+
contentReads += result.contentReads;
|
|
644
501
|
}
|
|
502
|
+
evidence.push({ directory: observation.directory, metadataReads: paths.length,
|
|
503
|
+
metadataReused: leaves.length - contentReads, contentReads,
|
|
504
|
+
gitInspections: 0, repositoryCaptures: 0, plannedRows: captured.length - first,
|
|
505
|
+
records: 0, settled: 0, recordBytes: 0, durationMs: performance.now() - started });
|
|
645
506
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
507
|
+
// Each captured name owes its object's other names (alias-examination.ts).
|
|
508
|
+
for (const path of aliasExaminations(captured.map(({ base, result }) => ({ before: base, after: result })), { objectRows: key => this.ground.objectRows(key), physical: uuid => this.ground.physical(uuid) }))
|
|
509
|
+
examine.add(path);
|
|
510
|
+
this.mark("collect");
|
|
511
|
+
const priors = new Map();
|
|
512
|
+
const sharing = new Map();
|
|
513
|
+
const adopted = new Set();
|
|
514
|
+
for (const { base, result } of captured) {
|
|
515
|
+
await turnBoundary();
|
|
516
|
+
const prior = base ? this.ground.sharing(result.uuid) : undefined;
|
|
517
|
+
if (prior && prior.status !== "active")
|
|
518
|
+
return widened("unshared-leaf", result.relativePath);
|
|
519
|
+
if (prior)
|
|
520
|
+
priors.set(result.uuid, prior);
|
|
521
|
+
// A new sharing identity allocates its address; another identity there
|
|
522
|
+
// is a divergence only the complete graph resolves.
|
|
523
|
+
else if (this.ground.atPath(result.rootUUID, result.relativePath, "detection_notebook").length) {
|
|
524
|
+
return widened("address-changed", result.relativePath);
|
|
660
525
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const after = repositoryIdentity(repository, refreshedRows);
|
|
665
|
-
if (sameGitIdentity(after, identity)) {
|
|
666
|
-
identity = [...captured.identity];
|
|
667
|
-
coherent = true;
|
|
668
|
-
break;
|
|
526
|
+
if (acceptedBasis.covers(result.resourceId, { entityId: result.uuid, result: portable(result) })) {
|
|
527
|
+
adopted.add(result.uuid);
|
|
528
|
+
sharing.set(result.uuid, result);
|
|
669
529
|
}
|
|
670
|
-
|
|
530
|
+
else if (prior)
|
|
531
|
+
sharing.set(result.uuid, prior);
|
|
671
532
|
}
|
|
672
|
-
|
|
673
|
-
|
|
533
|
+
const targets = new Set(captured.map(item => item.result.uuid));
|
|
534
|
+
const context = this.publicationContext([...captured.flatMap(({ base, result }) => base ? [base, result] : [result]),
|
|
535
|
+
...priors.values(), ...sharing.values()], targets);
|
|
536
|
+
if (context === null)
|
|
537
|
+
return widened("address-changed", "ancestor structure differs");
|
|
538
|
+
let selection;
|
|
539
|
+
try {
|
|
540
|
+
selection = selectPublication({ before: [...sharing.values()], observed: captured.map(item => item.result),
|
|
541
|
+
context, includes: (path, rootId) => {
|
|
542
|
+
const policy = policies.get(rootId);
|
|
543
|
+
if (!policy)
|
|
544
|
+
throw new Error("publication has no observed root policy");
|
|
545
|
+
return policy.includes(path);
|
|
546
|
+
} });
|
|
674
547
|
}
|
|
675
|
-
|
|
676
|
-
if (!
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
const uuid = priorByOwnedPath.get(entry.path);
|
|
681
|
-
const base = uuid ? (leafRows.get(uuid) ?? priorByUuid.get(uuid)) : undefined;
|
|
682
|
-
if (!base)
|
|
683
|
-
continue;
|
|
684
|
-
const type = entry.type;
|
|
685
|
-
leafRows.set(base.uuid, {
|
|
686
|
-
...base,
|
|
687
|
-
type,
|
|
688
|
-
version: canonicalVersion({
|
|
689
|
-
type,
|
|
690
|
-
parentUUID: base.parentUUID,
|
|
691
|
-
name: base.name,
|
|
692
|
-
status: base.status,
|
|
693
|
-
payloadVersion: entry.payloadVersion,
|
|
694
|
-
fileMode: portableFileMode(type, base.filesystemMode),
|
|
695
|
-
}, sha256Hex),
|
|
696
|
-
payloadVersion: entry.payloadVersion,
|
|
697
|
-
transportVersion: null,
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
if (captured.bytes) {
|
|
701
|
-
sealCapturedArtifact(this.cacheDir, {
|
|
702
|
-
entityId: repository.uuid,
|
|
703
|
-
entityType: "repo.git",
|
|
704
|
-
kind: "git",
|
|
705
|
-
contentHash: captured.transportVersion,
|
|
706
|
-
}, captured.bytes);
|
|
548
|
+
catch (error) {
|
|
549
|
+
if (!(error instanceof PublicationScopeExceeded))
|
|
550
|
+
throw error;
|
|
551
|
+
return widened("scope-exceeded", contextPath(context, error.entityIds[0]));
|
|
707
552
|
}
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
changedTimeMs: stats.ctimeMs,
|
|
727
|
-
filesystemMode: stats.mode,
|
|
728
|
-
};
|
|
729
|
-
const proposal = detectedChangeProposal({
|
|
730
|
-
base: portable(repository),
|
|
731
|
-
result: portable(result),
|
|
732
|
-
replay: {
|
|
733
|
-
kind: "repo.git",
|
|
734
|
-
stateId: layout.stateId,
|
|
735
|
-
cardId: layout.cardId,
|
|
736
|
-
checkpointId: layout.checkpointId,
|
|
737
|
-
cardChanged: priorLayout?.cardId !== layout.cardId,
|
|
738
|
-
checkpointChanged: priorLayout?.checkpointId !== layout.checkpointId,
|
|
739
|
-
parentTransportVersion: layout.parentTransportVersion,
|
|
740
|
-
transportVersion: captured.transportVersion,
|
|
741
|
-
},
|
|
553
|
+
const admitted = selection.selected.filter(row => selection.sources.get(row.uuid) === "observed" || adopted.has(row.uuid));
|
|
554
|
+
// Selection is complete; container preparation starts at a boundary.
|
|
555
|
+
await turnBoundary();
|
|
556
|
+
// Parents derive O from every observed result and S from the selected
|
|
557
|
+
// result only, each against its stored row; neither membership change is
|
|
558
|
+
// a Record of its own.
|
|
559
|
+
const observedContainers = finalizeContainers({
|
|
560
|
+
parents: [...membershipParents(captured.map(({ base, result }) => ({ before: base ? this.ground.physical(result.uuid) : null, after: result })))]
|
|
561
|
+
.map(uuid => required(this.ground.physical(uuid), uuid)),
|
|
562
|
+
children: uuid => this.ground.siblings("entities", uuid),
|
|
563
|
+
targets, results: captured.map(item => item.result), sha256Hex
|
|
564
|
+
});
|
|
565
|
+
const selected = new Map(selection.selected.map(row => [row.uuid, row]));
|
|
566
|
+
const sharedContainers = finalizeContainers({
|
|
567
|
+
parents: [...membershipParents([...targets].map(uuid => ({ before: priors.get(uuid) ?? null, after: selected.get(uuid) ?? null })))]
|
|
568
|
+
.map(uuid => required(this.ground.sharing(uuid), uuid)),
|
|
569
|
+
children: uuid => this.ground.siblings("detection_notebook", uuid),
|
|
570
|
+
targets, results: selection.selected, sha256Hex
|
|
742
571
|
});
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
572
|
+
// Payload replays read their results' cache names; no other stage is
|
|
573
|
+
// cargo. A payload observed through a reused proof is read once now.
|
|
574
|
+
const carried = admitted.filter(row => !adopted.has(row.uuid) && replaysPayload(priors.get(row.uuid) ?? null, row));
|
|
575
|
+
const unread = carried.filter(row => !this.cargo.holds(row.payloadVersion));
|
|
576
|
+
await settleInTurns(unread, row => this.sealUnread(row));
|
|
577
|
+
await this.cargo.publish(new Set(carried.map(row => row.payloadVersion)));
|
|
578
|
+
// Every replay starts before any is awaited: each reads the cache on a
|
|
579
|
+
// content thread.
|
|
580
|
+
const proposals = (await settleInTurns(admitted.filter(row => !adopted.has(row.uuid)), async (row) => {
|
|
581
|
+
const proposal = await this.proposalFor(priors.get(row.uuid) ?? null, row);
|
|
582
|
+
return proposal ? [{ authorityId: row.resourceId, proposal }] : [];
|
|
583
|
+
})).flat();
|
|
584
|
+
await makeRecordContentDurable(this.cacheDir, proposals.flatMap(({ proposal }) => proposal.cargo));
|
|
585
|
+
this.mark("durable");
|
|
586
|
+
// Consumed Git answers their witnesses cannot close are asked again;
|
|
587
|
+
// only native witnesses close inside admission.
|
|
588
|
+
await this.gitProof.proof.semantic();
|
|
589
|
+
this.mark("close");
|
|
590
|
+
const basis = this.ground.finish();
|
|
591
|
+
const ownership = this.ownership.seal();
|
|
749
592
|
const commitStarted = performance.now();
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
593
|
+
const accounted = admitted.filter(row => adopted.has(row.uuid)).length;
|
|
594
|
+
const written = await this.admit("named leaves: admission depends on their ancestor spine", basis, acceptedBasis, () => {
|
|
595
|
+
ownership.assertCurrent();
|
|
596
|
+
// One recheck after every leaf was observed closes each container.
|
|
597
|
+
if ([...contexts.values()].some(context => !context.unchanged()))
|
|
598
|
+
return null;
|
|
599
|
+
closePhysically(this.gitProof.proof);
|
|
600
|
+
for (const policy of policies.values())
|
|
601
|
+
policy.validate();
|
|
602
|
+
sources.validate();
|
|
603
|
+
const createdAt = Date.now();
|
|
604
|
+
return {
|
|
605
|
+
writes: {
|
|
606
|
+
repositoryObservations: [], seals: [],
|
|
607
|
+
materialized: captured.map(item => retainContentVerification(item.base, item.result)).concat(observedContainers),
|
|
608
|
+
materializedRemovals: [],
|
|
609
|
+
notebook: admitted.map(row => retainContentVerification(sharing.get(row.uuid), row)).concat(sharedContainers),
|
|
610
|
+
records: proposals.map(({ authorityId, proposal }) => createLocalEntityRecord({ mutationId: randomUUID(),
|
|
611
|
+
authorityId, lastSeenGlobalSequence: this.entityReceivedThrough(authorityId, proposal.entityId),
|
|
612
|
+
change: proposal, createdAt })),
|
|
613
|
+
},
|
|
614
|
+
counts: { proposed: proposals.length + accounted, admitted: proposals.length,
|
|
615
|
+
alreadyAccounted: accounted, withheld: captured.length - admitted.length },
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
if (!written)
|
|
619
|
+
return retry("admission-basis-changed");
|
|
620
|
+
const { records: committedRecords, ground, changes } = written;
|
|
621
|
+
if (changes)
|
|
622
|
+
this.onDetectCommit(Object.freeze({ changes, records: written.recordJSON }));
|
|
623
|
+
this.onRecordCommit?.({ durationMs: performance.now() - commitStarted,
|
|
624
|
+
records: committedRecords, ...(ground ? { ground } : {}) });
|
|
625
|
+
// A changed policy owes a proof wherever sharing is not yet its
|
|
626
|
+
// selection. Saves commit in posting order, so this commit made every
|
|
627
|
+
// admission that read the old policy visible to the read; any later one
|
|
628
|
+
// read this policy.
|
|
629
|
+
for (const { directory, rootUUID, text } of policyChanges) {
|
|
630
|
+
for (const path of await readOwedAddresses(this.database.name, rootUUID, text, this.bindingDir)) {
|
|
631
|
+
examine.add(join(directory, ...path.split("/")));
|
|
757
632
|
}
|
|
758
|
-
|
|
759
|
-
|
|
633
|
+
}
|
|
634
|
+
if (policyChanges.length)
|
|
635
|
+
this.mark("include");
|
|
636
|
+
return { ...settled(evidence.map(item => {
|
|
637
|
+
const root = observations.find(observation => observation.directory === item.directory);
|
|
638
|
+
const within = (row) => row.absolutePath.startsWith(`${root.directory}/`);
|
|
639
|
+
const ids = new Set(captured.filter(entry => within(entry.result)).map(entry => entry.result.uuid));
|
|
640
|
+
const records = committedRecords.filter(record => ids.has(record.entityId));
|
|
641
|
+
const late = unread.filter(within).length;
|
|
642
|
+
return { ...item, metadataReused: item.metadataReused - late, contentReads: item.contentReads + late,
|
|
643
|
+
records: records.length, settled: [...ids].filter(id => adopted.has(id)).length,
|
|
644
|
+
recordBytes: records.reduce((sum, record) => sum + Buffer.byteLength(encodeEntityRecord(record)), 0) };
|
|
645
|
+
}), captured.map(item => item.result.uuid), captured.length > 0 && excluded === captured.length),
|
|
646
|
+
...(examine.size ? { examine: [...examine] } : {}) };
|
|
647
|
+
}
|
|
648
|
+
/** The unchanged structure that closes the targets' collision-derived
|
|
649
|
+
* addresses, from each inventory separately: every container on the
|
|
650
|
+
* ancestry of any target row, and the complete active child group of each
|
|
651
|
+
* one a collision owes (Live's childGroupOwed), in both inventories. A
|
|
652
|
+
* container's members are the target rows and ancestry containers it
|
|
653
|
+
* holds; their slots are read one by one, never the container, so a leaf
|
|
654
|
+
* in a directory of any size reads its ancestry and a few slots. Ancestors
|
|
655
|
+
* agree structurally in both inventories; owed groups need not, since an
|
|
656
|
+
* O-only ignored sibling is observation, not sharing. The selector refuses
|
|
657
|
+
* any selection that would change a context member. Content outside the
|
|
658
|
+
* targets is neither loaded nor a validation dependency. */
|
|
659
|
+
publicationContext(rows, targets) {
|
|
660
|
+
const before = new Map(), observed = new Map();
|
|
661
|
+
const members = new Map();
|
|
662
|
+
const place = (row) => {
|
|
663
|
+
const placed = members.get(row.parentUUID);
|
|
664
|
+
if (placed)
|
|
665
|
+
placed.push(row);
|
|
666
|
+
else
|
|
667
|
+
members.set(row.parentUUID, [row]);
|
|
668
|
+
};
|
|
669
|
+
const shape = (row) => JSON.stringify([
|
|
670
|
+
row.uuid, row.parentUUID, row.name, row.type, row.relativePath, row.status,
|
|
671
|
+
]);
|
|
672
|
+
const node = (row) => row.relativePath === "" ? { ...row, parentUUID: null } : row;
|
|
673
|
+
for (const row of rows) {
|
|
674
|
+
place(row);
|
|
675
|
+
const path = new Set();
|
|
676
|
+
for (let parentUUID = row.parentUUID; parentUUID !== null && !observed.has(parentUUID);) {
|
|
677
|
+
if (path.has(parentUUID))
|
|
678
|
+
throw new Error("publication ancestry has a cycle");
|
|
679
|
+
path.add(parentUUID);
|
|
680
|
+
const current = this.ground.structure("entities", parentUUID);
|
|
681
|
+
const shared = this.ground.structure("detection_notebook", parentUUID);
|
|
682
|
+
if (!current || !shared || current.status !== "active" || shape(current) !== shape(shared))
|
|
683
|
+
return null;
|
|
684
|
+
before.set(shared.uuid, node(shared));
|
|
685
|
+
observed.set(current.uuid, node(current));
|
|
686
|
+
if (current.relativePath === "")
|
|
687
|
+
break;
|
|
688
|
+
place(current);
|
|
689
|
+
parentUUID = current.parentUUID;
|
|
760
690
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
691
|
+
}
|
|
692
|
+
const active = (row) => row.status === "active";
|
|
693
|
+
// An ancestor keeps the structure row proved above.
|
|
694
|
+
const ancestry = new Set(observed.keys());
|
|
695
|
+
const unchanged = (row) => active(row) && !targets.has(row.uuid) && !ancestry.has(row.uuid);
|
|
696
|
+
for (const [parentUUID, placed] of members) {
|
|
697
|
+
if (!["detection_notebook", "entities"].some(inventory => childGroupOwed(placed, name => this.ground.slot(inventory, parentUUID, name).filter(active).map(row => row.uuid))))
|
|
698
|
+
continue;
|
|
699
|
+
for (const sibling of this.ground.siblings("detection_notebook", parentUUID).filter(unchanged)) {
|
|
700
|
+
before.set(sibling.uuid, sibling);
|
|
764
701
|
}
|
|
765
|
-
this.
|
|
766
|
-
|
|
767
|
-
if (proposal && shouldRecord
|
|
768
|
-
&& !this.acceptedInbox.covers(repository.resourceId, proposal)) {
|
|
769
|
-
committedRecords.push(this.appendOutboxRecord(repository.resourceId, proposal));
|
|
770
|
-
committedRecord = true;
|
|
702
|
+
for (const sibling of this.ground.siblings("entities", parentUUID).filter(unchanged)) {
|
|
703
|
+
observed.set(sibling.uuid, sibling);
|
|
771
704
|
}
|
|
772
|
-
return true;
|
|
773
|
-
})();
|
|
774
|
-
if (!committed)
|
|
775
|
-
return { handled: false, evidence: [] };
|
|
776
|
-
this.onRecordCommit?.({
|
|
777
|
-
durationMs: performance.now() - commitStarted,
|
|
778
|
-
records: committedRecords,
|
|
779
|
-
});
|
|
780
|
-
return {
|
|
781
|
-
handled: true,
|
|
782
|
-
evidence: [{
|
|
783
|
-
directory,
|
|
784
|
-
paths: active.reduce((count, item) => count + item.suspicion.paths.length, 0),
|
|
785
|
-
metadataReads,
|
|
786
|
-
contentReads,
|
|
787
|
-
gitInspections: 1,
|
|
788
|
-
repositoryCaptures: 1,
|
|
789
|
-
plannedRows: 1,
|
|
790
|
-
records: committedRecord ? 1 : 0,
|
|
791
|
-
recordBytes: committedRecord ? recordBytes : 0,
|
|
792
|
-
durationMs: performance.now() - started,
|
|
793
|
-
}],
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
repositoryAtPath(rootUUID, path) {
|
|
797
|
-
const segments = path.split("/");
|
|
798
|
-
const marker = segments.indexOf(".git");
|
|
799
|
-
if (marker >= 0) {
|
|
800
|
-
const candidate = segments.slice(0, marker).join("/");
|
|
801
|
-
const row = this.rowAtPath.get(rootUUID, candidate);
|
|
802
|
-
return row?.type === "repo.git" ? row : null;
|
|
803
705
|
}
|
|
706
|
+
return { before: [...before.values()], observed: [...observed.values()] };
|
|
707
|
+
}
|
|
708
|
+
repositoryAtPath(rootUUID, path, directory) {
|
|
709
|
+
const route = this.gitProof.route(join(directory, path));
|
|
710
|
+
if (route.kind === "reconcile")
|
|
711
|
+
return null;
|
|
712
|
+
if (route.kind === "single")
|
|
713
|
+
return this.ground.physical(route.uuid) ?? null;
|
|
804
714
|
let candidate = path;
|
|
805
715
|
while (true) {
|
|
806
|
-
const row = this.
|
|
716
|
+
const row = this.activeRowAtPath(rootUUID, candidate);
|
|
807
717
|
if (row?.type === "repo.git")
|
|
808
718
|
return row;
|
|
809
719
|
if (candidate === "")
|
|
@@ -812,77 +722,77 @@ export class NamedDetectRuntime {
|
|
|
812
722
|
candidate = parent === "." ? "" : parent;
|
|
813
723
|
}
|
|
814
724
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
725
|
+
/** One admission of a registered collection, as one save
|
|
726
|
+
* (admission-save.ts): its ready fact commits beside its outcome, in the
|
|
727
|
+
* save on success or after it otherwise. Collection held no ground; what
|
|
728
|
+
* this thread owns is answered when the save is posted, and saves commit
|
|
729
|
+
* in post order. Null when the basis moved. */
|
|
730
|
+
async admit(unit, basis, accepted, prepare) {
|
|
731
|
+
const collection = this.register(unit, basis);
|
|
732
|
+
this.mark("ready");
|
|
733
|
+
const invalidated = { disposition: "invalidated", retry: null, phase: "admission-basis",
|
|
734
|
+
reason: "the admission basis changed after collection" };
|
|
735
|
+
try {
|
|
736
|
+
let prepared, posted = null;
|
|
737
|
+
try {
|
|
738
|
+
this.lease.assertUncrossed();
|
|
739
|
+
prepared = prepare();
|
|
740
|
+
if (prepared)
|
|
741
|
+
posted = save(this.database.name, "detect.admit", {
|
|
742
|
+
units: [{ ...prepared.writes, basis: basis.toData(), dispositions: collection.admission(prepared.counts) }],
|
|
743
|
+
accepted: accepted.reads(), observe: this.onDetectCommit !== undefined
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
collection.rejected(admissionRejection(error), null);
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
if (!prepared || !posted) {
|
|
751
|
+
collection.rejected(invalidated, null);
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
const requested = performance.now();
|
|
755
|
+
let saved;
|
|
756
|
+
try {
|
|
757
|
+
saved = await posted;
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
collection.rejected(admissionRejection(error), { requested, began: null, checked: null, ended: performance.now() });
|
|
761
|
+
throw error;
|
|
762
|
+
}
|
|
763
|
+
const result = saved.value;
|
|
764
|
+
const marks = { requested: saved.posted, began: saved.began,
|
|
765
|
+
checked: result.accepted ? result.checkedAt - performance.timeOrigin : null, ended: saved.ended };
|
|
766
|
+
if (!result.accepted || result.stale.length) {
|
|
767
|
+
collection.rejected(invalidated, marks);
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
collection.committed(marks, result.outcomes[0]);
|
|
771
|
+
return { records: prepared.writes.records, recordJSON: Object.freeze([...result.records]),
|
|
772
|
+
...(this.measureGround() || result.changes ? { ground: result.evidence } : {}),
|
|
773
|
+
...(result.changes ? { changes: result.changes } : {}) };
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
this.mark("admit");
|
|
777
|
+
}
|
|
835
778
|
}
|
|
836
779
|
entityReceivedThrough(authorityId, entityId) {
|
|
837
780
|
const row = this.receivedThrough.get(authorityId, entityId);
|
|
838
781
|
return row?.receivedThrough ?? 0;
|
|
839
782
|
}
|
|
840
|
-
/** Derived collision names are local addresses, not portable renames. A
|
|
841
|
-
* same-parent observation at either the portable name or the exact address
|
|
842
|
-
* derived from the current sibling set keeps the stored portable name. A
|
|
843
|
-
* different parent, or any third basename, is real move/rename evidence. */
|
|
844
|
-
capturedName(base, path, parentUUID) {
|
|
845
|
-
const seen = posix.basename(path);
|
|
846
|
-
if (!base || base.parentUUID !== parentUUID)
|
|
847
|
-
return seen;
|
|
848
|
-
let derived = this.slotNamesByParent.get(parentUUID);
|
|
849
|
-
if (!derived) {
|
|
850
|
-
derived = materializedSlotNames(this.materializedSiblings.all(parentUUID));
|
|
851
|
-
this.slotNamesByParent.set(parentUUID, derived);
|
|
852
|
-
}
|
|
853
|
-
const expected = derived.get(base.uuid) ?? base.name;
|
|
854
|
-
return seen === base.name || seen === expected ? base.name : seen;
|
|
855
|
-
}
|
|
856
783
|
async captureFile(input) {
|
|
857
|
-
const { root, path, absolutePath, stats, parent,
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (fingerprintTrusted)
|
|
866
|
-
return { item: null, contentReads: 0 };
|
|
867
|
-
const canReuseMovedContent = base !== null
|
|
868
|
-
&& base.relativePath !== path
|
|
869
|
-
&& base.deviceNumber === stats.dev
|
|
870
|
-
&& base.inode === stats.ino
|
|
871
|
-
&& base.byteSize === stats.size
|
|
872
|
-
&& base.modifiedTimeMs === stats.mtimeMs
|
|
873
|
-
&& base.filesystemMode === stats.mode
|
|
874
|
-
&& base.payloadVersion !== null;
|
|
875
|
-
const content = canReuseMovedContent ? null : await captureContentFile(absolutePath, stats, this.cacheDir);
|
|
876
|
-
const type = content ? classifyFile({ binary: content.binary }) : base?.type;
|
|
877
|
-
const payloadVersion = content?.contentHash ?? base?.payloadVersion ?? null;
|
|
878
|
-
if (!type || !["file.text", "file.binary"].includes(type) || !payloadVersion) {
|
|
879
|
-
throw new Error(`named Detect could not capture ${path}`);
|
|
880
|
-
}
|
|
881
|
-
const name = this.capturedName(base, path, parent.uuid);
|
|
784
|
+
const { root, path, absolutePath, stats, parent, base, uuid, sources } = input;
|
|
785
|
+
sources.remember(absolutePath, stats);
|
|
786
|
+
if (this.unchangedLeaf(input))
|
|
787
|
+
return { item: { base, result: base }, contentReads: 0 };
|
|
788
|
+
const content = await captureContent(absolutePath, stats, this.cacheDir, { lane: "named", cargo: this.cargo });
|
|
789
|
+
const type = classifyFile({ binary: content.binary });
|
|
790
|
+
const payloadVersion = content.contentHash;
|
|
791
|
+
const name = observedPortableName(base, parent.uuid, posix.basename(path));
|
|
882
792
|
const result = {
|
|
883
793
|
resourceId: root.resourceId,
|
|
884
794
|
rootUUID: root.rootUUID,
|
|
885
|
-
uuid
|
|
795
|
+
uuid,
|
|
886
796
|
type,
|
|
887
797
|
parentUUID: parent.uuid,
|
|
888
798
|
name,
|
|
@@ -893,222 +803,31 @@ export class NamedDetectRuntime {
|
|
|
893
803
|
name,
|
|
894
804
|
status: "active",
|
|
895
805
|
payloadVersion,
|
|
896
|
-
fileMode: portableFileMode(type, stats.mode),
|
|
806
|
+
fileMode: portableFileMode(type, Number(stats.mode)),
|
|
897
807
|
}, sha256Hex),
|
|
898
808
|
payloadVersion,
|
|
899
809
|
transportVersion: null,
|
|
900
810
|
relativePath: path,
|
|
901
811
|
absolutePath,
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
byteSize: stats.size,
|
|
905
|
-
modifiedTimeMs: stats.mtimeMs,
|
|
906
|
-
changedTimeMs: stats.ctimeMs,
|
|
907
|
-
filesystemMode: stats.mode,
|
|
908
|
-
contentVerifiedAtMs: content ? Date.now() : base?.contentVerifiedAtMs ?? null,
|
|
909
|
-
};
|
|
910
|
-
const replay = base !== null
|
|
911
|
-
&& base.type === result.type
|
|
912
|
-
&& base.payloadVersion === result.payloadVersion
|
|
913
|
-
&& base.transportVersion === result.transportVersion
|
|
914
|
-
? { kind: "structure" }
|
|
915
|
-
: this.replayFor(base, result);
|
|
916
|
-
return {
|
|
917
|
-
item: {
|
|
918
|
-
base,
|
|
919
|
-
result,
|
|
920
|
-
proposal: detectedChangeProposal({
|
|
921
|
-
base: base ? portable(base) : null,
|
|
922
|
-
result: portable(result),
|
|
923
|
-
replay,
|
|
924
|
-
}),
|
|
925
|
-
},
|
|
926
|
-
contentReads: content ? 1 : 0,
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
async captureDirectory(input) {
|
|
930
|
-
if (existsSync(join(input.absolutePath, ".git")))
|
|
931
|
-
return null;
|
|
932
|
-
const exact = this.rowAtPath.get(input.root.rootUUID, input.path);
|
|
933
|
-
if (exact && !["workspace", "folder"].includes(exact.type))
|
|
934
|
-
return null;
|
|
935
|
-
const base = exact ?? this.uniqueMovedBase(input.stats.dev, input.stats.ino, input.absolutePath);
|
|
936
|
-
if (base && !["workspace", "folder"].includes(base.type))
|
|
937
|
-
return null;
|
|
938
|
-
const uuid = base?.uuid ?? randomUUID();
|
|
939
|
-
const childItems = [];
|
|
940
|
-
let metadataReads = 1;
|
|
941
|
-
let contentReads = 0;
|
|
942
|
-
const children = [];
|
|
943
|
-
for (const child of readdirSync(input.absolutePath, { withFileTypes: true })
|
|
944
|
-
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
945
|
-
const path = `${input.path}/${child.name}`;
|
|
946
|
-
if (!input.policy(path))
|
|
947
|
-
continue;
|
|
948
|
-
const absolutePath = join(input.absolutePath, child.name);
|
|
949
|
-
const stats = lstatSync(absolutePath);
|
|
950
|
-
metadataReads += 1;
|
|
951
|
-
// A named new folder can still be a large subtree. Yield periodically so
|
|
952
|
-
// the wire and Apply progress while it is enumerated.
|
|
953
|
-
if (++this.walkedEntries % NAMED_WALK_YIELD_ENTRIES === 0) {
|
|
954
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
955
|
-
}
|
|
956
|
-
const parentRow = {
|
|
957
|
-
resourceId: input.root.resourceId,
|
|
958
|
-
rootUUID: input.root.rootUUID,
|
|
959
|
-
uuid,
|
|
960
|
-
type: "folder",
|
|
961
|
-
parentUUID: input.parent.uuid,
|
|
962
|
-
name: posix.basename(input.path),
|
|
963
|
-
status: "active",
|
|
964
|
-
version: "0".repeat(64),
|
|
965
|
-
payloadVersion: null,
|
|
966
|
-
transportVersion: null,
|
|
967
|
-
relativePath: input.path,
|
|
968
|
-
absolutePath: input.absolutePath,
|
|
969
|
-
deviceNumber: input.stats.dev,
|
|
970
|
-
inode: input.stats.ino,
|
|
971
|
-
byteSize: input.stats.size,
|
|
972
|
-
modifiedTimeMs: input.stats.mtimeMs,
|
|
973
|
-
changedTimeMs: input.stats.ctimeMs,
|
|
974
|
-
filesystemMode: input.stats.mode,
|
|
975
|
-
contentVerifiedAtMs: null,
|
|
976
|
-
};
|
|
977
|
-
if (stats.isSymbolicLink()) {
|
|
978
|
-
const link = this.captureLink({
|
|
979
|
-
root: input.root,
|
|
980
|
-
path,
|
|
981
|
-
absolutePath,
|
|
982
|
-
stats,
|
|
983
|
-
parent: parentRow,
|
|
984
|
-
exact: this.rowAtPath.get(input.root.rootUUID, path),
|
|
985
|
-
});
|
|
986
|
-
if (!link)
|
|
987
|
-
return null;
|
|
988
|
-
const identity = link.item?.result
|
|
989
|
-
?? this.rowAtPath.get(input.root.rootUUID, path);
|
|
990
|
-
if (!identity)
|
|
991
|
-
throw new Error(`captured link ${path} has no identity`);
|
|
992
|
-
children.push({ uuid: identity.uuid, name: identity.name });
|
|
993
|
-
if (link.item)
|
|
994
|
-
childItems.push(link.item);
|
|
995
|
-
contentReads += link.contentReads;
|
|
996
|
-
}
|
|
997
|
-
else if (stats.isDirectory()) {
|
|
998
|
-
const nested = await this.captureDirectory({
|
|
999
|
-
root: input.root,
|
|
1000
|
-
path,
|
|
1001
|
-
absolutePath,
|
|
1002
|
-
stats,
|
|
1003
|
-
parent: parentRow,
|
|
1004
|
-
policy: input.policy,
|
|
1005
|
-
});
|
|
1006
|
-
if (!nested)
|
|
1007
|
-
return null;
|
|
1008
|
-
const nestedRoot = nested.items[0];
|
|
1009
|
-
if (!nestedRoot)
|
|
1010
|
-
throw new Error(`captured directory ${path} has no root`);
|
|
1011
|
-
children.push({ uuid: nestedRoot.result.uuid, name: nestedRoot.result.name });
|
|
1012
|
-
childItems.push(...nested.items);
|
|
1013
|
-
metadataReads += nested.metadataReads - 1;
|
|
1014
|
-
contentReads += nested.contentReads;
|
|
1015
|
-
}
|
|
1016
|
-
else if (stats.isFile()) {
|
|
1017
|
-
const file = await this.captureFile({
|
|
1018
|
-
root: input.root,
|
|
1019
|
-
path,
|
|
1020
|
-
absolutePath,
|
|
1021
|
-
stats,
|
|
1022
|
-
parent: parentRow,
|
|
1023
|
-
exact: this.rowAtPath.get(input.root.rootUUID, path),
|
|
1024
|
-
});
|
|
1025
|
-
const identity = file.item?.result
|
|
1026
|
-
?? this.rowAtPath.get(input.root.rootUUID, path);
|
|
1027
|
-
if (!identity)
|
|
1028
|
-
throw new Error(`captured file ${path} has no identity`);
|
|
1029
|
-
children.push({ uuid: identity.uuid, name: identity.name });
|
|
1030
|
-
if (file.item)
|
|
1031
|
-
childItems.push(file.item);
|
|
1032
|
-
contentReads += file.contentReads;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
const name = this.capturedName(base, input.path, input.parent.uuid);
|
|
1036
|
-
const result = {
|
|
1037
|
-
resourceId: input.root.resourceId,
|
|
1038
|
-
rootUUID: input.root.rootUUID,
|
|
1039
|
-
uuid,
|
|
1040
|
-
type: "folder",
|
|
1041
|
-
parentUUID: input.parent.uuid,
|
|
1042
|
-
name,
|
|
1043
|
-
status: "active",
|
|
1044
|
-
version: canonicalVersion({
|
|
1045
|
-
type: "folder",
|
|
1046
|
-
parentUUID: input.parent.uuid,
|
|
1047
|
-
name,
|
|
1048
|
-
status: "active",
|
|
1049
|
-
payloadVersion: membershipHash(children, sha256Hex),
|
|
1050
|
-
}, sha256Hex),
|
|
1051
|
-
payloadVersion: null,
|
|
1052
|
-
transportVersion: null,
|
|
1053
|
-
relativePath: input.path,
|
|
1054
|
-
absolutePath: input.absolutePath,
|
|
1055
|
-
deviceNumber: input.stats.dev,
|
|
1056
|
-
inode: input.stats.ino,
|
|
1057
|
-
byteSize: input.stats.size,
|
|
1058
|
-
modifiedTimeMs: input.stats.mtimeMs,
|
|
1059
|
-
changedTimeMs: input.stats.ctimeMs,
|
|
1060
|
-
filesystemMode: input.stats.mode,
|
|
1061
|
-
contentVerifiedAtMs: null,
|
|
1062
|
-
};
|
|
1063
|
-
const proposal = detectedChangeProposal({
|
|
1064
|
-
base: base ? portable(base) : null,
|
|
1065
|
-
result: portable(result),
|
|
1066
|
-
replay: { kind: "structure" },
|
|
1067
|
-
});
|
|
1068
|
-
return {
|
|
1069
|
-
items: [{ base, result, proposal }, ...childItems],
|
|
1070
|
-
metadataReads,
|
|
1071
|
-
contentReads,
|
|
812
|
+
...sourceFingerprint(stats),
|
|
813
|
+
contentVerifiedNs: leafBoundary(this.clock, stats),
|
|
1072
814
|
};
|
|
815
|
+
return { item: { base, result }, contentReads: 1 };
|
|
1073
816
|
}
|
|
1074
|
-
captureLink(input) {
|
|
1075
|
-
const { root, path, absolutePath, stats, parent,
|
|
1076
|
-
|
|
1077
|
-
if (
|
|
1078
|
-
return
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
&& base.relativePath !== path
|
|
1089
|
-
&& base.deviceNumber === stats.dev
|
|
1090
|
-
&& base.inode === stats.ino
|
|
1091
|
-
&& base.byteSize === stats.size
|
|
1092
|
-
&& base.modifiedTimeMs === stats.mtimeMs
|
|
1093
|
-
&& base.filesystemMode === stats.mode
|
|
1094
|
-
&& base.payloadVersion !== null;
|
|
1095
|
-
const target = canReuseMovedTarget ? null : readlinkSync(absolutePath);
|
|
1096
|
-
const targetBytes = target === null ? null : Buffer.from(target, "utf8");
|
|
1097
|
-
const uuid = base?.uuid ?? randomUUID();
|
|
1098
|
-
const payloadVersion = target === null
|
|
1099
|
-
? base?.payloadVersion ?? null
|
|
1100
|
-
: sha256Hex(targetBytes);
|
|
1101
|
-
if (!payloadVersion)
|
|
1102
|
-
return null;
|
|
1103
|
-
if (targetBytes) {
|
|
1104
|
-
sealCapturedArtifact(this.cacheDir, {
|
|
1105
|
-
entityId: uuid,
|
|
1106
|
-
entityType: "link",
|
|
1107
|
-
kind: "link",
|
|
1108
|
-
contentHash: payloadVersion,
|
|
1109
|
-
}, targetBytes);
|
|
1110
|
-
}
|
|
1111
|
-
const name = this.capturedName(base, path, parent.uuid);
|
|
817
|
+
async captureLink(input) {
|
|
818
|
+
const { root, path, absolutePath, stats, parent, base, uuid, sources } = input;
|
|
819
|
+
sources.remember(absolutePath, stats);
|
|
820
|
+
if (this.unchangedLeaf(input))
|
|
821
|
+
return { item: { base, result: base }, contentReads: 0 };
|
|
822
|
+
const targetBytes = Buffer.from(readlinkSync(absolutePath), "utf8");
|
|
823
|
+
const payloadVersion = sha256Hex(targetBytes);
|
|
824
|
+
await sealContent(this.cacheDir, {
|
|
825
|
+
entityId: uuid,
|
|
826
|
+
entityType: "link",
|
|
827
|
+
kind: "link",
|
|
828
|
+
contentHash: payloadVersion,
|
|
829
|
+
}, targetBytes, "detect-link", this.cargo);
|
|
830
|
+
const name = observedPortableName(base, parent.uuid, posix.basename(path));
|
|
1112
831
|
const result = {
|
|
1113
832
|
resourceId: root.resourceId,
|
|
1114
833
|
rootUUID: root.rootUUID,
|
|
@@ -1128,99 +847,97 @@ export class NamedDetectRuntime {
|
|
|
1128
847
|
transportVersion: null,
|
|
1129
848
|
relativePath: path,
|
|
1130
849
|
absolutePath,
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
byteSize: stats.size,
|
|
1134
|
-
modifiedTimeMs: stats.mtimeMs,
|
|
1135
|
-
changedTimeMs: stats.ctimeMs,
|
|
1136
|
-
filesystemMode: stats.mode,
|
|
1137
|
-
contentVerifiedAtMs: target === null ? base?.contentVerifiedAtMs ?? null : Date.now(),
|
|
1138
|
-
};
|
|
1139
|
-
const replay = base !== null && base.payloadVersion === result.payloadVersion
|
|
1140
|
-
? { kind: "structure" }
|
|
1141
|
-
: target === null ? { kind: "structure" } : {
|
|
1142
|
-
kind: "link",
|
|
1143
|
-
resultPayloadVersion: payloadVersion,
|
|
1144
|
-
target,
|
|
1145
|
-
};
|
|
1146
|
-
return {
|
|
1147
|
-
item: {
|
|
1148
|
-
base,
|
|
1149
|
-
result,
|
|
1150
|
-
proposal: detectedChangeProposal({
|
|
1151
|
-
base: base ? portable(base) : null,
|
|
1152
|
-
result: portable(result),
|
|
1153
|
-
replay,
|
|
1154
|
-
}),
|
|
1155
|
-
},
|
|
1156
|
-
contentReads: target === null ? 0 : 1,
|
|
850
|
+
...sourceFingerprint(stats),
|
|
851
|
+
contentVerifiedNs: leafBoundary(this.clock, stats),
|
|
1157
852
|
};
|
|
853
|
+
return { item: { base, result }, contentReads: 1 };
|
|
854
|
+
}
|
|
855
|
+
/** An existing leaf still at its address whose exact source signature
|
|
856
|
+
* predates its last content verification needs no byte read. */
|
|
857
|
+
unchangedLeaf({ path, stats, parent, base }) {
|
|
858
|
+
return base !== null
|
|
859
|
+
&& base.relativePath === path
|
|
860
|
+
&& base.parentUUID === parent.uuid
|
|
861
|
+
&& this.ground.physical(base.uuid) !== undefined
|
|
862
|
+
&& sourceReusable(base, stats);
|
|
863
|
+
}
|
|
864
|
+
/** A result whose content proof this pass reused staged no cargo. Its
|
|
865
|
+
* bytes are read once, and must be the observed payload; the source the
|
|
866
|
+
* observation remembered is validated again at admission. */
|
|
867
|
+
async sealUnread(row) {
|
|
868
|
+
const payloadVersion = row.payloadVersion;
|
|
869
|
+
if (row.type === "link") {
|
|
870
|
+
const target = Buffer.from(readlinkSync(row.absolutePath), "utf8");
|
|
871
|
+
if (sha256Hex(target) !== payloadVersion) {
|
|
872
|
+
throw new CaptureSourceChangedError(`link changed before sealing: ${row.absolutePath}`);
|
|
873
|
+
}
|
|
874
|
+
await sealContent(this.cacheDir, { entityId: row.uuid, entityType: "link", kind: "link", contentHash: payloadVersion }, target, "detect-link", this.cargo);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const content = await captureContent(row.absolutePath, lstatSync(row.absolutePath, { bigint: true }), this.cacheDir, { lane: "named", cargo: this.cargo });
|
|
878
|
+
if (content.contentHash !== payloadVersion) {
|
|
879
|
+
throw new CaptureSourceChangedError(`file changed before sealing: ${row.absolutePath}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/** Replay construction follows the portable sharing decision. */
|
|
883
|
+
async proposalFor(shared, result) {
|
|
884
|
+
if (shared?.version === result.version)
|
|
885
|
+
return null;
|
|
886
|
+
const replay = !replaysPayload(shared, result) ? { kind: "structure" }
|
|
887
|
+
: await this.replayFor(shared, result);
|
|
888
|
+
return detectedChangeProposal({ base: shared ? portable(shared) : null, result: portable(result), replay });
|
|
1158
889
|
}
|
|
1159
890
|
enclosedByRepository(row) {
|
|
1160
891
|
let cursor = row;
|
|
1161
892
|
const seen = new Set();
|
|
1162
|
-
|
|
893
|
+
for (let parentUUID = localParentOf(cursor); parentUUID !== null; parentUUID = localParentOf(cursor)) {
|
|
1163
894
|
if (seen.has(cursor.uuid))
|
|
1164
|
-
throw new Error("Detect
|
|
895
|
+
throw new Error("Detect physical ground has a parent cycle");
|
|
1165
896
|
seen.add(cursor.uuid);
|
|
1166
|
-
const parent = this.
|
|
897
|
+
const parent = this.ground.physical(parentUUID);
|
|
1167
898
|
if (!parent)
|
|
1168
|
-
throw new Error(`Detect
|
|
899
|
+
throw new Error(`Detect physical ground has no parent ${parentUUID}`);
|
|
1169
900
|
if (parent.type === "repo.git")
|
|
1170
901
|
return true;
|
|
1171
902
|
cursor = parent;
|
|
1172
903
|
}
|
|
1173
904
|
return false;
|
|
1174
905
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
906
|
+
/** A trashed ancestor makes every old descendant ineligible for identity
|
|
907
|
+
* reuse, even when its stored status is still active. Each inventory's
|
|
908
|
+
* lifecycle is read along its own parent chain. */
|
|
909
|
+
isEffectivelyActive(row, inventory = "entities") {
|
|
910
|
+
return effectivelyActive(row, uuid => inventory === "entities" ? this.ground.physical(uuid) : this.ground.sharing(uuid));
|
|
1178
911
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
.filter((row) => row.absolutePath !== absolutePath
|
|
1182
|
-
// Windows may name only the destination of a rename. The entity
|
|
1183
|
-
// projection proves this physical identity was still materialized
|
|
1184
|
-
// before the current observation; notebook-only rows deliberately do
|
|
1185
|
-
// not qualify, so a later inode reuse cannot steal missing identity.
|
|
1186
|
-
&& this.materializedByUuid.get(row.uuid) !== undefined
|
|
1187
|
-
&& !existsSync(row.absolutePath));
|
|
1188
|
-
return candidates.length === 1 ? candidates[0] : null;
|
|
912
|
+
activeRowAtPath(rootUUID, path) {
|
|
913
|
+
return this.ground.atPath(rootUUID, path).find(row => this.isEffectivelyActive(row));
|
|
1189
914
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
entityId: result.uuid,
|
|
1197
|
-
basePayloadVersion: base?.payloadVersion ?? null,
|
|
1198
|
-
baseBytes,
|
|
1199
|
-
resultPayloadVersion: result.payloadVersion,
|
|
1200
|
-
resultBytes,
|
|
1201
|
-
sha256Hex,
|
|
1202
|
-
});
|
|
915
|
+
/** A text or link replay carries sealed cache bytes, built on a content
|
|
916
|
+
* thread; a binary replay carries its manifest. */
|
|
917
|
+
async replayFor(base, result) {
|
|
918
|
+
if (result.type === "file.text" || result.type === "link") {
|
|
919
|
+
return replayCachedContent(this.cacheDir, { entityId: result.uuid, type: result.type,
|
|
920
|
+
basePayloadVersion: base?.payloadVersion ?? null, resultPayloadVersion: result.payloadVersion }, "named");
|
|
1203
921
|
}
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
throw new Error(checked.error);
|
|
922
|
+
const artifact = artifactForRecord(portable(result));
|
|
923
|
+
if (!artifact)
|
|
924
|
+
throw new Error(`detected binary file ${result.uuid} has no content head`);
|
|
1208
925
|
return {
|
|
1209
926
|
kind: "file.binary",
|
|
1210
|
-
resultPayloadVersion:
|
|
1211
|
-
manifest:
|
|
927
|
+
resultPayloadVersion: artifact.contentHash,
|
|
928
|
+
manifest: await contentManifest(this.cacheDir, artifact),
|
|
1212
929
|
};
|
|
1213
930
|
}
|
|
1214
931
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
*
|
|
1217
|
-
function
|
|
1218
|
-
|
|
1219
|
-
|
|
932
|
+
/** A Record replays its result's payload unless it shares its base's payload,
|
|
933
|
+
* when it replays structure only. Only a payload replay carries cargo. */
|
|
934
|
+
function replaysPayload(shared, result) {
|
|
935
|
+
return shared === null || shared.type !== result.type
|
|
936
|
+
|| shared.payloadVersion !== result.payloadVersion || shared.transportVersion !== result.transportVersion;
|
|
1220
937
|
}
|
|
1221
938
|
function tryLstat(path) {
|
|
1222
939
|
try {
|
|
1223
|
-
return lstatSync(path);
|
|
940
|
+
return lstatSync(path, { bigint: true });
|
|
1224
941
|
}
|
|
1225
942
|
catch (error) {
|
|
1226
943
|
const code = error.code;
|
|
@@ -1229,16 +946,4 @@ function tryLstat(path) {
|
|
|
1229
946
|
throw error;
|
|
1230
947
|
}
|
|
1231
948
|
}
|
|
1232
|
-
function repositoryIdentity(repository, rows) {
|
|
1233
|
-
return indexRepositoryTerritory(rows).ownedBy(repository.uuid)
|
|
1234
|
-
.map((row) => ({
|
|
1235
|
-
path: repository.relativePath
|
|
1236
|
-
? row.relativePath.slice(repository.relativePath.length + 1)
|
|
1237
|
-
: row.relativePath,
|
|
1238
|
-
uuid: row.uuid,
|
|
1239
|
-
type: row.type,
|
|
1240
|
-
payloadVersion: row.type === "repo.git" ? null : row.payloadVersion,
|
|
1241
|
-
}))
|
|
1242
|
-
.sort((left, right) => left.path.localeCompare(right.path));
|
|
1243
|
-
}
|
|
1244
949
|
//# sourceMappingURL=runtime.js.map
|