ArcVelvet Home →

ArcVelvet Trust Infrastructure: Current State

Internal source-of-truth for external peer review. Drawn from the live codebase as of this writing. Where existing documentation drifts from the code, the code is authoritative. Calibrated for a peer architect with C2PA + CAWG + decentralised-identity context.

ArcVelvet is a creator-side provenance platform built on the C2PA stack. It ships two product surfaces (the ArcVelvet marketplace for digital creative work and ARC, a provenance kit that signs anything photographable with documentation of its making, from physical goods through documented creative practice) on top of a shared cryptographic substrate. This document describes what the platform trusts, how, and where the trust model has acknowledged gaps.

1. Certificate hierarchy

Today's production cert is the publicly distributed c2pa-rs pilot test fixture: es256.pub (X.509 cert chain) and es256.pem (PKCS#8 private key) from contentauth/c2pa-rs/sdk/tests/fixtures/certs/. It is loaded from Firebase Secret Manager via two named secrets:

The reason a c2pa-rs test fixture is in production rather than a custom self-signed cert is mechanical: the c2pa-rs library validates the signing certificate's profile at sign time and rejects custom self-signed certs regardless of verify_trust settings. The library accepts the c2pa-rs test fixture set explicitly for this dev/pre-production scenario. The fixture's private key is publicly distributed alongside the certificate; it is not, and was never, a secret. This is the right framing for a pre-production posture: the pilot fixture demonstrates that the cryptographic plumbing functions end to end (manifests sign correctly, embed correctly, verify against the fixture cert in Adobe CAI and contentcredentials.org), but it does not yet establish trust in the signatures produced. Trust arrives at the SSL.com cutover. Pre-cutover manifests remain cryptographically valid post-cutover because manifest validity is a property of the signature over the manifest, not of the issuer's trust-list status at any given moment.

Architecturally, the signing model is single-key, no intermediate, no chain construction in code. functions/src/c2pa/c2paService.ts:buildSigner() passes both PEMs directly to LocalSigner.newSigner(certBuf, keyBuf, 'es256'). There is no per-vendor key, no HSM/KMS layer, no DID-style key registry. One platform key signs every C2PA manifest the platform emits, across both product surfaces (ArcVelvet creator-side, ARC vendor-side).

