You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3085162
Browse filesBrowse the repository at this point in the historyBrowse files
Update docs: two seams (authz + provenance), not three
README and ARCHITECTURE.md described Seam A/B/C (AdminAuthz, Identity,
Provenance) and an ownerName field that no longer exist. Reflects the
current shape: AdminAuthz and Provenance are the only host seams, and
creatorKind needs no seam — it is a denormalized column set at write time.
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+26-15Lines changed: 26 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,8 +48,8 @@ Three options have no sensible default — `db`, `contentStore`,
48
48
`resolvePrincipal` — and the rest degrade a *feature*, never safety, when
49
49
omitted. The README's table of what a minimal host passes is the reference; what
50
50
matters architecturally is that every default **fails closed**: no `adminAuthz`
51
-
means nobody is an admin, no `identity` means no directory and no cross-tenant
52
-
reads, no `provenance` means no decoration.
51
+
means nobody is an admin and no tenant may read another's artifacts, no
52
+
`provenance` means no decoration.
53
53
54
54
What the package does **not** require of a host: no auth middleware, no session
55
55
library, no particular schema, no control-plane tables, no id scheme, no UI.
@@ -61,7 +61,7 @@ route surface.
61
61
62
62
## The ports
63
63
64
-
Five host seams. Four are types declared in `ports.ts`; `resolvePrincipal` is a
64
+
Four host seams. Three are types declared in `ports.ts`; `resolvePrincipal` is a
65
65
`mountArtifacts` option, typed at the mount boundary because it takes the host's
66
66
request context as `unknown`. Every optional seam has a **named, exported**
67
67
default.
@@ -70,18 +70,29 @@ default.
70
70
| --- | --- | --- |
71
71
|`resolvePrincipal`| Who the request runs as. Reads the host session; returns `null` when signed out. | none — required |
72
72
|`ContentStore`| Where an artifact's file bytes live. | none — required |
73
-
|`AdminAuthz` (Seam A) | Authorization. Only archive/unarchive consults it. |`denyAllAdminAuthz`|
74
-
|`Identity` (Seam B) | Owner display names, the agent→human ownership resolution, creator-kind principal sets, and cross-tenant membership. |`anonymousIdentity`|
75
-
|`Provenance` (Seam C) | A **display-only** decorator over serialized rows. |`noProvenance`|
76
-
77
-
Seam C's display-only status is a contract, not a convention: it may add fields
78
-
to rows on their way out and must never affect *what* is returned or *who* may
79
-
see it. That is why provenance is a port at all — joining a host's workflow
80
-
tables to decorate a row would make this package depend on a schema it must not
81
-
know, and the dep-guard fails the build on exactly that.
82
-
83
-
Seam B's `ownerIsMemberOfTenant` gates cross-tenant reads and must fail closed;
84
-
the shipped `anonymousIdentity` does.
73
+
|`AdminAuthz` (Seam A) | Authorization: who may administer (archive/unarchive) a row they do not exactly own, and who may cross a tenant boundary to read. |`denyAllAdminAuthz`|
74
+
|`Provenance` (Seam B) | A **display-only** decorator over serialized rows. |`noProvenance`|
75
+
76
+
TWO seams, not three. There is no directory port: owner display names are
77
+
gone from the serialized row entirely, and `?creatorKind=` is answered by a
78
+
denormalized column (`artifact.creator_kind`) set once, from the caller's own
79
+
scope, at write time — never resolved by a host lookup, so there is nothing
80
+
here for a host to skip and silently return zero rows for.
81
+
82
+
Seam B's display-only status is a contract, not a convention: it may add
83
+
fields to rows on their way out and must never affect *what* is returned or
84
+
*who* may see it. That is why provenance is a port at all — joining a host's
85
+
workflow tables to decorate a row would make this package depend on a schema
86
+
it must not know, and the dep-guard fails the build on exactly that.
87
+
88
+
`AdminAuthz` answers two independent verdicts, both must fail closed:
89
+
90
+
-`canAdminister(scope, row)` — called only after the core has already
91
+
granted the exact-owner match, so a host's implementation covers whatever
92
+
else it wants to allow (a tenant admin, or the human member behind the agent
93
+
that owns the row).
94
+
-`canReadTenant(scope, targetTenantId)` — the gate on a cross-tenant
95
+
`artifact_read`. The shipped `denyAllAdminAuthz` refuses both.
Copy file name to clipboardExpand all lines: packages/artifact-core/README.md
+20-4Lines changed: 20 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -74,19 +74,34 @@ whole `createApp` wiring.
74
74
75
75
## The seams
76
76
77
-
Three options have no sensible default; the rest fail closed and degrade a *feature*,
77
+
TWO seams — authorization and provenance. `?creatorKind=` needs no seam at all: it is a
78
+
denormalized column set once, from the caller's own scope, at write time.
79
+
80
+
Two options have no sensible default; the rest fail closed and degrade a *feature*,
78
81
never safety.
79
82
80
83
| Option | Required | Default and what omitting it costs |
81
84
| --- | --- | --- |
82
85
|`db`|**yes**| The drizzle handle the host already has |
83
86
|`contentStore`|**yes**|`InlineContentStore` for a minimal host |
84
87
|`resolvePrincipal`|**yes**|`(ctx: unknown) => { tenant, principal } \| null`. Identical to `@corbits/mailbox-core`'s, so a host mounting both passes one function to both. The resolved tenant is authoritative — no caller-supplied override. |
85
-
|`adminAuthz`| no |`denyAllAdminAuthz` — only the owner (and the member behind a producing agent) can archive. Nothing becomes more permissive. |
86
-
|`identity`| no |`anonymousIdentity` — `ownerName` is `null`, `?creatorKind=` matches nothing, cross-tenant reads refused. |
87
-
|`provenance`| no |`noProvenance` — rows carry no decoration. Display-only by contract, so it can never change what is returned or who sees it. |
88
+
|`adminAuthz` (Seam A) | no |`denyAllAdminAuthz` — nobody is an admin and no tenant may read another's artifacts. Nothing becomes more permissive. |
89
+
|`provenance` (Seam B) | no |`noProvenance` — rows carry no decoration. Display-only by contract, so it can never change what is returned or who sees it. |
88
90
|`uploadPolicy`| no |`ARTIFACT_UPLOAD_POLICY` — the standard document/image/spreadsheet allowlist. |
89
91
92
+
`AdminAuthz` answers two verdicts, both fail-closed:
93
+
94
+
```ts
95
+
typeAdminAuthz= {
96
+
// Called only after the core has already granted the exact-owner match, so
97
+
// a host implementation covers whatever else it wants to allow — a tenant
98
+
// admin, or the human member behind the agent that owns the row.
0 commit comments