@fourier-labs/harbour-contracts 0.6.0 → 0.10.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 (134) hide show
  1. package/README.md +49 -4
  2. package/contract-release.json +146 -0
  3. package/dist/gen-ts/ai-budget-ledger-event.d.ts +21 -0
  4. package/dist/gen-ts/ai-budget-ledger-event.js +3 -0
  5. package/dist/gen-ts/ai-classification-stamp.d.ts +25 -0
  6. package/dist/gen-ts/ai-classification-stamp.js +3 -0
  7. package/dist/gen-ts/ai-composite-policy-digest-helper.d.ts +5 -0
  8. package/dist/gen-ts/ai-composite-policy-digest-helper.js +13 -0
  9. package/dist/gen-ts/ai-composite-policy-digest.d.ts +12 -0
  10. package/dist/gen-ts/ai-composite-policy-digest.js +3 -0
  11. package/dist/gen-ts/ai-deployment-gate-decision.d.ts +27 -0
  12. package/dist/gen-ts/ai-deployment-gate-decision.js +3 -0
  13. package/dist/gen-ts/ai-gateway-key-lifecycle-event.d.ts +49 -0
  14. package/dist/gen-ts/ai-gateway-key-lifecycle-event.js +16 -0
  15. package/dist/gen-ts/ai-governance-decision.d.ts +12 -0
  16. package/dist/gen-ts/ai-metering-policy.d.ts +16 -0
  17. package/dist/gen-ts/ai-metering-policy.js +3 -0
  18. package/dist/gen-ts/ai-pricing-revision.d.ts +29 -0
  19. package/dist/gen-ts/ai-pricing-revision.js +3 -0
  20. package/dist/gen-ts/ai-runtime-binding.d.ts +14 -0
  21. package/dist/gen-ts/ai-runtime-binding.js +3 -0
  22. package/dist/gen-ts/ai-runtime-contract.d.ts +15 -0
  23. package/dist/gen-ts/ai-trace-event-v2.d.ts +31 -0
  24. package/dist/gen-ts/ai-trace-event-v2.js +3 -0
  25. package/dist/gen-ts/ai-verification-evidence.d.ts +1 -0
  26. package/dist/gen-ts/binding-classification-digest-helper.d.ts +11 -0
  27. package/dist/gen-ts/binding-classification-digest-helper.js +47 -0
  28. package/dist/gen-ts/binding-classification-subject.d.ts +56 -0
  29. package/dist/gen-ts/binding-classification-subject.js +3 -0
  30. package/dist/gen-ts/contract-release.d.ts +7 -0
  31. package/dist/gen-ts/contract-release.js +9 -0
  32. package/dist/gen-ts/deploy-request.d.ts +52 -0
  33. package/dist/gen-ts/deploy-response.d.ts +8 -0
  34. package/dist/gen-ts/index.d.ts +15 -0
  35. package/dist/gen-ts/index.js +15 -0
  36. package/dist/gen-ts/promotion-request.d.ts +52 -0
  37. package/dist/gen-ts/promotion-response.d.ts +8 -0
  38. package/dist/gen-ts/sanitized-binding-digest-helper.d.ts +7 -0
  39. package/dist/gen-ts/sanitized-binding-digest-helper.js +58 -0
  40. package/dist/gen-ts/sanitized-binding-evidence.d.ts +47 -0
  41. package/dist/gen-ts/sanitized-binding-evidence.js +3 -0
  42. package/fixtures/ai/adversarial-binding-classification-value.json +28 -0
  43. package/fixtures/ai/adversarial-sanitized-binding-relabel.json +32 -0
  44. package/fixtures/ai/valid-binding-classification-subject.json +46 -0
  45. package/fixtures/ai/valid-blocked-trace-event.json +31 -0
  46. package/fixtures/ai/valid-budget-ledger-event.json +15 -0
  47. package/fixtures/ai/valid-classification-stamp.json +19 -0
  48. package/fixtures/ai/valid-composite-policy-digest.json +11 -0
  49. package/fixtures/ai/valid-deployment-gate-decision.json +18 -0
  50. package/fixtures/ai/valid-failed-trace-event.json +50 -0
  51. package/fixtures/ai/valid-gateway-key-lifecycle-event.json +16 -0
  52. package/fixtures/ai/valid-gateway-key-lookup.json +14 -0
  53. package/fixtures/ai/valid-governance-decision.json +53 -0
  54. package/fixtures/ai/valid-inventory.json +30 -0
  55. package/fixtures/ai/valid-metering-policy.json +8 -0
  56. package/fixtures/ai/valid-policy-snapshot.json +117 -0
  57. package/fixtures/ai/valid-pricing-revision.json +14 -0
  58. package/fixtures/ai/valid-runtime-binding.json +7 -0
  59. package/fixtures/ai/valid-runtime-contract.json +24 -0
  60. package/fixtures/ai/valid-sanitized-binding-evidence.json +32 -0
  61. package/fixtures/ai/valid-source-revision-digest.json +6 -0
  62. package/fixtures/ai/valid-trace-event-v2.json +24 -0
  63. package/fixtures/ai/valid-trace-event.json +41 -0
  64. package/fixtures/ai/valid-transform-event.json +32 -0
  65. package/fixtures/ai/valid-transform-result.json +23 -0
  66. package/fixtures/ai/valid-verification-evidence.json +33 -0
  67. package/gen-ts/ai-budget-ledger-event.ts +25 -0
  68. package/gen-ts/ai-callsite-inventory.ts +64 -0
  69. package/gen-ts/ai-classification-stamp.ts +28 -0
  70. package/gen-ts/ai-composite-policy-digest-helper.ts +18 -0
  71. package/gen-ts/ai-composite-policy-digest.ts +16 -0
  72. package/gen-ts/ai-deployment-gate-decision.ts +30 -0
  73. package/gen-ts/ai-gateway-key-lifecycle-event.ts +81 -0
  74. package/gen-ts/ai-gateway-key-lookup.ts +23 -0
  75. package/gen-ts/ai-gateway-key-token.ts +7 -0
  76. package/gen-ts/ai-governance-decision.ts +90 -0
  77. package/gen-ts/ai-metering-policy.ts +20 -0
  78. package/gen-ts/ai-policy-snapshot.ts +169 -0
  79. package/gen-ts/ai-pricing-revision.ts +33 -0
  80. package/gen-ts/ai-runtime-binding.ts +18 -0
  81. package/gen-ts/ai-runtime-contract.ts +46 -0
  82. package/gen-ts/ai-trace-event-v2.ts +35 -0
  83. package/gen-ts/ai-trace-event.ts +72 -0
  84. package/gen-ts/ai-transform-event.ts +51 -0
  85. package/gen-ts/ai-transform-result.ts +36 -0
  86. package/gen-ts/ai-verification-evidence.ts +54 -0
  87. package/gen-ts/api-error-response.ts +16 -0
  88. package/gen-ts/artifact-contract.ts +81 -0
  89. package/gen-ts/binding-classification-digest-helper.ts +44 -0
  90. package/gen-ts/binding-classification-subject.ts +60 -0
  91. package/gen-ts/contract-release.ts +10 -0
  92. package/gen-ts/data-dependency.ts +46 -0
  93. package/gen-ts/data-grant.ts +71 -0
  94. package/gen-ts/data-source.ts +78 -0
  95. package/gen-ts/deploy-request.ts +117 -0
  96. package/gen-ts/deploy-response.ts +33 -0
  97. package/gen-ts/deployment-environment.ts +7 -0
  98. package/gen-ts/deployment-source.ts +16 -0
  99. package/gen-ts/deployment-status-failure.ts +12 -0
  100. package/gen-ts/deployment-status-request.ts +11 -0
  101. package/gen-ts/deployment-status-response.ts +39 -0
  102. package/gen-ts/env-bindings.ts +64 -0
  103. package/gen-ts/error-category-registry.ts +108 -0
  104. package/gen-ts/error-category.ts +7 -0
  105. package/gen-ts/error-envelope.ts +49 -0
  106. package/gen-ts/index.ts +51 -0
  107. package/gen-ts/kvs-routing-entry.ts +30 -0
  108. package/gen-ts/promotion-request.ts +95 -0
  109. package/gen-ts/promotion-response.ts +28 -0
  110. package/gen-ts/sanitized-binding-digest-helper.ts +33 -0
  111. package/gen-ts/sanitized-binding-evidence.ts +51 -0
  112. package/gen-ts/secret-reference.ts +40 -0
  113. package/gen-ts/transform-report.ts +105 -0
  114. package/gen-ts/version-state-machine.ts +52 -0
  115. package/gen-ts/version-state.ts +19 -0
  116. package/package.json +8 -4
  117. package/schema/ai-budget-ledger-event.schema.json +29 -0
  118. package/schema/ai-classification-stamp.schema.json +40 -0
  119. package/schema/ai-composite-policy-digest.schema.json +27 -0
  120. package/schema/ai-deployment-gate-decision.schema.json +38 -0
  121. package/schema/ai-gateway-key-lifecycle-event.schema.json +61 -0
  122. package/schema/ai-governance-decision.schema.json +23 -2
  123. package/schema/ai-metering-policy.schema.json +18 -0
  124. package/schema/ai-pricing-revision.schema.json +38 -0
  125. package/schema/ai-runtime-binding.schema.json +26 -0
  126. package/schema/ai-runtime-contract.schema.json +24 -2
  127. package/schema/ai-trace-event-v2.schema.json +41 -0
  128. package/schema/ai-verification-evidence.schema.json +1 -0
  129. package/schema/binding-classification-subject.schema.json +119 -0
  130. package/schema/deploy-request.schema.json +37 -2
  131. package/schema/deploy-response.schema.json +11 -1
  132. package/schema/promotion-request.schema.json +5 -1
  133. package/schema/promotion-response.schema.json +11 -1
  134. package/schema/sanitized-binding-evidence.schema.json +102 -0