Production cutover plan. The cutover to the SSL.com-issued production cert is a single env-var flip plus a Secret Manager rotation, no code change. The signer reads C2PA_PROD_MODE from process.env at module init. When the flag is true, buildSettings() returns TOML with verify_trust_list = true, verify_after_sign = true, verify_trust = true. When false (today's default), it injects the cert as user_anchors so chain validation has an explicit anchor without enabling the system trust list. The verifier endpoint's signingStatus.certificate disclosure string flips on the same flag (C-2 at commit b6c2666). The full operational checklist is at docs/PROVENANCE_CUTOVER.md: pre-swap validation, exact Secret Manager rotation commands, deploy sequence, post-swap verification against Adobe CAI + contentcredentials.org, and rollback procedures.

The SSL.com cert is pending issuance. The pilot fixture is the active cert until then. Manifests embedded under the pilot fixture remain cryptographically verifiable after cutover. Manifest validity is a property of the signature over the manifest, not of the issuer's trust-list status at any given moment.

Key rotation. No formal rotation cadence is in production yet because the pilot fixture's secrecy is moot. Post-cutover, the procedure documented at docs/KEY_ROTATION.md targets a 12-month cadence (or immediate on suspected compromise) with a 30-day dual-key overlap window: the new key signs new manifests, the old cert stays valid for verification of pre-rotation material. The forensic-trace property is what makes this work: every signature embeds the certificate that produced it inside the COSE structure, so rotated-key signatures remain verifiable against archived cert material long after the key itself is retired. This generalises cleanly to a future IoP-issued intermediate model: rotation cadence becomes a property of the intermediate's lifecycle, the leaf-rotation procedure stays the same.

Institute of Provenance partnership. Aspirational on the governance side. Zero code paths reference IoP today (grep across the entire repo returns no matches on InstituteOfProvenance, institute_of_provenance, or related strings). The structural-independence claim (that the trust root sits with an institutional authority distinct from any single platform) is operationally instantiated only at the C2PA standards-substrate level today (Adobe, DigiCert, SSL.com root authorities). The IoP-root migration is a configuration swap, not a refactor, when the partnership formalises.

2. Verifier endpoints

Two separate verifier surfaces, separated by product. Both follow a metadata-and-delegation positioning that I'll name explicitly because it's an architectural choice rather than a missing feature.

2.1 c2paVerify: creator-side HTTP function

functions/src/c2pa/c2paVerify.ts. Deployed to us-central1. Public, CORS-open, 256MiB. Routes:

The JSON envelope shape is consistent across routes:

{
  "ok": true,
  "type": "signal",
  "signalId": "...",
  "provenance": { ... per-route fields ... },
  "signingStatus": {
    "type": "internal",
    "algorithm": "ES256",
    "certificate": "c2pa-rs pilot test certificate (SSL.com production certificate pending)",
    "manifestConformance": "C2PA specification v2.2",
    "supportedFormats": ["image/jpeg", "image/png", "image/webp", "image/tiff", "video/mp4", "video/quicktime", "audio/wav"]
  },
  "credential": {
    "format": "JUMBF/COSE",
    "generator": "ArcVelvetOS/1.0.0",
    "substrate": "c2pa-node@0.5.4 (c2pa-rs 0.78.4)",
    "fileUrl": "<signed-url>"
  },
  "verifyWith": [
    "https://contentauthenticity.adobe.com/inspect",
    "https://verify.contentauthenticity.org"
  ]
}

Three adjacent fields name three different things and the distinction is worth holding onto. signingStatus.manifestConformance is the C2PA specification generation the platform's emitted manifests conform to (here, v2.2; the substrate's c2pa-node 0.5.4 release explicitly documents v2.2 conformance in its own README). credential.format is the embedding container family the credential lives in (JUMBF for the box format, COSE_Sign1 for the signature structure; this is what a verifier toolchain parses against, independent of which spec generation the manifest content conforms to). credential.substrate identifies the implementation library and version that produced the credential, so a relying party can cross-reference the spec-conformance claim against a known release rather than taking the platform's word for it.

The architectural rationale for the metadata-and-delegation choice is worth naming explicitly because it's the subtle move. c2paVerify does not perform cryptographic validation itself. It does not call Reader.fromBuffer or invoke c2pa-node's validation API. It returns application-level provenance metadata about the record and explicitly delegates cryptographic verification to Adobe CAI / contentcredentials.org by exposing those URLs in every response and (via ?format=file) by streaming back the credentialed binary so the third-party verifier has bytes to work against.

This is consistent with the minimum-trusted-intermediary posture. The platform is not an authority on whether its own manifests verify; the third-party tooling that consumes the embedded COSE structure is. The verifier surface's job is to enrich the asset's provenance context (signing status, who minted, when settled, what rights were transferred, whether the originating account still exists) and to route the relying party to a trust surface the platform doesn't control. A future in-process cryptographic validator (for offline verifier deployments, or for relying parties that cannot reach Adobe CAI and contentcredentials.org via the public network) is additive to the current architecture, not a replacement for the delegation surface.

2.2 verifyTransaction: ARC-side callable

functions/src/provenance/signatures.ts:verifyTransaction. Firebase callable (not HTTP). Handles type: 'signature' | 'sale'. The 'signature' path falls through to ARC items when no marketplace creator_signatures doc matches the id; this is how the same verifier endpoint serves both creator-side ARC signatures and the ARC item resolver.

The ARC verifier returns a richer chain-of-custody payload because ARC's chain extension is application-level (see §5 below):

