@opendatalabs/personal-server-ts-core 1.33.7 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/additions/budget.d.ts +28 -0
- package/dist/additions/budget.d.ts.map +1 -0
- package/dist/additions/budget.js +119 -0
- package/dist/additions/budget.js.map +1 -0
- package/dist/additions/durable.d.ts +64 -0
- package/dist/additions/durable.d.ts.map +1 -0
- package/dist/additions/durable.js +591 -0
- package/dist/additions/durable.js.map +1 -0
- package/dist/additions/first-added.d.ts +32 -48
- package/dist/additions/first-added.d.ts.map +1 -1
- package/dist/additions/first-added.js +68 -123
- package/dist/additions/first-added.js.map +1 -1
- package/dist/additions/index.d.ts +3 -0
- package/dist/additions/index.d.ts.map +1 -1
- package/dist/additions/index.js +3 -0
- package/dist/additions/index.js.map +1 -1
- package/dist/additions/ledger-store.js +1 -1
- package/dist/additions/record-rules.d.ts.map +1 -1
- package/dist/additions/record-rules.js +7 -0
- package/dist/additions/record-rules.js.map +1 -1
- package/dist/additions/summary.d.ts +88 -1
- package/dist/additions/summary.d.ts.map +1 -1
- package/dist/additions/summary.js +173 -8
- package/dist/additions/summary.js.map +1 -1
- package/dist/additions/version-inventory.d.ts +80 -0
- package/dist/additions/version-inventory.d.ts.map +1 -0
- package/dist/additions/version-inventory.js +232 -0
- package/dist/additions/version-inventory.js.map +1 -0
- package/dist/api/index.d.ts +70 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +506 -79
- package/dist/api/index.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/request.d.ts +6 -0
- package/dist/auth/request.d.ts.map +1 -1
- package/dist/auth/request.js +66 -4
- package/dist/auth/request.js.map +1 -1
- package/dist/contracts/data.d.ts +54 -8
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +178 -35
- package/dist/contracts/data.js.map +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/derivatives/registration.d.ts +0 -5
- package/dist/derivatives/registration.d.ts.map +1 -1
- package/dist/derivatives/registration.js +2 -1
- package/dist/derivatives/registration.js.map +1 -1
- package/dist/errors/pdpp-catalog.d.ts +36 -0
- package/dist/errors/pdpp-catalog.d.ts.map +1 -0
- package/dist/errors/pdpp-catalog.js +86 -0
- package/dist/errors/pdpp-catalog.js.map +1 -0
- package/dist/grants/index.d.ts +2 -0
- package/dist/grants/index.d.ts.map +1 -1
- package/dist/grants/index.js +4 -0
- package/dist/grants/index.js.map +1 -1
- package/dist/grants/pdpp-binding-store.d.ts +89 -0
- package/dist/grants/pdpp-binding-store.d.ts.map +1 -0
- package/dist/grants/pdpp-binding-store.js +112 -0
- package/dist/grants/pdpp-binding-store.js.map +1 -0
- package/dist/grants/pdpp-binding.d.ts +169 -0
- package/dist/grants/pdpp-binding.d.ts.map +1 -0
- package/dist/grants/pdpp-binding.js +236 -0
- package/dist/grants/pdpp-binding.js.map +1 -0
- package/dist/legacy-projection/bindings.d.ts +28 -1
- package/dist/legacy-projection/bindings.d.ts.map +1 -1
- package/dist/legacy-projection/bindings.js +622 -189
- package/dist/legacy-projection/bindings.js.map +1 -1
- package/dist/legacy-projection/claude-source-record.d.ts +22 -0
- package/dist/legacy-projection/claude-source-record.d.ts.map +1 -0
- package/dist/legacy-projection/claude-source-record.js +360 -0
- package/dist/legacy-projection/claude-source-record.js.map +1 -0
- package/dist/legacy-projection/declared-text.d.ts +41 -0
- package/dist/legacy-projection/declared-text.d.ts.map +1 -0
- package/dist/legacy-projection/declared-text.js +199 -0
- package/dist/legacy-projection/declared-text.js.map +1 -0
- package/dist/legacy-projection/github-browser-bindings.d.ts.map +1 -1
- package/dist/legacy-projection/github-browser-bindings.js +89 -5
- package/dist/legacy-projection/github-browser-bindings.js.map +1 -1
- package/dist/legacy-projection/github-legacy-fields.d.ts +6 -0
- package/dist/legacy-projection/github-legacy-fields.d.ts.map +1 -0
- package/dist/legacy-projection/github-legacy-fields.js +202 -0
- package/dist/legacy-projection/github-legacy-fields.js.map +1 -0
- package/dist/legacy-projection/index.d.ts +3 -1
- package/dist/legacy-projection/index.d.ts.map +1 -1
- package/dist/legacy-projection/index.js +49 -1
- package/dist/legacy-projection/index.js.map +1 -1
- package/dist/legacy-projection/oura-browser-bindings.d.ts.map +1 -1
- package/dist/legacy-projection/oura-browser-bindings.js +43 -41
- package/dist/legacy-projection/oura-browser-bindings.js.map +1 -1
- package/dist/legacy-projection/types.d.ts +38 -2
- package/dist/legacy-projection/types.d.ts.map +1 -1
- package/dist/mcp/connection-api.d.ts.map +1 -1
- package/dist/mcp/connection-api.js +4 -0
- package/dist/mcp/connection-api.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/oauth-redirect.d.ts +14 -0
- package/dist/mcp/oauth-redirect.d.ts.map +1 -0
- package/dist/mcp/oauth-redirect.js +51 -0
- package/dist/mcp/oauth-redirect.js.map +1 -0
- package/dist/mcp/resources.d.ts.map +1 -1
- package/dist/mcp/resources.js +3 -0
- package/dist/mcp/resources.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +11 -2
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pdpp/approval.d.ts +271 -0
- package/dist/pdpp/approval.d.ts.map +1 -0
- package/dist/pdpp/approval.js +467 -0
- package/dist/pdpp/approval.js.map +1 -0
- package/dist/pdpp/client-identity.d.ts +88 -0
- package/dist/pdpp/client-identity.d.ts.map +1 -0
- package/dist/pdpp/client-identity.js +196 -0
- package/dist/pdpp/client-identity.js.map +1 -0
- package/dist/pdpp/client-metadata.d.ts +95 -0
- package/dist/pdpp/client-metadata.d.ts.map +1 -0
- package/dist/pdpp/client-metadata.js +96 -0
- package/dist/pdpp/client-metadata.js.map +1 -0
- package/dist/pdpp/commitments.d.ts +75 -0
- package/dist/pdpp/commitments.d.ts.map +1 -0
- package/dist/pdpp/commitments.js +113 -0
- package/dist/pdpp/commitments.js.map +1 -0
- package/dist/pdpp/declaration.d.ts +118 -0
- package/dist/pdpp/declaration.d.ts.map +1 -0
- package/dist/pdpp/declaration.js +836 -0
- package/dist/pdpp/declaration.js.map +1 -0
- package/dist/pdpp/index.d.ts +37 -0
- package/dist/pdpp/index.d.ts.map +1 -0
- package/dist/pdpp/index.js +23 -0
- package/dist/pdpp/index.js.map +1 -0
- package/dist/pdpp/issuance.d.ts +126 -0
- package/dist/pdpp/issuance.d.ts.map +1 -0
- package/dist/pdpp/issuance.js +180 -0
- package/dist/pdpp/issuance.js.map +1 -0
- package/dist/pdpp/pkce.d.ts +53 -0
- package/dist/pdpp/pkce.d.ts.map +1 -0
- package/dist/pdpp/pkce.js +109 -0
- package/dist/pdpp/pkce.js.map +1 -0
- package/dist/pdpp/redirect.d.ts +60 -0
- package/dist/pdpp/redirect.d.ts.map +1 -0
- package/dist/pdpp/redirect.js +125 -0
- package/dist/pdpp/redirect.js.map +1 -0
- package/dist/pdpp/resolve.d.ts +109 -0
- package/dist/pdpp/resolve.d.ts.map +1 -0
- package/dist/pdpp/resolve.js +393 -0
- package/dist/pdpp/resolve.js.map +1 -0
- package/dist/pdpp/review.d.ts +280 -0
- package/dist/pdpp/review.d.ts.map +1 -0
- package/dist/pdpp/review.js +205 -0
- package/dist/pdpp/review.js.map +1 -0
- package/dist/pdpp/selection.d.ts +50 -0
- package/dist/pdpp/selection.d.ts.map +1 -0
- package/dist/pdpp/selection.js +442 -0
- package/dist/pdpp/selection.js.map +1 -0
- package/dist/pdpp/store.d.ts +228 -0
- package/dist/pdpp/store.d.ts.map +1 -0
- package/dist/pdpp/store.js +542 -0
- package/dist/pdpp/store.js.map +1 -0
- package/dist/pdpp/tokens.d.ts +181 -0
- package/dist/pdpp/tokens.d.ts.map +1 -0
- package/dist/pdpp/tokens.js +419 -0
- package/dist/pdpp/tokens.js.map +1 -0
- package/dist/pdpp/types.d.ts +441 -0
- package/dist/pdpp/types.d.ts.map +1 -0
- package/dist/pdpp/types.js +61 -0
- package/dist/pdpp/types.js.map +1 -0
- package/dist/pdpp-version.d.ts +28 -0
- package/dist/pdpp-version.d.ts.map +1 -0
- package/dist/pdpp-version.js +28 -0
- package/dist/pdpp-version.js.map +1 -0
- package/dist/policy/data-read.d.ts +24 -0
- package/dist/policy/data-read.d.ts.map +1 -1
- package/dist/policy/data-read.js +60 -3
- package/dist/policy/data-read.js.map +1 -1
- package/dist/policy/data-write.d.ts.map +1 -1
- package/dist/policy/data-write.js +3 -0
- package/dist/policy/data-write.js.map +1 -1
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +1 -0
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/reserved-scopes.d.ts +3 -0
- package/dist/policy/reserved-scopes.d.ts.map +1 -0
- package/dist/policy/reserved-scopes.js +5 -0
- package/dist/policy/reserved-scopes.js.map +1 -0
- package/dist/ports/index.d.ts +63 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/pdpp-auth.d.ts +129 -0
- package/dist/ports/pdpp-auth.d.ts.map +1 -0
- package/dist/ports/pdpp-auth.js +60 -0
- package/dist/ports/pdpp-auth.js.map +1 -0
- package/dist/ports/pdpp-auth.test-utils.d.ts +9 -0
- package/dist/ports/pdpp-auth.test-utils.d.ts.map +1 -0
- package/dist/ports/pdpp-auth.test-utils.js +18 -0
- package/dist/ports/pdpp-auth.test-utils.js.map +1 -0
- package/dist/schemas/server-config.d.ts +44 -0
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +91 -0
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/signing/request-signer.d.ts +2 -0
- package/dist/signing/request-signer.d.ts.map +1 -1
- package/dist/signing/request-signer.js +1 -0
- package/dist/signing/request-signer.js.map +1 -1
- package/dist/storage/adapters/interface.d.ts +3 -0
- package/dist/storage/adapters/interface.d.ts.map +1 -1
- package/dist/storage/adapters/vana-audience.d.ts +12 -0
- package/dist/storage/adapters/vana-audience.d.ts.map +1 -0
- package/dist/storage/adapters/vana-audience.js +72 -0
- package/dist/storage/adapters/vana-audience.js.map +1 -0
- package/dist/storage/adapters/vana.d.ts +2 -0
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +53 -20
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/canonical-legacy-projection.d.ts +262 -0
- package/dist/storage/canonical-legacy-projection.d.ts.map +1 -0
- package/dist/storage/canonical-legacy-projection.js +761 -0
- package/dist/storage/canonical-legacy-projection.js.map +1 -0
- package/dist/storage/index/manager.d.ts +29 -2
- package/dist/storage/index/manager.d.ts.map +1 -1
- package/dist/storage/index/schema.d.ts +1 -1
- package/dist/storage/index/schema.js +1 -1
- package/dist/storage/index/types.d.ts +7 -1
- package/dist/storage/index/types.d.ts.map +1 -1
- package/dist/storage/legacy-projection.d.ts +4 -3
- package/dist/storage/legacy-projection.d.ts.map +1 -1
- package/dist/storage/legacy-projection.js +145 -42
- package/dist/storage/legacy-projection.js.map +1 -1
- package/dist/storage/legacy-scope-coverage-exceptions.d.ts +8 -0
- package/dist/storage/legacy-scope-coverage-exceptions.d.ts.map +1 -0
- package/dist/storage/legacy-scope-coverage-exceptions.js +10 -0
- package/dist/storage/legacy-scope-coverage-exceptions.js.map +1 -0
- package/dist/storage/legacy-scope-coverage.d.ts +53 -0
- package/dist/storage/legacy-scope-coverage.d.ts.map +1 -0
- package/dist/storage/legacy-scope-coverage.js +403 -0
- package/dist/storage/legacy-scope-coverage.js.map +1 -0
- package/dist/storage/legacy-scope-entity-keys.d.ts +4 -0
- package/dist/storage/legacy-scope-entity-keys.d.ts.map +1 -0
- package/dist/storage/legacy-scope-entity-keys.js +92 -0
- package/dist/storage/legacy-scope-entity-keys.js.map +1 -0
- package/dist/storage/legacy-scope-view.d.ts +154 -0
- package/dist/storage/legacy-scope-view.d.ts.map +1 -0
- package/dist/storage/legacy-scope-view.js +735 -0
- package/dist/storage/legacy-scope-view.js.map +1 -0
- package/dist/storage/pdpp-records/completion.d.ts +91 -0
- package/dist/storage/pdpp-records/completion.d.ts.map +1 -0
- package/dist/storage/pdpp-records/completion.js +170 -0
- package/dist/storage/pdpp-records/completion.js.map +1 -0
- package/dist/storage/pdpp-records/cursor.d.ts +41 -0
- package/dist/storage/pdpp-records/cursor.d.ts.map +1 -0
- package/dist/storage/pdpp-records/cursor.js +49 -0
- package/dist/storage/pdpp-records/cursor.js.map +1 -0
- package/dist/storage/pdpp-records/declaration-witness.d.ts +58 -0
- package/dist/storage/pdpp-records/declaration-witness.d.ts.map +1 -0
- package/dist/storage/pdpp-records/declaration-witness.js +230 -0
- package/dist/storage/pdpp-records/declaration-witness.js.map +1 -0
- package/dist/storage/pdpp-records/enforcement.d.ts +52 -0
- package/dist/storage/pdpp-records/enforcement.d.ts.map +1 -0
- package/dist/storage/pdpp-records/enforcement.js +116 -0
- package/dist/storage/pdpp-records/enforcement.js.map +1 -0
- package/dist/storage/pdpp-records/index.d.ts +5 -0
- package/dist/storage/pdpp-records/index.d.ts.map +1 -0
- package/dist/storage/pdpp-records/index.js +5 -0
- package/dist/storage/pdpp-records/index.js.map +1 -0
- package/dist/storage/pdpp-records/ingest-plan.d.ts +39 -0
- package/dist/storage/pdpp-records/ingest-plan.d.ts.map +1 -0
- package/dist/storage/pdpp-records/ingest-plan.js +103 -0
- package/dist/storage/pdpp-records/ingest-plan.js.map +1 -0
- package/dist/storage/pdpp-records/interface.d.ts +104 -0
- package/dist/storage/pdpp-records/interface.d.ts.map +1 -0
- package/dist/storage/pdpp-records/interface.js +2 -0
- package/dist/storage/pdpp-records/interface.js.map +1 -0
- package/dist/storage/pdpp-records/memory-store.d.ts +9 -0
- package/dist/storage/pdpp-records/memory-store.d.ts.map +1 -0
- package/dist/storage/pdpp-records/memory-store.js +466 -0
- package/dist/storage/pdpp-records/memory-store.js.map +1 -0
- package/dist/storage/pdpp-records/portable.d.ts +9 -0
- package/dist/storage/pdpp-records/portable.d.ts.map +1 -0
- package/dist/storage/pdpp-records/portable.js +8 -0
- package/dist/storage/pdpp-records/portable.js.map +1 -0
- package/dist/storage/pdpp-records/read-port.d.ts +44 -0
- package/dist/storage/pdpp-records/read-port.d.ts.map +1 -0
- package/dist/storage/pdpp-records/read-port.js +22 -0
- package/dist/storage/pdpp-records/read-port.js.map +1 -0
- package/dist/storage/pdpp-records/record-key.d.ts +52 -0
- package/dist/storage/pdpp-records/record-key.d.ts.map +1 -0
- package/dist/storage/pdpp-records/record-key.js +97 -0
- package/dist/storage/pdpp-records/record-key.js.map +1 -0
- package/dist/storage/pdpp-records/schema.d.ts +41 -0
- package/dist/storage/pdpp-records/schema.d.ts.map +1 -0
- package/dist/storage/pdpp-records/schema.js +153 -0
- package/dist/storage/pdpp-records/schema.js.map +1 -0
- package/dist/storage/pdpp-records/stream-declaration.d.ts +88 -0
- package/dist/storage/pdpp-records/stream-declaration.d.ts.map +1 -0
- package/dist/storage/pdpp-records/stream-declaration.js +57 -0
- package/dist/storage/pdpp-records/stream-declaration.js.map +1 -0
- package/dist/storage/pdpp-records/types.d.ts +138 -0
- package/dist/storage/pdpp-records/types.d.ts.map +1 -0
- package/dist/storage/pdpp-records/types.js +38 -0
- package/dist/storage/pdpp-records/types.js.map +1 -0
- package/dist/sync/data-point-feed.d.ts +3 -0
- package/dist/sync/data-point-feed.d.ts.map +1 -1
- package/dist/sync/data-point-feed.js +17 -1
- package/dist/sync/data-point-feed.js.map +1 -1
- package/dist/sync/delete-data-port.d.ts +2 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -1
- package/dist/sync/delete-data-port.js +1 -1
- package/dist/sync/delete-data-port.js.map +1 -1
- package/dist/sync/engine/sync-manager.d.ts.map +1 -1
- package/dist/sync/engine/sync-manager.js +5 -2
- package/dist/sync/engine/sync-manager.js.map +1 -1
- package/dist/sync/index.d.ts +3 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +4 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/pdpp-import.d.ts +192 -0
- package/dist/sync/pdpp-import.d.ts.map +1 -0
- package/dist/sync/pdpp-import.js +441 -0
- package/dist/sync/pdpp-import.js.map +1 -0
- package/dist/sync/scope-deletions.d.ts +10 -0
- package/dist/sync/scope-deletions.d.ts.map +1 -1
- package/dist/sync/scope-deletions.js +40 -7
- package/dist/sync/scope-deletions.js.map +1 -1
- package/dist/sync/workers/download.d.ts +14 -0
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +149 -13
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +2 -0
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +7 -0
- package/dist/sync/workers/upload.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts +1 -1
- package/dist/test-utils/memory-storage.d.ts.map +1 -1
- package/dist/test-utils/memory-storage.js +11 -5
- package/dist/test-utils/memory-storage.js.map +1 -1
- package/package.json +54 -4
package/dist/api/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { isSystemScope } from "../policy/reserved-scopes.js";
|
|
1
2
|
import { DataDeletedError, DeleteTombstoneFailedError, GrantRequiredError, InvalidCascadeError, LineageCascadeUnavailableError, LineageGatewayError, LineageUnavailableError, ProtocolError, ServerNotConfiguredError, } from "../errors/catalog.js";
|
|
2
3
|
import { computeDataPointId } from "../sync/data-point-id.js";
|
|
3
4
|
import { isEntryCoveredByTombstone, } from "../sync/scope-deletions.js";
|
|
5
|
+
import { isPermanentRejection, } from "../sync/pdpp-import.js";
|
|
4
6
|
import { supersedeLegacyVersions, } from "../storage/legacy-projection.js";
|
|
5
7
|
import { deleteScope as deleteScopeLocally, } from "../sync/workers/delete.js";
|
|
6
|
-
import { ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, IngestPersistedError, } from "../contracts/index.js";
|
|
8
|
+
import { ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, IngestPersistedError, sha256Hex, } from "../contracts/index.js";
|
|
9
|
+
import { canonicalJsonBytes } from "../derivatives/e2ee/jcs.js";
|
|
7
10
|
// Imported directly: the contracts barrel does not re-export this addition.
|
|
8
11
|
import { summarizeDataAdditionsContract } from "../contracts/data.js";
|
|
9
12
|
import { scopeCoveredByGrant } from "@opendatalabs/vana-sdk/browser";
|
|
@@ -86,17 +89,80 @@ function methodNotAllowed() {
|
|
|
86
89
|
function notFound() {
|
|
87
90
|
return errorResponse(404, "NOT_FOUND", "Not found");
|
|
88
91
|
}
|
|
89
|
-
async function withApiErrors(handler) {
|
|
92
|
+
async function withApiErrors(handler, context) {
|
|
90
93
|
try {
|
|
91
|
-
|
|
94
|
+
const response = await handler();
|
|
95
|
+
// A handled 4xx that means "the index and the disk disagree" is an
|
|
96
|
+
// operator-facing fault even though it is not a 500 — log it once, here,
|
|
97
|
+
// rather than at the contract that cannot see the route.
|
|
98
|
+
if (response.status === 404 && context) {
|
|
99
|
+
await logMissingDataFile(response, context);
|
|
100
|
+
}
|
|
101
|
+
return response;
|
|
92
102
|
}
|
|
93
103
|
catch (err) {
|
|
94
104
|
if (err instanceof ProtocolError) {
|
|
95
105
|
return protocolErrorResponse(err);
|
|
96
106
|
}
|
|
107
|
+
if (context) {
|
|
108
|
+
context.logger?.error?.({
|
|
109
|
+
route: context.route,
|
|
110
|
+
requestId: context.requestId ?? crypto.randomUUID(),
|
|
111
|
+
errorCode: "INTERNAL_ERROR",
|
|
112
|
+
...stripUndefined(context.detail),
|
|
113
|
+
err: describeError(err),
|
|
114
|
+
}, "Unhandled error in personal server API");
|
|
115
|
+
}
|
|
97
116
|
return errorResponse(500, "INTERNAL_ERROR", "Internal server error");
|
|
98
117
|
}
|
|
99
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* The `DATA_FILE_MISSING` 404 the read contract produces, logged at the one
|
|
121
|
+
* layer that knows which route served it. Peeks at a clone so the response
|
|
122
|
+
* body stays readable by the caller.
|
|
123
|
+
*/
|
|
124
|
+
async function logMissingDataFile(response, context) {
|
|
125
|
+
if (!context.logger?.error)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
const body = await response.clone().json();
|
|
129
|
+
if (typeof body !== "object" ||
|
|
130
|
+
body === null ||
|
|
131
|
+
body.error !== "DATA_FILE_MISSING") {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
context.logger.error({
|
|
135
|
+
route: context.route,
|
|
136
|
+
requestId: context.requestId ?? crypto.randomUUID(),
|
|
137
|
+
errorCode: "DATA_FILE_MISSING",
|
|
138
|
+
...stripUndefined(context.detail),
|
|
139
|
+
}, "Indexed data file is missing on disk");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// A diagnostic log must not change the response sent to the caller.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* An error rendered for a log line — name, message and stack only. Never the
|
|
147
|
+
* whole object: a thrown error can carry a request, a config or a token on an
|
|
148
|
+
* arbitrary property, and a structured logger would serialize all of it.
|
|
149
|
+
*/
|
|
150
|
+
function describeError(err) {
|
|
151
|
+
if (err instanceof Error) {
|
|
152
|
+
return {
|
|
153
|
+
name: err.name,
|
|
154
|
+
message: err.message,
|
|
155
|
+
...("code" in err && { code: err.code }),
|
|
156
|
+
stack: err.stack,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return { message: String(err) };
|
|
160
|
+
}
|
|
161
|
+
function stripUndefined(detail) {
|
|
162
|
+
if (!detail)
|
|
163
|
+
return {};
|
|
164
|
+
return Object.fromEntries(Object.entries(detail).filter((pair) => pair[1] !== undefined));
|
|
165
|
+
}
|
|
100
166
|
function normalizeLimit(value, fallback) {
|
|
101
167
|
if (value === null)
|
|
102
168
|
return fallback;
|
|
@@ -129,7 +195,8 @@ export function selectedGrantId(request, url) {
|
|
|
129
195
|
undefined);
|
|
130
196
|
}
|
|
131
197
|
function listAuthCovers(listAuth, scope) {
|
|
132
|
-
return
|
|
198
|
+
return (!isSystemScope(scope) &&
|
|
199
|
+
scopeCoveredByGrant(scope, [...listAuth.grantedScopes]));
|
|
133
200
|
}
|
|
134
201
|
/**
|
|
135
202
|
* Run the X402 dispatch for one read:
|
|
@@ -293,33 +360,102 @@ export async function handleX402Cycle(input) {
|
|
|
293
360
|
}
|
|
294
361
|
return { kind: "gateway-error", status: gatewayRes.status, body: errorBody };
|
|
295
362
|
}
|
|
296
|
-
const MAX_INGEST_STAMP_ADVANCE_SEC = 60;
|
|
297
363
|
const pendingIngestStamps = new WeakMap();
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
for (let
|
|
307
|
-
|
|
308
|
-
.toISOString()
|
|
309
|
-
.replace(/\.\d{3}Z$/, "Z");
|
|
310
|
-
const key = `${scope}:${value}`;
|
|
311
|
-
if (pending.has(key) ||
|
|
312
|
-
storage.findEntry({ scope, at: value })?.collectedAt === value)
|
|
364
|
+
function reserveIngestStamp(now, storage, scope) {
|
|
365
|
+
// Bound allocation to 60 millisecond slots per scope, anchored to wall time.
|
|
366
|
+
// Failed requests release their slot; neither failures nor other scopes
|
|
367
|
+
// advance this scope's clock. The durable head protects restart/clock rollback.
|
|
368
|
+
const wall = now().getTime();
|
|
369
|
+
const latest = Date.parse(storage.findEntry({ scope })?.collectedAt ?? "");
|
|
370
|
+
let scopes = pendingIngestStamps.get(storage);
|
|
371
|
+
const pending = scopes?.get(scope) ?? new Set();
|
|
372
|
+
for (let stamp = wall; stamp < wall + 60; stamp += 1) {
|
|
373
|
+
if ((Number.isFinite(latest) && stamp <= latest) || pending.has(stamp)) {
|
|
313
374
|
continue;
|
|
314
|
-
|
|
375
|
+
}
|
|
376
|
+
if (!scopes) {
|
|
377
|
+
scopes = new Map();
|
|
378
|
+
pendingIngestStamps.set(storage, scopes);
|
|
379
|
+
}
|
|
380
|
+
scopes.set(scope, pending);
|
|
381
|
+
pending.add(stamp);
|
|
382
|
+
const reservations = scopes;
|
|
315
383
|
return {
|
|
316
|
-
|
|
317
|
-
release
|
|
318
|
-
pending.delete(
|
|
384
|
+
collectedAt: new Date(stamp).toISOString(),
|
|
385
|
+
release() {
|
|
386
|
+
pending.delete(stamp);
|
|
387
|
+
if (pending.size === 0)
|
|
388
|
+
reservations.delete(scope);
|
|
319
389
|
},
|
|
320
390
|
};
|
|
321
391
|
}
|
|
322
|
-
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
function parseLegacyPrecondition(request) {
|
|
395
|
+
const none = request.headers.get("if-none-match");
|
|
396
|
+
const match = request.headers.get("if-match");
|
|
397
|
+
if (none !== null && match !== null) {
|
|
398
|
+
return errorResponse(400, "INVALID_PRECONDITION", "Supply only one precondition");
|
|
399
|
+
}
|
|
400
|
+
if (none !== null) {
|
|
401
|
+
return none === "*"
|
|
402
|
+
? { kind: "none" }
|
|
403
|
+
: errorResponse(400, "INVALID_PRECONDITION", "If-None-Match must be *");
|
|
404
|
+
}
|
|
405
|
+
if (match !== null) {
|
|
406
|
+
const parsed = /^"([1-9]\d*)"$/.exec(match);
|
|
407
|
+
const version = parsed ? Number(parsed[1]) : NaN;
|
|
408
|
+
return Number.isSafeInteger(version)
|
|
409
|
+
? { kind: "match", version }
|
|
410
|
+
: errorResponse(400, "INVALID_PRECONDITION", "If-Match must be a quoted positive version");
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
function parseProducerHeader(request) {
|
|
415
|
+
const producer = request.headers.get("vana-producer");
|
|
416
|
+
const encodedProvenance = request.headers.get("vana-producer-provenance") ?? undefined;
|
|
417
|
+
if (producer === null && !encodedProvenance)
|
|
418
|
+
return {};
|
|
419
|
+
if (producer !== "pdpp-projector" && producer !== "pdpp-import-projection") {
|
|
420
|
+
return errorResponse(400, "INVALID_PRODUCER", "Vana-Producer is unrecognized or missing");
|
|
421
|
+
}
|
|
422
|
+
if (encodedProvenance &&
|
|
423
|
+
(encodedProvenance.length > 8192 ||
|
|
424
|
+
!/^[A-Za-z0-9_-]+$/.test(encodedProvenance))) {
|
|
425
|
+
return errorResponse(400, "INVALID_PROVENANCE", "Vana-Producer-Provenance is malformed");
|
|
426
|
+
}
|
|
427
|
+
return { producer, encodedProvenance };
|
|
428
|
+
}
|
|
429
|
+
async function parseProducerProvenance(encoded, body) {
|
|
430
|
+
if (!encoded)
|
|
431
|
+
return undefined;
|
|
432
|
+
try {
|
|
433
|
+
const value = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0))));
|
|
434
|
+
if (!value ||
|
|
435
|
+
typeof value !== "object" ||
|
|
436
|
+
typeof value.projector_version !== "string" ||
|
|
437
|
+
!value.projector_version ||
|
|
438
|
+
typeof value.declaration_digest !== "string" ||
|
|
439
|
+
!value.declaration_digest ||
|
|
440
|
+
!Array.isArray(value.inputs) ||
|
|
441
|
+
!value.inputs.every((input) => input &&
|
|
442
|
+
typeof input.stream === "string" &&
|
|
443
|
+
input.stream &&
|
|
444
|
+
typeof input.changes_since_token === "string" &&
|
|
445
|
+
input.changes_since_token) ||
|
|
446
|
+
typeof value.payload_sha256 !== "string" ||
|
|
447
|
+
!/^(?:0x)?[0-9a-fA-F]{64}$/.test(value.payload_sha256)) {
|
|
448
|
+
return errorResponse(400, "INVALID_PROVENANCE", "Producer provenance has invalid fields");
|
|
449
|
+
}
|
|
450
|
+
const actual = (await sha256Hex(canonicalJsonBytes(body))).slice(2);
|
|
451
|
+
if (actual !== value.payload_sha256.replace(/^0x/, "").toLowerCase()) {
|
|
452
|
+
return errorResponse(400, "PAYLOAD_HASH_MISMATCH", "Provenance payload_sha256 does not match the JSON body");
|
|
453
|
+
}
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return errorResponse(400, "INVALID_PROVENANCE", "Vana-Producer-Provenance is not base64url JSON");
|
|
458
|
+
}
|
|
323
459
|
}
|
|
324
460
|
/**
|
|
325
461
|
* The deletion that applies to a read of `scope`, or null when the read may
|
|
@@ -335,15 +471,25 @@ function reserveIngestStamp(storage, scope, now) {
|
|
|
335
471
|
* Gateway unreachability never fails the read: the tracker answers from its
|
|
336
472
|
* last known state, which is the documented offline behaviour.
|
|
337
473
|
*/
|
|
338
|
-
export async function resolveReadDeletion(deps, scope, entry) {
|
|
474
|
+
export async function resolveReadDeletion(deps, scope, entry, body = "file") {
|
|
339
475
|
if (!deps.scopeDeletions)
|
|
340
476
|
return null;
|
|
341
477
|
const verdict = await deps.scopeDeletions.resolve(scope, {
|
|
342
478
|
consultGateway: entry ? "if-stale" : "always",
|
|
343
479
|
});
|
|
344
|
-
|
|
480
|
+
// A live DPv2 file cannot establish that a different canonical body was
|
|
481
|
+
// re-added. Keep this refusal until a genuine canonical re-add seam exists.
|
|
482
|
+
const tombstone = body === "canonical"
|
|
483
|
+
? (deps.scopeDeletions.deletionHistory?.get(scope) ??
|
|
484
|
+
(verdict.deleted ? verdict : null))
|
|
485
|
+
: verdict.deleted
|
|
486
|
+
? verdict
|
|
487
|
+
: null;
|
|
488
|
+
if (!tombstone)
|
|
345
489
|
return null;
|
|
346
|
-
if (
|
|
490
|
+
if (body === "file" &&
|
|
491
|
+
entry &&
|
|
492
|
+
!isEntryCoveredByTombstone(entry, tombstone)) {
|
|
347
493
|
return null;
|
|
348
494
|
}
|
|
349
495
|
return {
|
|
@@ -351,7 +497,7 @@ export async function resolveReadDeletion(deps, scope, entry) {
|
|
|
351
497
|
dataPointId: deps.serverOwner
|
|
352
498
|
? computeDataPointId(deps.serverOwner, scope)
|
|
353
499
|
: null,
|
|
354
|
-
deletedAt:
|
|
500
|
+
deletedAt: tombstone.deletedAt,
|
|
355
501
|
};
|
|
356
502
|
}
|
|
357
503
|
/**
|
|
@@ -359,8 +505,8 @@ export async function resolveReadDeletion(deps, scope, entry) {
|
|
|
359
505
|
* served. Call it before any payment step: a builder must never be charged
|
|
360
506
|
* for a scope the owner deleted.
|
|
361
507
|
*/
|
|
362
|
-
export async function assertScopeNotDeleted(deps, scope, entry) {
|
|
363
|
-
const deletion = await resolveReadDeletion(deps, scope, entry);
|
|
508
|
+
export async function assertScopeNotDeleted(deps, scope, entry, body = "file") {
|
|
509
|
+
const deletion = await resolveReadDeletion(deps, scope, entry, body);
|
|
364
510
|
if (deletion)
|
|
365
511
|
throw new DataDeletedError(deletion);
|
|
366
512
|
}
|
|
@@ -374,6 +520,82 @@ function discoveryVisibility(deps) {
|
|
|
374
520
|
return undefined;
|
|
375
521
|
return async (scope, entry) => (await resolveReadDeletion(deps, scope, entry)) === null;
|
|
376
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Discovery filter for the scope listing: the deletion filter, and a
|
|
525
|
+
* canonical scope is listed only when the view could serve it.
|
|
526
|
+
*/
|
|
527
|
+
function listingVisibility(deps) {
|
|
528
|
+
const deletion = discoveryVisibility(deps);
|
|
529
|
+
const view = deps.legacyScopeView;
|
|
530
|
+
if (!view)
|
|
531
|
+
return deletion;
|
|
532
|
+
return async (scope, entry) => {
|
|
533
|
+
const listing = view.listingFor(scope);
|
|
534
|
+
if (listing.kind === "refused")
|
|
535
|
+
return false;
|
|
536
|
+
const canonicalOwned = view.ownsScope(scope);
|
|
537
|
+
// A file's re-add marker cannot revive the canonical body this row
|
|
538
|
+
// describes. Canonical re-adds have no registered version yet.
|
|
539
|
+
return ((await resolveReadDeletion(deps, scope, canonicalOwned ? undefined : entry, canonicalOwned ? "canonical" : "file")) === null);
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Bound scopes served only from canonical records, for the listing: no
|
|
544
|
+
* legacy write is needed to discover them. A gateway deletion hides them
|
|
545
|
+
* the way it hides a stored scope.
|
|
546
|
+
*/
|
|
547
|
+
function canonicalOnlyScopes(deps) {
|
|
548
|
+
const view = deps.legacyScopeView;
|
|
549
|
+
if (!view)
|
|
550
|
+
return undefined;
|
|
551
|
+
return async (scopePrefix, stored) => {
|
|
552
|
+
const listed = [];
|
|
553
|
+
for (const { scope, collectedAt } of view.listCanonicalScopes(scopePrefix)) {
|
|
554
|
+
if (stored.has(scope))
|
|
555
|
+
continue;
|
|
556
|
+
if ((await resolveReadDeletion(deps, scope, undefined, "canonical")) !==
|
|
557
|
+
null) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
// No legacy version exists to count or to ask `/versions` for.
|
|
561
|
+
listed.push({ scope, latestCollectedAt: collectedAt, versionCount: 0 });
|
|
562
|
+
}
|
|
563
|
+
return listed;
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* A canonical scope's listing row carries the canonical freshness. The
|
|
568
|
+
* legacy head's size and block readiness describe a file the latest read
|
|
569
|
+
* does not serve, so they are dropped.
|
|
570
|
+
*/
|
|
571
|
+
function withCanonicalListing(deps, summary) {
|
|
572
|
+
const listing = deps.legacyScopeView?.listingFor(summary.scope);
|
|
573
|
+
if (listing?.kind !== "canonical")
|
|
574
|
+
return summary;
|
|
575
|
+
return {
|
|
576
|
+
scope: summary.scope,
|
|
577
|
+
latestCollectedAt: listing.collectedAt,
|
|
578
|
+
versionCount: summary.versionCount,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* 409 when the owner must choose between connections, 503 otherwise. The
|
|
583
|
+
* body names the reason and stream names only: no instance id, no row data,
|
|
584
|
+
* no legacy content.
|
|
585
|
+
*/
|
|
586
|
+
function legacyViewRefusalResponse(scope, refusal) {
|
|
587
|
+
const status = refusal.reason === "ambiguous_connection" ? 409 : 503;
|
|
588
|
+
return jsonResponse({
|
|
589
|
+
error: "CANONICAL_VIEW_UNAVAILABLE",
|
|
590
|
+
message: `The latest data for scope "${scope}" cannot be served from the owner's connector records (${refusal.reason}).`,
|
|
591
|
+
status: refusal.status,
|
|
592
|
+
reason: refusal.reason,
|
|
593
|
+
...("detail" in refusal ? { detail: refusal.detail } : {}),
|
|
594
|
+
...("streams" in refusal ? { streams: refusal.streams } : {}),
|
|
595
|
+
...("stream" in refusal ? { stream: refusal.stream } : {}),
|
|
596
|
+
...("candidates" in refusal ? { candidates: refusal.candidates } : {}),
|
|
597
|
+
}, { status });
|
|
598
|
+
}
|
|
377
599
|
/**
|
|
378
600
|
* The causal marker a fresh ingest carries: the tombstone version this
|
|
379
601
|
* replica currently knows for the scope, if any, so the new entry is a
|
|
@@ -393,12 +615,11 @@ async function ingestTombstoneMarker(deps, scope) {
|
|
|
393
615
|
}
|
|
394
616
|
// The delete worker wants a full Logger; the API logger is all-optional.
|
|
395
617
|
function apiLoggerAsLogger(logger) {
|
|
396
|
-
const noop = () => undefined;
|
|
397
618
|
return {
|
|
398
|
-
debug: (payload, message) =>
|
|
399
|
-
info: (payload, message) =>
|
|
400
|
-
warn: (payload, message) =>
|
|
401
|
-
error: (payload, message) =>
|
|
619
|
+
debug: (payload, message) => logger?.debug?.(payload, message ?? ""),
|
|
620
|
+
info: (payload, message) => logger?.info?.(payload, message ?? ""),
|
|
621
|
+
warn: (payload, message) => logger?.warn?.(payload, message ?? ""),
|
|
622
|
+
error: (payload, message) => logger?.error?.(payload, message ?? ""),
|
|
402
623
|
};
|
|
403
624
|
}
|
|
404
625
|
function notifyDataWritten(deps, event) {
|
|
@@ -414,6 +635,50 @@ function notifyDataWritten(deps, event) {
|
|
|
414
635
|
}, "onDataWritten hook failed; record already stored");
|
|
415
636
|
}
|
|
416
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Offer a just-ingested envelope to the PDPP importer.
|
|
640
|
+
*
|
|
641
|
+
* The local-ingest twin of the download worker's `importIntoPdpp`, and
|
|
642
|
+
* deliberately the same shape: same `ImportableEnvelope`, same total handling
|
|
643
|
+
* of every outcome, same permanent-vs-transient logging split. Keeping the
|
|
644
|
+
* two symmetric is the point — a `$pdpp` envelope must mean the same thing
|
|
645
|
+
* and reach the same store whether the gateway handed it over or a local
|
|
646
|
+
* producer POSTed it, otherwise "imported" would depend on which door the
|
|
647
|
+
* bytes came through.
|
|
648
|
+
*
|
|
649
|
+
* `skipped` is the ordinary legacy case (no `$pdpp` block) and stays silent.
|
|
650
|
+
* A rejection is a WARNING, not an error: it means the producer sent
|
|
651
|
+
* something this deployment could not verify — actionable, but not a failure
|
|
652
|
+
* of the write, which has already committed.
|
|
653
|
+
*/
|
|
654
|
+
function importIngestedIntoPdpp(deps, envelope) {
|
|
655
|
+
if (!deps.pdppImporter)
|
|
656
|
+
return;
|
|
657
|
+
try {
|
|
658
|
+
const outcome = deps.pdppImporter.importEnvelope(envelope);
|
|
659
|
+
if (outcome.status === "rejected") {
|
|
660
|
+
const permanent = isPermanentRejection(outcome.rejection);
|
|
661
|
+
deps.logger?.warn?.({
|
|
662
|
+
scope: envelope.scope,
|
|
663
|
+
collectedAt: envelope.collectedAt,
|
|
664
|
+
code: outcome.rejection.code,
|
|
665
|
+
message: outcome.rejection.message,
|
|
666
|
+
permanent,
|
|
667
|
+
}, permanent
|
|
668
|
+
? "Ingested data point was refused by the PDPP importer and will not be retried"
|
|
669
|
+
: "PDPP import unavailable for this ingested data point");
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
// A throwing importer is infrastructure failing, not the envelope being
|
|
674
|
+
// wrong. The record is stored and indexed already, so this must never
|
|
675
|
+
// turn a committed write into a 500.
|
|
676
|
+
deps.logger?.warn?.({
|
|
677
|
+
scope: envelope.scope,
|
|
678
|
+
error: err instanceof Error ? err.message : String(err),
|
|
679
|
+
}, "PDPP import threw on ingest; record already stored and indexed");
|
|
680
|
+
}
|
|
681
|
+
}
|
|
417
682
|
function notifyDataRead(deps, event) {
|
|
418
683
|
if (!deps.onDataRead)
|
|
419
684
|
return;
|
|
@@ -571,6 +836,15 @@ export function redactEnvelopeForGrantee(envelope) {
|
|
|
571
836
|
}
|
|
572
837
|
export { redactJsonEnvelopeBytesForGrantee } from "./raw-envelope.js";
|
|
573
838
|
export async function handlePersonalServerDataRequest(request, deps, options = {}) {
|
|
839
|
+
// Filled in as the dispatch narrows, so a 500 thrown deep in a read is
|
|
840
|
+
// logged against the route that served it rather than a generic label. It
|
|
841
|
+
// is mutable for exactly that reason: the route is not known until the path
|
|
842
|
+
// has been parsed, and by then we are already inside the try.
|
|
843
|
+
const logContext = {
|
|
844
|
+
logger: deps.logger,
|
|
845
|
+
route: `${request.method} ${options.basePath ?? "/v1/data"}`,
|
|
846
|
+
requestId: crypto.randomUUID(),
|
|
847
|
+
};
|
|
574
848
|
return withApiErrors(async () => {
|
|
575
849
|
const url = new URL(request.url);
|
|
576
850
|
const pathname = stripBasePath(url.pathname, options.basePath);
|
|
@@ -585,12 +859,15 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
585
859
|
scopePrefix: url.searchParams.get("scopePrefix") ?? undefined,
|
|
586
860
|
limit: normalizeLimit(url.searchParams.get("limit"), 20),
|
|
587
861
|
offset: normalizeLimit(url.searchParams.get("offset"), 0),
|
|
588
|
-
isVisible:
|
|
589
|
-
isListable:
|
|
590
|
-
|
|
591
|
-
|
|
862
|
+
isVisible: listingVisibility(deps),
|
|
863
|
+
isListable: (scope) => !isSystemScope(scope) &&
|
|
864
|
+
(!listAuth || listAuthCovers(listAuth, scope)),
|
|
865
|
+
extraScopes: canonicalOnlyScopes(deps),
|
|
866
|
+
});
|
|
867
|
+
return jsonResponse({
|
|
868
|
+
...result.response,
|
|
869
|
+
scopes: result.response.scopes.map((summary) => withCanonicalListing(deps, summary)),
|
|
592
870
|
});
|
|
593
|
-
return jsonResponse(result.response);
|
|
594
871
|
}
|
|
595
872
|
// "additions" has no dot, so it can never be a real scope name (scopes are
|
|
596
873
|
// "source.dataset"); handle it before the single-segment scope path.
|
|
@@ -601,6 +878,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
601
878
|
await deps.auth.authorizeOwner(request);
|
|
602
879
|
const daysRaw = url.searchParams.get("days");
|
|
603
880
|
const result = await summarizeDataAdditionsContract({
|
|
881
|
+
additions: deps.additions,
|
|
604
882
|
storage: deps.storage,
|
|
605
883
|
isVisible: discoveryVisibility(deps),
|
|
606
884
|
timezone: url.searchParams.get("tz") ?? "UTC",
|
|
@@ -617,15 +895,80 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
617
895
|
return jsonResponse(result);
|
|
618
896
|
}
|
|
619
897
|
const parts = pathname.split("/").filter(Boolean);
|
|
898
|
+
if (parts.length === 3 && parts[1] === "imports") {
|
|
899
|
+
if (request.method !== "PUT")
|
|
900
|
+
return methodNotAllowed();
|
|
901
|
+
await deps.auth.authorizeOwner(request);
|
|
902
|
+
const scopeResult = parseDataScopeContract(decodePathPart(parts[0]));
|
|
903
|
+
if (!scopeResult.ok)
|
|
904
|
+
return contractErrorResponse(scopeResult);
|
|
905
|
+
const operation = decodePathPart(parts[2]);
|
|
906
|
+
if (!/^[a-f0-9]{64}$/.test(operation)) {
|
|
907
|
+
return errorResponse(400, "INVALID_IMPORT_OPERATION", "operation must be 64 lowercase hexadecimal characters");
|
|
908
|
+
}
|
|
909
|
+
if (!deps.storage.commitVersion) {
|
|
910
|
+
throw new ProtocolError(501, "IMPORT_UNSUPPORTED", "This storage backend does not support durable owner imports");
|
|
911
|
+
}
|
|
912
|
+
if (!deps.serverOwner)
|
|
913
|
+
throw new ServerNotConfiguredError({
|
|
914
|
+
reason: "serverOwner is required for durable owner imports",
|
|
915
|
+
});
|
|
916
|
+
if (!isJsonContentType(request) || url.search.length > 0) {
|
|
917
|
+
return errorResponse(400, "INVALID_IMPORT", "Owner imports accept JSON root writes without query parameters");
|
|
918
|
+
}
|
|
919
|
+
const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
|
|
920
|
+
if (!parsed.ok)
|
|
921
|
+
return contractResponse(parsed.result);
|
|
922
|
+
if ("lineage" in parsed.body || "$binary" in parsed.body) {
|
|
923
|
+
return errorResponse(400, "INVALID_IMPORT", "Owner imports do not accept binary or lineage writes");
|
|
924
|
+
}
|
|
925
|
+
const bodySha256 = await sha256Hex(canonicalJsonBytes(parsed.body));
|
|
926
|
+
const authority = deps.gatewayConfig
|
|
927
|
+
? `${deps.gatewayConfig.chainId}:${deps.gatewayConfig.contracts.dataRegistry.toLowerCase()}`
|
|
928
|
+
: "local";
|
|
929
|
+
const result = await ingestDataContract({
|
|
930
|
+
storage: deps.storage,
|
|
931
|
+
scopeParam: scopeResult.scope,
|
|
932
|
+
body: parsed.body,
|
|
933
|
+
collectedAt: (deps.now ?? (() => new Date()))().toISOString(),
|
|
934
|
+
status: deps.syncManager ? "syncing" : "stored",
|
|
935
|
+
afterTombstoneVersion: await ingestTombstoneMarker(deps, scopeResult.scope),
|
|
936
|
+
import: {
|
|
937
|
+
operation,
|
|
938
|
+
bodySha256,
|
|
939
|
+
owner: deps.serverOwner.toLowerCase(),
|
|
940
|
+
authority,
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
if (!result.ok)
|
|
944
|
+
return contractErrorResponse(result);
|
|
945
|
+
notifyNewData(deps.syncManager);
|
|
946
|
+
if (!result.replay)
|
|
947
|
+
notifyDataWritten(deps, {
|
|
948
|
+
scope: result.scope,
|
|
949
|
+
collectedAt: result.collectedAt,
|
|
950
|
+
});
|
|
951
|
+
return jsonResponse(result.response, {
|
|
952
|
+
status: result.replay ? 200 : 201,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
620
955
|
if (parts.length === 2 && parts[1] === "versions") {
|
|
621
956
|
if (request.method !== "GET")
|
|
622
957
|
return methodNotAllowed();
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
958
|
+
const versionsScopeParam = decodePathPart(parts[0]);
|
|
959
|
+
const versionsGrantId = selectedGrantId(request, url);
|
|
960
|
+
const listAuth = deps.auth.authorizeScopeVersions
|
|
961
|
+
? await deps.auth.authorizeScopeVersions({
|
|
962
|
+
request,
|
|
963
|
+
scope: versionsScopeParam,
|
|
964
|
+
grantId: versionsGrantId,
|
|
965
|
+
})
|
|
966
|
+
: await deps.auth.authorizeBuilderList(request, {
|
|
967
|
+
grantId: versionsGrantId,
|
|
968
|
+
});
|
|
626
969
|
// A scope's version history is as much its data as its content is, so
|
|
627
970
|
// a non-owner needs a grant covering it, exactly like a read.
|
|
628
|
-
const scopeResult = parseDataScopeContract(
|
|
971
|
+
const scopeResult = parseDataScopeContract(versionsScopeParam);
|
|
629
972
|
if (scopeResult.ok &&
|
|
630
973
|
listAuth &&
|
|
631
974
|
!listAuthCovers(listAuth, scopeResult.scope)) {
|
|
@@ -738,10 +1081,16 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
738
1081
|
if (parts.length !== 1)
|
|
739
1082
|
return notFound();
|
|
740
1083
|
const scopeParam = decodePathPart(parts[0]);
|
|
1084
|
+
logContext.route = `${request.method} ${options.basePath ?? "/v1/data"}/:scope`;
|
|
741
1085
|
if (request.method === "GET") {
|
|
742
1086
|
const scopeResult = parseDataScopeContract(scopeParam);
|
|
743
1087
|
if (!scopeResult.ok)
|
|
744
1088
|
return contractErrorResponse(scopeResult);
|
|
1089
|
+
logContext.detail = { scope: scopeResult.scope };
|
|
1090
|
+
// An empty `fileId=` or `at=` selects no version: it is the latest
|
|
1091
|
+
// read, as storage already treats it.
|
|
1092
|
+
const fileIdParam = url.searchParams.get("fileId") || undefined;
|
|
1093
|
+
const atParam = url.searchParams.get("at") || undefined;
|
|
745
1094
|
// `?view=stored` returns the body as stored, without the read-time
|
|
746
1095
|
// legacy projection. Owner only; checked after auth below.
|
|
747
1096
|
const view = url.searchParams.get("view");
|
|
@@ -750,8 +1099,8 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
750
1099
|
}
|
|
751
1100
|
const selectedEntry = deps.storage.findEntry({
|
|
752
1101
|
scope: scopeResult.scope,
|
|
753
|
-
fileId:
|
|
754
|
-
at:
|
|
1102
|
+
fileId: fileIdParam,
|
|
1103
|
+
at: atParam,
|
|
755
1104
|
});
|
|
756
1105
|
// Holds the base64-encoded gateway payForOperation response body when
|
|
757
1106
|
// X402 succeeds — emitted on the final read response as X-PAYMENT-RESPONSE.
|
|
@@ -761,13 +1110,22 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
761
1110
|
request,
|
|
762
1111
|
scope: scopeResult.scope,
|
|
763
1112
|
grantId,
|
|
764
|
-
fileId:
|
|
765
|
-
at:
|
|
1113
|
+
fileId: fileIdParam ?? selectedEntry?.fileId ?? undefined,
|
|
1114
|
+
at: atParam,
|
|
766
1115
|
});
|
|
1116
|
+
// Select the body after auth and before deletion or payment. A file's
|
|
1117
|
+
// version and re-add marker authorize that file, not canonical rows.
|
|
1118
|
+
// `?view=stored` asks for the stored file's body, so it never reads the
|
|
1119
|
+
// canonical view either.
|
|
1120
|
+
const latest = fileIdParam === undefined && atParam === undefined && view === null
|
|
1121
|
+
? await deps.legacyScopeView?.readLatest(scopeResult.scope)
|
|
1122
|
+
: undefined;
|
|
1123
|
+
const canonicalOwned = latest !== undefined &&
|
|
1124
|
+
deps.legacyScopeView?.ownsScope(scopeResult.scope);
|
|
767
1125
|
// Deletion gate, after auth (so unauthenticated callers learn nothing)
|
|
768
1126
|
// and before payment (so nobody is charged for deleted data). Covers
|
|
769
1127
|
// both a stale local copy of a tombstoned scope and a local miss.
|
|
770
|
-
await assertScopeNotDeleted(deps, scopeResult.scope, selectedEntry);
|
|
1128
|
+
await assertScopeNotDeleted(deps, scopeResult.scope, canonicalOwned ? undefined : selectedEntry, canonicalOwned ? "canonical" : "file");
|
|
771
1129
|
if (view === "stored" && !isOwnerView(authResult)) {
|
|
772
1130
|
return errorResponse(403, "FORBIDDEN", "Only the owner can read the stored view");
|
|
773
1131
|
}
|
|
@@ -780,6 +1138,13 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
780
1138
|
// answer unreachable through this route. Nothing here blocks: a
|
|
781
1139
|
// previous version is served now, the fresh one lands in a later read.
|
|
782
1140
|
notifyDataRead(deps, { scope: scopeResult.scope });
|
|
1141
|
+
// A canonical scope's latest body is the canonical view, and only it:
|
|
1142
|
+
// a refusal is answered, never replaced by the legacy file. Explicit
|
|
1143
|
+
// versions stay legacy history. Read before payment, so a builder is
|
|
1144
|
+
// never challenged or charged for a read that is then refused.
|
|
1145
|
+
if (latest?.kind === "refused") {
|
|
1146
|
+
return legacyViewRefusalResponse(scopeResult.scope, latest.refusal);
|
|
1147
|
+
}
|
|
783
1148
|
// X402 payment dance for builder reads. Owner-exempt reads (the
|
|
784
1149
|
// grantId sentinels "owner" / "policy-bypass") skip payment entirely
|
|
785
1150
|
// since there's no payable op to attach the payment to.
|
|
@@ -833,6 +1198,12 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
833
1198
|
willCharge,
|
|
834
1199
|
});
|
|
835
1200
|
}
|
|
1201
|
+
if (willCharge && latest?.kind === "canonical") {
|
|
1202
|
+
return legacyViewRefusalResponse(scopeResult.scope, {
|
|
1203
|
+
status: "unavailable",
|
|
1204
|
+
reason: "paid_read_unsupported",
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
836
1207
|
// Keep the full condition inline (not `if (willCharge)`) so TS narrows
|
|
837
1208
|
// deps.gateway/gatewayConfig/gatewayUrl to non-undefined in the block.
|
|
838
1209
|
if (deps.paymentEnabled &&
|
|
@@ -847,8 +1218,8 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
847
1218
|
deps,
|
|
848
1219
|
request,
|
|
849
1220
|
scope: scopeResult.scope,
|
|
850
|
-
fileIdParam
|
|
851
|
-
atParam
|
|
1221
|
+
fileIdParam,
|
|
1222
|
+
atParam,
|
|
852
1223
|
grantId: resolvedGrantId,
|
|
853
1224
|
builder: builder,
|
|
854
1225
|
gateway: deps.gateway,
|
|
@@ -869,25 +1240,38 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
869
1240
|
// breakdown / paidAt / paymentNonce without a second gateway round-trip.
|
|
870
1241
|
paymentResponseHeader = encodePaymentResponseHeader(x402Result.payResponse);
|
|
871
1242
|
}
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1243
|
+
const headers = {};
|
|
1244
|
+
headers["X-Vana-Data-Source"] =
|
|
1245
|
+
latest?.kind === "canonical" ? "canonical" : "legacy";
|
|
1246
|
+
let envelope;
|
|
1247
|
+
let servedFileId;
|
|
1248
|
+
if (latest?.kind === "canonical") {
|
|
1249
|
+
envelope = latest.envelope;
|
|
1250
|
+
headers.ETag = `"${latest.revision}"`;
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
const result = await readDataContract({
|
|
1254
|
+
storage: deps.storage,
|
|
1255
|
+
scopeParam: scopeResult.scope,
|
|
1256
|
+
fileId: fileIdParam,
|
|
1257
|
+
at: atParam,
|
|
1258
|
+
...(view === "stored" ? { view } : {}),
|
|
1259
|
+
});
|
|
1260
|
+
if (!result.ok) {
|
|
1261
|
+
if (result.status === 404) {
|
|
1262
|
+
// The entry selected above can vanish before the read (a sync
|
|
1263
|
+
// reconcile applying a tombstone concurrently); answer 410, not
|
|
1264
|
+
// 404, when that is why.
|
|
1265
|
+
await assertScopeNotDeleted(deps, scopeResult.scope, undefined);
|
|
1266
|
+
}
|
|
1267
|
+
return contractErrorResponse(result);
|
|
885
1268
|
}
|
|
886
|
-
|
|
1269
|
+
envelope = result.envelope;
|
|
1270
|
+
servedFileId = fileIdParam ?? selectedEntry?.fileId ?? undefined;
|
|
887
1271
|
}
|
|
888
1272
|
const servedEnvelope = isOwnerView(authResult)
|
|
889
|
-
?
|
|
890
|
-
: redactEnvelopeForGrantee(
|
|
1273
|
+
? envelope
|
|
1274
|
+
: redactEnvelopeForGrantee(envelope);
|
|
891
1275
|
const logId = deps.createLogId?.() ?? crypto.randomUUID();
|
|
892
1276
|
const timestamp = (deps.now ?? (() => new Date()))().toISOString();
|
|
893
1277
|
const ipAddress = request.headers.get("x-forwarded-for") ??
|
|
@@ -911,9 +1295,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
911
1295
|
return;
|
|
912
1296
|
reportPersonalServerReadFulfillment(deps, {
|
|
913
1297
|
builder: authResult.builder,
|
|
914
|
-
fileId:
|
|
915
|
-
selectedEntry?.fileId ??
|
|
916
|
-
undefined,
|
|
1298
|
+
fileId: servedFileId,
|
|
917
1299
|
grantId: authResult.grantId,
|
|
918
1300
|
ipAddress,
|
|
919
1301
|
logId,
|
|
@@ -924,7 +1306,6 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
924
1306
|
userAgent,
|
|
925
1307
|
});
|
|
926
1308
|
};
|
|
927
|
-
const headers = {};
|
|
928
1309
|
if (paymentResponseHeader) {
|
|
929
1310
|
headers["X-PAYMENT-RESPONSE"] = paymentResponseHeader;
|
|
930
1311
|
}
|
|
@@ -989,7 +1370,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
989
1370
|
// so the builder's retry with the same still-valid proof is accepted;
|
|
990
1371
|
// after commit the proof stays consumed (a retry would be a duplicate).
|
|
991
1372
|
let committed = false;
|
|
992
|
-
let
|
|
1373
|
+
let ingestStamp;
|
|
993
1374
|
const failWrite = async (response) => {
|
|
994
1375
|
await writeAuth?.releaseProof?.();
|
|
995
1376
|
return response;
|
|
@@ -998,9 +1379,20 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
998
1379
|
const scopeResult = parseDataScopeContract(scopeParam);
|
|
999
1380
|
if (!scopeResult.ok)
|
|
1000
1381
|
return failWrite(contractErrorResponse(scopeResult));
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1382
|
+
const parsedPrecondition = parseLegacyPrecondition(request);
|
|
1383
|
+
if (parsedPrecondition instanceof Response)
|
|
1384
|
+
return failWrite(parsedPrecondition);
|
|
1385
|
+
const producerHeader = parseProducerHeader(request);
|
|
1386
|
+
if (producerHeader instanceof Response)
|
|
1387
|
+
return failWrite(producerHeader);
|
|
1388
|
+
if (writeAuth && producerHeader.producer) {
|
|
1389
|
+
return failWrite(errorResponse(403, "NOT_OWNER", "Only the owner may assert a producer"));
|
|
1390
|
+
}
|
|
1391
|
+
ingestStamp = reserveIngestStamp(deps.now ?? (() => new Date()), deps.storage, scopeResult.scope);
|
|
1392
|
+
if (!ingestStamp) {
|
|
1393
|
+
return failWrite(errorResponse(429, "INGEST_CAPACITY", "Too many writes for this scope; retry when the clock advances"));
|
|
1394
|
+
}
|
|
1395
|
+
const collectedAtValue = ingestStamp.collectedAt;
|
|
1004
1396
|
const status = deps.syncManager ? "syncing" : "stored";
|
|
1005
1397
|
const afterTombstoneVersion = await ingestTombstoneMarker(deps, scopeResult.scope);
|
|
1006
1398
|
// Builder writes land in the same access log as builder reads, so the
|
|
@@ -1040,6 +1432,9 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1040
1432
|
// data needs no schema at all — we ingest it schemaless. (Structured JSON
|
|
1041
1433
|
// below still resolves a schema for validation/metadata.)
|
|
1042
1434
|
if (!isJsonContentType(request)) {
|
|
1435
|
+
if (producerHeader.producer) {
|
|
1436
|
+
return failWrite(errorResponse(400, "INVALID_PRODUCER", "Producer attribution requires a JSON body"));
|
|
1437
|
+
}
|
|
1043
1438
|
if (supersede !== null) {
|
|
1044
1439
|
return failWrite(errorResponse(400, "INVALID_SUPERSEDE", "supersede applies to JSON writes only"));
|
|
1045
1440
|
}
|
|
@@ -1060,6 +1455,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1060
1455
|
attribution: writeAuth?.attribution,
|
|
1061
1456
|
lineage,
|
|
1062
1457
|
afterTombstoneVersion,
|
|
1458
|
+
precondition: parsedPrecondition,
|
|
1063
1459
|
});
|
|
1064
1460
|
if (!result.ok)
|
|
1065
1461
|
return failWrite(contractErrorResponse(result));
|
|
@@ -1084,6 +1480,9 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1084
1480
|
const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
|
|
1085
1481
|
if (!parsed.ok)
|
|
1086
1482
|
return failWrite(contractResponse(parsed.result));
|
|
1483
|
+
const provenance = await parseProducerProvenance(producerHeader.encodedProvenance, parsed.body);
|
|
1484
|
+
if (provenance instanceof Response)
|
|
1485
|
+
return failWrite(provenance);
|
|
1087
1486
|
// Derivative writes: validate the caller's `lineage` before anything
|
|
1088
1487
|
// is stored (a failure here throws, releasing the builder's proof).
|
|
1089
1488
|
const lineage = await prepareWriteLineage(deps, scopeResult.scope, extractLineageField(parsed.body));
|
|
@@ -1098,9 +1497,14 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1098
1497
|
attribution: writeAuth?.attribution,
|
|
1099
1498
|
lineage,
|
|
1100
1499
|
afterTombstoneVersion,
|
|
1500
|
+
precondition: parsedPrecondition,
|
|
1501
|
+
producer: producerHeader.producer,
|
|
1502
|
+
producerProvenance: provenance,
|
|
1101
1503
|
});
|
|
1102
1504
|
if (!result.ok)
|
|
1103
1505
|
return failWrite(contractErrorResponse(result));
|
|
1506
|
+
if (result.replay)
|
|
1507
|
+
throw new Error("Ordinary POST cannot replay an import");
|
|
1104
1508
|
committed = true;
|
|
1105
1509
|
deps.logger?.info?.({
|
|
1106
1510
|
scope: scopeResult.scope,
|
|
@@ -1113,6 +1517,14 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1113
1517
|
? await supersedeAfterCommit(deps, scopeResult.scope, collectedAtValue)
|
|
1114
1518
|
: undefined;
|
|
1115
1519
|
notifyNewData(deps.syncManager);
|
|
1520
|
+
// The parsed body is the envelope's `data`, which is where a producer
|
|
1521
|
+
// stamps `$pdpp`. Offered here rather than inside the ingest contract
|
|
1522
|
+
// so the import stays strictly after the record is durable.
|
|
1523
|
+
importIngestedIntoPdpp(deps, {
|
|
1524
|
+
scope: scopeResult.scope,
|
|
1525
|
+
collectedAt: collectedAtValue,
|
|
1526
|
+
data: parsed.body,
|
|
1527
|
+
});
|
|
1116
1528
|
notifyDataWritten(deps, {
|
|
1117
1529
|
scope: scopeResult.scope,
|
|
1118
1530
|
collectedAt: collectedAtValue,
|
|
@@ -1139,7 +1551,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1139
1551
|
error: err.cause instanceof Error
|
|
1140
1552
|
? err.cause.message
|
|
1141
1553
|
: String(err.cause),
|
|
1142
|
-
}, "Envelope
|
|
1554
|
+
}, "Envelope write incomplete after durable commit; boot recovery may finish it");
|
|
1143
1555
|
}
|
|
1144
1556
|
else if (!committed) {
|
|
1145
1557
|
await writeAuth?.releaseProof?.();
|
|
@@ -1147,7 +1559,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1147
1559
|
throw err;
|
|
1148
1560
|
}
|
|
1149
1561
|
finally {
|
|
1150
|
-
|
|
1562
|
+
ingestStamp?.release();
|
|
1151
1563
|
}
|
|
1152
1564
|
}
|
|
1153
1565
|
if (request.method === "DELETE") {
|
|
@@ -1170,6 +1582,18 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1170
1582
|
// first tombstone). Until that lands the only honest answer is 501.
|
|
1171
1583
|
throw new LineageCascadeUnavailableError({ scope: parsed.scope });
|
|
1172
1584
|
}
|
|
1585
|
+
// A canonical-owned scope's latest body is its connection's rows. No
|
|
1586
|
+
// store seam deletes one legacy scope's rows (streams can be shared,
|
|
1587
|
+
// and the account and its generation are the unit of erasure), so a
|
|
1588
|
+
// 200 here would leave the data readable. Refuse before any gateway or
|
|
1589
|
+
// file change; the connection's own delete erases its rows.
|
|
1590
|
+
if (deps.legacyScopeView?.ownsScope(parsed.scope)) {
|
|
1591
|
+
return jsonResponse({
|
|
1592
|
+
error: "CANONICAL_DELETE_UNSUPPORTED",
|
|
1593
|
+
message: `Scope "${parsed.scope}" is served from the owner's connector records, which this route cannot delete. Nothing was deleted; delete the connection to erase its records.`,
|
|
1594
|
+
scope: parsed.scope,
|
|
1595
|
+
}, { status: 409 });
|
|
1596
|
+
}
|
|
1173
1597
|
// Durable order: gateway tombstone -> storage blobs -> local copy. The
|
|
1174
1598
|
// sync manager owns the remote ports; without one (sync disabled or
|
|
1175
1599
|
// no owner key material) only the local copy goes and the result says
|
|
@@ -1225,7 +1649,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
|
|
|
1225
1649
|
return jsonResponse(result, { status: 200 });
|
|
1226
1650
|
}
|
|
1227
1651
|
return methodNotAllowed();
|
|
1228
|
-
});
|
|
1652
|
+
}, logContext);
|
|
1229
1653
|
}
|
|
1230
1654
|
export async function handlePersonalServerAccessLogsRequest(request, deps, options = {}) {
|
|
1231
1655
|
return withApiErrors(async () => {
|
|
@@ -1271,6 +1695,9 @@ export async function handlePersonalServerSyncRequest(request, deps, options = {
|
|
|
1271
1695
|
}));
|
|
1272
1696
|
}
|
|
1273
1697
|
return notFound();
|
|
1698
|
+
}, {
|
|
1699
|
+
logger: deps.logger,
|
|
1700
|
+
route: `${request.method} ${options.basePath ?? "/v1/sync"}`,
|
|
1274
1701
|
});
|
|
1275
1702
|
}
|
|
1276
1703
|
export async function handlePersonalServerGrantsRequest(request, deps, options = {}) {
|