@adcp/sdk 14.4.0 → 15.2.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/bin/adcp-grade.js +46 -2
- package/bin/adcp.js +13 -3
- package/dist/lib/auth/oauth/ClientCredentialsFlow.js +4 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +4 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.js +95 -30
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +96 -30
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.mts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.ts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.js +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +1 -1
- package/dist/lib/auth/oauth/client-credentials-schema.d.mts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.d.ts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.js +37 -0
- package/dist/lib/auth/oauth/client-credentials-schema.mjs +13 -0
- package/dist/lib/auth/oauth/file-storage.d.mts +3 -3
- package/dist/lib/auth/oauth/file-storage.d.ts +3 -3
- package/dist/lib/auth/oauth/file-storage.js +12 -5
- package/dist/lib/auth/oauth/file-storage.mjs +12 -5
- package/dist/lib/auth/oauth/index.d.mts +10 -6
- package/dist/lib/auth/oauth/index.d.ts +10 -6
- package/dist/lib/auth/oauth/index.js +9 -3
- package/dist/lib/auth/oauth/index.mjs +8 -3
- package/dist/lib/auth/oauth/types.d.mts +9 -2
- package/dist/lib/auth/oauth/types.d.ts +9 -2
- package/dist/lib/auth/oauth/types.js +3 -2
- package/dist/lib/auth/oauth/types.mjs +3 -2
- package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
- package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
- package/dist/lib/auth/oauth/web-flow.js +42 -9
- package/dist/lib/auth/oauth/web-flow.mjs +42 -9
- package/dist/lib/canonical-references/index.d.mts +19 -1
- package/dist/lib/canonical-references/index.d.ts +19 -1
- package/dist/lib/canonical-references/index.js +81 -9
- package/dist/lib/canonical-references/index.mjs +80 -9
- package/dist/lib/client/core.d.mts +1 -0
- package/dist/lib/client/core.d.ts +1 -0
- package/dist/lib/core/AgentClient.d.mts +9 -1
- package/dist/lib/core/AgentClient.d.ts +9 -1
- package/dist/lib/core/AgentClient.js +10 -0
- package/dist/lib/core/AgentClient.mjs +10 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -2
- package/dist/lib/core/SingleAgentClient.d.ts +13 -2
- package/dist/lib/core/SingleAgentClient.js +23 -5
- package/dist/lib/core/SingleAgentClient.mjs +23 -5
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
- package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
- package/dist/lib/reporting/gcs/adapter.js +342 -0
- package/dist/lib/reporting/gcs/adapter.mjs +325 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
- package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
- package/dist/lib/reporting/gcs/errors.d.mts +5 -0
- package/dist/lib/reporting/gcs/errors.d.ts +5 -0
- package/dist/lib/reporting/gcs/errors.js +41 -0
- package/dist/lib/reporting/gcs/errors.mjs +16 -0
- package/dist/lib/reporting/gcs/fence.d.mts +36 -0
- package/dist/lib/reporting/gcs/fence.d.ts +36 -0
- package/dist/lib/reporting/gcs/fence.js +250 -0
- package/dist/lib/reporting/gcs/fence.mjs +230 -0
- package/dist/lib/reporting/gcs/index.d.mts +7 -0
- package/dist/lib/reporting/gcs/index.d.ts +7 -0
- package/dist/lib/reporting/gcs/index.js +41 -0
- package/dist/lib/reporting/gcs/index.mjs +12 -0
- package/dist/lib/reporting/gcs/io.d.mts +7 -0
- package/dist/lib/reporting/gcs/io.d.ts +7 -0
- package/dist/lib/reporting/gcs/io.js +95 -0
- package/dist/lib/reporting/gcs/io.mjs +69 -0
- package/dist/lib/reporting/gcs/reader.d.mts +43 -0
- package/dist/lib/reporting/gcs/reader.d.ts +43 -0
- package/dist/lib/reporting/gcs/reader.js +193 -0
- package/dist/lib/reporting/gcs/reader.mjs +169 -0
- package/dist/lib/reporting/gcs/validation.d.mts +5 -0
- package/dist/lib/reporting/gcs/validation.d.ts +5 -0
- package/dist/lib/reporting/gcs/validation.js +49 -0
- package/dist/lib/reporting/gcs/validation.mjs +25 -0
- package/dist/lib/reporting/ledger/handler.d.mts +1 -0
- package/dist/lib/reporting/ledger/handler.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.d.mts +1 -0
- package/dist/lib/reporting/ledger/index.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.js +2 -0
- package/dist/lib/reporting/ledger/index.mjs +1 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
- package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
- package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
- package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
- package/dist/lib/reporting/ledger/object-writes.js +147 -0
- package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
- package/dist/lib/schemas/index.d.mts +2 -0
- package/dist/lib/schemas/index.d.ts +2 -0
- package/dist/lib/schemas/index.js +3 -0
- package/dist/lib/schemas/index.mjs +2 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.d.mts +50 -1
- package/dist/lib/server/a2a-adapter.d.ts +50 -1
- package/dist/lib/server/a2a-adapter.js +230 -7
- package/dist/lib/server/a2a-adapter.mjs +237 -7
- package/dist/lib/server/auth-signature.d.mts +84 -46
- package/dist/lib/server/auth-signature.d.ts +84 -46
- package/dist/lib/server/auth-signature.js +48 -14
- package/dist/lib/server/auth-signature.mjs +58 -15
- package/dist/lib/server/create-adcp-server.js +5 -16
- package/dist/lib/server/create-adcp-server.mjs +5 -16
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-fetch.js +3 -33
- package/dist/lib/signing/agent-fetch.mjs +3 -33
- package/dist/lib/signing/middleware.d.mts +9 -1
- package/dist/lib/signing/middleware.d.ts +9 -1
- package/dist/lib/signing/operation-resolution.d.mts +37 -0
- package/dist/lib/signing/operation-resolution.d.ts +37 -0
- package/dist/lib/signing/operation-resolution.js +154 -0
- package/dist/lib/signing/operation-resolution.mjs +125 -0
- package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
- package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
- package/dist/lib/signing/redis-replay-store.d.mts +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +1 -1
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.js +7 -0
- package/dist/lib/signing/server.mjs +8 -0
- package/dist/lib/signing/verifier.d.mts +36 -1
- package/dist/lib/signing/verifier.d.ts +36 -1
- package/dist/lib/signing/verifier.js +66 -34
- package/dist/lib/signing/verifier.mjs +61 -34
- package/dist/lib/testing/client.js +8 -3
- package/dist/lib/testing/client.mjs +8 -3
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
- package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/types.d.mts +10 -1
- package/dist/lib/testing/types.d.ts +10 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/ZOD-SCHEMAS.md +19 -0
- package/docs/guides/BUILD-AN-AGENT.md +9 -4
- package/docs/guides/CREATIVE-DELIVERY.md +34 -0
- package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
- package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
- package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
- package/docs/guides/REPORTING-OPERATIONS.md +4 -0
- package/docs/guides/SIGNING-GUIDE.md +20 -6
- package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
- package/docs/guides/WEB-OAUTH.md +58 -163
- package/docs/guides/account-resolution.md +1 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +1 -1
- package/docs/migration-14.x-rc-worksheet.md +8 -7
- package/docs/migration-buyer-agent-registry.md +1 -1
- package/docs/migration-oauth-issuer-bindings.md +14 -79
- package/package.json +19 -1
- package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
|
@@ -26,6 +26,7 @@ var import_node_crypto = require("node:crypto");
|
|
|
26
26
|
var import_jcs = require('../../utils/jcs.js');
|
|
27
27
|
var import_evidence = require('../evidence.js');
|
|
28
28
|
var import_types = require('./types.js');
|
|
29
|
+
var import_object_writes = require('./object-writes.js');
|
|
29
30
|
var import_managed = require('./managed.js');
|
|
30
31
|
const REPORTING_MANAGED_DELIVERY_MIGRATION = `
|
|
31
32
|
CREATE TABLE IF NOT EXISTS adcp_reporting_destination_authorizations (
|
|
@@ -1522,6 +1523,231 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1522
1523
|
const row = result.rows[0];
|
|
1523
1524
|
return row ? { materialization: structuredClone(row.materialization), binding: structuredClone(row.binding) } : null;
|
|
1524
1525
|
}
|
|
1526
|
+
/** Freeze all writable names under the same locks as revokeDestination. */
|
|
1527
|
+
async registerObjectWritePlan(input, context = {}) {
|
|
1528
|
+
const plan = structuredClone(input);
|
|
1529
|
+
const fingerprint = (0, import_object_writes.reportingObjectWritePlanFingerprint)(plan);
|
|
1530
|
+
return this.transaction(async (client) => {
|
|
1531
|
+
await advisoryLock(client, accountLock(plan.account_id));
|
|
1532
|
+
await advisoryLock(client, authLock(plan.account_id, plan.destination_ref));
|
|
1533
|
+
const authorized = await client.query(
|
|
1534
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1535
|
+
WHERE account_id = $1 AND destination_ref = $2 AND generation = $3 AND revoked_at IS NULL`,
|
|
1536
|
+
[plan.account_id, plan.destination_ref, plan.generation]
|
|
1537
|
+
);
|
|
1538
|
+
if (!authorized.rowCount) return "revoked";
|
|
1539
|
+
await client.query(
|
|
1540
|
+
`INSERT INTO adcp_reporting_object_write_bindings (account_id,destination_ref,generation,bucket,namespace_key)
|
|
1541
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`,
|
|
1542
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.provider.bucket, plan.provider.namespace_key]
|
|
1543
|
+
);
|
|
1544
|
+
const binding = await client.query(
|
|
1545
|
+
`SELECT bucket,namespace_key FROM adcp_reporting_object_write_bindings
|
|
1546
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3`,
|
|
1547
|
+
[plan.account_id, plan.destination_ref, plan.generation]
|
|
1548
|
+
);
|
|
1549
|
+
if (binding.rows[0]?.bucket !== plan.provider.bucket || binding.rows[0]?.namespace_key !== plan.provider.namespace_key)
|
|
1550
|
+
throw new import_object_writes.ReportingObjectWriteConflictError();
|
|
1551
|
+
const inserted = await client.query(
|
|
1552
|
+
`INSERT INTO adcp_reporting_object_write_plans (account_id, destination_ref, generation, plan_id, fingerprint)
|
|
1553
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`,
|
|
1554
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.plan_id, fingerprint]
|
|
1555
|
+
);
|
|
1556
|
+
if (!inserted.rowCount) {
|
|
1557
|
+
const existing = await client.query(
|
|
1558
|
+
`SELECT fingerprint FROM adcp_reporting_object_write_plans
|
|
1559
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4`,
|
|
1560
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.plan_id]
|
|
1561
|
+
);
|
|
1562
|
+
if (existing.rows[0]?.fingerprint !== fingerprint) throw new import_object_writes.ReportingObjectWriteConflictError();
|
|
1563
|
+
return "unchanged";
|
|
1564
|
+
}
|
|
1565
|
+
await client.query(
|
|
1566
|
+
`INSERT INTO adcp_reporting_object_writes
|
|
1567
|
+
(account_id,destination_ref,generation,plan_id,object_index,bucket,object_name,sha256,size_bytes)
|
|
1568
|
+
SELECT $1,$2,$3,$4,(ordinality-1)::integer,value->>'bucket',value->>'object_name',value->>'sha256',
|
|
1569
|
+
(value->>'size_bytes')::bigint FROM jsonb_array_elements($5::jsonb) WITH ORDINALITY`,
|
|
1570
|
+
[
|
|
1571
|
+
plan.account_id,
|
|
1572
|
+
plan.destination_ref,
|
|
1573
|
+
plan.generation,
|
|
1574
|
+
plan.plan_id,
|
|
1575
|
+
JSON.stringify(
|
|
1576
|
+
plan.objects.map(({ bucket, object_name, sha256, size_bytes }) => ({
|
|
1577
|
+
bucket,
|
|
1578
|
+
object_name,
|
|
1579
|
+
sha256,
|
|
1580
|
+
size_bytes
|
|
1581
|
+
}))
|
|
1582
|
+
)
|
|
1583
|
+
]
|
|
1584
|
+
);
|
|
1585
|
+
return "registered";
|
|
1586
|
+
}, context.signal);
|
|
1587
|
+
}
|
|
1588
|
+
async listRevokedObjectWrites(inputScope, inputOptions = {}, context = {}) {
|
|
1589
|
+
const scope = structuredClone(inputScope);
|
|
1590
|
+
const options = structuredClone(inputOptions);
|
|
1591
|
+
(0, import_object_writes.assertReportingObjectWriteScope)(scope);
|
|
1592
|
+
const limit = options.limit ?? 100;
|
|
1593
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3 || options.after && (!/^[a-f0-9]{64}$/.test(options.after.plan_id) || !Number.isSafeInteger(options.after.object_index) || options.after.object_index < 0 || options.after.object_index >= 128)) {
|
|
1594
|
+
throw new Error("Invalid reporting object-write page");
|
|
1595
|
+
}
|
|
1596
|
+
return this.transaction(async (client) => {
|
|
1597
|
+
await advisoryLock(client, accountLock(scope.account_id));
|
|
1598
|
+
await advisoryLock(client, authLock(scope.account_id, scope.destination_ref));
|
|
1599
|
+
const revoked = await client.query(
|
|
1600
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1601
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND revoked_at IS NOT NULL`,
|
|
1602
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1603
|
+
);
|
|
1604
|
+
if (!revoked.rowCount) throw new import_object_writes.ReportingObjectWriteNotRevokedError();
|
|
1605
|
+
const rows = await client.query(
|
|
1606
|
+
`SELECT plan_id,object_index,bucket,object_name,sha256,size_bytes::float8 AS size_bytes
|
|
1607
|
+
FROM adcp_reporting_object_writes WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND fenced_at IS NULL
|
|
1608
|
+
AND ($4::text IS NULL OR (plan_id,object_index) > ($4,$5::integer))
|
|
1609
|
+
ORDER BY plan_id,object_index LIMIT $6`,
|
|
1610
|
+
[
|
|
1611
|
+
scope.account_id,
|
|
1612
|
+
scope.destination_ref,
|
|
1613
|
+
scope.generation,
|
|
1614
|
+
options.after?.plan_id ?? null,
|
|
1615
|
+
options.after?.object_index ?? null,
|
|
1616
|
+
limit
|
|
1617
|
+
]
|
|
1618
|
+
);
|
|
1619
|
+
return rows.rows;
|
|
1620
|
+
}, context.signal);
|
|
1621
|
+
}
|
|
1622
|
+
async markObjectWriteFenced(input, context = {}) {
|
|
1623
|
+
const frozen = structuredClone(input);
|
|
1624
|
+
(0, import_object_writes.assertReportingObjectWriteScope)(frozen);
|
|
1625
|
+
if (!/^[a-f0-9]{64}$/.test(frozen.plan_id) || !Number.isSafeInteger(frozen.object_index) || frozen.object_index < 0 || frozen.object_index >= 128 || !/^[1-9][0-9]{0,39}$/.test(frozen.tombstone_generation)) {
|
|
1626
|
+
throw new Error("Invalid reporting object-write fence");
|
|
1627
|
+
}
|
|
1628
|
+
await this.transaction(async (client) => {
|
|
1629
|
+
await advisoryLock(client, accountLock(frozen.account_id));
|
|
1630
|
+
await advisoryLock(client, authLock(frozen.account_id, frozen.destination_ref));
|
|
1631
|
+
const updated = await client.query(
|
|
1632
|
+
`UPDATE adcp_reporting_object_writes object SET fenced_at=COALESCE(fenced_at,clock_timestamp()), tombstone_generation=$6
|
|
1633
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4 AND object_index=$5
|
|
1634
|
+
AND (tombstone_generation IS NULL OR tombstone_generation=$6)
|
|
1635
|
+
AND EXISTS (SELECT 1 FROM adcp_reporting_destination_authorizations authz
|
|
1636
|
+
WHERE authz.account_id=object.account_id AND authz.destination_ref=object.destination_ref
|
|
1637
|
+
AND authz.generation=object.generation AND authz.revoked_at IS NOT NULL)`,
|
|
1638
|
+
[
|
|
1639
|
+
frozen.account_id,
|
|
1640
|
+
frozen.destination_ref,
|
|
1641
|
+
frozen.generation,
|
|
1642
|
+
frozen.plan_id,
|
|
1643
|
+
frozen.object_index,
|
|
1644
|
+
frozen.tombstone_generation
|
|
1645
|
+
]
|
|
1646
|
+
);
|
|
1647
|
+
if (updated.rowCount !== 1) throw new Error("Reporting object-write fence is not eligible");
|
|
1648
|
+
}, context.signal);
|
|
1649
|
+
}
|
|
1650
|
+
async getObjectWriteBinding(input, context = {}) {
|
|
1651
|
+
const scope = structuredClone(input);
|
|
1652
|
+
(0, import_object_writes.assertReportingObjectWriteScope)(scope);
|
|
1653
|
+
return this.transaction(async (client) => {
|
|
1654
|
+
await advisoryLock(client, accountLock(scope.account_id));
|
|
1655
|
+
await advisoryLock(client, authLock(scope.account_id, scope.destination_ref));
|
|
1656
|
+
const revoked = await client.query(
|
|
1657
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1658
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND revoked_at IS NOT NULL`,
|
|
1659
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1660
|
+
);
|
|
1661
|
+
if (!revoked.rowCount) throw new import_object_writes.ReportingObjectWriteNotRevokedError();
|
|
1662
|
+
const binding = await client.query(
|
|
1663
|
+
`SELECT bucket,namespace_key FROM adcp_reporting_object_write_bindings
|
|
1664
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3`,
|
|
1665
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1666
|
+
);
|
|
1667
|
+
return binding.rows[0] ?? null;
|
|
1668
|
+
}, context.signal);
|
|
1669
|
+
}
|
|
1670
|
+
async probeObjectWriteAuthority(context = {}) {
|
|
1671
|
+
await this.transaction(async (client) => {
|
|
1672
|
+
await client.query(
|
|
1673
|
+
`SELECT installation_id,schema_version FROM adcp_reporting_object_write_authority WHERE singleton AND schema_version=1`
|
|
1674
|
+
);
|
|
1675
|
+
await client.query(
|
|
1676
|
+
`SELECT account_id,destination_ref,generation,bucket,namespace_key FROM adcp_reporting_object_write_bindings LIMIT 0`
|
|
1677
|
+
);
|
|
1678
|
+
await client.query(
|
|
1679
|
+
`SELECT account_id,destination_ref,generation,plan_id,fingerprint FROM adcp_reporting_object_write_plans LIMIT 0`
|
|
1680
|
+
);
|
|
1681
|
+
await client.query(
|
|
1682
|
+
`SELECT plan_id,object_index,bucket,object_name,sha256,size_bytes,fenced_at,tombstone_generation FROM adcp_reporting_object_writes LIMIT 0`
|
|
1683
|
+
);
|
|
1684
|
+
}, context.signal);
|
|
1685
|
+
await this.getObjectWriteAuthorityId(context);
|
|
1686
|
+
return true;
|
|
1687
|
+
}
|
|
1688
|
+
async getObjectWriteAuthorityId(context = {}) {
|
|
1689
|
+
return this.transaction(async (client) => {
|
|
1690
|
+
const result = await client.query(
|
|
1691
|
+
`SELECT installation_id::text FROM adcp_reporting_object_write_authority WHERE singleton AND schema_version=1`
|
|
1692
|
+
);
|
|
1693
|
+
const id = result.rows[0]?.installation_id;
|
|
1694
|
+
if (result.rowCount !== 1 || !id || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(id))
|
|
1695
|
+
throw new Error("Reporting object-write authority migration is unavailable");
|
|
1696
|
+
return id;
|
|
1697
|
+
}, context.signal);
|
|
1698
|
+
}
|
|
1699
|
+
async getAuthorizedObjectWriteBinding(input, context = {}) {
|
|
1700
|
+
const scope = {
|
|
1701
|
+
account_id: input.account_id,
|
|
1702
|
+
destination_ref: input.destination_ref,
|
|
1703
|
+
generation: input.generation
|
|
1704
|
+
};
|
|
1705
|
+
(0, import_object_writes.assertReportingObjectWriteScope)(scope);
|
|
1706
|
+
return this.transaction(async (client) => {
|
|
1707
|
+
const result = await client.query(
|
|
1708
|
+
`SELECT binding.bucket,binding.namespace_key FROM adcp_reporting_object_write_bindings binding
|
|
1709
|
+
JOIN adcp_reporting_destination_authorizations authz USING (account_id,destination_ref,generation)
|
|
1710
|
+
WHERE binding.account_id=$1 AND binding.destination_ref=$2 AND binding.generation=$3 AND authz.revoked_at IS NULL`,
|
|
1711
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1712
|
+
);
|
|
1713
|
+
return result.rows[0] ?? null;
|
|
1714
|
+
}, context.signal);
|
|
1715
|
+
}
|
|
1716
|
+
async getObjectWritePlan(input, planId, context = {}) {
|
|
1717
|
+
const scope = {
|
|
1718
|
+
account_id: input.account_id,
|
|
1719
|
+
destination_ref: input.destination_ref,
|
|
1720
|
+
generation: input.generation
|
|
1721
|
+
};
|
|
1722
|
+
(0, import_object_writes.assertReportingObjectWriteScope)(scope);
|
|
1723
|
+
if (typeof planId !== "string" || !/^[a-f0-9]{64}$/.test(planId))
|
|
1724
|
+
throw new Error("Invalid reporting object-write plan identity");
|
|
1725
|
+
return this.transaction(async (client) => {
|
|
1726
|
+
const selected = await client.query(
|
|
1727
|
+
`SELECT binding.bucket,binding.namespace_key,plan.fingerprint FROM adcp_reporting_object_write_plans plan
|
|
1728
|
+
JOIN adcp_reporting_object_write_bindings binding USING (account_id,destination_ref,generation)
|
|
1729
|
+
JOIN adcp_reporting_destination_authorizations authz USING (account_id,destination_ref,generation)
|
|
1730
|
+
WHERE plan.account_id=$1 AND plan.destination_ref=$2 AND plan.generation=$3 AND plan.plan_id=$4 AND authz.revoked_at IS NULL`,
|
|
1731
|
+
[scope.account_id, scope.destination_ref, scope.generation, planId]
|
|
1732
|
+
);
|
|
1733
|
+
if (!selected.rows[0]) return null;
|
|
1734
|
+
const selectedBinding = selected.rows[0];
|
|
1735
|
+
const objects = await client.query(
|
|
1736
|
+
`SELECT bucket,object_name,sha256,size_bytes::float8 AS size_bytes FROM adcp_reporting_object_writes
|
|
1737
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4 ORDER BY object_index`,
|
|
1738
|
+
[scope.account_id, scope.destination_ref, scope.generation, planId]
|
|
1739
|
+
);
|
|
1740
|
+
const plan = {
|
|
1741
|
+
...scope,
|
|
1742
|
+
plan_id: planId,
|
|
1743
|
+
provider: { bucket: selectedBinding.bucket, namespace_key: selectedBinding.namespace_key },
|
|
1744
|
+
objects: objects.rows
|
|
1745
|
+
};
|
|
1746
|
+
if ((0, import_object_writes.reportingObjectWritePlanFingerprint)(plan) !== selectedBinding.fingerprint)
|
|
1747
|
+
throw new import_object_writes.ReportingObjectWriteConflictError();
|
|
1748
|
+
return plan;
|
|
1749
|
+
}, context.signal);
|
|
1750
|
+
}
|
|
1525
1751
|
async isAuthorizationCurrent(input) {
|
|
1526
1752
|
const result = await this.query(
|
|
1527
1753
|
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
@@ -1822,7 +2048,8 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1822
2048
|
);
|
|
1823
2049
|
return result.rows[0] ? { subjectId: receipt.reporting_adjustment_id, adjustment: result.rows[0].adjustment } : null;
|
|
1824
2050
|
}
|
|
1825
|
-
async transaction(body) {
|
|
2051
|
+
async transaction(body, signal) {
|
|
2052
|
+
signal?.throwIfAborted();
|
|
1826
2053
|
let client;
|
|
1827
2054
|
try {
|
|
1828
2055
|
client = await this.pool.connect();
|
|
@@ -1832,9 +2059,23 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1832
2059
|
let releaseError;
|
|
1833
2060
|
let transactionStarted = false;
|
|
1834
2061
|
try {
|
|
2062
|
+
signal?.throwIfAborted();
|
|
1835
2063
|
await client.query("BEGIN");
|
|
1836
2064
|
transactionStarted = true;
|
|
1837
|
-
|
|
2065
|
+
if (signal) await client.query("SET LOCAL lock_timeout = '5s'; SET LOCAL statement_timeout = '10s'");
|
|
2066
|
+
const guarded = signal ? {
|
|
2067
|
+
query: async (sql, values) => {
|
|
2068
|
+
signal.throwIfAborted();
|
|
2069
|
+
const result = await client.query(sql, values);
|
|
2070
|
+
signal.throwIfAborted();
|
|
2071
|
+
return result;
|
|
2072
|
+
},
|
|
2073
|
+
release: () => {
|
|
2074
|
+
throw new Error("Transaction body cannot release connection");
|
|
2075
|
+
}
|
|
2076
|
+
} : client;
|
|
2077
|
+
const value = await body(guarded);
|
|
2078
|
+
signal?.throwIfAborted();
|
|
1838
2079
|
await client.query("COMMIT");
|
|
1839
2080
|
return value;
|
|
1840
2081
|
} catch (cause) {
|
|
@@ -2,6 +2,12 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { canonicalize } from "../../utils/jcs.mjs";
|
|
3
3
|
import { isReportingAdjustmentReceiptEvidence, isReportingReceiptEvidence } from "../evidence.mjs";
|
|
4
4
|
import { REPORTING_LEDGER_AUTHORITY } from "./types.mjs";
|
|
5
|
+
import {
|
|
6
|
+
assertReportingObjectWriteScope,
|
|
7
|
+
reportingObjectWritePlanFingerprint,
|
|
8
|
+
ReportingObjectWriteConflictError,
|
|
9
|
+
ReportingObjectWriteNotRevokedError
|
|
10
|
+
} from "./object-writes.mjs";
|
|
5
11
|
import {
|
|
6
12
|
adjustmentReceiptEvidenceMatches,
|
|
7
13
|
assertMaterializationOutcome,
|
|
@@ -1502,6 +1508,231 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1502
1508
|
const row = result.rows[0];
|
|
1503
1509
|
return row ? { materialization: structuredClone(row.materialization), binding: structuredClone(row.binding) } : null;
|
|
1504
1510
|
}
|
|
1511
|
+
/** Freeze all writable names under the same locks as revokeDestination. */
|
|
1512
|
+
async registerObjectWritePlan(input, context = {}) {
|
|
1513
|
+
const plan = structuredClone(input);
|
|
1514
|
+
const fingerprint = reportingObjectWritePlanFingerprint(plan);
|
|
1515
|
+
return this.transaction(async (client) => {
|
|
1516
|
+
await advisoryLock(client, accountLock(plan.account_id));
|
|
1517
|
+
await advisoryLock(client, authLock(plan.account_id, plan.destination_ref));
|
|
1518
|
+
const authorized = await client.query(
|
|
1519
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1520
|
+
WHERE account_id = $1 AND destination_ref = $2 AND generation = $3 AND revoked_at IS NULL`,
|
|
1521
|
+
[plan.account_id, plan.destination_ref, plan.generation]
|
|
1522
|
+
);
|
|
1523
|
+
if (!authorized.rowCount) return "revoked";
|
|
1524
|
+
await client.query(
|
|
1525
|
+
`INSERT INTO adcp_reporting_object_write_bindings (account_id,destination_ref,generation,bucket,namespace_key)
|
|
1526
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`,
|
|
1527
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.provider.bucket, plan.provider.namespace_key]
|
|
1528
|
+
);
|
|
1529
|
+
const binding = await client.query(
|
|
1530
|
+
`SELECT bucket,namespace_key FROM adcp_reporting_object_write_bindings
|
|
1531
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3`,
|
|
1532
|
+
[plan.account_id, plan.destination_ref, plan.generation]
|
|
1533
|
+
);
|
|
1534
|
+
if (binding.rows[0]?.bucket !== plan.provider.bucket || binding.rows[0]?.namespace_key !== plan.provider.namespace_key)
|
|
1535
|
+
throw new ReportingObjectWriteConflictError();
|
|
1536
|
+
const inserted = await client.query(
|
|
1537
|
+
`INSERT INTO adcp_reporting_object_write_plans (account_id, destination_ref, generation, plan_id, fingerprint)
|
|
1538
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`,
|
|
1539
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.plan_id, fingerprint]
|
|
1540
|
+
);
|
|
1541
|
+
if (!inserted.rowCount) {
|
|
1542
|
+
const existing = await client.query(
|
|
1543
|
+
`SELECT fingerprint FROM adcp_reporting_object_write_plans
|
|
1544
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4`,
|
|
1545
|
+
[plan.account_id, plan.destination_ref, plan.generation, plan.plan_id]
|
|
1546
|
+
);
|
|
1547
|
+
if (existing.rows[0]?.fingerprint !== fingerprint) throw new ReportingObjectWriteConflictError();
|
|
1548
|
+
return "unchanged";
|
|
1549
|
+
}
|
|
1550
|
+
await client.query(
|
|
1551
|
+
`INSERT INTO adcp_reporting_object_writes
|
|
1552
|
+
(account_id,destination_ref,generation,plan_id,object_index,bucket,object_name,sha256,size_bytes)
|
|
1553
|
+
SELECT $1,$2,$3,$4,(ordinality-1)::integer,value->>'bucket',value->>'object_name',value->>'sha256',
|
|
1554
|
+
(value->>'size_bytes')::bigint FROM jsonb_array_elements($5::jsonb) WITH ORDINALITY`,
|
|
1555
|
+
[
|
|
1556
|
+
plan.account_id,
|
|
1557
|
+
plan.destination_ref,
|
|
1558
|
+
plan.generation,
|
|
1559
|
+
plan.plan_id,
|
|
1560
|
+
JSON.stringify(
|
|
1561
|
+
plan.objects.map(({ bucket, object_name, sha256, size_bytes }) => ({
|
|
1562
|
+
bucket,
|
|
1563
|
+
object_name,
|
|
1564
|
+
sha256,
|
|
1565
|
+
size_bytes
|
|
1566
|
+
}))
|
|
1567
|
+
)
|
|
1568
|
+
]
|
|
1569
|
+
);
|
|
1570
|
+
return "registered";
|
|
1571
|
+
}, context.signal);
|
|
1572
|
+
}
|
|
1573
|
+
async listRevokedObjectWrites(inputScope, inputOptions = {}, context = {}) {
|
|
1574
|
+
const scope = structuredClone(inputScope);
|
|
1575
|
+
const options = structuredClone(inputOptions);
|
|
1576
|
+
assertReportingObjectWriteScope(scope);
|
|
1577
|
+
const limit = options.limit ?? 100;
|
|
1578
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e3 || options.after && (!/^[a-f0-9]{64}$/.test(options.after.plan_id) || !Number.isSafeInteger(options.after.object_index) || options.after.object_index < 0 || options.after.object_index >= 128)) {
|
|
1579
|
+
throw new Error("Invalid reporting object-write page");
|
|
1580
|
+
}
|
|
1581
|
+
return this.transaction(async (client) => {
|
|
1582
|
+
await advisoryLock(client, accountLock(scope.account_id));
|
|
1583
|
+
await advisoryLock(client, authLock(scope.account_id, scope.destination_ref));
|
|
1584
|
+
const revoked = await client.query(
|
|
1585
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1586
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND revoked_at IS NOT NULL`,
|
|
1587
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1588
|
+
);
|
|
1589
|
+
if (!revoked.rowCount) throw new ReportingObjectWriteNotRevokedError();
|
|
1590
|
+
const rows = await client.query(
|
|
1591
|
+
`SELECT plan_id,object_index,bucket,object_name,sha256,size_bytes::float8 AS size_bytes
|
|
1592
|
+
FROM adcp_reporting_object_writes WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND fenced_at IS NULL
|
|
1593
|
+
AND ($4::text IS NULL OR (plan_id,object_index) > ($4,$5::integer))
|
|
1594
|
+
ORDER BY plan_id,object_index LIMIT $6`,
|
|
1595
|
+
[
|
|
1596
|
+
scope.account_id,
|
|
1597
|
+
scope.destination_ref,
|
|
1598
|
+
scope.generation,
|
|
1599
|
+
options.after?.plan_id ?? null,
|
|
1600
|
+
options.after?.object_index ?? null,
|
|
1601
|
+
limit
|
|
1602
|
+
]
|
|
1603
|
+
);
|
|
1604
|
+
return rows.rows;
|
|
1605
|
+
}, context.signal);
|
|
1606
|
+
}
|
|
1607
|
+
async markObjectWriteFenced(input, context = {}) {
|
|
1608
|
+
const frozen = structuredClone(input);
|
|
1609
|
+
assertReportingObjectWriteScope(frozen);
|
|
1610
|
+
if (!/^[a-f0-9]{64}$/.test(frozen.plan_id) || !Number.isSafeInteger(frozen.object_index) || frozen.object_index < 0 || frozen.object_index >= 128 || !/^[1-9][0-9]{0,39}$/.test(frozen.tombstone_generation)) {
|
|
1611
|
+
throw new Error("Invalid reporting object-write fence");
|
|
1612
|
+
}
|
|
1613
|
+
await this.transaction(async (client) => {
|
|
1614
|
+
await advisoryLock(client, accountLock(frozen.account_id));
|
|
1615
|
+
await advisoryLock(client, authLock(frozen.account_id, frozen.destination_ref));
|
|
1616
|
+
const updated = await client.query(
|
|
1617
|
+
`UPDATE adcp_reporting_object_writes object SET fenced_at=COALESCE(fenced_at,clock_timestamp()), tombstone_generation=$6
|
|
1618
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4 AND object_index=$5
|
|
1619
|
+
AND (tombstone_generation IS NULL OR tombstone_generation=$6)
|
|
1620
|
+
AND EXISTS (SELECT 1 FROM adcp_reporting_destination_authorizations authz
|
|
1621
|
+
WHERE authz.account_id=object.account_id AND authz.destination_ref=object.destination_ref
|
|
1622
|
+
AND authz.generation=object.generation AND authz.revoked_at IS NOT NULL)`,
|
|
1623
|
+
[
|
|
1624
|
+
frozen.account_id,
|
|
1625
|
+
frozen.destination_ref,
|
|
1626
|
+
frozen.generation,
|
|
1627
|
+
frozen.plan_id,
|
|
1628
|
+
frozen.object_index,
|
|
1629
|
+
frozen.tombstone_generation
|
|
1630
|
+
]
|
|
1631
|
+
);
|
|
1632
|
+
if (updated.rowCount !== 1) throw new Error("Reporting object-write fence is not eligible");
|
|
1633
|
+
}, context.signal);
|
|
1634
|
+
}
|
|
1635
|
+
async getObjectWriteBinding(input, context = {}) {
|
|
1636
|
+
const scope = structuredClone(input);
|
|
1637
|
+
assertReportingObjectWriteScope(scope);
|
|
1638
|
+
return this.transaction(async (client) => {
|
|
1639
|
+
await advisoryLock(client, accountLock(scope.account_id));
|
|
1640
|
+
await advisoryLock(client, authLock(scope.account_id, scope.destination_ref));
|
|
1641
|
+
const revoked = await client.query(
|
|
1642
|
+
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
1643
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND revoked_at IS NOT NULL`,
|
|
1644
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1645
|
+
);
|
|
1646
|
+
if (!revoked.rowCount) throw new ReportingObjectWriteNotRevokedError();
|
|
1647
|
+
const binding = await client.query(
|
|
1648
|
+
`SELECT bucket,namespace_key FROM adcp_reporting_object_write_bindings
|
|
1649
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3`,
|
|
1650
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1651
|
+
);
|
|
1652
|
+
return binding.rows[0] ?? null;
|
|
1653
|
+
}, context.signal);
|
|
1654
|
+
}
|
|
1655
|
+
async probeObjectWriteAuthority(context = {}) {
|
|
1656
|
+
await this.transaction(async (client) => {
|
|
1657
|
+
await client.query(
|
|
1658
|
+
`SELECT installation_id,schema_version FROM adcp_reporting_object_write_authority WHERE singleton AND schema_version=1`
|
|
1659
|
+
);
|
|
1660
|
+
await client.query(
|
|
1661
|
+
`SELECT account_id,destination_ref,generation,bucket,namespace_key FROM adcp_reporting_object_write_bindings LIMIT 0`
|
|
1662
|
+
);
|
|
1663
|
+
await client.query(
|
|
1664
|
+
`SELECT account_id,destination_ref,generation,plan_id,fingerprint FROM adcp_reporting_object_write_plans LIMIT 0`
|
|
1665
|
+
);
|
|
1666
|
+
await client.query(
|
|
1667
|
+
`SELECT plan_id,object_index,bucket,object_name,sha256,size_bytes,fenced_at,tombstone_generation FROM adcp_reporting_object_writes LIMIT 0`
|
|
1668
|
+
);
|
|
1669
|
+
}, context.signal);
|
|
1670
|
+
await this.getObjectWriteAuthorityId(context);
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
async getObjectWriteAuthorityId(context = {}) {
|
|
1674
|
+
return this.transaction(async (client) => {
|
|
1675
|
+
const result = await client.query(
|
|
1676
|
+
`SELECT installation_id::text FROM adcp_reporting_object_write_authority WHERE singleton AND schema_version=1`
|
|
1677
|
+
);
|
|
1678
|
+
const id = result.rows[0]?.installation_id;
|
|
1679
|
+
if (result.rowCount !== 1 || !id || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(id))
|
|
1680
|
+
throw new Error("Reporting object-write authority migration is unavailable");
|
|
1681
|
+
return id;
|
|
1682
|
+
}, context.signal);
|
|
1683
|
+
}
|
|
1684
|
+
async getAuthorizedObjectWriteBinding(input, context = {}) {
|
|
1685
|
+
const scope = {
|
|
1686
|
+
account_id: input.account_id,
|
|
1687
|
+
destination_ref: input.destination_ref,
|
|
1688
|
+
generation: input.generation
|
|
1689
|
+
};
|
|
1690
|
+
assertReportingObjectWriteScope(scope);
|
|
1691
|
+
return this.transaction(async (client) => {
|
|
1692
|
+
const result = await client.query(
|
|
1693
|
+
`SELECT binding.bucket,binding.namespace_key FROM adcp_reporting_object_write_bindings binding
|
|
1694
|
+
JOIN adcp_reporting_destination_authorizations authz USING (account_id,destination_ref,generation)
|
|
1695
|
+
WHERE binding.account_id=$1 AND binding.destination_ref=$2 AND binding.generation=$3 AND authz.revoked_at IS NULL`,
|
|
1696
|
+
[scope.account_id, scope.destination_ref, scope.generation]
|
|
1697
|
+
);
|
|
1698
|
+
return result.rows[0] ?? null;
|
|
1699
|
+
}, context.signal);
|
|
1700
|
+
}
|
|
1701
|
+
async getObjectWritePlan(input, planId, context = {}) {
|
|
1702
|
+
const scope = {
|
|
1703
|
+
account_id: input.account_id,
|
|
1704
|
+
destination_ref: input.destination_ref,
|
|
1705
|
+
generation: input.generation
|
|
1706
|
+
};
|
|
1707
|
+
assertReportingObjectWriteScope(scope);
|
|
1708
|
+
if (typeof planId !== "string" || !/^[a-f0-9]{64}$/.test(planId))
|
|
1709
|
+
throw new Error("Invalid reporting object-write plan identity");
|
|
1710
|
+
return this.transaction(async (client) => {
|
|
1711
|
+
const selected = await client.query(
|
|
1712
|
+
`SELECT binding.bucket,binding.namespace_key,plan.fingerprint FROM adcp_reporting_object_write_plans plan
|
|
1713
|
+
JOIN adcp_reporting_object_write_bindings binding USING (account_id,destination_ref,generation)
|
|
1714
|
+
JOIN adcp_reporting_destination_authorizations authz USING (account_id,destination_ref,generation)
|
|
1715
|
+
WHERE plan.account_id=$1 AND plan.destination_ref=$2 AND plan.generation=$3 AND plan.plan_id=$4 AND authz.revoked_at IS NULL`,
|
|
1716
|
+
[scope.account_id, scope.destination_ref, scope.generation, planId]
|
|
1717
|
+
);
|
|
1718
|
+
if (!selected.rows[0]) return null;
|
|
1719
|
+
const selectedBinding = selected.rows[0];
|
|
1720
|
+
const objects = await client.query(
|
|
1721
|
+
`SELECT bucket,object_name,sha256,size_bytes::float8 AS size_bytes FROM adcp_reporting_object_writes
|
|
1722
|
+
WHERE account_id=$1 AND destination_ref=$2 AND generation=$3 AND plan_id=$4 ORDER BY object_index`,
|
|
1723
|
+
[scope.account_id, scope.destination_ref, scope.generation, planId]
|
|
1724
|
+
);
|
|
1725
|
+
const plan = {
|
|
1726
|
+
...scope,
|
|
1727
|
+
plan_id: planId,
|
|
1728
|
+
provider: { bucket: selectedBinding.bucket, namespace_key: selectedBinding.namespace_key },
|
|
1729
|
+
objects: objects.rows
|
|
1730
|
+
};
|
|
1731
|
+
if (reportingObjectWritePlanFingerprint(plan) !== selectedBinding.fingerprint)
|
|
1732
|
+
throw new ReportingObjectWriteConflictError();
|
|
1733
|
+
return plan;
|
|
1734
|
+
}, context.signal);
|
|
1735
|
+
}
|
|
1505
1736
|
async isAuthorizationCurrent(input) {
|
|
1506
1737
|
const result = await this.query(
|
|
1507
1738
|
`SELECT 1 FROM adcp_reporting_destination_authorizations
|
|
@@ -1802,7 +2033,8 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1802
2033
|
);
|
|
1803
2034
|
return result.rows[0] ? { subjectId: receipt.reporting_adjustment_id, adjustment: result.rows[0].adjustment } : null;
|
|
1804
2035
|
}
|
|
1805
|
-
async transaction(body) {
|
|
2036
|
+
async transaction(body, signal) {
|
|
2037
|
+
signal?.throwIfAborted();
|
|
1806
2038
|
let client;
|
|
1807
2039
|
try {
|
|
1808
2040
|
client = await this.pool.connect();
|
|
@@ -1812,9 +2044,23 @@ class PostgresReportingManagedDeliveryStore {
|
|
|
1812
2044
|
let releaseError;
|
|
1813
2045
|
let transactionStarted = false;
|
|
1814
2046
|
try {
|
|
2047
|
+
signal?.throwIfAborted();
|
|
1815
2048
|
await client.query("BEGIN");
|
|
1816
2049
|
transactionStarted = true;
|
|
1817
|
-
|
|
2050
|
+
if (signal) await client.query("SET LOCAL lock_timeout = '5s'; SET LOCAL statement_timeout = '10s'");
|
|
2051
|
+
const guarded = signal ? {
|
|
2052
|
+
query: async (sql, values) => {
|
|
2053
|
+
signal.throwIfAborted();
|
|
2054
|
+
const result = await client.query(sql, values);
|
|
2055
|
+
signal.throwIfAborted();
|
|
2056
|
+
return result;
|
|
2057
|
+
},
|
|
2058
|
+
release: () => {
|
|
2059
|
+
throw new Error("Transaction body cannot release connection");
|
|
2060
|
+
}
|
|
2061
|
+
} : client;
|
|
2062
|
+
const value = await body(guarded);
|
|
2063
|
+
signal?.throwIfAborted();
|
|
1818
2064
|
await client.query("COMMIT");
|
|
1819
2065
|
return value;
|
|
1820
2066
|
} catch (cause) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare class ReportingObjectWriteConflictError extends Error {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
/** Symbol branding preserves errors when CJS stores are paired with ESM fences. */
|
|
5
|
+
export declare function isReportingObjectWriteConflictError(error: unknown): boolean;
|
|
6
|
+
export declare class ReportingObjectWriteNotRevokedError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export declare function isReportingObjectWriteNotRevokedError(error: unknown): boolean;
|
|
10
|
+
/** Non-secret destination identity, scoped to the store's isolated tenant database. */
|
|
11
|
+
export interface ReportingObjectWriteScopeV1 {
|
|
12
|
+
account_id: string;
|
|
13
|
+
destination_ref: string;
|
|
14
|
+
generation: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ReportingObjectWriteV1 {
|
|
17
|
+
bucket: string;
|
|
18
|
+
object_name: string;
|
|
19
|
+
sha256: string;
|
|
20
|
+
size_bytes: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ReportingObjectWriteBindingV1 {
|
|
23
|
+
bucket: string;
|
|
24
|
+
/** SHA-256 of a host-owned deployment namespace; immutable per authorization generation. */
|
|
25
|
+
namespace_key: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ReportingObjectWriteContextV1 {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export declare function reportingObjectWriteScopeKey(scope: ReportingObjectWriteScopeV1): string;
|
|
31
|
+
export declare function reportingObjectWriteName(scope: ReportingObjectWriteScopeV1, binding: ReportingObjectWriteBindingV1, planId: string, index: number): string;
|
|
32
|
+
export interface ReportingObjectWritePlanV1 extends ReportingObjectWriteScopeV1 {
|
|
33
|
+
provider: ReportingObjectWriteBindingV1;
|
|
34
|
+
/** Stable opaque identity for the same logical delivery across retries. */
|
|
35
|
+
plan_id: string;
|
|
36
|
+
objects: readonly ReportingObjectWriteV1[];
|
|
37
|
+
}
|
|
38
|
+
export interface ReportingObjectWriteCursorV1 {
|
|
39
|
+
plan_id: string;
|
|
40
|
+
object_index: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ReportingObjectWriteStoreV1 {
|
|
43
|
+
/** Registration and destination revocation MUST share one authority/lock boundary. */
|
|
44
|
+
registerObjectWritePlan(plan: ReportingObjectWritePlanV1, context?: ReportingObjectWriteContextV1): Promise<'registered' | 'unchanged' | 'revoked'>;
|
|
45
|
+
/** Authoritative cleanup routing, available only for a closed generation. */
|
|
46
|
+
getObjectWriteBinding(scope: ReportingObjectWriteScopeV1, context?: ReportingObjectWriteContextV1): Promise<ReportingObjectWriteBindingV1 | null>;
|
|
47
|
+
/** Only reads closed (revoked) generations; inventory never prunes automatically. */
|
|
48
|
+
listRevokedObjectWrites(scope: ReportingObjectWriteScopeV1, options?: {
|
|
49
|
+
after?: ReportingObjectWriteCursorV1;
|
|
50
|
+
limit?: number;
|
|
51
|
+
}, context?: ReportingObjectWriteContextV1): Promise<Array<ReportingObjectWriteV1 & ReportingObjectWriteCursorV1>>;
|
|
52
|
+
/** Trusted provider code calls this only after verifying a persistent tombstone. */
|
|
53
|
+
markObjectWriteFenced(input: ReportingObjectWriteScopeV1 & ReportingObjectWriteCursorV1 & {
|
|
54
|
+
tombstone_generation: string;
|
|
55
|
+
}, context?: ReportingObjectWriteContextV1): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/** Apply after REPORTING_MANAGED_DELIVERY_MIGRATION. Additive and independently opt-in. */
|
|
58
|
+
export declare const REPORTING_OBJECT_WRITE_MIGRATION = "\nCREATE TABLE IF NOT EXISTS adcp_reporting_object_write_bindings (\n account_id TEXT NOT NULL,\n destination_ref TEXT NOT NULL,\n generation BIGINT NOT NULL,\n bucket TEXT NOT NULL,\n namespace_key TEXT NOT NULL CHECK (namespace_key ~ '^[a-f0-9]{64}$'),\n PRIMARY KEY (account_id, destination_ref, generation),\n FOREIGN KEY (account_id, destination_ref, generation)\n REFERENCES adcp_reporting_destination_authorizations(account_id, destination_ref, generation)\n);\nCREATE TABLE IF NOT EXISTS adcp_reporting_object_write_plans (\n account_id TEXT NOT NULL,\n destination_ref TEXT NOT NULL,\n generation BIGINT NOT NULL,\n plan_id TEXT NOT NULL CHECK (plan_id ~ '^[a-f0-9]{64}$'),\n fingerprint TEXT NOT NULL,\n PRIMARY KEY (account_id, destination_ref, generation, plan_id),\n FOREIGN KEY (account_id, destination_ref, generation)\n REFERENCES adcp_reporting_destination_authorizations(account_id, destination_ref, generation)\n);\nCREATE TABLE IF NOT EXISTS adcp_reporting_object_writes (\n account_id TEXT NOT NULL,\n destination_ref TEXT NOT NULL,\n generation BIGINT NOT NULL,\n plan_id TEXT NOT NULL,\n object_index INTEGER NOT NULL CHECK (object_index >= 0 AND object_index < 128),\n bucket TEXT NOT NULL,\n object_name TEXT NOT NULL,\n sha256 TEXT NOT NULL CHECK (sha256 ~ '^[a-f0-9]{64}$'),\n size_bytes BIGINT NOT NULL CHECK (size_bytes >= 0 AND size_bytes <= 67108864),\n fenced_at TIMESTAMPTZ,\n tombstone_generation TEXT,\n PRIMARY KEY (account_id, destination_ref, generation, plan_id, object_index),\n UNIQUE (bucket, object_name),\n FOREIGN KEY (account_id, destination_ref, generation, plan_id)\n REFERENCES adcp_reporting_object_write_plans(account_id, destination_ref, generation, plan_id)\n);\n";
|
|
59
|
+
export declare function assertReportingObjectWriteScope(scope: ReportingObjectWriteScopeV1): void;
|
|
60
|
+
export declare function reportingObjectWritePlanFingerprint(plan: ReportingObjectWritePlanV1): string;
|
|
61
|
+
/** Opt-in authority for complete provider adapters; the original primitive store port is unchanged. */
|
|
62
|
+
export interface ReportingObjectWriteAuthorityStoreV1 extends ReportingObjectWriteStoreV1 {
|
|
63
|
+
probeObjectWriteAuthority(context?: ReportingObjectWriteContextV1): Promise<boolean>;
|
|
64
|
+
getObjectWriteAuthorityId(context?: ReportingObjectWriteContextV1): Promise<string>;
|
|
65
|
+
getAuthorizedObjectWriteBinding(scope: ReportingObjectWriteScopeV1, context?: ReportingObjectWriteContextV1): Promise<ReportingObjectWriteBindingV1 | null>;
|
|
66
|
+
getObjectWritePlan(scope: ReportingObjectWriteScopeV1, planId: string, context?: ReportingObjectWriteContextV1): Promise<ReportingObjectWritePlanV1 | null>;
|
|
67
|
+
}
|
|
68
|
+
/** PostgreSQL 13+; apply after REPORTING_OBJECT_WRITE_MIGRATION. Identity survives replica/worker restart. */
|
|
69
|
+
export declare const REPORTING_OBJECT_WRITE_AUTHORITY_MIGRATION = "\nCREATE TABLE IF NOT EXISTS adcp_reporting_object_write_authority (\n singleton BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (singleton),\n installation_id UUID NOT NULL DEFAULT gen_random_uuid(),\n schema_version INTEGER NOT NULL DEFAULT 1 CHECK (schema_version = 1)\n);\nINSERT INTO adcp_reporting_object_write_authority (singleton) VALUES (TRUE) ON CONFLICT DO NOTHING;\n";
|