{
  "valid": true,
  "type": "signature",
  "signatureId": "arc_item_id",
  "creatorId": "vendorUid",
  "creatorName": "vendor display name or 'Account Deleted'",
  "title": "item name",
  "isArcItem": true,
  "arcItemType": "Art | Real Estate | ...",
  "arcC2paEmbedded": true,
  "signedFileUrl": "<1h signed url to the C2PA-embedded image>",
  "arcCaptureMetadata": { ... EXIF/XMP/IPTC public bucket ... },
  "arcDocumentationRecords": [ { kind, filename, sha256, sizeBytes, signedUrl }, ... ],
  "parentItemId": "...",
  "parentExisted": true,
  "chain": [ { itemId, ledgerId, signedAt, vendorName, isCurrent, chainHashVersion }, ... ],
  "totalChainLength": 5,
  "chainIntegrity": {
    "complete": true,
    "missingParents": [],
    "chainDepth": 3,
    "chainRootId": "arc_root_id",
    "truncated": false
  },
  "ledgerId": "AV · {7-char-hash} · {6-char-uid}",
  "chainHashVersion": "jcs-v1 | legacy-v0",
  "vendorAccountDeleted": false,
  "vendorAccountDeletedAt": null
}

The chainIntegrity block ships in commit 32a2f48 (M-2). It is computed by walking the chain query result at verify time without additional Firestore round-trips and explicitly surfaces parentExisted: false cases to relying parties. The walk is soft-capped at 20 nodes (MAX_CHAIN_INTEGRITY_SCAN), with truncated: true set when the cap is hit.

The metadata-and-delegation rationale applies on the ARC side too. verifyTransaction does not re-verify the embedded C2PA manifest; that is delegated to whichever tool consumes signedFileUrl. It is, however, the authoritative source for the application-level chain-integrity claims because the ARC chain references are stored at the Firestore level, not embedded as cryptographic links between manifests (this is one of the H-2 research surfaces; see §11).

3. Certificate discovery surfaces

Two well-known endpoints shipped in commit adeaa41 (M-3), in functions/src/c2pa/wellKnownCerts.ts:

The kid derivation is SHA-256(cert PEM).slice(0, 16). This is shared with the COSE envelope kid header pattern in loraWorkflowAttestation.ts:deriveKidFromCertPem, so a verifier with a COSE envelope can cross-reference the envelope's kid against the JWKS without needing additional out-of-band material.