@@ -0,0 +1,28 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ /**
5
+ * 202 Accepted body returned after the production version and its durable idempotency binding exist.
6
+ */
7
+ export interface PromotionResponse {
8
+ idempotencyKey: string;
9
+ /**
10
+ * Immutable identity of the admitted production version.
11
+ */
12
+ versionId: string;
13
+ previewVersionId: string;
14
+ tenant: string;
15
+ app: string;
16
+ environment: 'prod';
17
+ status: 'QUEUED';
18
+ /**
19
+ * Digest of the validated production BindingClassificationSubject.
20
+ */
21
+ classificationDigest: string;
22
+ /**
23
+ * Digest of the exact production-version SanitizedBindingEvidence persisted atomically before this response.
24
+ */
25
+ sanitizedBindingDigest: string;
26
+ acceptedAt: string;
27
+ statusUrl: string;
28
+ }
@@ -0,0 +1,33 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ import { createHash } from 'node:crypto';
5
+ import type { BindingClassificationSubject } from './binding-classification-subject.js';
6
+ import type { SanitizedBindingEvidence } from './sanitized-binding-evidence.js';
7
+
8
+ const domain = 'harbour.sanitized-binding-evidence/1.0';
9
+ const token = (value: string): string => Buffer.byteLength(value, 'utf8') + ':' + value;
10
+ const record = (tag: string, values: string[]): string => tag + values.map(token).join('');
11
+
12
+ function classificationDigestForReceipt(input: BindingClassificationSubject): string { const sourcePreimage = [record('D', ['harbour.source-revision/1.0']), record('S', [input.sourceRef, input.sourceRevision])].join('\n'); if (input.sourceRevisionDigest !== 'sha256:' + createHash('sha256').update(sourcePreimage, 'utf8').digest('hex')) throw new Error('sourceRevisionDigest mismatch'); const rows = [record('D', ['harbour.binding-classification-subject/1.0']), record('H', [input.schema, input.status, input.tenantId, input.appId, input.environment, input.sourceRef, input.sourceRevision, input.sourceRevisionDigest, input.classifierRevision])]; for (const name of Object.keys(input.classifications).sort()) { const entry = input.classifications[name]!; const mapping = entry.requestedAiMappingId ? input.requestedAiMappings[entry.requestedAiMappingId] : undefined; if (entry.kind === 'provider_credential' && (!mapping || mapping.providerId !== entry.providerId)) throw new Error('requested AI mapping mismatch for ' + name); rows.push(record('C', [name, entry.kind, entry.confidence, entry.providerId ?? '', entry.category ?? '', entry.requestedAiMappingId ?? ''])); } for (const mappingId of Object.keys(input.requestedAiMappings).sort()) { const mapping = input.requestedAiMappings[mappingId]!; rows.push(record('M', [mappingId, mapping.mappingVersion, mapping.providerId])); } return 'sha256:' + createHash('sha256').update(rows.join('\n'), 'utf8').digest('hex'); }
13
+
14
+ /** Exact UTF-8 length-prefixed preimage. Maps are sorted by key; sanitizedBindingDigest is excluded. */
15
+ export function sanitizedBindingPreimage(input: SanitizedBindingEvidence): string {
16
+ const rows = [record('D', [domain]), record('H', [input.schema, input.tenantId, input.appId, input.environment, input.versionId, input.sourceRef, input.sourceRevision, input.sourceRevisionDigest, input.classificationDigest])];
17
+ for (const name of Object.keys(input.bindings).sort()) { const binding = input.bindings[name]!; if (binding.kind === 'configuration' && binding.value === undefined) throw new Error('configuration value missing for ' + name); if (binding.kind === 'secret' && binding.secretRef === undefined) throw new Error('secretRef missing for ' + name); rows.push(record('B', [name, binding.kind, binding.value ?? '', binding.secretRef ?? ''])); }
18
+ for (const mappingId of Object.keys(input.requiredAiMappings).sort()) { const mapping = input.requiredAiMappings[mappingId]!; rows.push(record('M', [mappingId, mapping.mappingVersion, mapping.providerId])); }
19
+ return rows.join('\n');
20
+ }
21
+
22
+ export function computeSanitizedBindingDigest(input: SanitizedBindingEvidence): string { return 'sha256:' + createHash('sha256').update(sanitizedBindingPreimage(input), 'utf8').digest('hex'); }
23
+
24
+ /** Verifies the atomic allocation receipt cannot relabel source/classification/mappings or retain a provider credential. */
25
+ export function assertSanitizedBindingEvidence(subject: BindingClassificationSubject, evidence: SanitizedBindingEvidence, allocatedVersionId: string): void {
26
+ for (const field of ['tenantId', 'appId', 'environment', 'sourceRef', 'sourceRevision', 'sourceRevisionDigest'] as const) if (subject[field] !== evidence[field]) throw new Error(field + ' mismatch');
27
+ if (evidence.versionId !== allocatedVersionId) throw new Error('versionId mismatch');
28
+ if (subject.classificationDigest !== classificationDigestForReceipt(subject) || evidence.classificationDigest !== subject.classificationDigest) throw new Error('classificationDigest mismatch');
29
+ const requested = Object.keys(subject.requestedAiMappings).sort(); const required = Object.keys(evidence.requiredAiMappings).sort(); if (requested.length !== required.length || requested.some((id, index) => { const a = subject.requestedAiMappings[id]!; const b = evidence.requiredAiMappings[id]; return id !== required[index] || !b || a.mappingVersion !== b.mappingVersion || a.providerId !== b.providerId; })) throw new Error('requiredAiMappings mismatch');
30
+ const expectedNames = Object.keys(subject.classifications).filter(name => subject.classifications[name]!.kind !== 'provider_credential').sort(); const actualNames = Object.keys(evidence.bindings).sort(); if (expectedNames.length !== actualNames.length || expectedNames.some((name, index) => name !== actualNames[index])) throw new Error('sanitized binding coverage mismatch');
31
+ for (const name of expectedNames) if (subject.classifications[name]!.kind !== evidence.bindings[name]!.kind) throw new Error('sanitized binding kind mismatch for ' + name);
32
+ if (evidence.sanitizedBindingDigest !== computeSanitizedBindingDigest(evidence)) throw new Error('sanitizedBindingDigest mismatch');
33
+ }
@@ -0,0 +1,51 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ export type SanitizedBindingIdentifier = string;
5
+ export type SanitizedBindingSourceRef = string;
6
+ export type SanitizedBindingSourceRevision = string;
7
+ export type SanitizedBindingDigest = string;
8
+ export type SanitizedRuntimeBinding = {
9
+ kind: 'configuration' | 'secret';
10
+ /**
11
+ * Approved non-secret configuration literal. Provider-credential values are never approved or persisted here.
12
+ */
13
+ value?: string;
14
+ /**
15
+ * Value-free app-secret reference; tenant integration and provider credential references are outside this contract.
16
+ */
17
+ secretRef?: string;
18
+ };
19
+ export type SanitizedBindingProviderId = 'openai' | 'anthropic' | 'gemini' | 'openrouter';
20
+
21
+ /**
22
+ * Immutable DCP allocation receipt and sanitized runtime-binding set for one exact allocated version and normalized source revision. DCP persists this evidence atomically with version admission before publish or provisioning. classificationDigest chains to the caller's pre-allocation BindingClassificationSubject; bindings contain only approved configuration or app-secret bindings and never an AI-mapping-owned provider credential.
23
+ */
24
+ export interface SanitizedBindingEvidence {
25
+ schema: 'harbour.sanitized-binding-evidence/1.0';
26
+ tenantId: SanitizedBindingIdentifier;
27
+ appId: SanitizedBindingIdentifier;
28
+ environment: 'preview' | 'prod';
29
+ versionId: string;
30
+ sourceRef: SanitizedBindingSourceRef;
31
+ sourceRevision: SanitizedBindingSourceRevision;
32
+ sourceRevisionDigest: SanitizedBindingDigest;
33
+ classificationDigest: SanitizedBindingDigest;
34
+ /**
35
+ * Exact approved runtime-binding set keyed by environment name. Object keys make duplicate bindings unrepresentable. A provider_credential kind and rejected binding metadata/value are unrepresentable.
36
+ */
37
+ bindings: {
38
+ [k: string]: SanitizedRuntimeBinding;
39
+ };
40
+ /**
41
+ * DCP-validated supported AI mapping requirements keyed by mapping ID. This records what later PLAN/provisioning milestones must enforce; it does not claim those later controls are already active.
42
+ */
43
+ requiredAiMappings: {
44
+ [k: string]: SanitizedBindingAiMapping;
45
+ };
46
+ sanitizedBindingDigest: SanitizedBindingDigest;
47
+ }
48
+ export interface SanitizedBindingAiMapping {
49
+ mappingVersion: string;
50
+ providerId: SanitizedBindingProviderId;
51
+ }
@@ -0,0 +1,40 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ /**
5
+ * app = shared by the whole app; personal = owned by a single builder (ownerBuilderId).
6
+ */
7
+ export type SecretReferenceScope = 'app' | 'personal';
8
+ /**
9
+ * UNSET slots block promotion until a human sets a value (secrets.unresolvable).
10
+ */
11
+ export type SecretReferenceStatus = 'SET' | 'UNSET';
12
+ /**
13
+ * builder = created by a builder; minted = created by the platform (e.g. a data-grant credential).
14
+ */
15
+ export type SecretReferenceOrigin = 'builder' | 'minted';
16
+
17
+ /**
18
+ * Secret-Manager MVP: a named secret slot an app depends on. Only the reference and its lifecycle cross this boundary — the secret VALUE never appears in any contract. scope=app secrets are shared by the app; scope=personal secrets belong to one builder (ownerBuilderId). origin records whether a builder created the slot or the platform minted it (e.g. for a data grant).
19
+ */
20
+ export interface SecretReference {
21
+ /**
22
+ * App the secret slot belongs to.
23
+ */
24
+ appId: string;
25
+ /**
26
+ * Slot name as the app references it (typically the environment-variable name).
27
+ */
28
+ name: string;
29
+ scope: SecretReferenceScope;
30
+ /**
31
+ * Owning builder; present only when scope is personal.
32
+ */
33
+ ownerBuilderId?: string;
34
+ /**
35
+ * secretref URI of the stored value. The value itself never crosses this boundary.
36
+ */
37
+ secretRef: string;
38
+ status: SecretReferenceStatus;
39
+ origin: SecretReferenceOrigin;
40
+ }
@@ -0,0 +1,105 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ /**
5
+ * Structured output an agent must return from one Harbour transformation attempt. Handed to the agent as its output schema, then decoded and semantically re-validated by the control plane, which remains the authority. The enum on applied_playbooks.items is the one dynamic part: the adapter injects the names of the playbooks supplied for that invocation before writing the document, so the model is structurally prevented from naming a playbook it was not given. Application-secret handling was removed platform-wide, so this document carries no redactions and no secrets fields.
6
+ */
7
+ export interface TransformReport {
8
+ /**
9
+ * @minItems 1
10
+ */
11
+ applied_playbooks: string[];
12
+ static_rebuild_required: boolean;
13
+ analysis: {
14
+ deployable: boolean;
15
+ needsTransformation: boolean;
16
+ /**
17
+ * @maxItems 64
18
+ */
19
+ blockers: string[];
20
+ /**
21
+ * @minItems 1
22
+ * @maxItems 1024
23
+ */
24
+ surfaces: string[];
25
+ /**
26
+ * @maxItems 1024
27
+ */
28
+ evidence: {
29
+ path: string;
30
+ line: number;
31
+ reason: string;
32
+ }[];
33
+ };
34
+ database: {
35
+ mode: 'none' | 'sql' | 'unsupported';
36
+ changeType: 'none' | 'code_only' | 'schema_only' | 'code_and_schema';
37
+ requiresCodeChanges: boolean;
38
+ requiresSchemaMigration: boolean;
39
+ desiredSchemaPath: string;
40
+ /**
41
+ * @maxItems 128
42
+ */
43
+ migrationPaths: string[];
44
+ /**
45
+ * @maxItems 512
46
+ */
47
+ changedFiles: string[];
48
+ /**
49
+ * @maxItems 128
50
+ */
51
+ evidence: {
52
+ path: string;
53
+ line: number;
54
+ reason: string;
55
+ }[];
56
+ /**
57
+ * @maxItems 64
58
+ */
59
+ unsupportedFeatures: string[];
60
+ };
61
+ application: {
62
+ runtime: string;
63
+ buildKind: 'container' | 'static';
64
+ topology: 'static' | 'backend' | 'frontend_and_backend';
65
+ entrypoint: string;
66
+ build: {
67
+ /**
68
+ * @minItems 1
69
+ * @maxItems 64
70
+ */
71
+ installCommand: string[];
72
+ /**
73
+ * @minItems 1
74
+ * @maxItems 64
75
+ */
76
+ buildCommand: string[];
77
+ /**
78
+ * @minItems 1
79
+ * @maxItems 16
80
+ */
81
+ outputs: {
82
+ path: string;
83
+ kind: 'public' | 'backend';
84
+ route: string;
85
+ }[];
86
+ };
87
+ basePath: {
88
+ required: boolean;
89
+ environmentVariable: 'HARBOUR_BASE_PATH';
90
+ };
91
+ /**
92
+ * @minItems 1
93
+ * @maxItems 64
94
+ */
95
+ evidence: {
96
+ path: string;
97
+ line: number;
98
+ reason: string;
99
+ }[];
100
+ /**
101
+ * @maxItems 64
102
+ */
103
+ unsupportedFeatures: string[];
104
+ };
105
+ }
@@ -0,0 +1,52 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ export const States = {
5
+ QUEUED: "QUEUED",
6
+ ANALYZING: "ANALYZING",
7
+ TRANSFORMING: "TRANSFORMING",
8
+ VALIDATING: "VALIDATING",
9
+ BUILDING: "BUILDING",
10
+ VERIFYING: "VERIFYING",
11
+ GATING: "GATING",
12
+ LIVE: "LIVE",
13
+ AWAITINGPROMOTION: "AWAITING-PROMOTION",
14
+ PROMOTING: "PROMOTING",
15
+ FAILED: "FAILED",
16
+ STALE: "STALE",
17
+ } as const;
18
+ export type StateName = (typeof States)[keyof typeof States];
19
+
20
+ export const Environments = {
21
+ Preview: "preview",
22
+ Prod: "prod",
23
+ } as const;
24
+ export type EnvironmentName = (typeof Environments)[keyof typeof Environments];
25
+
26
+ const legalTransitions: Record<string, Record<string, Record<string, true>>> = {
27
+ "preview": {
28
+ "QUEUED": { "ANALYZING": true, "FAILED": true, "STALE": true },
29
+ "ANALYZING": { "BUILDING": true, "TRANSFORMING": true, "FAILED": true, "STALE": true },
30
+ "TRANSFORMING": { "VALIDATING": true, "FAILED": true, "STALE": true },
31
+ "VALIDATING": { "BUILDING": true, "TRANSFORMING": true, "FAILED": true, "STALE": true },
32
+ "BUILDING": { "VERIFYING": true, "FAILED": true, "STALE": true },
33
+ "VERIFYING": { "GATING": true, "FAILED": true, "STALE": true },
34
+ "GATING": { "LIVE": true, "FAILED": true, "STALE": true },
35
+ "LIVE": { "AWAITING-PROMOTION": true },
36
+ "AWAITING-PROMOTION": { "PROMOTING": true },
37
+ "PROMOTING": { "LIVE": true, "FAILED": true },
38
+ },
39
+ "prod": {
40
+ "QUEUED": { "PROMOTING": true, "FAILED": true },
41
+ "PROMOTING": { "LIVE": true, "FAILED": true },
42
+ },
43
+ };
44
+ const terminalStates = new Set<string>(["FAILED", "STALE"]);
45
+
46
+ /** True iff from→to is a legal edge for environment in the T1a state machine. */
47
+ export function canTransition(environment: string, from: string, to: string): boolean {
48
+ return Boolean(legalTransitions[environment]?.[from]?.[to]);
49
+ }
50
+
51
+ /** True iff the state is terminal (FAILED, STALE). */
52
+ export function isTerminal(state: string): boolean { return terminalStates.has(state); }
@@ -0,0 +1,19 @@
1
+ /* eslint-disable */
2
+ // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
+
4
+ /**
5
+ * The app-version state machine T1a (LLD §1). Root type is the state token itself. `FAILED` carries a separate error category (see error-category.enum.json); the state token is the bare word `FAILED`. Legal edges live in x-transitions; codegen turns them into a shared transition-validation table so control-plane and any consumer agree on legality.
6
+ */
7
+ export type VersionState =
8
+ | 'QUEUED'
9
+ | 'ANALYZING'
10
+ | 'TRANSFORMING'
11
+ | 'VALIDATING'
12
+ | 'BUILDING'
13
+ | 'VERIFYING'
14
+ | 'GATING'
15
+ | 'LIVE'
16
+ | 'AWAITING-PROMOTION'
17
+ | 'PROMOTING'
18
+ | 'FAILED'
19
+ | 'STALE';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour-contracts",
3
- "version": "0.6.0",
3
+ "version": "0.10.0",
4
4
  "description": "Single source of truth for every data shape that crosses a boundary between Harbour components.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -11,7 +11,10 @@
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
14
- "schema"
14
+ "schema",
15
+ "fixtures/ai",
16
+ "gen-ts",
17
+ "contract-release.json"
15
18
  ],
