Skip to content

Commit d8c761b

Browse files
committed
style: format with oxfmt
1 parent 2a09b29 commit d8c761b

39 files changed

Lines changed: 1788 additions & 690 deletions

‎ARCHITECTURE.md‎

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ opens no pool unless asked. A host calls two functions:
1717
## Where the routes are served
1818

1919
The core registers root-relative paths (`/artifacts*`,
20-
`/instances/:id/mail-attachments`) and takes no base path, so the *mount point*
20+
`/instances/:id/mail-attachments`) and takes no base path, so the _mount point_
2121
is the host's decision. The convention every `@corbits/*-core` package
2222
documents, and every example here demonstrates, is **`/api`** — the same prefix
2323
Interchange serves its own routes under (`app.route("/api/me", …)`,
@@ -47,7 +47,7 @@ context; this package reads them natively and never invents a second principal
4747
resolution path.
4848

4949
Three options have no sensible default — `db`, `contentStore`, `requireGrant` —
50-
and the rest degrade a *feature*, never safety, when omitted. The README's table
50+
and the rest degrade a _feature_, never safety, when omitted. The README's table
5151
of what a minimal host passes is the reference; what matters architecturally is
5252
that optional seams **fail closed**: no `decorate` means no decoration.
5353

@@ -95,16 +95,16 @@ only handing it the row and the scope that made it.
9595
options. Who the request runs as is read from `TenantEnv`, not passed as a
9696
callback.
9797

98-
| Option | What it is for | Default |
99-
| --- | --- | --- |
100-
| `requireGrant` | Host-owned Interchange grant middleware factory. Single-artifact mutations (revise, archive/unarchive, …) run `requireGrant(idResource("artifact", "id"), <action>)`. | none — required |
101-
| `contentStore` | Where an artifact's file bytes live (`ContentStore`). | none — required |
102-
| `decorate` | A **display-only** decorator over serialized rows (provenance labels, host joins). | no-op |
103-
| `onArtifactCreated` | Host hook run inside the same transaction as artifact creation — where a host mints grants for the row it just made. | no-op |
98+
| Option | What it is for | Default |
99+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
100+
| `requireGrant` | Host-owned Interchange grant middleware factory. Single-artifact mutations (revise, archive/unarchive, …) run `requireGrant(idResource("artifact", "id"), <action>)`. | none — required |
101+
| `contentStore` | Where an artifact's file bytes live (`ContentStore`). | none — required |
102+
| `decorate` | A **display-only** decorator over serialized rows (provenance labels, host joins). | no-op |
103+
| `onArtifactCreated` | Host hook run inside the same transaction as artifact creation — where a host mints grants for the row it just made. | no-op |
104104

105105
`decorate`'s display-only status is a contract, not a convention: it may add
106-
fields to rows on their way out and must never affect *what* is returned or
107-
*who* may see it. Joining a host's workflow tables inside this package would
106+
fields to rows on their way out and must never affect _what_ is returned or
107+
_who_ may see it. Joining a host's workflow tables inside this package would
108108
couple it to a schema it must not know, so the host supplies the decorator.
109109
Clients that need an owner display name resolve `ownerPrincipalId` themselves;
110110
this package never ships directory names on the wire.
@@ -151,18 +151,18 @@ which store is installed.
151151

152152
## Modules
153153

154-
| File | Role |
155-
| --- | --- |
156-
| `mount.ts` | HTTP surface: parsing, validation, status codes; reads `TenantEnv` principal; wires host `requireGrant`. |
157-
| `artifacts.ts` | The core domain — create, revise, find-or-version, list, get, archive, serialize. |
158-
| `uploads.ts` | `createFileArtifact`, the MIME policies, and the size caps. |
159-
| `download.ts` | One download path over the three storage conventions. |
160-
| `content-store.ts` | The two shipped `ContentStore` implementations. |
161-
| `tools.ts` | Agent-facing tool definitions and windowed artifact reads (caller tenant only). |
162-
| `web-site.ts` | The `web-site` kind's content encoding and validation. |
163-
| `mail-attachments.ts` | Artifact↔message associations. |
164-
| `ports.ts` | The `ContentStore` type and the shared `ResolvedPrincipal` shape. |
165-
| `schema.ts` / `migrations.ts` | The four tables, and the DDL that creates them. |
154+
| File | Role |
155+
| ----------------------------- | -------------------------------------------------------------------------------------------------------- |
156+
| `mount.ts` | HTTP surface: parsing, validation, status codes; reads `TenantEnv` principal; wires host `requireGrant`. |
157+
| `artifacts.ts` | The core domain — create, revise, find-or-version, list, get, archive, serialize. |
158+
| `uploads.ts` | `createFileArtifact`, the MIME policies, and the size caps. |
159+
| `download.ts` | One download path over the three storage conventions. |
160+
| `content-store.ts` | The two shipped `ContentStore` implementations. |
161+
| `tools.ts` | Agent-facing tool definitions and windowed artifact reads (caller tenant only). |
162+
| `web-site.ts` | The `web-site` kind's content encoding and validation. |
163+
| `mail-attachments.ts` | Artifact↔message associations. |
164+
| `ports.ts` | The `ContentStore` type and the shared `ResolvedPrincipal` shape. |
165+
| `schema.ts` / `migrations.ts` | The four tables, and the DDL that creates them. |
166166

167167
## Data model
168168

@@ -194,7 +194,7 @@ never plants a cross-tenant principal. Operators cleaning legacy rows before the
194194
the migration fails with an explicit message if null `tenant_id` rows remain.
195195

196196
**`kind` is free-form text, not a pg enum,** validated at the application edge.
197-
New kinds cost no migration. What is *not* free-form is the import allowlist:
197+
New kinds cost no migration. What is _not_ free-form is the import allowlist:
198198
`POST /api/artifacts` may only mint `link` or `document`, so an untrusted caller
199199
cannot stamp a file-shaped, downloadable kind onto a row whose content is a URL
200200
or a pasted body.
@@ -226,8 +226,8 @@ every one of those paths — a coworker uploading `report.pdf` twice, or two
226226
agents each linking a file named `notes.md`, are not bugs. A hard
227227
`UNIQUE(tenant_id, title, kind)` constraint would reject those ordinary
228228
inserts outright, not just gate on a one-time backfill of legacy
229-
duplicates. Uniqueness on that triple is a property of the *find-or-version
230-
pattern specifically*, not an invariant of the table, so it does not belong
229+
duplicates. Uniqueness on that triple is a property of the _find-or-version
230+
pattern specifically_, not an invariant of the table, so it does not belong
231231
in the schema — it belongs exactly where it now lives, inside the one code
232232
path that promises it.
233233

@@ -251,18 +251,18 @@ callers for a different tenant, kind, or title never contend with each
251251
other. This guarantee holds only for callers that go through
252252
`findOrVersionArtifact`; a caller that instead calls `createArtifact`
253253
directly is unconstrained by design, as above, and a caller that hand-rolls
254-
its own find-then-create against a *different* lock is not serialized
254+
its own find-then-create against a _different_ lock is not serialized
255255
against this one — the primitive closes the race for its own call path, not
256256
for every possible way to write an artifact.
257257

258258
**`upload` is never a standalone resource.** There is no `POST /uploads`; every
259259
upload eagerly mints its artifact, and the row is reachable only through
260260
`source.upload.id`. `mail_attachment_ref` carries no bytes at all — the file
261-
already *is* an artifact, and the ref only records which artifacts rode with
261+
already _is_ an artifact, and the ref only records which artifacts rode with
262262
which message.
263263

264264
The list index is `(tenant_id, updated_at, id)`. The `id` is the list's
265-
tie-break and must be *in* the index, or the keyset cursor's row-value
265+
tie-break and must be _in_ the index, or the keyset cursor's row-value
266266
comparison falls out of the index condition into a filter and drags a sort
267267
behind it.
268268

@@ -334,7 +334,7 @@ authenticated `tenant`/`principal` on the request context; the host's
334334

335335
- **No file parsing, ever.** No PDF parser, no spreadsheet parser, no text
336336
extractor, and none is planned. An extractor is a heavyweight, fast-moving
337-
native dependency, and what the extracted text is *for* is the host's product.
337+
native dependency, and what the extracted text is _for_ is the host's product.
338338
The contract the package offers a parsing host instead is **parse before you
339339
store**: `createFileArtifact` is the only way a file becomes an artifact, so a
340340
host that parses first and fails leaves nothing orphaned.

‎CHANGELOG.md‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ always called out under their own heading.
1919
`400`. `artifact_create` and `artifact_write` in `ARTIFACT_TOOL_DEFINITIONS`
2020
gain a matching optional `metadata` object parameter, and the sidecar
2121
bundle forwards it to the route unchanged. `source` (`{ origin: "workflow",
22-
runId }`) and `generatedBy` stay server-stamped from the resolved run
22+
runId }`) and `generatedBy` stay server-stamped from the resolved run
2323
scope — never read from `metadata` or any other body field.
2424
- `artifact_version.content_sha256` (text, nullable), added by the new
2525
`0005_version_content_digest` migration and mirrored onto
@@ -35,29 +35,28 @@ always called out under their own heading.
3535
digests existed, and never backfilled.
3636
- `expectedVersion` (positive integer, optional) on
3737
`POST /api/artifacts/:id/versions`. Checked under the same `SELECT ... FOR
38-
UPDATE` that guards the version bump: a mismatch answers `409
39-
{"error":"Version conflict","currentVersion":N}` and writes nothing.
38+
UPDATE` that guards the version bump: a mismatch answers `409
39+
{"error":"Version conflict","currentVersion":N}` and writes nothing.
4040
Omitting it is today's unconditional-write behavior. Lets a caller bind a
4141
revise to the exact version it last read — for example, a human approval on
4242
specific content — instead of silently overwriting a change it never saw.
4343
- `ARTIFACT_UPLOAD_POLICY` accepts packaged archives — `application/gzip` /
4444
`application/x-gzip` (`.tar.gz`, `.tgz`, `.gz`) and `application/x-tar`
45-
(`.tar`) — so consumers storing packaged builds are no longer refused with
46-
415. An archive mints kind `file`, is never inline-previewable (the
45+
(`.tar`) — so consumers storing packaged builds are no longer refused with 415. An archive mints kind `file`, is never inline-previewable (the
4746
download path's inline allow-list is `application/pdf` only), and is still
4847
subject to the existing `MAX_UPLOAD_BYTES` per-file ceiling.
4948
- `GET /api/artifacts/:id/versions/:version` — one version including its
5049
content, reusing `getArtifactVersion`, the same read authorization as
5150
`GET /api/artifacts/:id`, and the same response shape. A malformed or
5251
sub-1 version is `400`; an unknown version collapses into the same `404
53-
Artifact not found` every other single-artifact failure mode does.
52+
Artifact not found` every other single-artifact failure mode does.
5453
- `GET /api/artifacts/:id/download?version=N` — pins the download to that
5554
version's content for the data-URL and downloadable-text conventions,
5655
where content really is per-version; omitting `version` is unchanged. Same
5756
`400`/`404` rules as the new versions route, plus one more: a blob-backed
5857
upload's `ContentStore` reference lives on the artifact row's own
5958
`source`, never per-version, so `?version=N` for one is `400 "Uploaded
60-
file content is not versioned"` unless `N` names the current version —
59+
file content is not versioned"` unless `N` names the current version —
6160
rather than silently answering with today's blob under an older version's
6261
name.
6362
- `artifact_version.metadata` (jsonb, nullable) and

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ declares, no more and no less. Change one, change the other, in the same commit.
6565
## Pull requests
6666

6767
- Keep commits focused, and keep the diff to the change you are describing.
68-
- Explain *why* in the commit message; the code already says what.
68+
- Explain _why_ in the commit message; the code already says what.
6969
- CI must be green: dependency check, typecheck, unit + integration, build, reference-host
7070
acceptance, and a Node consumer smoke test that installs the packed tarball.
7171
- Contributions are accepted under the repository's LGPL-2.1-only licence.

‎README.md‎

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ There are three surfaces a host wires up, in the order a hub typically mounts th
2121

2222
Mounted under the hub's tenant prefix, alongside a host's other session-authenticated routes. It reads `principal`/`tenant` off the Hono context (placed there by the host's own auth + tenant middleware) and authorizes mutations through the host's `requireGrant` — built from Interchange's `createRequireGrant` over the host's own `GrantStore` and `ConditionRegistry`.
2323

24-
| `opts` | Type | What the host provides |
25-
| --- | --- | --- |
26-
| `db` | `ArtifactDb` | Artifacts are stored there. `createArtifactDb` opens a handle for a host with none; a hub that already has one passes it through. |
27-
| `contentStore` | `ContentStore` | Blob storage for file bytes. `InlineContentStore` (exported by this package) fits a minimal host; bring your own store for object storage. |
28-
| `requireGrant` | `RequireGrant` | The host's grant middleware factory. This package implements no ownership or membership policy of its own — every mutating single-artifact route is gated through it. |
29-
| `countSegments` | `ArtifactCountSegments` (optional) | Named predicates over `ArtifactListRow` for `GET /artifacts/counts` (e.g. bucket by `kind`). The taxonomy is entirely host-owned; omitted, the route still answers with the tenant-wide `all` total. |
30-
| `onArtifactCreated`, `decorate`, `uploadPolicy` | — (optional) | See [ARCHITECTURE.md](./ARCHITECTURE.md) for the grant-provisioning hook, the display-only row decorator, and the upload policy. |
24+
| `opts` | Type | What the host provides |
25+
| ----------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26+
| `db` | `ArtifactDb` | Artifacts are stored there. `createArtifactDb` opens a handle for a host with none; a hub that already has one passes it through. |
27+
| `contentStore` | `ContentStore` | Blob storage for file bytes. `InlineContentStore` (exported by this package) fits a minimal host; bring your own store for object storage. |
28+
| `requireGrant` | `RequireGrant` | The host's grant middleware factory. This package implements no ownership or membership policy of its own — every mutating single-artifact route is gated through it. |
29+
| `countSegments` | `ArtifactCountSegments` (optional) | Named predicates over `ArtifactListRow` for `GET /artifacts/counts` (e.g. bucket by `kind`). The taxonomy is entirely host-owned; omitted, the route still answers with the tenant-wide `all` total. |
30+
| `onArtifactCreated`, `decorate`, `uploadPolicy` | — (optional) | See [ARCHITECTURE.md](./ARCHITECTURE.md) for the grant-provisioning hook, the display-only row decorator, and the upload policy. |
3131

3232
```ts
3333
import type { Hono } from "hono";
@@ -57,7 +57,9 @@ export function mountArtifactRoutes(
5757
grantStore: deps.grantStore,
5858
conditionRegistry: deps.conditionRegistry,
5959
}),
60-
...(deps.countSegments !== undefined ? { countSegments: deps.countSegments } : {}),
60+
...(deps.countSegments !== undefined
61+
? { countSegments: deps.countSegments }
62+
: {}),
6163
});
6264
}
6365
```
@@ -66,12 +68,12 @@ export function mountArtifactRoutes(
6668

6769
A parallel mount for a workflow run, which has no browser session — only a bearer token and an `x-workflow-run-address` header. Mount it at its own path (`/api/workflow-artifacts` by convention; see the sidecar bundle below) rather than under the tenant prefix. `resolveRunScope` is the host's existing sidecar-token → run lookup; `agentToken` is a second, optional auth path so a deployed agent can present the bearer the hub minted for its own definition instead of the sidecar's token.
6870

69-
| `opts` | Type | What the host provides |
70-
| --- | --- | --- |
71-
| `db`, `contentStore` | `ArtifactDb`, `ContentStore` | Same as above. |
72-
| `resolveRunScope` | `WorkflowRunResolver` | `(bearerToken, runAddress) => ResolvedWorkflowRunScope \| null`. Returning `null` answers 401 — this package makes no assumption about how a host issues or verifies its sidecar tokens. |
73-
| `agentToken` | `AgentTokenAuth` (optional) | `{ verify(ctx), resolveRun(runAddress) }`. `verify` returns `undefined` for "not an agent token" (the sidecar path is tried instead) and refuses a bearer whose tenant doesn't match the resolved run's. |
74-
| `uploadPolicy`, `maxBinaryBytes`, `maxContentChars` | — (optional) | Per-run ceilings; see [ARCHITECTURE.md](./ARCHITECTURE.md). |
71+
| `opts` | Type | What the host provides |
72+
| --------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73+
| `db`, `contentStore` | `ArtifactDb`, `ContentStore` | Same as above. |
74+
| `resolveRunScope` | `WorkflowRunResolver` | `(bearerToken, runAddress) => ResolvedWorkflowRunScope \| null`. Returning `null` answers 401 — this package makes no assumption about how a host issues or verifies its sidecar tokens. |
75+
| `agentToken` | `AgentTokenAuth` (optional) | `{ verify(ctx), resolveRun(runAddress) }`. `verify` returns `undefined` for "not an agent token" (the sidecar path is tried instead) and refuses a bearer whose tenant doesn't match the resolved run's. |
76+
| `uploadPolicy`, `maxBinaryBytes`, `maxContentChars` | — (optional) | Per-run ceilings; see [ARCHITECTURE.md](./ARCHITECTURE.md). |
7577

7678
```ts
7779
import type { Hono } from "hono";

0 commit comments

Comments
 (0)