@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 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
- - 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
@@ -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
@@ -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) {
@@ -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) {
@@ -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
- * 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;
@@ -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" | "createdAt" | "updatedAt" | `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.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 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.3",
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",