@vantageos/convex-doc-forge 0.1.3 → 0.1.4
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/CHANGELOG.md +27 -0
- package/README.md +25 -4
- package/dist/mutations.d.ts +27 -0
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +70 -0
- package/dist/queries.d.ts +12 -0
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +24 -0
- package/dist/schema.d.ts +34 -10
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +22 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) + [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [0.1.4] - 2026-06-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`doc_forge_tenant_tokens` table** — dynamic tenant token registry (schema.ts). Stores `token_hash` (sha256 hex of the bearer token — NEVER the raw token), `org_id`, `role`, `createdAt`, `revokedAt`. Index: `by_token_hash`. Security invariant: raw bearer token never stored or logged.
|
|
10
|
+
- **`resolve_tenant_token({ token_hash })`** query (queries.ts) — returns `{ org_id, role }` or `null`. Returns `null` for unknown hashes and for revoked tokens (`revokedAt` set). Uses `by_token_hash` index for O(1) lookup.
|
|
11
|
+
- **`issue_tenant_token({ token_hash, org_id, role })`** mutation (mutations.ts) — inserts a token row with `createdAt = now()`. Idempotent: skips insert if an active row with the same hash already exists.
|
|
12
|
+
- **`revoke_tenant_token({ token_hash })`** mutation (mutations.ts) — patches `revokedAt`; row kept for audit. No-op if hash not found (idempotent).
|
|
13
|
+
- All three are exported from `index.ts` via barrel so host apps can wire them as `api.docForge.issueTenantToken` / `api.docForge.revokeTenantToken` / `api.docForge.resolveTenantToken`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **docs: clarify no component-side JSON-Schema validation (Eta advisory #3).** Inline comments on `input_schema` (templates table) and `context` (render_jobs table) in `schema.ts` previously implied runtime validation. Comments now state the truth: the component stores both fields as-is, performs NO ajv / JSON-Schema validation, and delegates conformance checks upstream (MCP-server Zod layer) and to the Python renderer. Option B chosen — correct comments, do not add ajv (V1 scope; gold-plating risk; renderer fails cleanly on non-conforming context).
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
|
|
21
|
+
- `src/__tests__/tokens.test.ts` — 13 tests covering full lifecycle: issue → resolve → revoke → resolve(null), idempotency, empty-field validation, no-op revoke.
|
|
22
|
+
- `mutations.test.ts`: added `validation-boundary contract (Eta advisory #3)` describe block (2 regression tests) documenting that `queue_render` accepts both conforming and non-conforming context without throwing — encoding the delegated-validation contract so the comment cannot silently drift.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- Non-breaking migration: adds a 4th table, no existing table changes.
|
|
27
|
+
- tsc: 0 errors. biome: 0 errors. Test ratio: 45/45 PASS (4 test files).
|
|
28
|
+
- Relates to: VantagePeers task k174ctwng. Layers on / supersedes PR #23's static `MCP_TENANT_TOKENS` env-map (which is implemented as a fallback in the MCP server — see mcp-server CHANGELOG for full resolution chain).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
5
32
|
## [0.1.3] - 2026-06-19
|
|
6
33
|
|
|
7
34
|
### Fixed
|
package/README.md
CHANGED
|
@@ -13,16 +13,37 @@ Consumer (vantage-immo / vCRM / MCP server)
|
|
|
13
13
|
│
|
|
14
14
|
▼ ctx.runAction(components.docForge.actions.render_now, {...})
|
|
15
15
|
@vantageos/convex-doc-forge (this package)
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- doc_forge_tenant_tokens table ← dynamic API token registry (sha256 hashes only)
|
|
17
|
+
- templates table ← template metadata + asset_storage_id
|
|
18
|
+
- render_jobs table ← job status machine (queued → rendering → done|failed)
|
|
19
|
+
- render_audit table ← immutable audit log per render
|
|
20
|
+
- render_now action → HTTP POST /v1/render → Railway renderer sidecar
|
|
20
21
|
│
|
|
21
22
|
▼
|
|
22
23
|
vantage-doc-forge-renderer (Python Railway)
|
|
23
24
|
docxtpl + python-pptx + LibreOffice headless
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
### Token management (v0.1.4+)
|
|
28
|
+
|
|
29
|
+
The `doc_forge_tenant_tokens` table enables dynamic self-service API key issuance. The dashboard team writes tokens via:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// In host app — wrap component mutations as api.docForge.*
|
|
33
|
+
await ctx.runMutation(components.docForge.mutations.issue_tenant_token, {
|
|
34
|
+
token_hash: sha256hex(rawBearerToken), // NEVER store the raw token
|
|
35
|
+
org_id: "org_clerk_xxx",
|
|
36
|
+
role: "admin", // or "readonly"
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Revoke
|
|
40
|
+
await ctx.runMutation(components.docForge.mutations.revoke_tenant_token, {
|
|
41
|
+
token_hash: sha256hex(rawBearerToken),
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The MCP server resolves tokens against this table on every authenticated request (primary). The `MCP_TENANT_TOKENS` env JSON map is a static fallback for dev/bootstrap.
|
|
46
|
+
|
|
26
47
|
---
|
|
27
48
|
|
|
28
49
|
## Install
|
package/dist/mutations.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { Id } from "./_generated/dataModel";
|
|
2
|
+
export declare function issueTenantTokenHandler(ctx: AnyCtx, args: {
|
|
3
|
+
token_hash: string;
|
|
4
|
+
org_id: string;
|
|
5
|
+
role: "admin" | "readonly";
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function revokeTenantTokenHandler(ctx: AnyCtx, args: {
|
|
8
|
+
token_hash: string;
|
|
9
|
+
}): Promise<void>;
|
|
2
10
|
type AnyCtx = any;
|
|
3
11
|
export declare function upsertTemplateHandler(ctx: AnyCtx, args: {
|
|
4
12
|
name: string;
|
|
@@ -82,5 +90,24 @@ export declare const _write_audit: import("convex/server").RegisteredMutation<"i
|
|
|
82
90
|
actor: string;
|
|
83
91
|
source: "manual" | "workflow" | "agent";
|
|
84
92
|
}, Promise<Id<"render_audit">>>;
|
|
93
|
+
/**
|
|
94
|
+
* Issue a new tenant token. The dashboard team calls this via their host
|
|
95
|
+
* app's api.docForge.issueTenantToken wrapper.
|
|
96
|
+
*
|
|
97
|
+
* Security: only `token_hash` (sha256 hex of the raw bearer) is persisted —
|
|
98
|
+
* the raw bearer must NEVER be passed to or stored in Convex.
|
|
99
|
+
*/
|
|
100
|
+
export declare const issue_tenant_token: import("convex/server").RegisteredMutation<"public", {
|
|
101
|
+
token_hash: string;
|
|
102
|
+
org_id: string;
|
|
103
|
+
role: "admin" | "readonly";
|
|
104
|
+
}, Promise<null>>;
|
|
105
|
+
/**
|
|
106
|
+
* Revoke a tenant token by its hash. Sets `revokedAt`; the row is kept for
|
|
107
|
+
* audit purposes. After revocation `resolve_tenant_token` returns null.
|
|
108
|
+
*/
|
|
109
|
+
export declare const revoke_tenant_token: import("convex/server").RegisteredMutation<"public", {
|
|
110
|
+
token_hash: string;
|
|
111
|
+
}, Promise<null>>;
|
|
85
112
|
export {};
|
|
86
113
|
//# sourceMappingURL=mutations.d.ts.map
|
package/dist/mutations.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../src/mutations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../src/mutations.ts"],"names":[],"mappings":"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"}
|
package/dist/mutations.js
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
import { mutation, internalMutation } from "./_generated/server";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
|
+
// issue_tenant_token / revoke_tenant_token — dynamic token lifecycle
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export async function issueTenantTokenHandler(ctx, args) {
|
|
8
|
+
if (!args.token_hash)
|
|
9
|
+
throw new Error("token_hash is required");
|
|
10
|
+
if (!args.org_id)
|
|
11
|
+
throw new Error("org_id is required");
|
|
12
|
+
// Idempotency guard: if an active (non-revoked) token with the same hash
|
|
13
|
+
// already exists for this org, skip the insert to avoid duplicates.
|
|
14
|
+
const existing = await ctx.db
|
|
15
|
+
.query("doc_forge_tenant_tokens")
|
|
16
|
+
.withIndex("by_token_hash", (q) => q.eq("token_hash", args.token_hash))
|
|
17
|
+
.first();
|
|
18
|
+
if (existing !== null && existing.revokedAt === undefined) {
|
|
19
|
+
// Already active — nothing to do (idempotent)
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await ctx.db.insert("doc_forge_tenant_tokens", {
|
|
23
|
+
token_hash: args.token_hash,
|
|
24
|
+
org_id: args.org_id,
|
|
25
|
+
role: args.role,
|
|
26
|
+
createdAt: Date.now(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function revokeTenantTokenHandler(ctx, args) {
|
|
30
|
+
const row = await ctx.db
|
|
31
|
+
.query("doc_forge_tenant_tokens")
|
|
32
|
+
.withIndex("by_token_hash", (q) => q.eq("token_hash", args.token_hash))
|
|
33
|
+
.first();
|
|
34
|
+
if (row === null) {
|
|
35
|
+
// Nothing to revoke — treat as no-op (idempotent)
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
await ctx.db.patch(row._id, { revokedAt: Date.now() });
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
5
41
|
// Handler functions — exported separately for unit testing
|
|
6
42
|
// ---------------------------------------------------------------------------
|
|
7
43
|
export async function upsertTemplateHandler(ctx, args) {
|
|
@@ -172,3 +208,37 @@ export const _write_audit = internalMutation({
|
|
|
172
208
|
returns: v.id("render_audit"),
|
|
173
209
|
handler: writeAuditHandler,
|
|
174
210
|
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Token lifecycle — Convex registrations (public; called by host app wrapper)
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Issue a new tenant token. The dashboard team calls this via their host
|
|
216
|
+
* app's api.docForge.issueTenantToken wrapper.
|
|
217
|
+
*
|
|
218
|
+
* Security: only `token_hash` (sha256 hex of the raw bearer) is persisted —
|
|
219
|
+
* the raw bearer must NEVER be passed to or stored in Convex.
|
|
220
|
+
*/
|
|
221
|
+
export const issue_tenant_token = mutation({
|
|
222
|
+
args: {
|
|
223
|
+
token_hash: v.string(),
|
|
224
|
+
org_id: v.string(),
|
|
225
|
+
role: v.union(v.literal("admin"), v.literal("readonly")),
|
|
226
|
+
},
|
|
227
|
+
returns: v.null(),
|
|
228
|
+
handler: async (ctx, args) => {
|
|
229
|
+
await issueTenantTokenHandler(ctx, args);
|
|
230
|
+
return null;
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* Revoke a tenant token by its hash. Sets `revokedAt`; the row is kept for
|
|
235
|
+
* audit purposes. After revocation `resolve_tenant_token` returns null.
|
|
236
|
+
*/
|
|
237
|
+
export const revoke_tenant_token = mutation({
|
|
238
|
+
args: { token_hash: v.string() },
|
|
239
|
+
returns: v.null(),
|
|
240
|
+
handler: async (ctx, args) => {
|
|
241
|
+
await revokeTenantTokenHandler(ctx, args);
|
|
242
|
+
return null;
|
|
243
|
+
},
|
|
244
|
+
});
|
package/dist/queries.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { Id } from "./_generated/dataModel";
|
|
2
|
+
export declare function resolveTenantTokenHandler(ctx: AnyCtx, args: {
|
|
3
|
+
token_hash: string;
|
|
4
|
+
}): Promise<{
|
|
5
|
+
org_id: string;
|
|
6
|
+
role: "admin" | "readonly";
|
|
7
|
+
} | null>;
|
|
2
8
|
type AnyCtx = any;
|
|
3
9
|
export declare function listTemplatesHandler(ctx: AnyCtx, args: {
|
|
4
10
|
tenant_id: string;
|
|
@@ -26,5 +32,11 @@ export declare const list_render_history: import("convex/server").RegisteredQuer
|
|
|
26
32
|
template_id?: Id<"templates">;
|
|
27
33
|
since?: number;
|
|
28
34
|
}, Promise<any[]>>;
|
|
35
|
+
export declare const resolve_tenant_token: import("convex/server").RegisteredQuery<"public", {
|
|
36
|
+
token_hash: string;
|
|
37
|
+
}, Promise<{
|
|
38
|
+
org_id: string;
|
|
39
|
+
role: "admin" | "readonly";
|
|
40
|
+
} | null>>;
|
|
29
41
|
export {};
|
|
30
42
|
//# sourceMappingURL=queries.d.ts.map
|
package/dist/queries.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAMjD,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,GAAG,IAAI,CAAC,CAUhE;AAID,KAAK,MAAM,GAAG,GAAG,CAAC;AAMlB,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAA;CAAE,kBAsBhE;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,CAAA;CAAE,gBAGvC;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,kBA0B3E;AAMD,eAAO,MAAM,cAAc;eAjEN,MAAM;WAAS,MAAM;WAAS,YAAY;kBA8F7D,CAAC;AAEH,eAAO,MAAM,YAAY;iBAtEF,EAAE,CAAC,WAAW,CAAC;gBAgGpC,CAAC;AAEH,eAAO,MAAM,mBAAmB;eA3FX,MAAM;kBAAgB,EAAE,CAAC,WAAW,CAAC;YAAU,MAAM;kBAsHxE,CAAC;AAMH,eAAO,MAAM,oBAAoB;gBApLX,MAAM;;YACP,MAAM;UAAQ,OAAO,GAAG,UAAU;UA6LrD,CAAC"}
|
package/dist/queries.js
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
import { query } from "./_generated/server";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
|
+
// resolve_tenant_token — dynamic token table primary lookup
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export async function resolveTenantTokenHandler(ctx, args) {
|
|
8
|
+
const row = await ctx.db
|
|
9
|
+
.query("doc_forge_tenant_tokens")
|
|
10
|
+
.withIndex("by_token_hash", (q) => q.eq("token_hash", args.token_hash))
|
|
11
|
+
.first();
|
|
12
|
+
// No row found OR row has been revoked → return null (token rejected)
|
|
13
|
+
if (row === null || row.revokedAt !== undefined)
|
|
14
|
+
return null;
|
|
15
|
+
return { org_id: row.org_id, role: row.role };
|
|
16
|
+
}
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
5
18
|
// Handler functions — exported separately for unit testing
|
|
6
19
|
// ---------------------------------------------------------------------------
|
|
7
20
|
export async function listTemplatesHandler(ctx, args) {
|
|
@@ -115,3 +128,14 @@ export const list_render_history = query({
|
|
|
115
128
|
})),
|
|
116
129
|
handler: listRenderHistoryHandler,
|
|
117
130
|
});
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// resolve_tenant_token — Convex registration
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
export const resolve_tenant_token = query({
|
|
135
|
+
args: { token_hash: v.string() },
|
|
136
|
+
returns: v.union(v.object({
|
|
137
|
+
org_id: v.string(),
|
|
138
|
+
role: v.union(v.literal("admin"), v.literal("readonly")),
|
|
139
|
+
}), v.null()),
|
|
140
|
+
handler: resolveTenantTokenHandler,
|
|
141
|
+
});
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal schema for the @vantageos/convex-doc-forge component.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - templates
|
|
6
|
-
* - render_jobs
|
|
7
|
-
* - render_audit: immutable audit log (who/when/what)
|
|
4
|
+
* Four tables:
|
|
5
|
+
* - templates : template metadata + asset reference
|
|
6
|
+
* - render_jobs : per-render job tracking (status machine)
|
|
7
|
+
* - render_audit : immutable audit log (who/when/what)
|
|
8
|
+
* - doc_forge_tenant_tokens: dynamic tenant token registry (sha256 hashes only,
|
|
9
|
+
* NEVER the raw bearer token). Written by the dashboard
|
|
10
|
+
* team via the host app's api.docForge.* wrapper;
|
|
11
|
+
* read by the MCP server's auth layer.
|
|
8
12
|
*/
|
|
9
13
|
declare const _default: import("convex/server").SchemaDefinition<{
|
|
14
|
+
/**
|
|
15
|
+
* Tenant API token registry.
|
|
16
|
+
* Security invariant: token_hash is a sha256 hex digest of the raw bearer.
|
|
17
|
+
* The raw bearer is NEVER stored here or logged anywhere.
|
|
18
|
+
*/
|
|
19
|
+
doc_forge_tenant_tokens: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
20
|
+
revokedAt?: number | undefined;
|
|
21
|
+
token_hash: string;
|
|
22
|
+
org_id: string;
|
|
23
|
+
role: "admin" | "readonly";
|
|
24
|
+
createdAt: number;
|
|
25
|
+
}, {
|
|
26
|
+
token_hash: import("convex/values").VString<string, "required">;
|
|
27
|
+
org_id: import("convex/values").VString<string, "required">;
|
|
28
|
+
role: import("convex/values").VUnion<"admin" | "readonly", [import("convex/values").VLiteral<"admin", "required">, import("convex/values").VLiteral<"readonly", "required">], "required", never>;
|
|
29
|
+
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
30
|
+
revokedAt: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
31
|
+
}, "required", "token_hash" | "org_id" | "role" | "createdAt" | "revokedAt">, {
|
|
32
|
+
by_token_hash: ["token_hash", "_creationTime"];
|
|
33
|
+
}, {}, {}>;
|
|
10
34
|
templates: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
11
35
|
metadata?: Record<string, string> | undefined;
|
|
36
|
+
createdAt: number;
|
|
12
37
|
name: string;
|
|
13
38
|
team: string;
|
|
14
39
|
tenant_id: string;
|
|
@@ -18,7 +43,6 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
18
43
|
asset_storage_id: import("convex/values").GenericId<"_storage">;
|
|
19
44
|
output_formats: string[];
|
|
20
45
|
version: string;
|
|
21
|
-
createdAt: number;
|
|
22
46
|
updatedAt: number;
|
|
23
47
|
}, {
|
|
24
48
|
name: import("convex/values").VString<string, "required">;
|
|
@@ -33,7 +57,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
33
57
|
version: import("convex/values").VString<string, "required">;
|
|
34
58
|
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
35
59
|
updatedAt: import("convex/values").VFloat64<number, "required">;
|
|
36
|
-
}, "required", "name" | "team" | "tenant_id" | "template_type" | "renderer_kind" | "input_schema" | "asset_storage_id" | "output_formats" | "metadata" | "version" | "
|
|
60
|
+
}, "required", "createdAt" | "name" | "team" | "tenant_id" | "template_type" | "renderer_kind" | "input_schema" | "asset_storage_id" | "output_formats" | "metadata" | "version" | "updatedAt" | `metadata.${string}`>, {
|
|
37
61
|
by_tenant: ["tenant_id", "_creationTime"];
|
|
38
62
|
by_tenant_team: ["tenant_id", "team", "_creationTime"];
|
|
39
63
|
by_tenant_type: ["tenant_id", "template_type", "_creationTime"];
|
|
@@ -42,8 +66,8 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
42
66
|
output_storage_id?: import("convex/values").GenericId<"_storage"> | undefined;
|
|
43
67
|
error?: string | undefined;
|
|
44
68
|
completedAt?: number | undefined;
|
|
45
|
-
tenant_id: string;
|
|
46
69
|
createdAt: number;
|
|
70
|
+
tenant_id: string;
|
|
47
71
|
template_id: import("convex/values").GenericId<"templates">;
|
|
48
72
|
context: any;
|
|
49
73
|
output_format: string;
|
|
@@ -60,14 +84,14 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
60
84
|
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
61
85
|
completedAt: import("convex/values").VFloat64<number | undefined, "optional">;
|
|
62
86
|
createdBy: import("convex/values").VString<string, "required">;
|
|
63
|
-
}, "required", "
|
|
87
|
+
}, "required", "createdAt" | "tenant_id" | "template_id" | "context" | "output_format" | "status" | "output_storage_id" | "error" | "completedAt" | "createdBy" | `context.${string}`>, {
|
|
64
88
|
by_tenant: ["tenant_id", "_creationTime"];
|
|
65
89
|
by_template: ["template_id", "createdAt", "_creationTime"];
|
|
66
90
|
by_tenant_status: ["tenant_id", "status", "_creationTime"];
|
|
67
91
|
}, {}, {}>;
|
|
68
92
|
render_audit: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
69
|
-
tenant_id: string;
|
|
70
93
|
createdAt: number;
|
|
94
|
+
tenant_id: string;
|
|
71
95
|
template_id: import("convex/values").GenericId<"templates">;
|
|
72
96
|
render_job_id: import("convex/values").GenericId<"render_jobs">;
|
|
73
97
|
template_version: string;
|
|
@@ -87,7 +111,7 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
87
111
|
actor: import("convex/values").VString<string, "required">;
|
|
88
112
|
source: import("convex/values").VUnion<"manual" | "workflow" | "agent", [import("convex/values").VLiteral<"manual", "required">, import("convex/values").VLiteral<"workflow", "required">, import("convex/values").VLiteral<"agent", "required">], "required", never>;
|
|
89
113
|
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
90
|
-
}, "required", "
|
|
114
|
+
}, "required", "createdAt" | "tenant_id" | "template_id" | "render_job_id" | "template_version" | "context_hash" | "output_signed_url" | "expires_at" | "actor" | "source">, {
|
|
91
115
|
by_tenant: ["tenant_id", "_creationTime"];
|
|
92
116
|
by_template: ["template_id", "createdAt", "_creationTime"];
|
|
93
117
|
by_job: ["render_job_id", "_creationTime"];
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;;IAEF;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AALJ,wBA4EG"}
|
package/dist/schema.js
CHANGED
|
@@ -3,19 +3,35 @@ import { v } from "convex/values";
|
|
|
3
3
|
/**
|
|
4
4
|
* Internal schema for the @vantageos/convex-doc-forge component.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* - templates
|
|
8
|
-
* - render_jobs
|
|
9
|
-
* - render_audit: immutable audit log (who/when/what)
|
|
6
|
+
* Four tables:
|
|
7
|
+
* - templates : template metadata + asset reference
|
|
8
|
+
* - render_jobs : per-render job tracking (status machine)
|
|
9
|
+
* - render_audit : immutable audit log (who/when/what)
|
|
10
|
+
* - doc_forge_tenant_tokens: dynamic tenant token registry (sha256 hashes only,
|
|
11
|
+
* NEVER the raw bearer token). Written by the dashboard
|
|
12
|
+
* team via the host app's api.docForge.* wrapper;
|
|
13
|
+
* read by the MCP server's auth layer.
|
|
10
14
|
*/
|
|
11
15
|
export default defineSchema({
|
|
16
|
+
/**
|
|
17
|
+
* Tenant API token registry.
|
|
18
|
+
* Security invariant: token_hash is a sha256 hex digest of the raw bearer.
|
|
19
|
+
* The raw bearer is NEVER stored here or logged anywhere.
|
|
20
|
+
*/
|
|
21
|
+
doc_forge_tenant_tokens: defineTable({
|
|
22
|
+
token_hash: v.string(), // sha256 hex of the bearer token — NEVER the raw token
|
|
23
|
+
org_id: v.string(), // canonical tenant id (== Clerk org_id)
|
|
24
|
+
role: v.union(v.literal("admin"), v.literal("readonly")),
|
|
25
|
+
createdAt: v.number(),
|
|
26
|
+
revokedAt: v.optional(v.number()), // set on revoke; presence means the token is dead
|
|
27
|
+
}).index("by_token_hash", ["token_hash"]),
|
|
12
28
|
templates: defineTable({
|
|
13
29
|
name: v.string(),
|
|
14
30
|
team: v.string(),
|
|
15
31
|
tenant_id: v.string(), // Clerk org ID
|
|
16
32
|
template_type: v.literal("doc-binary"),
|
|
17
33
|
renderer_kind: v.union(v.literal("docxtpl"), v.literal("python-pptx"), v.literal("jinja2-html")),
|
|
18
|
-
input_schema: v.string(), // JSON Schema serialized as a JSON string (avoids $-key rejection)
|
|
34
|
+
input_schema: v.string(), // JSON Schema serialized as a JSON string (avoids $-key rejection). The component stores this value as-is — NO runtime JSON-Schema validation is performed here. Conformance of caller-supplied context against this schema is delegated upstream (MCP-server Zod layer) and enforced by the renderer, which rejects malformed context.
|
|
19
35
|
asset_storage_id: v.id("_storage"),
|
|
20
36
|
output_formats: v.array(v.string()),
|
|
21
37
|
metadata: v.optional(v.record(v.string(), v.string())),
|
|
@@ -29,7 +45,7 @@ export default defineSchema({
|
|
|
29
45
|
render_jobs: defineTable({
|
|
30
46
|
template_id: v.id("templates"),
|
|
31
47
|
tenant_id: v.string(),
|
|
32
|
-
context: v.any(), // Caller-supplied JSON
|
|
48
|
+
context: v.any(), // Caller-supplied render context (any JSON value). The component does NOT validate this against template.input_schema at the Convex layer — validation boundary is the upstream MCP-server Zod schema and the Python renderer.
|
|
33
49
|
output_format: v.string(),
|
|
34
50
|
status: v.union(v.literal("queued"), v.literal("rendering"), v.literal("done"), v.literal("failed")),
|
|
35
51
|
output_storage_id: v.optional(v.id("_storage")),
|