16
19
  "main": "dist/gen-ts/index.js",
17
20
  "types": "dist/gen-ts/index.d.ts",
@@ -28,12 +31,13 @@
28
31
  "scripts": {
29
32
  "gen": "node tools/gen/gen.mjs",
30
33
  "build": "tsc -p tsconfig.json",
31
- "test": "node --test tools/gen/*.test.mjs resolver/*.test.mjs",
32
- "verify": "node tools/gen/gen.mjs && { [ -z \"$(git status --porcelain -- gen-ts gen-go)\" ] || { echo 'STALE: generated code out of date or uncommitted — run make gen and commit'; exit 1; }; }"
34
+ "test": "node --experimental-strip-types --test tools/gen/*.test.mjs resolver/*.test.mjs",
35
+ "verify": "node tools/release/verify-release.mjs && node tools/gen/gen.mjs && { [ -z \"$(git status --porcelain -- gen-ts gen-go contract-release.json)\" ] || { echo 'STALE: generated code or release manifest out of date or uncommitted — run make gen and commit'; exit 1; }; }"
33
36
  },
34
37
  "devDependencies": {
35
38
  "ajv": "^8.17.1",
36
39
  "ajv-formats": "^3.0.1",
40
+ "@types/node": "^22.10.2",
37
41
  "json-schema-to-typescript": "^15.0.4",
38
42
  "typescript": "^5.7.2"
39
43
  }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://schemas.harbour.dev/ai-budget-ledger-event.schema.json",