The discovery surface is the operational complement to the embedded-in-COSE distribution path that C2PA defines. Every signed asset already carries the signing certificate inline inside its COSE structure (that's how external verifiers like Adobe CAI work without contacting the platform). The well-known endpoints exist for verifier toolchains that prefer to resolve trust material via JWKS conventions (in-toto-adjacent, SPIFFE-adjacent), and for the inevitable case of a verifier that has a COSE envelope but wants to confirm the cert against a discovery surface rather than trust the envelope's embedded copy in isolation.

The endpoint reads C2PA_CERT_PEM from process.env at request time, so the cutover to the SSL.com production cert automatically updates these endpoints via the secret swap. No additional deploy step.

4. C2PA assertion namespace

All assertions live under the reverse-DNS namespace com.arcvelvet.* (the platform's public domain is arcvelvet.com, reverse-DNS resolves to this namespace; this is consistent with C2PA assertion-labelling conventions). Six labels are emitted across the two product surfaces. Manifest position notation: primary means the assertion drives the embed; sibling means it is re-emitted alongside a primary on later manifest revisions for context.

4.1 com.arcvelvet.signal

Emitted by embedSignalCredential (c2paService.ts:121-170). Primary on the signal creation manifest; sibling on sale and reversal manifests.

Field Type Source
signalId string posts/{signalId} doc id
creatorId string signal.authorId || signal.creatorId
creatorName string signal.authorName || signal.author (subject to AD-4 replacement; see §9)
title string signal.title or first 80 chars of signal.content
platform string URL PUBLIC_PLATFORM_URL constant
verifyUrl string URL ${PLATFORM_URL}/verify?id={signalId}&type=signal
issuedAt string ISO 8601 sign-time clock (primary only; omitted on sibling re-emission)
selfContentHash hex string JCS-canonicalised SHA-256 over the assertions array; see §8.1. Optional on type, always populated on new manifests.
chainHashVersion string 'jcs-v1' on new manifests; absent on pre-L-2 records

4.2 com.arcvelvet.sale

Emitted by embedSaleCredential (c2paService.ts:174-232). Primary on the sale manifest; sibling on the reversal manifest.

Field Type Source
saleId string sale_records/{saleId} doc id
signalId string foreign key into posts/
sellerId string creatorId from the originating signal
buyerId string sale.buyerId
priceAeye number in-platform currency amount
priceUsd number computed via AE_TO_USD_RATE
licenseType enum string P-1.5a, see §8.2. One of ALL_RIGHTS_RESERVED | CC_BY | CC_BY_NC | COMMERCIAL | EXCLUSIVE | UNSPECIFIED
platform string URL PUBLIC_PLATFORM_URL
verifyUrl string URL ${PLATFORM_URL}/verify?id={saleId}&type=sale
settledAt string ISO 8601 sign-time clock (primary only)
selfContentHash, chainHashVersion as §4.1 applies to the sale manifest's assertion content

4.3 com.arcvelvet.sale_reversed

Emitted by embedReversalCredential (c2paService.ts:511-582). Primary on the reversal manifest. Reversal triggers when sale_records/{saleId}.status transitions into 'reversed' | 'refunded' | 'disputed'.

Field Type Source
saleId string the reversed sale's id
signalId string underlying signal
reversalReason string one of reversed | refunded | disputed (the new status value)
reversedAt string ISO 8601 trigger-time clock
originalSellerUid string carried from the sale
originalBuyerUid string carried from the sale
originalPriceAEYE number carried from the sale
reversalType 'full' | 'partial' H-5, see §8.3
reversedAmountAeye number H-5, present only when reversalType === 'partial' (conditional spread)
platform, verifyUrl string URL standard
selfContentHash, chainHashVersion as §4.1

4.4 com.arcvelvet.arc_sign

Emitted by embedArcSignCredential (c2paService.ts:393-507). Primary on every ARC vendor signing.

Field Type Conditional
itemId string always
vendorId string always; Firebase Auth uid
vendorName string always; resolved via lookupVendorName (display name → @username → email local-part → "ARC Vendor")
itemName, itemDescription, itemType string always; validated, length-capped at intake
creatorName, originLocation, materials string conditional spread, omitted when absent
saleValueUsd number conditional spread
platform, verifyUrl, issuedAt as standard
parentItemId string conditional spread, present only on chain-extension signings
parentContentHash hex string conditional spread, present only on chain-extension signings
parentExisted boolean always; defaults false on origination
chainDepth number always; 0 on origination
chainRootId string always; itemId on origination

The arc_sign assertion is application-level chain-bearing. See §5 for the chain-extension mechanics.

4.5 com.arcvelvet.capture_meta

Emitted by embedArcSignCredential as a sibling to the standard cawg.metadata assertion when the source file carries any extractable EXIF/XMP/IPTC fields. The cawg.metadata assertion carries the CAWG-standard namespaced fields (tiff:Make, exif:DateTimeOriginal, dc:creator, xmp:CreatorTool, etc.) per the CAWG metadata spec. The com.arcvelvet.capture_meta sibling carries fields that have no CAWG slot: extraction-time and privacy-disclosure.

Field Type Source
extractedAt string ISO 8601 sign-time clock
privacyNote string static disclosure: "GPS coordinates and device serial numbers, when present in the source file, are recorded privately on ArcVelvet but withheld from this public assertion."

The sibling design is deliberate; see §10 on CAWG-adjacent positioning. GPS / serial / MakerNote are filtered upstream by captureExtractor.ts into a private bucket that never reaches the manifest.

4.6 com.arcvelvet.documentation

Emitted by embedArcSignCredential when the ARC signing was accompanied by document deposits (receipts, certificates of authenticity, etc.). Anchors deposit-file hashes in the manifest without leaking the files themselves.

Field Type
records[] array of { kind, filename, sha256, sizeBytes, hasInternalTimestamp, extractedTimestamp?, description? }
recordCount number
depositedAt string ISO 8601
privacyNote string: "Document hashes are anchored in this manifest. The deposited files themselves are stored privately and disclosed only via vendor-controlled signed URLs."

The deposited files stay in Cloud Storage and are served via vendor-controlled signed URLs from the ARC verify path. Only their SHA-256 digests reach the cryptographic claim.

5. Chain-extension model on ARC

ARC signed items form an application-level chain of custody. Each signing references its parent by foreign-key plus content-hash:

The protocol is single-side signing, not synchronous co-signing. The new vendor signs a fresh manifest with their own vendorId; the parent reference is encoded in the four fields above. There is no buyer/transferor signature on the new manifest.

The rationale for single-side signing rather than synchronous co-signing is operational. Co-signing in physical-goods custody chains and multi-stage creative chains requires both parties to be reachable, authenticated, and ready to sign at the same moment. In practice this fails: estate auctions sell items whose original creator died decades ago; secondary-market galleries acquire items whose prior owners cannot be contacted; multi-stage commercial illustration involves stakeholders (publishers, agencies, clients) who never had a direct chain-of-custody role. A protocol that requires synchronous co-signing forecloses these flows entirely.

Single-side signing accepts that the new vendor's claim about the prior owner is a claim, and uses the parentExisted flag as the honesty mechanism. When a vendor enters a linkedDepositId for a parent item that isn't on the platform, resolveChainExtension in signArcItemCore.ts:633-671 records the claim (parentItemId populated, parentContentHash: null) and sets parentExisted: false. The verifier surface (M-2 chainIntegrity block) surfaces this explicitly to relying parties. The chain is technically valid (the receiving vendor's signature is real) but extends from an unverifiable predecessor, and the relying party sees that distinction.

The chainHashVersion field on each arc_signed_items doc ('legacy-v0' | 'jcs-v1') allows pre-H-3 records (JSON.stringify-hashed) and post-H-3 records (JCS-canonicalised-hashed) to coexist in the same chain. The legacy-v0 backfill ran via the migrateArcChainHashVersion admin callable; existing selfContentHash values were not recomputed (would have invalidated the chain), only tagged with their algorithm. Mixed chains (legacy parent → JCS child) verify cleanly: each node carries its own algorithm tag, and a verifier walking the chain dispatches per-node via computeManifestHashByVersion.

The cryptographic chain itself sits at the application layer, not inside the C2PA manifest. A future H-2 (inter-manifest chaining) could lift this into the assertion layer, where the sale manifest's parentContentHash references the signal manifest's selfContentHash and the cryptographic chain becomes verifiable from the asset alone without Firestore. See §11.

6. Cross-product certificate hierarchy

The same C2PA_CERT_PEM / C2PA_KEY_PEM secrets sign every C2PA manifest the platform emits. There is no per-product key, no per-vendor key, no per-creator key. Both embedSignalCredential / embedSaleCredential / embedReversalCredential (creator-side) and embedArcSignCredential (ARC-side) call the same buildSigner() helper.

The shared com.arcvelvet.* reverse-DNS namespace covers both products. Assertion labels are distinguished by their second segment: signal, sale, sale_reversed, arc_sign, capture_meta, documentation.

Verifier endpoints are separated by product for application-level reasons:

This separation is product-side, not trust-side. The cryptographic substrate underneath is uniform.

7. COSE wrapper for LoRA and workflow attestation

C2PA does not cover .safetensors, .ckpt, .pt, or arbitrary .json formats. The c2pa-rs library has no JUMBF embedder for these: there is no current standards-substrate path to embed a C2PA manifest into a model file or a workflow JSON.

The platform-1.5 bridge shipped in commit 004185c (P-1.5b) is a COSE_Sign1 envelope using the same platform certificate, outside the JUMBF/C2PA box format. The module is at functions/src/c2pa/loraWorkflowAttestation.ts. Shape:

The envelope is written to Cloud Storage at attestations/{uid}/{kind}s/{filename}.cose, deliberately outside the uploads/ namespace so the upload validator (which scopes its enforcement to uploads/{uid}/**) does not recurse on the envelope writes.

The verification flow: c2paVerify?type=attestation&uid={uid}&entryId={id} (or &storagePath={path}, M-4 lookup convenience) loads the upload validation entry, downloads the COSE envelope from Storage, verifies the signature against the platform cert, and confirms the embedded fileSha256 matches the validation record. The verifier returns {verification: {valid, reason, kid}, payload, envelope: {storagePath, downloadUrl, format, algorithm}, source: {downloadUrl, fileName, fileSizeBytes}}.

The architectural distinction is important. The COSE envelope uses the same cryptographic identity material as C2PA manifests. The signing certificate is the same. The kid cross-references the same JWKS. The upgrade path when C2PA extends to model / workflow formats is a wiring step replacing the COSE wrapper with a JUMBF embedding using the same cert: no re-issuance of signing material, no new key custody, no chain-of-trust break. The COSE envelope is a forward-compatible bridge, not a parallel trust system.

8. Recent cryptographic claims added to assertions

Three additions over the past two months tightened the assertion content cryptographically rather than at the application layer.

8.1 selfContentHash on creator manifests (L-2 / commit ff5891e)

JCS-canonicalised SHA-256 over the manifest's assertions array, embedded inside the primary assertion's data block. New manifests carry selfContentHash and chainHashVersion: 'jcs-v1' on the com.arcvelvet.signal / com.arcvelvet.sale / com.arcvelvet.sale_reversed data block. The hash is computed pre-insertion (over the assertion array as it stands before the hash field is added) and then inserted into the primary assertion; the COSE signature then covers the hash and the substantive content together.

The field placement (inside the assertion data, not as a sibling Firestore field) makes the hash part of the cryptographic claim. This is the inverse of ARC's chainHashVersion placement (sibling field on the doc, not inside manifestData). The trade-off for ARC was preserving the 21-field hashed shape across the migration; for creator-side there was no prior hash to preserve, so the cleaner placement won.

The asymmetry between ARC (selfContentHash computed at app layer, stored in Firestore) and creator-side (selfContentHash now inside the cryptographic claim) is what closes the L-2 work item. A unified verifier no longer has to handle a "has app-level hash" vs "doesn't" branch. Both surfaces now carry an algorithm-tagged content hash.

8.2 licenseType on com.arcvelvet.sale (P-1.5a / commit 9e21437)

License type moved from application-layer Firestore metadata into the cryptographic claim. The enum mirrors the canonical LicenseType union from types.ts:668-673:

'ALL_RIGHTS_RESERVED' | 'CC_BY' | 'CC_BY_NC' | 'COMMERCIAL' | 'EXCLUSIVE' | 'UNSPECIFIED'

'UNSPECIFIED' is a deliberate sentinel. A sale that doesn't carry a known license type produces a manifest that records the unspecified state cryptographically, rather than silently omitting the field. Honesty-by-mechanism applies here too: omission would create ambiguity between "no license type was set" and "the field didn't exist when this manifest was signed" (true of any pre-P-1.5a manifest), and the sentinel disambiguates the live case from the historical-omission case.

The verifier returns the license type from the cryptographic claim when available and falls back to the Firestore sale.licenseType field when the manifest is pre-P-1.5a. A licenseTypeSource: 'cryptographic_claim' | 'firestore_fallback' discriminator names which path served the value so consumers can make informed decisions.

This is the phase-1.5 bridge to H-1 (full rights-bundle structure). The license type itself is anchored cryptographically; scope, territory, exclusivity, duration, permittedUses remain Phase I research scope (see §11).

8.3 Partial-amount fields on com.arcvelvet.sale_reversed (H-5 / commit 14ffb5b)

Two new fields:

Classification rule: reversedAmountAeye is finite, positive, and strictly less than priceAeye'partial'. Anything else (undefined, zero, NaN, equal-or-greater) → 'full' with the amount field omitted. The defensive cases (equal-or-greater amounts treated as full) are because a refund larger than the sale is meaningless and recording it would mislead. The unit test pins this behavior.

The trigger reads sale_records.{saleId}.reversedAmountAeye (added by whoever writes the reversal status transition). The verifier's ?type=reversal path mirrors the classification and includes both fields with the same rule.

9. Account deletion and signed-content retention

The user-initiated account-deletion flow (commits 3abbc44 through 8aa8657; AD-1 through AD-7) is implemented at functions/src/admin/deleteAccount.ts and documented at docs/ACCOUNT_DELETION_POLICY.md.

The signed-content retention rule (AD-4 / commit ae7c114) addresses an asymmetry the original cascade had between posts/ deletion and sale_records/ preservation. The current policy:

The cryptographic honesty surface is explicit and worth naming. The original creator name is preserved inside the COSE signature. The signature covers it and cannot be retroactively modified without invalidating the signature. The "Account Deleted" replacement is an application-layer presentation choice, not a cryptographic statement. The verifier response surfaces creatorAccountDeleted / vendorAccountDeleted flags on the signal and ARC routes respectively, so a relying party sees both layers: the embedded COSE manifest's original name (visible in Adobe CAI / contentcredentials.org by inspecting the file) and the platform's current display ("Account Deleted"). The architecture treats these as two layers of accurate information rather than papering over the inconsistency.

The escrow mechanism for the unwithdrawn AE balance at deletion (AD-1) holds the withdrawable portion in a deletion_escrow/{escrowDocId} record with a 12-month dormancy if auto-payout to the user's Stripe Connect account fails on the deletion date. A token-based recovery flow (AD-2) lets the deleted-account holder reclaim the balance within the dormancy window by supplying their registered email + a new Stripe Connect destination. After 12 months the unrecovered balance is absorbed to platform_revenue via a daily sweep (AD-3); absorbed records are never deleted, they remain as the audit trail.

The escrow mechanism is independent of the signed-content retention rule but shares the same posture: the platform commits to not silently erasing anything that has third-party trust implications (buyers verifying purchases, recovering creators retrieving their balance). What is retained, why, and for how long is operationally documented in ACCOUNT_DELETION_POLICY.md.

10. Standards engagement posture

C2PA. The platform is a C2PA implementer, not a standards-body member. Manifests are emitted conformant to C2PA specification v2.2 (declared in the signingStatus.manifestConformance field on every verifier response, matching the spec generation the c2pa-node 0.5.4 / c2pa-rs 0.78.4 substrate targets). The library substrate is @contentauth/c2pa-node over c2pa-rs; the trust-list configuration is the standard one (Adobe / DigiCert / SSL.com production CAs in production mode, c2pa-rs test fixture in pre-production). The custom assertion namespace com.arcvelvet.* follows the C2PA reverse-DNS labelling convention. No proposed-spec contributions are upstreamed to the C2PA TC at this time. The com.arcvelvet.capture_meta sibling pattern (carrying extraction-time and privacy-disclosure fields adjacent to cawg.metadata) is a generalizable design candidate; whether it migrates into CAWG as a standard sibling-assertion shape is a conversation the architecture is structurally ready for but has not yet initiated.

CAWG. The capture-metadata assertion design in §4.5 is intentionally CAWG-adjacent. ARC signings emit the standard cawg.metadata assertion (CAWG metadata 1.0 namespaced fields with @context mapping exif, exifEX, tiff, xmp, dc, photoshop, Iptc4xmpCore) when the source file carries any extractable public capture metadata. The com.arcvelvet.capture_meta sibling exists only to carry fields that have no CAWG slot: extraction-time + privacy disclosure. Reconciliation rules between ArcVelvet's flat capture model and the CAWG namespaced model are encoded in c2paService.ts:toCawgMetadataAssertion. The sibling design preserves CAWG-compatibility for any CAWG-aware verifier; the ArcVelvet-specific fields are additive, not displacement.

Institute of Provenance. Active development on the governance side. Structural-independence is the target: the trust root sits with an institutional authority distinct from any single platform, the certificate hierarchy ArcVelvet operates under is issued by an authority that doesn't have the platform's commercial interests. No code paths reference IoP today; the migration to an IoP-issued intermediate cert is the same configuration-swap path the SSL.com production cert cutover uses. The structural-independence claim is therefore aspirational on the governance side pending IoP partnership formalisation; operationally instantiated only at the C2PA standards-substrate level today.

11. Honest limits and open research

Research surfaces and known gaps drawn from docs/PHASE_I_NOTES.md, grouped by maturity.

Phase I research surfaces

H-1: full rights-bundle structure on com.arcvelvet.sale. The license type itself is now in the cryptographic claim (§8.2). The full bundle (scope, territory, exclusivity, duration, permittedUses) remains research scope. The design trade-off is whether to embed structured license fields directly in the assertion data block (cryptographically anchored, larger manifest) or to hash-link to a separate license document (smaller manifest, runtime fetch dependency). The phase-1.5 bridge raised the floor; the full design is open.

H-2: inter-manifest cryptographic chaining for creator-side manifests. The current creator-side flow emits three independent manifests (signal → sale → reversal). Each re-includes prior assertions as plaintext siblings; there is no cryptographic linkage between manifest revisions beyond what c2pa-rs provides at the asset-content-hash layer. The H-3 versioning mechanism that ARC uses for chainHashVersion is product-agnostic and directly reusable for creator-side; the harder question is the chain SHAPE. Sales and reversals form a tree from a signal root (one signal can have multiple sales; one sale can have one reversal), not a line like ARC. A parentContentHash-style scheme works but the verifier UI must render the tree.

H-4: final-sale cryptographic event for ARC with consumer key custody. ARC's "buyer takes possession" event is currently an email to buyerEmail with the certificate PDF + the signed source file attached. There is no cryptographic record of buyer acceptance. The open question is consumer key custody. The most promising path documented in PHASE_I_NOTES is WebAuthn / passkey: medium friction, no key escrow, integrates with existing browser identity, the buyer's signing key is bound to their device the way Stripe Identity bindings work. The signature target is the seller's signed manifest hash, embedded as a sibling assertion in a new buyer-side manifest that re-includes the seller's prior manifest as a C2PA ingredient reference. The H-3 chain hash mechanism extends here too; the verifier's existing chain walk gains a buyer-side leg.

Structural-independence aspirational on the governance side. Pending IoP partnership formalisation. The platform key today is operated by the platform itself; the trust root sits at the C2PA standards-substrate level (Adobe / DigiCert / SSL.com), not at an institutional authority. The IoP-root migration is configuration, not refactor, when the partnership formalises.

Pending architectural transitions

SSL.com production cert cutover pending; the pilot fixture remains active. Cutover plan in §1 + docs/PROVENANCE_CUTOVER.md. Single env flip + secret swap; no code change. Manifests embedded under the pilot fixture remain verifiable after cutover.

LoRA / workflow upgrade to native C2PA pending standards extension. The COSE wrapper in §7 is a forward-compatible bridge. When C2PA extends to model and workflow formats, the wrapper becomes a JUMBF embedding using the same cert and the same identity material.

Documentation and routing cleanup

The /trust or /safety URL surface was never URL-routable. A "Safety Console" surface exists in-app (components/SurfaceSafety.tsx, reachable via the radial nav's Shield pill) but there is no /safety or /trust URL route in Firebase Hosting rewrites or the App.tsx pathname dispatcher. The commit that introduced it (dc8ca71) used the word "route" in the loose in-app surface-state-machine sense rather than the URL sense. External visitors cannot reach it by URL today.

12. Operational docs referenced throughout

End of document.