@fourier-labs/harbour-contracts
v0.63.0
Published
Single source of truth for every data shape that crosses a boundary between Isomorph components.
Readme
harbour-contracts
The single source of truth for every data shape that crosses a boundary between Isomorph
components. Schema-first: hand-written JSON Schema in schema/ is the source; the Go
package (gen-go/) and TypeScript package (gen-ts/ → @fourier-labs/harbour-contracts) are generated
and committed. No handler, infra stack, or MCP op is built against a shape that does not
exist here first.
Layout
| Path | What |
|---|---|
| schema/*.json | JSON Schema (draft-07). The only files you hand-edit. |
| gen-go/ | Generated Go package contracts (import github.com/Fourier-Labs-AI/harbour-contracts/gen-go). |
| gen-ts/ | Generated TS sources; published as @fourier-labs/harbour-contracts. |
| contract-release.json | Generated release/version plus aggregate and per-file schema, AI-fixture, Go-source and TypeScript-source digests for strict consumer pins. |
| tools/gen/gen.mjs | The single codegen driver (Node). Validates every schema, then emits Go + TS. |
| docs/adr/ | Decision records (e.g. 0001-t1-ratified.md). |
The schemas
| File | Shape | LLD |
|---|---|---|
| deploy-request.schema.json | MCP deploy(app, {commit, branch}) payload; new preview deploys default to branch_select unless sequential is explicit | §7 |
| error-envelope.schema.json | The failure envelope emitted once per FAILED transition | §2.1 |
| error-category.enum.json | Closed failure-category enum + per-category retryable registry | §2.2 |
| artifact-contract.schema.json | Build output contract (the provider-portability seam), including the digest-pinned well-defined app manifest reference | §3 |
| well-defined-app-manifest.schema.json | Versioned runtime wiring for static, full-stack, and browser-first apps: workloads, schedules, queues, service bindings, language runtime, browser capabilities, and route authentication | Well-defined apps P0 |
| version-state.schema.json | State machine T1a — states, legal transitions, versionId format | §1 |
| env-bindings.schema.json | EnvBindings / Target / Binding (BYOC-complete) | §4.2.2 |
| kvs-routing-entry.schema.json | CloudFront KVS routing value ({static, api}) | §8 |
| ai-composite-policy-digest.schema.json | Ordered composite-policy digest identity and generated Go/TS helpers | AI governance §5.1 |
| ai-classification-stamp.schema.json | Immutable exact-version classification evidence | AI governance §5.2 |
| ai-deployment-gate-decision.schema.json | Append-only explicit deployment gate decision | AI governance §5.2 |
| ai-governance-decision.schema.json | AI governance decision, including value-free descriptors for reviewed OpenAI-compatible client configuration boundaries | AI governance §5 |
| ai-gateway-key-lifecycle-event.schema.json | Value-free version-key lifecycle evidence | AI governance §7 |
| ai-runtime-binding.schema.json | Platform-neutral workload identity/secret binding | AI governance §5.8 |
| binding-classification-subject.schema.json | Complete value-free binding-name classification before version allocation | AI governance §5.7 / SEC-UP-01 |
| sanitized-binding-evidence.schema.json | Exact-version sanitized runtime-binding allocation receipt | AI governance §5.7 / SEC-UP-02 |
| ai-pricing-revision, ai-metering-policy, ai-budget-ledger-event, ai-trace-event-v2 | Additive metering v2 records, including media dimensions and P5 trace attribution; v1 records remain readable | AI governance §9 |
| app-integrations-declaration.schema.json | Committed .isomorph/integrations.json (isomorph.app-integrations/2.0): closed five-operation set (Slack read/post, Gmail read-only, warehouse view read) with fixed identity modes; owns the shared operation/identity/environment enums | Dev kit §4 |
| integration-execute-request, integration-execute-result, integration-connect-result, integration-error-details | App SDK integrations wire contract on /_harbour/integrations/*: per-operation input bounds, the five result shapes, consent states and the closed details.code reason set | Dev kit §4, §8 |
| saas-connection.schema.json, data-grant-v2.schema.json | Tenant SaaS connection record (one installation and channel map per tenant and provider) and the additive harbour.data-grant/2.0 grant record with structured resources and provisioning lease/cleanup (status machine generated; legacy data-grant untouched) | Dev kit §5 |
| kit-bundle-manifest.schema.json, kit-lock.schema.json | Published kit bundle identity (SDK tarball digest plus optional sdk.version and https sdk.url download location) and the committed .isomorph/kit.lock.json the CodeBuild SDK-vendoring gate compares against | Dev kit §3 |
| deploy-response.schema.json | 202 Accepted body for POST /v1/tenants/{tenant}/apps/{app}/deployments, returned only after the authoritative QUEUED version exists and its SQS message is accepted | — |
| deployment-source.schema.json | Credential-free GitHub source in the DeployRequest body; credentials are resolved internally by MaterializeSource | — |
| deployment-environment.schema.json | Public environment accepted by the preview APIs (preview only; production is reachable only through promotion) | — |
| api-error-response.schema.json | Safe JSON error body for Deploy/Status Lambda 4xx/5xx responses; distinct from the internal ErrorEnvelope | — |
| deployment-status-request.schema.json | Validated path parameters for GET /v1/tenants/{tenant}/apps/{app}/deployments/{versionId} | — |
| deployment-status-response.schema.json | 200 OK body for GET status: that exact immutable version, with the progress trail, deployTarget and diagnosticRef | — |
| deployment-status-failure.schema.json | Frontend-safe failure projection nested in a failed status response; a digest-bound diagnosticRef locates the redacted diagnostic | — |
| promotion-request.schema.json | Body for POST /v1/tenants/{tenant}/apps/{app}/promotions; repository identity comes from stored records, not the caller | ADR 0003 |
| promotion-response.schema.json | 202 Accepted body returned once the production version and its durable idempotency binding exist | ADR 0003 |
| transform-report.schema.json | Structured output an agent must return from one transformation attempt; handed to the agent as its output schema and re-validated by the control plane | — |
| transform-parked-result.schema.json | Sequential-preview result after a durable checkpoint when builder input is required; not a successful report | — |
| builder-input-proposal.schema.json | Value-free proposal emitted when sequential preview cannot continue without builder-owned input | — |
| deployment-continuation.schema.json | Immutable reference admitting one child deployment from a parked input request; never credential values | — |
| security-scan-input.schema.json | Input to the Security Assurance machine: SOURCE (M1) scans an ingested commit, ARTIFACT (M2) scans built outputs | Security Assurance §2 |
| security-verdict.schema.json | Security Assurance decision; BLOCKED surfaces as the non-retryable gate.security-scan category | Security Assurance §2 |
| data-source.schema.json | Admin-registered external data source; credentialRef is a secretref URI and the credential never crosses this boundary | Data-Access MVP |
| data-dependency.schema.json | One external data dependency of an app, declared or detected, classified against the tenant's data-source inventory | Data-Access MVP |
| secret-reference.schema.json | Named secret slot an app depends on; only the reference and its lifecycle cross, never the value | Secret-Manager MVP |
| ai-callsite-inventory.schema.json | Immutable, sanitized AI call-site discovery for one exact deployment version (detection only) | AI governance |
| ai-runtime-contract.schema.json | Immutable runtime contract derived by an AI Governance PLAN decision; scoped configuration plus a Secrets Manager reference | AI governance |
| ai-policy-snapshot.schema.json | Immutable, content-addressed AI policy snapshot consumed by the AI Gateway | AI governance |
| ai-gateway-key-lookup.schema.json | Value-free Gateway authentication metadata for one version-scoped Isomorph key | AI governance |
| ai-trace-event.schema.json | Value-free asynchronous AI Gateway trace metadata (v1); raw prompts and responses never enter the queue | AI governance |
| ai-transform-event.schema.json | Checksum-bound deterministic AI interception task input carrying the ordered PLAN interception plan | AI governance |
| ai-transform-result.schema.json | Bounded deterministic interception result: descriptors and digest evidence only | AI governance |
| ai-verification-evidence.schema.json | Bounded, value-free evidence submitted by deployment verification for one governed version | AI governance |
Runtime resource profiles are a closed contract shared by transformation, artifacts, and
well-defined application manifests. The supported profiles are standard, memory-1g, and
memory-2g; consumers must reject unrecognized raw CPU or memory overrides.
Two schemas carry logic beyond structural types, generated into helpers:
error-category.enum.json→IsRetryable(category)(Go) /isRetryable(category)(TS), handling both concrete categories and family prefixes (storage.unsupported-engine.<engine>). Unknown categories fail closed (non-retryable).version-state.schema.json→CanTransition(environment, from, to)/IsTerminal(state)(Go) andcanTransition/isTerminal(TS), driven by thex-transitionsedge list — so control-plane and any consumer agree on legality from one source.binding-classification-subject.schema.jsonandsanitized-binding-evidence.schema.json→ canonical length-prefixed SHA-256 preimage/digest helpers plus exact-name, requested-mapping and allocation-receipt validators in Go and TypeScript.integration-execute-request.schema.json→IntegrationExecuteInputRules+validateIntegrationExecuteInput(operation, input)(TS) /ValidateIntegrationExecuteInput(Go), derived from each operation'sthen-block (propertyNames.enum,required, per-fieldmaximum/default). The governance execute route, the app SDK and the kit's local check all refuse a request with the same sentence (limit must be an integer between 1 and 15); the SDK vendors the TypeScript file because it ships with no dependencies.
Workflow
make gen # regenerate gen-go/ + gen-ts/ from schema/ (Node only — no Go needed)
make verify # regenerate, then fail if git shows a diff ← the staleness gate (CI runs this)
make build # compile-check the generated Go + TS (needs go + local tsc)
make test # generator determinism + contract-logic tests (Node; Go when installed)Codegen is Node-only so make gen is reproducible anywhere; a Go toolchain and tsc are
used solely to compile-check the output (make build), not to generate it.
Changing a shape
- Edit the schema in
schema/. The category enum and state machine are closed — extend them only by PR to this repo (that PR is where consumers are notified). make genand commitgen-go/+gen-ts/alongside the schema change.- CI (
make verify) fails any PR whose committed generated code does not match its schema. - A shape change is a
(contracts)change: it lands via/review+/code-review ultra→/ship.
Consumers should pin both the three-part publish version and the
schemasDigest/aiFixturesDigest in contract-release.json. Their CI should
validate the canonical fixtures against the pinned generated types or schemas;
it must not silently skip validation when the contracts checkout is absent.
The npm artifact contains schema/, fixtures/ai/, gen-ts/ and the release
manifest, so every TypeScript byte named by the manifest is available from the
same release. Go source remains available through the tagged Go module.
generatedGo and generatedTypeScript seal every .go/.ts file in their
package directories. gen-go/contract_release.go and
gen-ts/contract-release.ts are the only exclusions: those generated files
embed the aggregate digests themselves, so including them would create a
self-reference with no finite digest. The manifest names both exclusions and
their reason; tests reject any different exclusion set and independently
recompute every included file and aggregate.
Platform-neutral workload and secret references are identifiers, not general
URLs. They use scheme://authority/path with a reviewed additive scheme
registry (aws-sm, iam-role, k8s-sa, secretref, spiffe, vault,
vault-v2). A new platform extends that registry in a contracts release.
Userinfo, ports, percent encoding, query strings, fragments and empty path
segments are deliberately unrepresentable, preventing reference fields from
becoming a credential or arbitrary-value transport.
The v2 metering schemas are projections linked to existing v1 policy/trace identities. During migration, consumers dual-read v1 and v2 and may dual-write both. Old records are never rewritten; corrections are append-only adjustment or superseding records. Removal of v1 writes requires separately recorded consumer cutover evidence.
Consuming
Go:
go get github.com/Fourier-Labs-AI/harbour-contracts/[email protected]Two things make this work, and the second is not the obvious one.
The publish tag must be 3-part semver. Go and npm require
MAJOR.MINOR.PATCH, while this repository recordsMAJOR.MINOR.PATCH.MICRO. A publishable release therefore requires MICRO to be zero and maps deterministically: repository0.9.0.0→ package0.9.0→ publish tagv0.9.0. The optional four-part bookkeeping tagv0.9.0.0may point at the same commit but does not publish. CI verifies this mapping, the changelog heading and either tag form.The repo is private, and Go fetches it from a global cache. Set this once per machine:
go env -w GOPRIVATE=github.com/Fourier-Labs-AI/* git config --global credential."https://github.com/Fourier-Labs-AI".helper '!gh auth git-credential' git config --global credential."https://github.com/Fourier-Labs-AI".useHttpPath trueThe credential config has to be global and URL-scoped, and that combination is not incidental:
- It cannot be folder-scoped (
includeIf gitdir:…). Go runsgit ls-remoteinside~/go/pkg/mod/cache/vcs/…, not inside your repo, so a per-directory helper never applies. This fails identically from/tmpand from inside an Isomorph repo, which makes it look like a bad tag rather than an auth problem. - Scoping by URL prefix keeps it off your other GitHub accounts: only
github.com/Fourier-Labs-AI/*uses this credential. - An SSH
insteadOfrewrite is the usual advice and is wrong here unless your SSH key belongs to an account with Fourier org access. Check withssh -T [email protected]before reaching for it.
Without the above,
go getfails withRepository not found— which reads as a missing tag, not as missing access.- It cannot be folder-scoped (
TS:
npm i @fourier-labs/harbour-contracts— published from the tag by CI. The hand-written AI policy resolver (resolver/index.ts) ships in the same package as the@fourier-labs/harbour-contracts/resolversubpath export (package.jsonexports).
Notes
gen.mjs produces both languages deterministically from Node. Go struct field alignment is
not gofmt-normalized by the generator (gofmt is optional hygiene via make fmt); this never
affects compilation, and the staleness gate compares raw generator output, so it stays green.