4
+ "title": "AiBudgetLedgerEvent",
5
+ "description": "Append-only v2 committed-exposure ledger event. Amounts are integer micro-USD and replay is identified by idempotencyKey.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "eventId", "idempotencyKey", "budgetScope", "period", "eventType", "reservationId", "amountMicroUsd", "committedMicroUsdAfter", "eventDigest", "occurredAt"],
9
+ "properties": {
10
+ "schema": { "type": "string", "const": "harbour.ai-budget-ledger-event/2.0" },
11
+ "eventId": { "$ref": "#/definitions/AiBudgetIdentifier" },
12
+ "idempotencyKey": { "$ref": "#/definitions/AiBudgetIdentifier" },
13
+ "budgetScope": { "$ref": "#/definitions/AiBudgetIdentifier" },
14
+ "period": { "$ref": "#/definitions/AiBudgetIdentifier" },
15
+ "eventType": { "type": "string", "enum": ["RESERVED", "SETTLED", "REPAIRED", "ADJUSTED"] },
16
+ "reservationId": { "$ref": "#/definitions/AiBudgetIdentifier" },
17
+ "requestId": { "$ref": "#/definitions/AiBudgetIdentifier" },
18
+ "amountMicroUsd": { "type": "integer" },
19
+ "committedMicroUsdAfter": { "type": "integer", "minimum": 0 },
20
+ "pricingRevisionDigest": { "$ref": "#/definitions/AiBudgetDigest" },
21
+ "supersedesEventId": { "$ref": "#/definitions/AiBudgetIdentifier" },
22
+ "eventDigest": { "$ref": "#/definitions/AiBudgetDigest" },
23
+ "occurredAt": { "type": "string", "format": "date-time" }
24
+ },
25
+ "definitions": {
26
+ "AiBudgetIdentifier": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.@/#-]*$" },
27
+ "AiBudgetDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
28
+ }
29
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://schemas.harbour.dev/ai-classification-stamp.schema.json",
4
+ "title": "AiClassificationStamp",
5
+ "description": "Immutable, value-free classification evidence for one exact version and source revision. Unavailable evidence is explicit and never interpreted as clean.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "stampId", "tenantId", "appId", "environment", "versionId", "sourceRevision", "status", "classifierId", "classifierVersion", "sourceDigest", "evidenceDigest", "stampDigest", "stampedAt"],
9
+ "properties": {
10
+ "schema": { "type": "string", "const": "harbour.ai-classification-stamp/1.0" },
11
+ "stampId": { "$ref": "#/definitions/AiClassificationIdentifier" },
12
+ "tenantId": { "$ref": "#/definitions/AiClassificationIdentifier" },
13
+ "appId": { "$ref": "#/definitions/AiClassificationIdentifier" },
14
+ "environment": { "$ref": "#/definitions/AiClassificationIdentifier" },
15
+ "versionId": { "type": "string", "pattern": "^[^#]+#[^#]+#[^#]+#[0-9]{6,}$", "maxLength": 320 },
16
+ "sourceRevision": { "type": "string", "minLength": 7, "maxLength": 128 },
17
+ "status": { "type": "string", "enum": ["AVAILABLE", "UNAVAILABLE"] },
18
+ "classification": { "type": "string", "enum": ["Green", "Yellow", "Orange", "Red"] },
19
+ "classifierId": { "$ref": "#/definitions/AiClassificationIdentifier" },
20
+ "classifierVersion": { "type": "string", "minLength": 1, "maxLength": 128 },
21
+ "sourceDigest": { "$ref": "#/definitions/AiClassificationDigest" },
22
+ "inventoryDigest": { "$ref": "#/definitions/AiClassificationDigest" },
23
+ "securityEvidenceDigest": { "$ref": "#/definitions/AiClassificationDigest" },
24
+ "evidenceDigest": { "$ref": "#/definitions/AiClassificationDigest" },
25
+ "stampDigest": { "$ref": "#/definitions/AiClassificationDigest" },
26
+ "unavailableReason": { "$ref": "#/definitions/AiClassificationIdentifier" },
27
+ "stampedAt": { "type": "string", "format": "date-time" }
28
+ },
29
+ "allOf": [
30
+ {
31
+ "if": { "properties": { "status": { "const": "AVAILABLE" } }, "required": ["status"] },
32
+ "then": { "required": ["classification", "inventoryDigest", "securityEvidenceDigest"], "not": { "required": ["unavailableReason"] } },
33
+ "else": { "required": ["unavailableReason"], "not": { "anyOf": [{ "required": ["classification"] }, { "required": ["inventoryDigest"] }, { "required": ["securityEvidenceDigest"] }] } }
34
+ }
35
+ ],
36
+ "definitions": {
37
+ "AiClassificationIdentifier": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.@/-]*$" },
38
+ "AiClassificationDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
39
+ }
40
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://schemas.harbour.dev/ai-composite-policy-digest.schema.json",
4
+ "title": "AiCompositePolicyDigestInput",
5
+ "description": "The complete ordered identity input for one composite policy digest. The digest is SHA-256 over the UTF-8 preimage specified by x-digest; missing optional layer digests use the literal hyphen sentinel.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "baselineDigest", "classificationStampDigest"],
9
+ "properties": {
10
+ "schema": { "type": "string", "const": "harbour.ai-composite-policy-digest/1.0" },
11
+ "baselineDigest": { "$ref": "#/definitions/AiCompositePolicyDigest" },
12
+ "classificationStampDigest": { "$ref": "#/definitions/AiCompositePolicyDigest" },
13
+ "tenantPolicyDigest": { "$ref": "#/definitions/AiCompositePolicyDigest" },
14
+ "applicationPolicyDigest": { "$ref": "#/definitions/AiCompositePolicyDigest" },
15
+ "versionOverrideDigest": { "$ref": "#/definitions/AiCompositePolicyDigest" }
16
+ },
17
+ "definitions": {
18
+ "AiCompositePolicyDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
19
+ },
20
+ "x-digest": {
21
+ "algorithm": "sha256",
22
+ "encoding": "utf8",
23
+ "domain": "harbour.ai-composite-policy-digest/1",
24
+ "absent": "-",
25
+ "order": ["baselineDigest", "classificationStampDigest", "tenantPolicyDigest", "applicationPolicyDigest", "versionOverrideDigest"]
26
+ }
27
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://schemas.harbour.dev/ai-deployment-gate-decision.schema.json",
4
+ "title": "AiDeploymentGateDecision",
5
+ "description": "One append-only deployment gate decision. Corrections are new records linked by supersedesDecisionId; existing records are never overwritten.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "decisionId", "sequence", "tenantId", "appId", "environment", "versionId", "sourceRevision", "decision", "reasonCodes", "classificationStampDigest", "planDigest", "policyDigest", "evidenceDigest", "decisionDigest", "decidedAt"],
9
+ "properties": {
10
+ "schema": { "type": "string", "const": "harbour.ai-deployment-gate-decision/1.0" },
11
+ "decisionId": { "$ref": "#/definitions/AiGateIdentifier" },
12
+ "sequence": { "type": "integer", "minimum": 1 },
13
+ "supersedesDecisionId": { "$ref": "#/definitions/AiGateIdentifier" },
14
+ "tenantId": { "$ref": "#/definitions/AiGateIdentifier" },
15
+ "appId": { "$ref": "#/definitions/AiGateIdentifier" },
16
+ "environment": { "$ref": "#/definitions/AiGateIdentifier" },
17
+ "versionId": { "type": "string", "pattern": "^[^#]+#[^#]+#[^#]+#[0-9]{6,}$", "maxLength": 320 },
18
+ "sourceRevision": { "type": "string", "minLength": 7, "maxLength": 128 },
19
+ "decision": { "type": "string", "enum": ["PASS", "BLOCKED"] },
20
+ "reasonCodes": { "type": "array", "maxItems": 64, "uniqueItems": true, "items": { "$ref": "#/definitions/AiGateIdentifier" } },
21
+ "classificationStampDigest": { "$ref": "#/definitions/AiGateDigest" },
22
+ "planDigest": { "$ref": "#/definitions/AiGateDigest" },
23
+ "policyDigest": { "$ref": "#/definitions/AiGateDigest" },
24
+ "evidenceDigest": { "$ref": "#/definitions/AiGateDigest" },
25
+ "decisionDigest": { "$ref": "#/definitions/AiGateDigest" },
26
+ "decidedAt": { "type": "string", "format": "date-time" }
27
+ },
28
+ "allOf": [
29
+ {
30
+ "if": { "properties": { "decision": { "const": "BLOCKED" } }, "required": ["decision"] },
31
+ "then": { "properties": { "reasonCodes": { "minItems": 1 } } }
32
+ }
33
+ ],
34
+ "definitions": {
35
+ "AiGateIdentifier": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.@/-]*$" },
36
+ "AiGateDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
37
+ }
38
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://schemas.harbour.dev/ai-gateway-key-lifecycle-event.schema.json",
4
+ "title": "AiGatewayKeyLifecycleEvent",
5
+ "description": "Append-only, value-free lifecycle evidence for one version-scoped Gateway key.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schema", "eventId", "sequence", "keyId", "tenantId", "appId", "environment", "versionId", "fromStatus", "toStatus", "cause", "eventDigest", "occurredAt"],
9
+ "properties": {
10
+ "schema": { "type": "string", "const": "harbour.ai-gateway-key-lifecycle-event/1.0" },
11
+ "eventId": { "$ref": "#/definitions/AiKeyLifecycleIdentifier" },
12
+ "sequence": { "type": "integer", "minimum": 1 },
13
+ "keyId": { "type": "string", "pattern": "^[0-9a-f]{32}$" },
14
+ "tenantId": { "$ref": "#/definitions/AiKeyLifecycleIdentifier" },
15
+ "appId": { "$ref": "#/definitions/AiKeyLifecycleIdentifier" },
16
+ "environment": { "$ref": "#/definitions/AiKeyLifecycleIdentifier" },
17
+ "versionId": { "type": "string", "pattern": "^[^#]+#[^#]+#[^#]+#[0-9]{6,}$", "maxLength": 320 },
18
+ "fromStatus": { "$ref": "#/definitions/AiKeyLifecycleStatus" },
19
+ "toStatus": { "$ref": "#/definitions/AiKeyLifecycleStatus" },
20
+ "cause": { "type": "string", "enum": ["PLAN", "ACTIVATION", "FAILED", "STALE", "SUPERSEDED", "VERSION_DELETED", "MANUAL_REVOKE", "OVERLAP_EXPIRED", "RECONCILIATION"] },
21
+ "secretRef": { "$ref": "#/definitions/AiKeyLifecycleSafeReference" },
22
+ "overlapExpiresAt": { "type": "string", "format": "date-time" },
23
+ "eventDigest": { "$ref": "#/definitions/AiKeyLifecycleDigest" },
24
+ "occurredAt": { "type": "string", "format": "date-time" }
25
+ },
26
+ "definitions": {
27
+ "AiKeyLifecycleIdentifier": { "type": "string", "minLength": 1, "maxLength": 160, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.@/-]*$" },
28
+ "AiKeyLifecycleDigest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
29
+ "AiKeyLifecycleStatus": { "type": "string", "enum": ["NONE", "PENDING", "ACTIVE", "REVOKED", "DELETION_SCHEDULED", "DELETED"] },
30
+ "AiKeyLifecycleSafeReference": {
31
+ "description": "Value-free hierarchical secret reference using the additive reviewed scheme registry; no userinfo, query, fragment, port or percent encoding.",
32
+ "type": "string",
33
+ "maxLength": 1024,
34
+ "pattern": "^(?:aws-sm|iam-role|k8s-sa|secretref|spiffe|vault|vault-v2)://[A-Za-z0-9][A-Za-z0-9._-]{0,127}(?:/[A-Za-z0-9][A-Za-z0-9._~+=-]{0,255})*$"
35
+ }
36
+ },
37
+ "allOf": [
38
+ {
39
+ "oneOf": [
40
+ { "properties": { "fromStatus": { "const": "NONE" }, "toStatus": { "const": "PENDING" } } },
41
+ { "properties": { "fromStatus": { "const": "PENDING" }, "toStatus": { "const": "ACTIVE" } } },
42
+ { "properties": { "fromStatus": { "const": "PENDING" }, "toStatus": { "const": "REVOKED" } } },
43
+ { "properties": { "fromStatus": { "const": "ACTIVE" }, "toStatus": { "const": "REVOKED" } } },
44
+ { "properties": { "fromStatus": { "const": "REVOKED" }, "toStatus": { "const": "DELETION_SCHEDULED" } } },
45
+ { "properties": { "fromStatus": { "const": "DELETION_SCHEDULED" }, "toStatus": { "const": "DELETED" } } }
46
+ ]
47
+ }
48
+ ],
49
+ "x-safe-reference-pattern": "^(?:aws-sm|iam-role|k8s-sa|secretref|spiffe|vault|vault-v2)://[A-Za-z0-9][A-Za-z0-9._-]{0,127}(?:/[A-Za-z0-9][A-Za-z0-9._~+=-]{0,255})*$",
50
+ "x-approved-reference-schemes": ["aws-sm", "iam-role", "k8s-sa", "secretref", "spiffe", "vault", "vault-v2"],
51
+ "x-status-definition": "AiKeyLifecycleStatus",
52
+ "x-terminal": ["DELETED"],
53
+ "x-transitions": [
54
+ { "from": "NONE", "to": "PENDING" },
55
+ { "from": "PENDING", "to": "ACTIVE" },
56
+ { "from": "PENDING", "to": "REVOKED" },
57
+ { "from": "ACTIVE", "to": "REVOKED" },
58
+ { "from": "REVOKED", "to": "DELETION_SCHEDULED" },
59
+ { "from": "DELETION_SCHEDULED", "to": "DELETED" }
60
+ ]
61
+ }
@@ -58,7 +58,7 @@
58
58
  "sourceRevision": { "type": "string", "minLength": 7, "maxLength": 128 },
