@odla-ai/brand 0.2.1 → 0.4.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.
- package/README.md +123 -5
- package/dist/{index-bj73h3qo.d.cts → index-CRl3IXHB.d.cts} +12 -168
- package/dist/{index-bj73h3qo.d.ts → index-CRl3IXHB.d.ts} +12 -168
- package/dist/index.cjs +2242 -332
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +671 -76
- package/dist/index.d.ts +671 -76
- package/dist/index.js +2243 -333
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.d.cts +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,9 +19,38 @@ npm i @odla-ai/brand
|
|
|
19
19
|
|
|
20
20
|
The package is a **schema + rules + an agent skill + a color engine + a token compiler + a route factory** — not a service you run.
|
|
21
21
|
|
|
22
|
-
- **Proposals are the
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
- **Proposals are the human checkpoint.** Brand direction, image/logo choices,
|
|
23
|
+
typography, voice, and colors stay proposals in the conversation. Palette
|
|
24
|
+
candidates are the executable path today: the agent explores them with
|
|
25
|
+
deterministic OKLCH/WCAG math and writes an open `brand_proposal`. A human
|
|
26
|
+
approves, rejects, or requests a revision before any palette effect. The
|
|
27
|
+
agent skill has no resolution tool: both `propose_palette` and
|
|
28
|
+
`update_section` park open proposals. A current book member who is also a
|
|
29
|
+
direct Clerk app owner/co-owner posts the exact reviewed snapshot to the
|
|
30
|
+
guarded route. It consumes one central `human_exact` `brand.approve` use and
|
|
31
|
+
atomically writes the effect plus an immutable `brand_approval_receipt`.
|
|
32
|
+
The central receipt names the exact Registry app incarnation, so an approval
|
|
33
|
+
from a purged project cannot resolve content in a same-id replacement.
|
|
34
|
+
Review clients must retain one mutation id for each exact proposal digest +
|
|
35
|
+
accepted/rejected decision across pending requests and retries. Proposal
|
|
36
|
+
author names must come from a Registry-owned principal projection; mutable
|
|
37
|
+
chat participants and raw ids are not identity presentation.
|
|
38
|
+
- **Vision happens through a semantic bridge.** `view_asset` asks the trusted
|
|
39
|
+
host for bytes by asset id and returns image/PDF blocks inside the tool
|
|
40
|
+
result when the model supports them. The rules-scoped agent credential
|
|
41
|
+
cannot read raw files, mint signed URLs, or write `brand_*` rows. There is
|
|
42
|
+
no raw or signed-URL fallback. Message attachment ids are revalidated against
|
|
43
|
+
the exact live Brand book before they enter the prompt; every facet proposal
|
|
44
|
+
may preserve the source asset's immutable byte snapshot.
|
|
45
|
+
- **Brand child writes are worker-mediated.** CEL permits members to read their
|
|
46
|
+
sections, palettes, proposals, and approval receipts, but all writes are
|
|
47
|
+
closed. `createBrandRoutes` owns guarded human decisions and proxies
|
|
48
|
+
private multipart uploads, short-lived signed downloads, and human effects.
|
|
49
|
+
Agent tools call only the host-provided `BrandAgentBridge` operations in
|
|
50
|
+
`BRAND_AGENT_PROFILE`.
|
|
51
|
+
- **The upload surface is intentionally narrow.** Raster PNG/JPEG/GIF/WebP and
|
|
52
|
+
PDF inputs are supported. SVG active content and binary font files are not;
|
|
53
|
+
typography remains an exact, reviewable family/style proposal.
|
|
25
54
|
- **Tokens compile to the @odla-ai/ui contract.** `compileBrandTokens` maps swatch roles onto `--ui-*` names — always emitting every required token plus the accent-composing derived set, so a scoped preview island never keeps stale root composites — and `renderTokensCss` emits theme-structured CSS (light, dark, invert). Dark is derived algorithmically and contrast re-tuned per token.
|
|
26
55
|
- **Zero runtime dependencies.** The db client is injected structurally (a real `@odla-ai/db` `AdminDb` satisfies `BrandDb`); `@odla-ai/ai` is a types-only optional peer.
|
|
27
56
|
|
|
@@ -38,7 +67,26 @@ const db = init({ appId, adminToken, endpoint });
|
|
|
38
67
|
|
|
39
68
|
const routes = createBrandRoutes({
|
|
40
69
|
db,
|
|
41
|
-
|
|
70
|
+
appId,
|
|
71
|
+
authorize: verifyDirectCaller,
|
|
72
|
+
// Read-only: exact brand/brand.read/audience/app-incarnation assertion.
|
|
73
|
+
authorizeDiscussionReferences: verifyDiscussionReferenceAssertion,
|
|
74
|
+
// Optional test/edge fetch; it receives only a Brand-minted private URL.
|
|
75
|
+
fetchPrivateAsset: fetch,
|
|
76
|
+
authorizeCapability: verifyCurrentHumanAppOwner,
|
|
77
|
+
consumeHumanExact: async (input) =>
|
|
78
|
+
(await appsForBearer(input.req).consumeHumanExactAuthority(
|
|
79
|
+
input.appId,
|
|
80
|
+
{
|
|
81
|
+
capability: input.capability,
|
|
82
|
+
projectCapability: input.projectCapability,
|
|
83
|
+
effect: input.effect,
|
|
84
|
+
resource: input.resource,
|
|
85
|
+
actionDigest: input.actionDigest,
|
|
86
|
+
consumptionIdempotencyKey: input.consumptionIdempotencyKey,
|
|
87
|
+
},
|
|
88
|
+
)).receipt,
|
|
89
|
+
verifySourceAssetSnapshot: verifyPrivateObjectExactly,
|
|
42
90
|
});
|
|
43
91
|
|
|
44
92
|
export default {
|
|
@@ -48,6 +96,53 @@ export default {
|
|
|
48
96
|
};
|
|
49
97
|
```
|
|
50
98
|
|
|
99
|
+
`GET /api/brand/discussion-references?q=…` searches only books on the
|
|
100
|
+
asserted principal's current roster and their live assets, palettes, proposals,
|
|
101
|
+
and receipts. Supplying `kind` + `id` performs exact lookup and returns no item
|
|
102
|
+
unless that row still exists in the named book. Returned links use stable
|
|
103
|
+
`odla-ref=brand:kind/book-id[/resource-id]` identities; the destination calls
|
|
104
|
+
`parseBrandDiscussionReference` to select the book and review surface. Each
|
|
105
|
+
projection includes a bounded product-authored `summary`, `status`, and
|
|
106
|
+
`destination` for people and read-only agent tools. Registry assertions bind
|
|
107
|
+
the exact `brand` product, `brand.read`, current canonical endpoint audience,
|
|
108
|
+
app incarnation, environment, and principal; an agent assertion also binds its
|
|
109
|
+
manager and live grant revision. The host must set its current
|
|
110
|
+
`ODLA_APP_INCARNATION` and fail closed on a mismatch. This assertion cannot
|
|
111
|
+
authorize proposal resolution or any other Brand mutation.
|
|
112
|
+
|
|
113
|
+
Palette projections additionally carry at most 24 validated semantic roles,
|
|
114
|
+
normalized `#rrggbb` colors, and bounded optional names so Discussion can use
|
|
115
|
+
the shared `@odla-ai/ui` `PaletteStrip` without reconstructing brand state.
|
|
116
|
+
An assertion-authorized exact lookup may request
|
|
117
|
+
`inspect=asset-content&kind=brand:asset&id=…`. Brand resolves the linked row,
|
|
118
|
+
mints its own short-lived private-object URL, and returns only bounded PNG,
|
|
119
|
+
JPEG, GIF, WebP, or PDF bytes—never the URL or a separate object path, storage
|
|
120
|
+
id, app id, or book id. Only the already-discovered canonical reference id
|
|
121
|
+
crosses the boundary. It validates type and magic, size, and SHA-256, then
|
|
122
|
+
reloads both
|
|
123
|
+
membership and the exact linked asset row after I/O. SVG, malformed, spoofed,
|
|
124
|
+
changed, unsupported, and over-budget assets fail closed. Returned bytes carry
|
|
125
|
+
`untrusted_project_material` taint for the hosted inference boundary; the
|
|
126
|
+
native link remains navigation only and human proposal approval is unchanged.
|
|
127
|
+
|
|
128
|
+
Resolve a proposal from a human review UI with one stable mutation id. Send
|
|
129
|
+
back exactly the proposal snapshot that UI rendered; a stale or concurrently
|
|
130
|
+
resolved snapshot answers `409`, while an identical retry returns the original
|
|
131
|
+
receipt with `duplicate: true`.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
await fetch(`/api/brand/books/${book.id}/proposals/${proposal.id}/resolve`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "content-type": "application/json" },
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
mutationId: crypto.randomUUID(),
|
|
139
|
+
resolution: "accepted", // or "rejected"
|
|
140
|
+
reviewedProposal: proposal,
|
|
141
|
+
note: "Approved in palette review",
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
51
146
|
Wire the agent by attaching `brandSkill` to a persona (or let the chat-agent worker route a `brandBotTrigger` dispatch into `dispatchBrandTurn`):
|
|
52
147
|
|
|
53
148
|
```ts
|
|
@@ -57,8 +152,14 @@ const spec = inference.catalog[model];
|
|
|
57
152
|
const persona = createBrandPersona({
|
|
58
153
|
model,
|
|
59
154
|
brand: {
|
|
60
|
-
db
|
|
155
|
+
db: rulesScopedAgentDb,
|
|
156
|
+
bookId,
|
|
61
157
|
self: { selfId: botId, kind: "bot" },
|
|
158
|
+
agentDbBinding: { principalId: botId, credentialRef },
|
|
159
|
+
agentJobId: jobId,
|
|
160
|
+
agentBridge,
|
|
161
|
+
authorizeCapability: verifyLiveAgentGrant,
|
|
162
|
+
resolvePrincipals: resolveSafeDirectoryProjection,
|
|
62
163
|
visionInToolResults: supportsBrandVision(spec),
|
|
63
164
|
},
|
|
64
165
|
});
|
|
@@ -75,3 +176,20 @@ import { compileBrandTokens, renderTokensCss } from "@odla-ai/brand/tokens";
|
|
|
75
176
|
const { light, dark, warnings } = compileBrandTokens({ swatches });
|
|
76
177
|
const css = renderTokensCss(light, { dark }); // :root / [data-theme="dark"] / media guard
|
|
77
178
|
```
|
|
179
|
+
|
|
180
|
+
## Authority and asset contract
|
|
181
|
+
|
|
182
|
+
- Install `BRAND_AGENT_PROFILE`: agents receive only `brand.read` and
|
|
183
|
+
`brand.edit`, and may create proposals, record analysis, and read bounded
|
|
184
|
+
asset content only through semantic host operations. Deny raw Brand writes,
|
|
185
|
+
raw file reads, signing, and admin credentials.
|
|
186
|
+
- `createBrandRoutes` requires all security seams. `authorize` derives the
|
|
187
|
+
actor from a verified bearer; `authorizeCapability` revalidates current app
|
|
188
|
+
ownership; `consumeHumanExact` consumes the exact reviewed action; and
|
|
189
|
+
`verifySourceAssetSnapshot` revalidates private path, ETag, size, media type,
|
|
190
|
+
and SHA-256 immediately before approval. The returned authority receipt must
|
|
191
|
+
carry the current 32-character app incarnation and is stored losslessly with
|
|
192
|
+
the Brand decision.
|
|
193
|
+
- `409` means the review or authority conflicted, `410` means its authority or
|
|
194
|
+
source is gone, and registry/storage/network outages remain opaque `500`s.
|
|
195
|
+
Definite identity or object mismatches fail closed.
|
|
@@ -4,6 +4,7 @@ declare const BRAND_NS: {
|
|
|
4
4
|
readonly section: "brand_section";
|
|
5
5
|
readonly palette: "brand_palette";
|
|
6
6
|
readonly proposal: "brand_proposal";
|
|
7
|
+
readonly approvalReceipt: "brand_approval_receipt";
|
|
7
8
|
readonly asset: "brand_asset";
|
|
8
9
|
};
|
|
9
10
|
/** Brand-book lifecycle: authored → adopted → retired (delete is owner-only). */
|
|
@@ -27,8 +28,8 @@ type PaletteStatus = (typeof PALETTE_STATUSES)[number];
|
|
|
27
28
|
declare const PALETTE_SOURCES: readonly ["extracted", "derived", "manual"];
|
|
28
29
|
/** Provenance of a palette's swatches. */
|
|
29
30
|
type PaletteSource = (typeof PALETTE_SOURCES)[number];
|
|
30
|
-
/**
|
|
31
|
-
declare const PROPOSAL_KINDS: readonly ["palette", "typography", "voice", "logo"];
|
|
31
|
+
/** Every mutable brand facet uses the same proposal/approval contract. */
|
|
32
|
+
declare const PROPOSAL_KINDS: readonly ["palette", "typography", "voice", "logo", "imagery"];
|
|
32
33
|
/** Which brand facet a proposal targets. */
|
|
33
34
|
type ProposalKind = (typeof PROPOSAL_KINDS)[number];
|
|
34
35
|
/** Proposal lifecycle: open until a HUMAN accepts/rejects it (or a newer
|
|
@@ -37,56 +38,13 @@ declare const PROPOSAL_STATUSES: readonly ["open", "accepted", "rejected", "supe
|
|
|
37
38
|
/** Where a proposal stands in the human-gated review flow. */
|
|
38
39
|
type ProposalStatus = (typeof PROPOSAL_STATUSES)[number];
|
|
39
40
|
/** What kind of upload an asset row records. */
|
|
40
|
-
declare const ASSET_KINDS: readonly ["logo", "wordmark", "inspiration", "document", "
|
|
41
|
+
declare const ASSET_KINDS: readonly ["logo", "wordmark", "inspiration", "document", "other"];
|
|
41
42
|
/** The declared purpose of an uploaded asset. */
|
|
42
43
|
type AssetKind = (typeof ASSET_KINDS)[number];
|
|
43
44
|
/** Every swatch role a palette may assign. `chart` may repeat (a series);
|
|
44
45
|
* `custom` is the escape hatch for roles the token compiler doesn't map. */
|
|
45
46
|
declare const SWATCH_ROLES: readonly ["primary", "secondary", "highlight", "bg", "surface", "text", "neutral", "good", "warn", "danger", "chart", "custom"];
|
|
46
47
|
|
|
47
|
-
/** Scalar values odla-db attrs and where-clauses accept. */
|
|
48
|
-
type BrandScalar = string | number | boolean | null;
|
|
49
|
-
/** Natural-key lookup ref for idempotent upserts (odla-db `Lookup` shape). */
|
|
50
|
-
interface BrandLookup {
|
|
51
|
-
ns: string;
|
|
52
|
-
attr: string;
|
|
53
|
-
value: BrandScalar;
|
|
54
|
-
}
|
|
55
|
-
/** How an op names its row: a raw entity id, or a natural-key lookup. */
|
|
56
|
-
type BrandEntityRef = string | BrandLookup;
|
|
57
|
-
/** Attr payload of one op. Typed `any` on purpose: the real odla-db
|
|
58
|
-
* `transact` constrains attrs to its own `Value` type, and `any` is the one
|
|
59
|
-
* shape assignable in both directions with no cast. */
|
|
60
|
-
type BrandAttrs = Record<string, any>;
|
|
61
|
-
/** One transact operation (update = attr-merge upsert; merge = deep-merge
|
|
62
|
-
* into a json attribute; retract = clear the named attributes, since a typed
|
|
63
|
-
* scalar can't store null). */
|
|
64
|
-
type BrandOp = {
|
|
65
|
-
t: "update";
|
|
66
|
-
ns: string;
|
|
67
|
-
id: BrandEntityRef;
|
|
68
|
-
attrs: BrandAttrs;
|
|
69
|
-
} | {
|
|
70
|
-
t: "merge";
|
|
71
|
-
ns: string;
|
|
72
|
-
id: BrandEntityRef;
|
|
73
|
-
attrs: BrandAttrs;
|
|
74
|
-
} | {
|
|
75
|
-
t: "retract";
|
|
76
|
-
ns: string;
|
|
77
|
-
id: BrandEntityRef;
|
|
78
|
-
attrs: string[];
|
|
79
|
-
} | {
|
|
80
|
-
t: "delete";
|
|
81
|
-
ns: string;
|
|
82
|
-
id: BrandEntityRef;
|
|
83
|
-
};
|
|
84
|
-
/** A row as odla-db returns it: hydrated id plus attrs. */
|
|
85
|
-
type BrandRow = {
|
|
86
|
-
id: string;
|
|
87
|
-
} & Record<string, unknown>;
|
|
88
|
-
/** Query result: namespace → rows. */
|
|
89
|
-
type BrandResult = Record<string, BrandRow[]>;
|
|
90
48
|
/** The role a swatch plays in the palette (what the token compiler maps). */
|
|
91
49
|
type SwatchRole = (typeof SWATCH_ROLES)[number];
|
|
92
50
|
/** One palette color: a role, a `#rrggbb` hex, and optional naming/why. */
|
|
@@ -134,132 +92,18 @@ interface AssetAnalysis {
|
|
|
134
92
|
dominantColors: string[];
|
|
135
93
|
tags: string[];
|
|
136
94
|
}
|
|
137
|
-
/** The compiled-token cache stored on `brand_book.tokens`.
|
|
138
|
-
* the compiler's `TokenWarning[]` (typed loosely here so the row types don't
|
|
139
|
-
* depend on the token compiler's module). */
|
|
95
|
+
/** The compiled-token cache stored on `brand_book.tokens`. */
|
|
140
96
|
interface BrandTokensSnapshot {
|
|
141
97
|
light: Record<string, string>;
|
|
142
98
|
dark: Record<string, string>;
|
|
143
99
|
warnings: unknown[];
|
|
144
100
|
compiledAt: number;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
name: string;
|
|
152
|
-
status: BookStatus;
|
|
153
|
-
ownerId: string;
|
|
154
|
-
memberIds: string[];
|
|
155
|
-
channelId?: string;
|
|
156
|
-
activePaletteId?: string;
|
|
157
|
-
tokens?: BrandTokensSnapshot;
|
|
158
|
-
summary?: string;
|
|
159
|
-
createdAt: number;
|
|
160
|
-
updatedAt: number;
|
|
161
|
-
}
|
|
162
|
-
/** A section row — one per (book, kind), upserted by its natural `key`. */
|
|
163
|
-
interface BrandSection {
|
|
164
|
-
id: string;
|
|
165
|
-
key: string;
|
|
166
|
-
bookId: string;
|
|
167
|
-
kind: SectionKind;
|
|
168
|
-
status: SectionStatus;
|
|
169
|
-
content: Record<string, unknown>;
|
|
170
|
-
audience: string[];
|
|
171
|
-
updatedBy: string;
|
|
172
|
-
updatedAt: number;
|
|
173
|
-
}
|
|
174
|
-
/** A palette row. Swatches are json ON the palette, not a namespace: the
|
|
175
|
-
* palette is the atomic proposal/approval unit and swatches are never
|
|
176
|
-
* queried across palettes. */
|
|
177
|
-
interface BrandPalette {
|
|
178
|
-
id: string;
|
|
179
|
-
bookId: string;
|
|
180
|
-
name: string;
|
|
181
|
-
status: PaletteStatus;
|
|
182
|
-
swatches: Swatch[];
|
|
183
|
-
seedHex?: string;
|
|
184
|
-
source: PaletteSource;
|
|
185
|
-
rationale?: string;
|
|
186
|
-
proposalId?: string;
|
|
187
|
-
audience: string[];
|
|
188
|
-
createdAt: number;
|
|
189
|
-
updatedAt: number;
|
|
190
|
-
}
|
|
191
|
-
/** A proposal row: agent output parked for explicit human resolution. */
|
|
192
|
-
interface BrandProposal {
|
|
193
|
-
id: string;
|
|
194
|
-
bookId: string;
|
|
195
|
-
kind: ProposalKind;
|
|
196
|
-
status: ProposalStatus;
|
|
197
|
-
payload: Record<string, unknown>;
|
|
198
|
-
rationale: string;
|
|
199
|
-
sourceAssetId?: string;
|
|
200
|
-
messageId?: string;
|
|
201
|
-
audience: string[];
|
|
202
|
-
createdBy: string;
|
|
203
|
-
createdAt: number;
|
|
204
|
-
resolvedBy?: string;
|
|
205
|
-
resolvedAt?: number;
|
|
206
|
-
resolutionNote?: string;
|
|
207
|
-
}
|
|
208
|
-
/** An asset row mirroring one real uploaded file (worker-mediated; rules
|
|
209
|
-
* close the namespace). Delete is a tombstone (`deletedAt`), never a row
|
|
210
|
-
* delete, so analyses/proposals referencing it stay coherent. */
|
|
211
|
-
interface BrandAsset {
|
|
212
|
-
id: string;
|
|
213
|
-
bookId: string;
|
|
214
|
-
kind: AssetKind;
|
|
215
|
-
path: string;
|
|
216
|
-
url: string;
|
|
217
|
-
contentType: string;
|
|
218
|
-
size: number;
|
|
219
|
-
title?: string;
|
|
220
|
-
analysis?: AssetAnalysis;
|
|
221
|
-
analyzedAt?: number;
|
|
222
|
-
audience: string[];
|
|
223
|
-
uploadedBy: string;
|
|
224
|
-
createdAt: number;
|
|
225
|
-
deletedAt?: number;
|
|
226
|
-
}
|
|
227
|
-
/** What `db.storage.upload` resolves to (odla-db's file record shape). */
|
|
228
|
-
interface BrandFileRecord {
|
|
229
|
-
id: string;
|
|
230
|
-
path: string;
|
|
231
|
-
url: string;
|
|
232
|
-
size: number;
|
|
233
|
-
contentType: string;
|
|
234
|
-
}
|
|
235
|
-
/** Bodies `storage.upload` accepts — a practical subset of the platform's
|
|
236
|
-
* `BodyInit` (which has no ambient type under a DOM-less lib). The real
|
|
237
|
-
* @odla-ai/db client accepts a superset, so it still structurally satisfies
|
|
238
|
-
* {@link BrandStorage}. */
|
|
239
|
-
type BrandUploadBody = string | Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>;
|
|
240
|
-
/** The injected file store — @odla-ai/db's `storage` API satisfies this. */
|
|
241
|
-
interface BrandStorage {
|
|
242
|
-
upload(path: string, data: BrandUploadBody, contentType?: string): Promise<BrandFileRecord>;
|
|
243
|
-
delete(path: string): Promise<void>;
|
|
244
|
-
}
|
|
245
|
-
/** The injected odla client (structural — a real @odla-ai/db `AdminDb`
|
|
246
|
-
* satisfies it). `transact` accepts flat op arrays and `mutationId` gives
|
|
247
|
-
* exactly-once application (`duplicate: true` on replay). */
|
|
248
|
-
interface BrandDb {
|
|
249
|
-
query(q: Record<string, unknown>): Promise<Record<string, unknown[]>>;
|
|
250
|
-
transact(ops: unknown[], opts?: {
|
|
251
|
-
mutationId?: string;
|
|
252
|
-
}): Promise<{
|
|
253
|
-
txId: number;
|
|
254
|
-
duplicate?: boolean;
|
|
255
|
-
}>;
|
|
256
|
-
storage: BrandStorage;
|
|
257
|
-
}
|
|
258
|
-
/** Who is acting: a human member or the book's bot agent. */
|
|
259
|
-
interface BrandActor {
|
|
260
|
-
id: string;
|
|
261
|
-
kind: "human" | "agent";
|
|
262
|
-
email?: string;
|
|
101
|
+
paletteId: string;
|
|
102
|
+
paletteReceiptId: string;
|
|
103
|
+
paletteActionDigest: string;
|
|
104
|
+
typographyReceiptId?: string;
|
|
105
|
+
typographyActionDigest?: string;
|
|
106
|
+
sourceDigest: string;
|
|
263
107
|
}
|
|
264
108
|
|
|
265
109
|
/**
|
|
@@ -406,4 +250,4 @@ declare function renderTokensCss(light: BrandTokens, opts?: RenderTokensCssOptio
|
|
|
406
250
|
*/
|
|
407
251
|
declare function compileBrandTokens(input: CompileInput): CompiledBrandTokens;
|
|
408
252
|
|
|
409
|
-
export {
|
|
253
|
+
export { type AssetKind as A, type BookStatus as B, type CompileInput as C, DARK_FLIP_L_MAX as D, SWATCH_ROLES as E, type SwatchRole as F, type TypographySection as G, compileBrandTokens as H, type ImagerySection as I, deriveDarkTokens as J, mapPaletteToTokens as K, type LogoSection as L, type MapResult as M, renderTokensCss as N, type ProposalKind as P, type RenderTokensCssOptions as R, type Swatch as S, type TokenWarning as T, type VoiceSection as V, type ProposalStatus as a, type AssetAnalysis as b, type BrandTokensSnapshot as c, type PaletteStatus as d, type PaletteSource as e, type SectionKind as f, type SectionStatus as g, ASSET_KINDS as h, BOOK_STATUSES as i, BRAND_CHART_TOKENS as j, BRAND_CHAT_TOKENS as k, BRAND_DERIVED_TOKENS as l, BRAND_EMITTED_TOKENS as m, BRAND_NS as n, BRAND_REQUIRED_TOKENS as o, type BrandTokens as p, type CompiledBrandTokens as q, DARK_FLIP_L_MIN as r, DEFAULT_ACCENT_SEED as s, PALETTE_SOURCES as t, PALETTE_STATUSES as u, PROPOSAL_KINDS as v, PROPOSAL_STATUSES as w, type PaletteSection as x, SECTION_KINDS as y, SECTION_STATUSES as z };
|
|
@@ -4,6 +4,7 @@ declare const BRAND_NS: {
|
|
|
4
4
|
readonly section: "brand_section";
|
|
5
5
|
readonly palette: "brand_palette";
|
|
6
6
|
readonly proposal: "brand_proposal";
|
|
7
|
+
readonly approvalReceipt: "brand_approval_receipt";
|
|
7
8
|
readonly asset: "brand_asset";
|
|
8
9
|
};
|
|
9
10
|
/** Brand-book lifecycle: authored → adopted → retired (delete is owner-only). */
|
|
@@ -27,8 +28,8 @@ type PaletteStatus = (typeof PALETTE_STATUSES)[number];
|
|
|
27
28
|
declare const PALETTE_SOURCES: readonly ["extracted", "derived", "manual"];
|
|
28
29
|
/** Provenance of a palette's swatches. */
|
|
29
30
|
type PaletteSource = (typeof PALETTE_SOURCES)[number];
|
|
30
|
-
/**
|
|
31
|
-
declare const PROPOSAL_KINDS: readonly ["palette", "typography", "voice", "logo"];
|
|
31
|
+
/** Every mutable brand facet uses the same proposal/approval contract. */
|
|
32
|
+
declare const PROPOSAL_KINDS: readonly ["palette", "typography", "voice", "logo", "imagery"];
|
|
32
33
|
/** Which brand facet a proposal targets. */
|
|
33
34
|
type ProposalKind = (typeof PROPOSAL_KINDS)[number];
|
|
34
35
|
/** Proposal lifecycle: open until a HUMAN accepts/rejects it (or a newer
|
|
@@ -37,56 +38,13 @@ declare const PROPOSAL_STATUSES: readonly ["open", "accepted", "rejected", "supe
|
|
|
37
38
|
/** Where a proposal stands in the human-gated review flow. */
|
|
38
39
|
type ProposalStatus = (typeof PROPOSAL_STATUSES)[number];
|
|
39
40
|
/** What kind of upload an asset row records. */
|
|
40
|
-
declare const ASSET_KINDS: readonly ["logo", "wordmark", "inspiration", "document", "
|
|
41
|
+
declare const ASSET_KINDS: readonly ["logo", "wordmark", "inspiration", "document", "other"];
|
|
41
42
|
/** The declared purpose of an uploaded asset. */
|
|
42
43
|
type AssetKind = (typeof ASSET_KINDS)[number];
|
|
43
44
|
/** Every swatch role a palette may assign. `chart` may repeat (a series);
|
|
44
45
|
* `custom` is the escape hatch for roles the token compiler doesn't map. */
|
|
45
46
|
declare const SWATCH_ROLES: readonly ["primary", "secondary", "highlight", "bg", "surface", "text", "neutral", "good", "warn", "danger", "chart", "custom"];
|
|
46
47
|
|
|
47
|
-
/** Scalar values odla-db attrs and where-clauses accept. */
|
|
48
|
-
type BrandScalar = string | number | boolean | null;
|
|
49
|
-
/** Natural-key lookup ref for idempotent upserts (odla-db `Lookup` shape). */
|
|
50
|
-
interface BrandLookup {
|
|
51
|
-
ns: string;
|
|
52
|
-
attr: string;
|
|
53
|
-
value: BrandScalar;
|
|
54
|
-
}
|
|
55
|
-
/** How an op names its row: a raw entity id, or a natural-key lookup. */
|
|
56
|
-
type BrandEntityRef = string | BrandLookup;
|
|
57
|
-
/** Attr payload of one op. Typed `any` on purpose: the real odla-db
|
|
58
|
-
* `transact` constrains attrs to its own `Value` type, and `any` is the one
|
|
59
|
-
* shape assignable in both directions with no cast. */
|
|
60
|
-
type BrandAttrs = Record<string, any>;
|
|
61
|
-
/** One transact operation (update = attr-merge upsert; merge = deep-merge
|
|
62
|
-
* into a json attribute; retract = clear the named attributes, since a typed
|
|
63
|
-
* scalar can't store null). */
|
|
64
|
-
type BrandOp = {
|
|
65
|
-
t: "update";
|
|
66
|
-
ns: string;
|
|
67
|
-
id: BrandEntityRef;
|
|
68
|
-
attrs: BrandAttrs;
|
|
69
|
-
} | {
|
|
70
|
-
t: "merge";
|
|
71
|
-
ns: string;
|
|
72
|
-
id: BrandEntityRef;
|
|
73
|
-
attrs: BrandAttrs;
|
|
74
|
-
} | {
|
|
75
|
-
t: "retract";
|
|
76
|
-
ns: string;
|
|
77
|
-
id: BrandEntityRef;
|
|
78
|
-
attrs: string[];
|
|
79
|
-
} | {
|
|
80
|
-
t: "delete";
|
|
81
|
-
ns: string;
|
|
82
|
-
id: BrandEntityRef;
|
|
83
|
-
};
|
|
84
|
-
/** A row as odla-db returns it: hydrated id plus attrs. */
|
|
85
|
-
type BrandRow = {
|
|
86
|
-
id: string;
|
|
87
|
-
} & Record<string, unknown>;
|
|
88
|
-
/** Query result: namespace → rows. */
|
|
89
|
-
type BrandResult = Record<string, BrandRow[]>;
|
|
90
48
|
/** The role a swatch plays in the palette (what the token compiler maps). */
|
|
91
49
|
type SwatchRole = (typeof SWATCH_ROLES)[number];
|
|
92
50
|
/** One palette color: a role, a `#rrggbb` hex, and optional naming/why. */
|
|
@@ -134,132 +92,18 @@ interface AssetAnalysis {
|
|
|
134
92
|
dominantColors: string[];
|
|
135
93
|
tags: string[];
|
|
136
94
|
}
|
|
137
|
-
/** The compiled-token cache stored on `brand_book.tokens`.
|
|
138
|
-
* the compiler's `TokenWarning[]` (typed loosely here so the row types don't
|
|
139
|
-
* depend on the token compiler's module). */
|
|
95
|
+
/** The compiled-token cache stored on `brand_book.tokens`. */
|
|
140
96
|
interface BrandTokensSnapshot {
|
|
141
97
|
light: Record<string, string>;
|
|
142
98
|
dark: Record<string, string>;
|
|
143
99
|
warnings: unknown[];
|
|
144
100
|
compiledAt: number;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
name: string;
|
|
152
|
-
status: BookStatus;
|
|
153
|
-
ownerId: string;
|
|
154
|
-
memberIds: string[];
|
|
155
|
-
channelId?: string;
|
|
156
|
-
activePaletteId?: string;
|
|
157
|
-
tokens?: BrandTokensSnapshot;
|
|
158
|
-
summary?: string;
|
|
159
|
-
createdAt: number;
|
|
160
|
-
updatedAt: number;
|
|
161
|
-
}
|
|
162
|
-
/** A section row — one per (book, kind), upserted by its natural `key`. */
|
|
163
|
-
interface BrandSection {
|
|
164
|
-
id: string;
|
|
165
|
-
key: string;
|
|
166
|
-
bookId: string;
|
|
167
|
-
kind: SectionKind;
|
|
168
|
-
status: SectionStatus;
|
|
169
|
-
content: Record<string, unknown>;
|
|
170
|
-
audience: string[];
|
|
171
|
-
updatedBy: string;
|
|
172
|
-
updatedAt: number;
|
|
173
|
-
}
|
|
174
|
-
/** A palette row. Swatches are json ON the palette, not a namespace: the
|
|
175
|
-
* palette is the atomic proposal/approval unit and swatches are never
|
|
176
|
-
* queried across palettes. */
|
|
177
|
-
interface BrandPalette {
|
|
178
|
-
id: string;
|
|
179
|
-
bookId: string;
|
|
180
|
-
name: string;
|
|
181
|
-
status: PaletteStatus;
|
|
182
|
-
swatches: Swatch[];
|
|
183
|
-
seedHex?: string;
|
|
184
|
-
source: PaletteSource;
|
|
185
|
-
rationale?: string;
|
|
186
|
-
proposalId?: string;
|
|
187
|
-
audience: string[];
|
|
188
|
-
createdAt: number;
|
|
189
|
-
updatedAt: number;
|
|
190
|
-
}
|
|
191
|
-
/** A proposal row: agent output parked for explicit human resolution. */
|
|
192
|
-
interface BrandProposal {
|
|
193
|
-
id: string;
|
|
194
|
-
bookId: string;
|
|
195
|
-
kind: ProposalKind;
|
|
196
|
-
status: ProposalStatus;
|
|
197
|
-
payload: Record<string, unknown>;
|
|
198
|
-
rationale: string;
|
|
199
|
-
sourceAssetId?: string;
|
|
200
|
-
messageId?: string;
|
|
201
|
-
audience: string[];
|
|
202
|
-
createdBy: string;
|
|
203
|
-
createdAt: number;
|
|
204
|
-
resolvedBy?: string;
|
|
205
|
-
resolvedAt?: number;
|
|
206
|
-
resolutionNote?: string;
|
|
207
|
-
}
|
|
208
|
-
/** An asset row mirroring one real uploaded file (worker-mediated; rules
|
|
209
|
-
* close the namespace). Delete is a tombstone (`deletedAt`), never a row
|
|
210
|
-
* delete, so analyses/proposals referencing it stay coherent. */
|
|
211
|
-
interface BrandAsset {
|
|
212
|
-
id: string;
|
|
213
|
-
bookId: string;
|
|
214
|
-
kind: AssetKind;
|
|
215
|
-
path: string;
|
|
216
|
-
url: string;
|
|
217
|
-
contentType: string;
|
|
218
|
-
size: number;
|
|
219
|
-
title?: string;
|
|
220
|
-
analysis?: AssetAnalysis;
|
|
221
|
-
analyzedAt?: number;
|
|
222
|
-
audience: string[];
|
|
223
|
-
uploadedBy: string;
|
|
224
|
-
createdAt: number;
|
|
225
|
-
deletedAt?: number;
|
|
226
|
-
}
|
|
227
|
-
/** What `db.storage.upload` resolves to (odla-db's file record shape). */
|
|
228
|
-
interface BrandFileRecord {
|
|
229
|
-
id: string;
|
|
230
|
-
path: string;
|
|
231
|
-
url: string;
|
|
232
|
-
size: number;
|
|
233
|
-
contentType: string;
|
|
234
|
-
}
|
|
235
|
-
/** Bodies `storage.upload` accepts — a practical subset of the platform's
|
|
236
|
-
* `BodyInit` (which has no ambient type under a DOM-less lib). The real
|
|
237
|
-
* @odla-ai/db client accepts a superset, so it still structurally satisfies
|
|
238
|
-
* {@link BrandStorage}. */
|
|
239
|
-
type BrandUploadBody = string | Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>;
|
|
240
|
-
/** The injected file store — @odla-ai/db's `storage` API satisfies this. */
|
|
241
|
-
interface BrandStorage {
|
|
242
|
-
upload(path: string, data: BrandUploadBody, contentType?: string): Promise<BrandFileRecord>;
|
|
243
|
-
delete(path: string): Promise<void>;
|
|
244
|
-
}
|
|
245
|
-
/** The injected odla client (structural — a real @odla-ai/db `AdminDb`
|
|
246
|
-
* satisfies it). `transact` accepts flat op arrays and `mutationId` gives
|
|
247
|
-
* exactly-once application (`duplicate: true` on replay). */
|
|
248
|
-
interface BrandDb {
|
|
249
|
-
query(q: Record<string, unknown>): Promise<Record<string, unknown[]>>;
|
|
250
|
-
transact(ops: unknown[], opts?: {
|
|
251
|
-
mutationId?: string;
|
|
252
|
-
}): Promise<{
|
|
253
|
-
txId: number;
|
|
254
|
-
duplicate?: boolean;
|
|
255
|
-
}>;
|
|
256
|
-
storage: BrandStorage;
|
|
257
|
-
}
|
|
258
|
-
/** Who is acting: a human member or the book's bot agent. */
|
|
259
|
-
interface BrandActor {
|
|
260
|
-
id: string;
|
|
261
|
-
kind: "human" | "agent";
|
|
262
|
-
email?: string;
|
|
101
|
+
paletteId: string;
|
|
102
|
+
paletteReceiptId: string;
|
|
103
|
+
paletteActionDigest: string;
|
|
104
|
+
typographyReceiptId?: string;
|
|
105
|
+
typographyActionDigest?: string;
|
|
106
|
+
sourceDigest: string;
|
|
263
107
|
}
|
|
264
108
|
|
|
265
109
|
/**
|
|
@@ -406,4 +250,4 @@ declare function renderTokensCss(light: BrandTokens, opts?: RenderTokensCssOptio
|
|
|
406
250
|
*/
|
|
407
251
|
declare function compileBrandTokens(input: CompileInput): CompiledBrandTokens;
|
|
408
252
|
|
|
409
|
-
export {
|
|
253
|
+
export { type AssetKind as A, type BookStatus as B, type CompileInput as C, DARK_FLIP_L_MAX as D, SWATCH_ROLES as E, type SwatchRole as F, type TypographySection as G, compileBrandTokens as H, type ImagerySection as I, deriveDarkTokens as J, mapPaletteToTokens as K, type LogoSection as L, type MapResult as M, renderTokensCss as N, type ProposalKind as P, type RenderTokensCssOptions as R, type Swatch as S, type TokenWarning as T, type VoiceSection as V, type ProposalStatus as a, type AssetAnalysis as b, type BrandTokensSnapshot as c, type PaletteStatus as d, type PaletteSource as e, type SectionKind as f, type SectionStatus as g, ASSET_KINDS as h, BOOK_STATUSES as i, BRAND_CHART_TOKENS as j, BRAND_CHAT_TOKENS as k, BRAND_DERIVED_TOKENS as l, BRAND_EMITTED_TOKENS as m, BRAND_NS as n, BRAND_REQUIRED_TOKENS as o, type BrandTokens as p, type CompiledBrandTokens as q, DARK_FLIP_L_MIN as r, DEFAULT_ACCENT_SEED as s, PALETTE_SOURCES as t, PALETTE_STATUSES as u, PROPOSAL_KINDS as v, PROPOSAL_STATUSES as w, type PaletteSection as x, SECTION_KINDS as y, SECTION_STATUSES as z };
|