diff --git a/packages/contracts/src/data-rights-contract.typecheck.ts b/packages/contracts/src/data-rights-contract.typecheck.ts new file mode 100644 index 00000000..1c361aea --- /dev/null +++ b/packages/contracts/src/data-rights-contract.typecheck.ts @@ -0,0 +1,50 @@ +import { + DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION, + type DataRightsContributorEraseRequest, + type DataRightsContributorExportResponse, + type DataRightsContributorRequest, + type DataRightsContributorResponse, +} from './data-rights.js'; + +const WORKSPACE_ID = '22222222-2222-4222-8222-222222222222'; +const USER_ID = '33333333-3333-4333-8333-333333333333'; +const REQUEST_ID = '11111111-1111-4111-8111-111111111111'; +const IDEMPOTENCY_KEY = '44444444-4444-4444-8444-444444444444'; + +/** Compile-time proof that erase authority cannot omit its replay identity. */ +const eraseRequest: DataRightsContributorEraseRequest = { + contractVersion: DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION, + operation: 'erase', + workspaceId: WORKSPACE_ID, + requestedByUserId: USER_ID, + requestId: REQUEST_ID, + idempotencyKey: IDEMPOTENCY_KEY, +}; + +/** Compile-time proof that every operation belongs to the versioned request union. */ +const requestUnion: DataRightsContributorRequest = eraseRequest; + +/** Compile-time proof that export evidence is contributor-owned and digest-bearing. */ +const exportResponse: DataRightsContributorExportResponse = { + contractVersion: DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION, + operation: 'export', + contributor: 'planning.service', + requestId: REQUEST_ID, + schemaVersion: 'planning.data-rights.v1', + recordCount: 1, + sha256: 'a'.repeat(64), + data: Object.freeze({ + goals: Object.freeze([ + Object.freeze({ + id: '55555555-5555-4555-8555-555555555555', + title: 'Example goal', + }), + ]), + }), +}; + +/** Compile-time proof that concrete evidence remains assignable to the response union. */ +const responseUnion: DataRightsContributorResponse = exportResponse; + +void requestUnion; +void responseUnion; diff --git a/packages/contracts/src/data-rights.ts b/packages/contracts/src/data-rights.ts new file mode 100644 index 00000000..d964f2eb --- /dev/null +++ b/packages/contracts/src/data-rights.ts @@ -0,0 +1,113 @@ +/** Versioned internal contract used by independently deployable data-rights contributors. */ +export const DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION = + 'life-os.data-rights-contributor.v1' as const; + +/** JSON-safe primitive allowed in a bounded contributor export section. */ +export type DataRightsJsonPrimitive = boolean | number | string | null; + +/** JSON-safe array allowed in a bounded contributor export section. */ +export interface DataRightsJsonArray extends ReadonlyArray {} + +/** JSON-safe object allowed in a bounded contributor export section. */ +export interface DataRightsJsonObject { + readonly [key: string]: DataRightsJsonValue; +} + +/** JSON-safe value exchanged across the contributor contract. */ +export type DataRightsJsonValue = + | DataRightsJsonPrimitive + | DataRightsJsonArray + | DataRightsJsonObject; + +/** Operations exposed by one service-owned data-rights contributor. */ +export type DataRightsContributorOperation = + | 'export' + | 'erase_preflight' + | 'erase' + | 'verify_erased'; + +interface DataRightsContributorRequestBase { + readonly contractVersion: typeof DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION; + readonly workspaceId: string; + readonly requestedByUserId: string; + readonly requestId: string; +} + +/** Requests one deterministic bounded export section from the owning service. */ +export interface DataRightsContributorExportRequest + extends DataRightsContributorRequestBase { + readonly operation: 'export'; +} + +/** Requests fail-closed erasure readiness without mutating service-owned data. */ +export interface DataRightsContributorErasePreflightRequest + extends DataRightsContributorRequestBase { + readonly operation: 'erase_preflight'; +} + +/** Requests one idempotent owning-service erasure using an opaque replay key. */ +export interface DataRightsContributorEraseRequest + extends DataRightsContributorRequestBase { + readonly operation: 'erase'; + readonly idempotencyKey: string; +} + +/** Requests post-erasure verification from the service that owns the data. */ +export interface DataRightsContributorVerifyErasedRequest + extends DataRightsContributorRequestBase { + readonly operation: 'verify_erased'; +} + +/** Request union for the complete v1 contributor lifecycle. */ +export type DataRightsContributorRequest = + | DataRightsContributorExportRequest + | DataRightsContributorErasePreflightRequest + | DataRightsContributorEraseRequest + | DataRightsContributorVerifyErasedRequest; + +interface DataRightsContributorResponseBase { + readonly contractVersion: typeof DATA_RIGHTS_CONTRIBUTOR_CONTRACT_VERSION; + readonly contributor: string; + readonly requestId: string; +} + +/** Deterministic service-owned export section plus exact digest evidence. */ +export interface DataRightsContributorExportResponse + extends DataRightsContributorResponseBase { + readonly operation: 'export'; + readonly schemaVersion: string; + readonly recordCount: number; + readonly sha256: string; + readonly data: DataRightsJsonValue; +} + +/** Readiness result that cannot claim ready while blockers remain. */ +export interface DataRightsContributorErasePreflightResponse + extends DataRightsContributorResponseBase { + readonly operation: 'erase_preflight'; + readonly ready: boolean; + readonly blockers: readonly string[]; +} + +/** Owning-service erasure receipt with bounded aggregate count and digest. */ +export interface DataRightsContributorEraseResponse + extends DataRightsContributorResponseBase { + readonly operation: 'erase'; + readonly erasedRecords: number; + readonly receiptSha256: string; +} + +/** Post-erasure verification evidence from the owning service. */ +export interface DataRightsContributorVerifyErasedResponse + extends DataRightsContributorResponseBase { + readonly operation: 'verify_erased'; + readonly erased: boolean; + readonly evidenceSha256: string; +} + +/** Response union for the complete v1 contributor lifecycle. */ +export type DataRightsContributorResponse = + | DataRightsContributorExportResponse + | DataRightsContributorErasePreflightResponse + | DataRightsContributorEraseResponse + | DataRightsContributorVerifyErasedResponse; diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index e1a97e3f..183e81fa 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -1,3 +1,5 @@ +export * from './data-rights.js'; + export type WorkspaceRole = 'owner' | 'admin' | 'member' | 'viewer'; export interface RequestContext {