59
59
  "mode": { "type": "string", "enum": ["observe", "enforce"] },
60
60
  "gatewayBaseUrl": { "type": "string", "format": "uri", "maxLength": 512, "pattern": "^https://[^?#]+/v1$" },
61
- "gatewayKeySecretRef": { "type": "string", "maxLength": 512, "pattern": "^aws-sm://[A-Za-z0-9/_+=.@-]+$" },
61
+ "gatewayKeySecretRef": { "type": "string", "maxLength": 512, "pattern": "^aws-sm://[A-Za-z0-9][A-Za-z0-9._-]{0,127}(?:/[A-Za-z0-9][A-Za-z0-9._~+=-]{0,255})*$" },
62
62
  "modelAliases": {
63
63
  "type": "object",
64
64
  "additionalProperties": false,
@@ -69,9 +69,28 @@
69
69
  }
70
70
  },
71
71
  "policyDigest": { "$ref": "#/definitions/AiDecisionDigest" },
72
+ "runtimeBinding": { "$ref": "#/definitions/AiDecisionRuntimeBinding" },
72
73
  "requiredStrategies": { "type": "array", "minItems": 1, "maxItems": 2, "uniqueItems": true, "items": { "type": "string", "enum": ["deployment_injection", "deterministic_adapter"] } }
