@oxyhq/crowdsource-contracts 0.1.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.
Files changed (57) hide show
  1. package/README.md +129 -0
  2. package/dist/case-envelope.d.ts +1130 -0
  3. package/dist/case-envelope.d.ts.map +1 -0
  4. package/dist/case-envelope.js +383 -0
  5. package/dist/case-envelope.js.map +1 -0
  6. package/dist/decisions.d.ts +353 -0
  7. package/dist/decisions.d.ts.map +1 -0
  8. package/dist/decisions.js +198 -0
  9. package/dist/decisions.js.map +1 -0
  10. package/dist/index.d.ts +45 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +61 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/json-schema.d.ts +43 -0
  15. package/dist/json-schema.d.ts.map +1 -0
  16. package/dist/json-schema.js +83 -0
  17. package/dist/json-schema.js.map +1 -0
  18. package/dist/policies.d.ts +286 -0
  19. package/dist/policies.d.ts.map +1 -0
  20. package/dist/policies.js +178 -0
  21. package/dist/policies.js.map +1 -0
  22. package/dist/primitives.d.ts +185 -0
  23. package/dist/primitives.d.ts.map +1 -0
  24. package/dist/primitives.js +231 -0
  25. package/dist/primitives.js.map +1 -0
  26. package/dist/reputation-events.d.ts +349 -0
  27. package/dist/reputation-events.d.ts.map +1 -0
  28. package/dist/reputation-events.js +128 -0
  29. package/dist/reputation-events.js.map +1 -0
  30. package/dist/resources.d.ts +484 -0
  31. package/dist/resources.d.ts.map +1 -0
  32. package/dist/resources.js +436 -0
  33. package/dist/resources.js.map +1 -0
  34. package/dist/reviews.d.ts +276 -0
  35. package/dist/reviews.d.ts.map +1 -0
  36. package/dist/reviews.js +144 -0
  37. package/dist/reviews.js.map +1 -0
  38. package/dist/taxonomy.d.ts +266 -0
  39. package/dist/taxonomy.d.ts.map +1 -0
  40. package/dist/taxonomy.js +282 -0
  41. package/dist/taxonomy.js.map +1 -0
  42. package/dist/webhooks.d.ts +604 -0
  43. package/dist/webhooks.d.ts.map +1 -0
  44. package/dist/webhooks.js +192 -0
  45. package/dist/webhooks.js.map +1 -0
  46. package/package.json +56 -0
  47. package/src/case-envelope.ts +433 -0
  48. package/src/decisions.ts +216 -0
  49. package/src/index.ts +45 -0
  50. package/src/json-schema.ts +89 -0
  51. package/src/policies.ts +203 -0
  52. package/src/primitives.ts +283 -0
  53. package/src/reputation-events.ts +144 -0
  54. package/src/resources.ts +489 -0
  55. package/src/reviews.ts +159 -0
  56. package/src/taxonomy.ts +313 -0
  57. package/src/webhooks.ts +215 -0
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @oxyhq/crowdsource-contracts
2
+
3
+ The versioned contracts every CrowdSource surface agrees on — the backend, the
4
+ reviewer and console clients, the published SDKs, and third-party integrators.
5
+
6
+ Authored with Zod; the same contracts are exported as JSON Schema
7
+ (`crowdSourceJsonSchema(name)`) so integrators who are not on TypeScript
8
+ validate against the same definition.
9
+
10
+ ## Rules
11
+
12
+ - Contracts only. No application logic, no transport clients, no runtime that
13
+ belongs to one surface.
14
+ - `schemaVersion` travels inside the payload (`crowdsource.case.v1`) and is
15
+ validated separately from the `/v1` route version. Additive changes bump
16
+ neither.
17
+ - A published contract version is immutable. Widening is additive; narrowing
18
+ needs a new version.
19
+ - Nothing here may depend on a single tenant's vocabulary. Application-specific
20
+ subject types and policies are data, registered per application, never types
21
+ in this package.
22
+
23
+ ## Where strictness lands, and why
24
+
25
+ §10.11 says unknown fields must not break clients, "except where the schema
26
+ forbids them for safety". Both halves of that sentence are load-bearing, so the
27
+ package splits by direction rather than picking one setting.
28
+
29
+ | Direction | Setting | Reason |
30
+ | --- | --- | --- |
31
+ | Inbound from a tenant or reviewer — the Case Envelope tree, review submissions, recusals, policy sets, resource-schema registrations | `.strict()` | A silently dropped field is context an application believes it sent and a jury never sees, or an unreviewed rendering input. A review submission additionally must not tolerate `caseId` / `assignmentId` / `reviewerId`: those come from the assignment the server issued, and "nobody chooses the case they review" is only true if they cannot be supplied. |
32
+ | Outbound to a tenant — decisions, webhook envelopes, event payloads | `.loose()` | A newer CrowdSource must never break an older client, and a receiver that persists `event.data` for later processing keeps everything that arrived rather than a stripped subset. |
33
+ | Internal, to Oxy Trust — the reputation event | `.strict()` | The event deliberately carries no resource ids and no free text; an unrecognised field is exactly how content reaches a reputation ledger or a signed attestation. Evolution is handled by the `.v1` in the event type. |
34
+
35
+ Open bags — envelope `metadata`, §5.7 custom payloads, registered JSON Schemas —
36
+ are the exception in both directions. They are open by definition but flat or
37
+ depth-bounded, scalar-typed where they can be, key-restricted, and free of
38
+ prototype-bearing names.
39
+
40
+ There is deliberately **no lexical filter on content**. CrowdSource carries the
41
+ reported material, which is hostile by nature: a harassment report quotes the
42
+ harassment, a phishing report quotes the link. A blocklist on text values would
43
+ reject the evidence and protect nothing. §5.7's boundary is structural — the
44
+ contract has no field anywhere whose value is ever interpreted as markup, a
45
+ template, a component or a remote reference.
46
+
47
+ ## What the JSON Schema does not carry
48
+
49
+ Zod refinements have no JSON Schema equivalent and are dropped by the
50
+ conversion. Everything structural survives; every cross-field and
51
+ cross-reference rule does not — §5.5 reference resolution, the `oxy_user`
52
+ binding-proof requirement, exactly-one-of `uploadId`/`url`, media type
53
+ agreement, coarse coordinates, the jury arithmetic, the supersession chain.
54
+
55
+ A payload that passes the JSON Schema is well-formed, not accepted. The server
56
+ validates with Zod and that is the authority.
57
+
58
+ ## Ambiguities in the plan, and how they were resolved
59
+
60
+ The plan was written as prose, so some of its examples disagree with each other.
61
+ Every such case is listed here; none was resolved silently.
62
+
63
+ | # | Where | The disagreement | Resolution |
64
+ | --- | --- | --- | --- |
65
+ | 1 | Appendix A vs §5.8 | Digests are written `"sha256:..."` in Appendix A and `"..."` in §5.8. | One canonical form, `sha256:<64 lowercase hex>`. Two notations for one value hash two ways, giving one piece of content two envelope hashes, two `caseDedupKey`s (§7.3), two cases and two penalties for one incident. Appendix A is the reference document, so its notation wins and the bare form is rejected rather than normalised. |
66
+ | 2 | Appendix B vs §10.7 | `recommendedActions` is a list of objects (`{action, targetResourceIds}`) in Appendix B and a list of bare strings in §10.7. | Objects on a decision, strings on a review. Appendix B is the reference Decision; a decision that recommends removal without naming what to remove is not actionable. §9.3 writes a review's recommendations as strings and that is kept. `reference-documents.test.ts` pins the §10.7 divergence so widening the contract to accept it has to be a deliberate act. |
67
+ | 3 | Appendix B vs §11.6 vs §6.4 | The universal taxonomy version is spelled `taxonomy`, `universal` and `universalTaxonomyVersion` in three places. | Each surface keeps the spelling of its own reference payload: `DecisionPolicyVersions` uses `taxonomy`, `ReputationPolicyVersions` uses `universal`. Both are reference documents in the approved plan and rewriting either here would be a silent edit. **Recommended follow-up:** unify on `{taxonomy, application, oxyConduct}` when the event contract is agreed with OxyHQServices. |
68
+ | 4 | §5.3 vs §5.8 | §5.3 lists `dimensions` as minimum data for an image; §5.8's own image resource omits them. | Optional. §5.3's "minimum data" is guidance and its own examples contradict it. Duration IS required for video and audio, where no example contradicts §5.3. |
69
+ | 5 | §5.2 vs §5.3 | §5.2 makes `language` apply "where applicable"; §5.3 lists it as minimum data for text. | Optional. §5.2 is the field definition and text of unknown language is real; making it required would push applications into guessing. |
70
+ | 6 | §5.5 | `authored_by` relates a resource to a principal, but the id spaces are separate and no example exercises it. | `authored_by` resolves `to` against `principalBindings`; every other relation resolves it against `resources`. Pinned by a test. |
71
+ | 7 | §5.1 vs §5.8 vs Appendix A | Which root keys are required. | Required: `schemaVersion`, `applicationId`, `externalReportId`, `subject`, `principalBindings`, `resources`, `relations`, `allegations`, `policy`, `privacy`. Optional: `source`, `urgency`, `metadata` — §5.8 omits exactly those three and Appendix A carries all of them. `resources` and `allegations` additionally need at least one entry. |
72
+ | 8 | §9.6 | Enumerates decision outcomes; review outcomes are never enumerated. | Review outcomes are narrower: `violation`, `no_violation`, `insufficient_context`, `content_unavailable`. `inconclusive` is what the engine reports when a panel does not agree — a reviewer cannot fail to agree with themselves, and "the absence of consensus is neither guilt nor innocence" only holds if it is never voted for. `duplicate` and `escalated` are case states. |
73
+ | 9 | §11.6 vs §5.1 | The envelope's `principalBindings` always show a `bindingProofId`, but non-Oxy principals have no Oxy identity to prove. | Required when `type` is `oxy_user`, optional otherwise. That keeps "no binding proof, no Oxy Trust effect" structural without locking out every tenant whose users are not Oxy users. On the reputation event, `subject.bindingProofId` is required unconditionally (§11.7.4). |
74
+ | 10 | §12.4 | Proposes sandbox + staging + production. | `source.environment` is `production` or `sandbox`. CrowdSource deploys once; sandbox is an application-trust state inside it, per this repository's `AGENTS.md`. |
75
+ | 11 | Appendix F vs §5.1 | `applicationId` "comes from the credential, never from the request body", yet every example envelope carries one. | Kept and required, documented as an assertion to be COMPARED with the credential-derived id and rejected on mismatch. The contract cannot enforce this — **the ingress route must**, and it is the highest-risk invariant in the package. |
76
+
77
+ ### Values invented rather than quoted
78
+
79
+ Three tokens do not appear anywhere in the plan and are named here. They are
80
+ listed separately because they are the places a product decision could still
81
+ overrule this package.
82
+
83
+ - **`application_local`** (`FindingScope`). §11.7.5 lets only `oxy_network` and
84
+ `identity_integrity` reach Oxy Trust, so the complement must exist and be
85
+ nameable — §6.5's whole argument is that a local restriction does not become a
86
+ global sanction. Modelling it as an absent field instead would make §11.7.5 a
87
+ presence check, which fails open.
88
+ - **`local_user`, `organization`, `bot`, `federated_actor`** (`PrincipalType`).
89
+ §3 defines a principal as "an Oxy user, a local user, an organization, a bot or
90
+ a federated actor" but only ever writes `oxy_user`. The concepts are the
91
+ plan's; the tokens are this contract's.
92
+ - **`conflict_of_interest`, `language`, `too_sensitive`,
93
+ `insufficient_context`** (`RecusalReason`). §4.1 lists exactly these four
94
+ grounds in prose and §10.3 asks for a "structured reason".
95
+
96
+ ### Left open on purpose
97
+
98
+ - **`sensitivityHint`** and **`urgency.hint`** are bounded lowercase tokens, not
99
+ enums. The plan names one value each (`standard`, `normal`) and §7.5 clearly
100
+ implies more. Both are HINTS: the authoritative `sensitivity_class` and
101
+ priority are computed server-side by triage (§7.4, §12.8), and access to
102
+ sensitive material is gated on the computed class, never on what a tenant
103
+ asserted. Closing either list is a product decision that has not been made.
104
+ - **The remaining reputation event types.** §11.5 names four bridge operations
105
+ (apply, finalize, reverse, reconcile) but §11.6 specifies only
106
+ `moderation.decision.finalized.v1`. The others are not invented here;
107
+ `ReputationEventSchema` is a discriminated union of one so adding them is
108
+ additive and consumers already switch on `type`.
109
+ - **Size limits.** §7.2.3 requires ingress to bound sizes and resource counts
110
+ but states no numbers. `CONTRACT_LIMITS` declares them — generous enough not to
111
+ reject real material, finite so nothing in the contract is unbounded. A tenant
112
+ may be held to something tighter by quota.
113
+
114
+ ## Tests
115
+
116
+ ```bash
117
+ bun run --cwd packages/contracts test # vitest, valid and invalid examples
118
+ bun run --cwd packages/contracts lint # tsc for src/ and again for the tests
119
+ bun run --cwd packages/contracts build # tsc → dist/ (CommonJS + .d.ts)
120
+ ```
121
+
122
+ `src/__tests__/fixtures/` holds Appendix A, Appendix B, §5.8 and §10.7 verbatim,
123
+ and `reference-documents.test.ts` parses them. Where a reference document does
124
+ not validate as written, the test names the exact tokens responsible and the
125
+ suite asserts that nothing else in the document is refused.
126
+
127
+ Every negative test asserts the issue PATHS, not merely that parsing failed — a
128
+ negative example that fails for an unintended reason passes just as loudly as a
129
+ correct one, and then stops testing anything the day its rule is removed.