@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 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
- - templates table template metadata + asset_storage_id
17
- - render_jobs table job status machine (queued → rendering → done|failed)
18
- - render_audit table immutable audit log per render
19
- - render_now action → HTTP POST /v1/render → Railway renderer sidecar
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
- ## Required env vars (set in Convex dashboard)
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` | object | JSON Schema for context validation |
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? // "manual" | "workflow" | "agent" (default "manual")
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: { type: "object", properties: { client_name: { type: "string" } } },
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
@@ -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;CAC1C,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;;;;;;OAsBrB,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 = process.env.CONVEX_DOC_FORGE_RENDERER_URL;
22
- const rendererKey = process.env.CONVEX_DOC_FORGE_RENDERER_KEY;
21
+ const rendererUrl = args.renderer_url;
22
+ const rendererKey = args.renderer_key;
23
23
  if (!rendererUrl)
24
- throw new Error("CONVEX_DOC_FORGE_RENDERER_URL not set");
24
+ throw new Error("renderer_url arg is required");
25
25
  if (!rendererKey)
26
- throw new Error("CONVEX_DOC_FORGE_RENDERER_KEY not set");
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(),
@@ -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: unknown;
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: unknown;
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
@@ -1 +1 @@
1
- {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../src/mutations.ts"],"names":[],"mappings":"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"}
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.any(),
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
@@ -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;AAGjD,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"}
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.any(),
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.any(),
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
- * Three 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)
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: any;
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").VAny<any, "required", string>;
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" | "createdAt" | "updatedAt" | `input_schema.${string}` | `metadata.${string}`>, {
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", "tenant_id" | "createdAt" | "template_id" | "context" | "output_format" | "status" | "output_storage_id" | "error" | "completedAt" | "createdBy" | `context.${string}`>, {
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", "tenant_id" | "createdAt" | "template_id" | "render_job_id" | "template_version" | "context_hash" | "output_signed_url" | "expires_at" | "actor" | "source">, {
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"];
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACH,wBA+DG"}
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
- * Three 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)
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.any(), // JSON Schema objectvalidated at runtime
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 matching template.input_schema
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")),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantageos/convex-doc-forge",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Convex component — data layer for VantageDocForge (templates, render jobs, audit)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",