@vantageos/convex-doc-forge 0.1.2 → 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 +44 -0
- package/README.md +47 -8
- package/dist/actions.d.ts +4 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +6 -4
- package/dist/mutations.d.ts +29 -2
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +71 -1
- package/dist/queries.d.ts +12 -0
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +26 -2
- package/dist/schema.d.ts +36 -12
- 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,50 @@
|
|
|
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
|
+
|
|
32
|
+
## [0.1.3] - 2026-06-19
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **GAP 1 (data)** `input_schema` changed from `v.any()` to `v.string()` in `schema.ts`, `mutations.ts` (arg + handler type), and `queries.ts` (returns validators for both `list_templates` and `get_template`). JSON Schemas containing `$schema`/`$ref`/`$defs` keys (which start with `$`, reserved by Convex) now round-trip correctly — callers `JSON.stringify()` before upsert, `JSON.parse()` after retrieval. No handler logic change; store-as-is.
|
|
37
|
+
- **GAP 2 (BLOCKER)** `render_now` no longer reads `process.env.CONVEX_DOC_FORGE_RENDERER_URL` / `CONVEX_DOC_FORGE_RENDERER_KEY`. Convex components cannot access the host deployment's env vars (`convex env set` has no `--component` flag). Config is now passed as args: `renderer_url: v.string()` + `renderer_key: v.string()`. The host app reads its own env and threads these values when calling `ctx.runAction(components.docForge.actions.render_now, { ..., renderer_url, renderer_key })`.
|
|
38
|
+
|
|
39
|
+
### Tests
|
|
40
|
+
|
|
41
|
+
- `actions.test.ts`: removed `process.env.*RENDERER*` setup/teardown; all `renderNowHandler` calls now pass `renderer_url`/`renderer_key` as args; guard tests updated accordingly; added `$schema` round-trip test.
|
|
42
|
+
- `mutations.test.ts`: `baseArgs.input_schema` changed to `JSON.stringify(...)` throughout; added `$schema`/`$ref`/`$defs` round-trip test.
|
|
43
|
+
|
|
44
|
+
### Notes
|
|
45
|
+
|
|
46
|
+
- tsc: 0 errors. V8-pure: zero `"use node"` or `node:*` in src/. `peerDependencies.convex` unchanged (`>=1.17.0`).
|
|
47
|
+
- Breaking change for callers of `render_now`: must now pass `renderer_url` + `renderer_key` as args. See README + `scripts/smoke-render.md` for updated call pattern.
|
|
48
|
+
|
|
5
49
|
## [0.1.2] - 2026-06-19
|
|
6
50
|
|
|
7
51
|
### Added
|
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
|
|
@@ -44,7 +65,19 @@ export default app;
|
|
|
44
65
|
|
|
45
66
|
---
|
|
46
67
|
|
|
47
|
-
##
|
|
68
|
+
## Renderer config (passed as args by the host app)
|
|
69
|
+
|
|
70
|
+
`render_now` does **not** read env vars from the Convex dashboard — Convex components cannot access the host deployment's env vars (`convex env set` has no `--component` flag). The host app reads its own env and passes config as args on every call:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const result = await ctx.runAction(components.docForge.actions.render_now, {
|
|
74
|
+
// ...
|
|
75
|
+
renderer_url: process.env.CONVEX_DOC_FORGE_RENDERER_URL!,
|
|
76
|
+
renderer_key: process.env.CONVEX_DOC_FORGE_RENDERER_KEY!,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Set these in the **host** deployment's Convex dashboard (not inside the component):
|
|
48
81
|
|
|
49
82
|
| Variable | Description |
|
|
50
83
|
|---|---|
|
|
@@ -64,7 +97,7 @@ export default app;
|
|
|
64
97
|
| `tenant_id` | string | Clerk org ID |
|
|
65
98
|
| `template_type` | `"doc-binary"` | Always `doc-binary` (V1) |
|
|
66
99
|
| `renderer_kind` | `"docxtpl" \| "python-pptx" \| "jinja2-html"` | Which renderer to use |
|
|
67
|
-
| `input_schema` |
|
|
100
|
+
| `input_schema` | string | JSON Schema serialized as a JSON string (`JSON.stringify(schema)`) — avoids Convex `$`-key rejection |
|
|
68
101
|
| `asset_storage_id` | Id<"_storage"> | Convex file storage reference to the .docx/.pptx asset |
|
|
69
102
|
| `output_formats` | string[] | e.g. `["docx", "pdf"]` |
|
|
70
103
|
| `version` | string | SemVer |
|
|
@@ -115,7 +148,9 @@ docForge.mutations.queue_render({ template_id, context, output_format, createdBy
|
|
|
115
148
|
docForge.actions.render_now({
|
|
116
149
|
template_id, context, output_format,
|
|
117
150
|
actor, // Clerk subject
|
|
118
|
-
source
|
|
151
|
+
source?, // "manual" | "workflow" | "agent" (default "manual")
|
|
152
|
+
renderer_url, // host app reads from its own env (e.g. process.env.CONVEX_DOC_FORGE_RENDERER_URL)
|
|
153
|
+
renderer_key, // host app reads from its own env (e.g. process.env.CONVEX_DOC_FORGE_RENDERER_KEY)
|
|
119
154
|
})
|
|
120
155
|
// → { output_url, render_job_id, duration_ms, pages? }
|
|
121
156
|
```
|
|
@@ -159,7 +194,8 @@ const templateId = await ctx.runMutation(
|
|
|
159
194
|
team: "iris-rh",
|
|
160
195
|
tenant_id: "org_clerk_abc",
|
|
161
196
|
renderer_kind: "docxtpl",
|
|
162
|
-
input_schema
|
|
197
|
+
// input_schema must be a JSON-stringified string — Convex rejects $-prefixed keys (e.g. $schema, $ref)
|
|
198
|
+
input_schema: JSON.stringify({ type: "object", properties: { client_name: { type: "string" } } }),
|
|
163
199
|
asset_storage_id: storageId, // from step 2
|
|
164
200
|
output_formats: ["pdf", "docx"],
|
|
165
201
|
version: "1.0.0",
|
|
@@ -168,12 +204,15 @@ const templateId = await ctx.runMutation(
|
|
|
168
204
|
);
|
|
169
205
|
|
|
170
206
|
// Now render_now works end-to-end:
|
|
207
|
+
// renderer_url + renderer_key are read from the host app's env and passed as args
|
|
171
208
|
const result = await ctx.runAction(components.docForge.actions.render_now, {
|
|
172
209
|
template_id: templateId,
|
|
173
210
|
context: { client_name: "Iris RH" },
|
|
174
211
|
output_format: "pdf",
|
|
175
212
|
actor: "clerk_user_id",
|
|
176
213
|
source: "manual",
|
|
214
|
+
renderer_url: process.env.CONVEX_DOC_FORGE_RENDERER_URL!,
|
|
215
|
+
renderer_key: process.env.CONVEX_DOC_FORGE_RENDERER_KEY!,
|
|
177
216
|
});
|
|
178
217
|
// → { output_url, render_job_id, duration_ms }
|
|
179
218
|
```
|
package/dist/actions.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare function renderNowHandler(ctx: any, args: {
|
|
|
4
4
|
output_format: string;
|
|
5
5
|
actor: string;
|
|
6
6
|
source?: "manual" | "workflow" | "agent";
|
|
7
|
+
renderer_url: string;
|
|
8
|
+
renderer_key: string;
|
|
7
9
|
}): Promise<{
|
|
8
10
|
output_url: string;
|
|
9
11
|
render_job_id: string;
|
|
@@ -16,5 +18,7 @@ export declare const render_now: import("convex/server").RegisteredAction<"publi
|
|
|
16
18
|
context: any;
|
|
17
19
|
output_format: string;
|
|
18
20
|
actor: string;
|
|
21
|
+
renderer_url: string;
|
|
22
|
+
renderer_key: string;
|
|
19
23
|
}, any>;
|
|
20
24
|
//# sourceMappingURL=actions.d.ts.map
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAiCA,wBAAsB,gBAAgB,CAEpC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAiCA,wBAAsB,gBAAgB,CAEpC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,GACA,OAAO,CAAC;IACT,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoID;AAMD,eAAO,MAAM,UAAU;;;;;;;;OAwBrB,CAAC"}
|
package/dist/actions.js
CHANGED
|
@@ -18,12 +18,12 @@ const SIGNED_URL_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
18
18
|
export async function renderNowHandler(
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
20
|
ctx, args) {
|
|
21
|
-
const rendererUrl =
|
|
22
|
-
const rendererKey =
|
|
21
|
+
const rendererUrl = args.renderer_url;
|
|
22
|
+
const rendererKey = args.renderer_key;
|
|
23
23
|
if (!rendererUrl)
|
|
24
|
-
throw new Error("
|
|
24
|
+
throw new Error("renderer_url arg is required");
|
|
25
25
|
if (!rendererKey)
|
|
26
|
-
throw new Error("
|
|
26
|
+
throw new Error("renderer_key arg is required");
|
|
27
27
|
// 1. Load template
|
|
28
28
|
const template = await ctx.runQuery(internal.queries.get_template, {
|
|
29
29
|
template_id: args.template_id,
|
|
@@ -149,6 +149,8 @@ export const render_now = action({
|
|
|
149
149
|
output_format: v.string(),
|
|
150
150
|
actor: v.string(),
|
|
151
151
|
source: v.optional(v.union(v.literal("manual"), v.literal("workflow"), v.literal("agent"))),
|
|
152
|
+
renderer_url: v.string(),
|
|
153
|
+
renderer_key: v.string(),
|
|
152
154
|
},
|
|
153
155
|
returns: v.object({
|
|
154
156
|
output_url: v.string(),
|
package/dist/mutations.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
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;
|
|
5
13
|
team: string;
|
|
6
14
|
tenant_id: string;
|
|
7
15
|
renderer_kind: "docxtpl" | "python-pptx" | "jinja2-html";
|
|
8
|
-
input_schema:
|
|
16
|
+
input_schema: string;
|
|
9
17
|
asset_storage_id: Id<"_storage">;
|
|
10
18
|
output_formats: string[];
|
|
11
19
|
metadata?: Record<string, string>;
|
|
@@ -47,7 +55,7 @@ export declare const upsert_template: import("convex/server").RegisteredMutation
|
|
|
47
55
|
team: string;
|
|
48
56
|
tenant_id: string;
|
|
49
57
|
renderer_kind: "docxtpl" | "python-pptx" | "jinja2-html";
|
|
50
|
-
input_schema:
|
|
58
|
+
input_schema: string;
|
|
51
59
|
asset_storage_id: Id<"_storage">;
|
|
52
60
|
output_formats: string[];
|
|
53
61
|
metadata?: Record<string, 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) {
|
|
@@ -116,7 +152,7 @@ export const upsert_template = mutation({
|
|
|
116
152
|
team: v.string(),
|
|
117
153
|
tenant_id: v.string(),
|
|
118
154
|
renderer_kind: v.union(v.literal("docxtpl"), v.literal("python-pptx"), v.literal("jinja2-html")),
|
|
119
|
-
input_schema: v.
|
|
155
|
+
input_schema: v.string(),
|
|
120
156
|
asset_storage_id: v.id("_storage"),
|
|
121
157
|
output_formats: v.array(v.string()),
|
|
122
158
|
metadata: v.optional(v.record(v.string(), v.string())),
|
|
@@ -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) {
|
|
@@ -63,7 +76,7 @@ export const list_templates = query({
|
|
|
63
76
|
tenant_id: v.string(),
|
|
64
77
|
template_type: v.literal("doc-binary"),
|
|
65
78
|
renderer_kind: v.union(v.literal("docxtpl"), v.literal("python-pptx"), v.literal("jinja2-html")),
|
|
66
|
-
input_schema: v.
|
|
79
|
+
input_schema: v.string(),
|
|
67
80
|
asset_storage_id: v.id("_storage"),
|
|
68
81
|
output_formats: v.array(v.string()),
|
|
69
82
|
metadata: v.optional(v.record(v.string(), v.string())),
|
|
@@ -83,7 +96,7 @@ export const get_template = query({
|
|
|
83
96
|
tenant_id: v.string(),
|
|
84
97
|
template_type: v.literal("doc-binary"),
|
|
85
98
|
renderer_kind: v.union(v.literal("docxtpl"), v.literal("python-pptx"), v.literal("jinja2-html")),
|
|
86
|
-
input_schema: v.
|
|
99
|
+
input_schema: v.string(),
|
|
87
100
|
asset_storage_id: v.id("_storage"),
|
|
88
101
|
output_formats: v.array(v.string()),
|
|
89
102
|
metadata: v.optional(v.record(v.string(), v.string())),
|
|
@@ -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,24 +1,48 @@
|
|
|
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;
|
|
15
40
|
template_type: "doc-binary";
|
|
16
41
|
renderer_kind: "docxtpl" | "python-pptx" | "jinja2-html";
|
|
17
|
-
input_schema:
|
|
42
|
+
input_schema: string;
|
|
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">;
|
|
@@ -26,14 +50,14 @@ declare const _default: import("convex/server").SchemaDefinition<{
|
|
|
26
50
|
tenant_id: import("convex/values").VString<string, "required">;
|
|
27
51
|
template_type: import("convex/values").VLiteral<"doc-binary", "required">;
|
|
28
52
|
renderer_kind: import("convex/values").VUnion<"docxtpl" | "python-pptx" | "jinja2-html", [import("convex/values").VLiteral<"docxtpl", "required">, import("convex/values").VLiteral<"python-pptx", "required">, import("convex/values").VLiteral<"jinja2-html", "required">], "required", never>;
|
|
29
|
-
input_schema: import("convex/values").
|
|
53
|
+
input_schema: import("convex/values").VString<string, "required">;
|
|
30
54
|
asset_storage_id: import("convex/values").VId<import("convex/values").GenericId<"_storage">, "required">;
|
|
31
55
|
output_formats: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
|
|
32
56
|
metadata: import("convex/values").VRecord<Record<string, string> | undefined, import("convex/values").VString<string, "required">, import("convex/values").VString<string, "required">, "optional", string>;
|
|
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.
|
|
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")),
|