73
74
  }
74
75
  },
76
+ "AiDecisionRuntimeBinding": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["schema", "platform", "workloadIdentityRef", "secretRef", "runtimeBindingDigest"],
80
+ "properties": {
81
+ "schema": { "type": "string", "const": "harbour.ai-runtime-binding/1.0" },
82
+ "platform": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z][a-z0-9_.-]*$" },
83
+ "workloadIdentityRef": { "$ref": "#/definitions/AiDecisionSafeReference" },
84
+ "secretRef": { "$ref": "#/definitions/AiDecisionSafeReference" },
85
+ "runtimeBindingDigest": { "$ref": "#/definitions/AiDecisionDigest" }
86
+ }
87
+ },
88
+ "AiDecisionSafeReference": {
89
+ "description": "Platform-neutral value-free hierarchical reference using the additive reviewed scheme registry; no userinfo, query, fragment, port or percent encoding.",
90
+ "type": "string",
91
+ "maxLength": 1024,
92
+ "pattern": "^(?:aws-sm|iam-role|k8s-sa|secretref|spiffe|vault|vault-v2)://[A-Za-z0-9][A-Za-z0-9._-]{0,127}(?:/[A-Za-z0-9][A-Za-z0-9._~+=-]{0,255})*$"
93
+ },
75
94
  "AiInterceptionPlanEntry": {
76
95
  "type": "object",
77
96
  "additionalProperties": false,
@@ -101,5 +120,7 @@
101
120
  }
102
121
  ]
103
122
  }
104
- }
123
+ },
124
+ "x-safe-reference-pattern": "^(?:aws-sm|iam-role|k8s-sa|secretref|spiffe|vault|vault-v2)://[A-Za-z0-9][A-Za-z0-9._-]{0,127}(?:/[A-Za-z0-9][A-Za-z0-9._~+=-]{0,255})*$",
125
+ "x-approved-reference-schemes": ["aws-sm", "iam-role", "k8s-sa", "secretref", "spiffe", "vault", "vault-v2"]
105
126
  }