@native-knowledge/cli 0.1.0-rc.1 → 0.1.0-rc.3

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.
Files changed (94) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +29 -0
  3. package/dist/agent-entry-cli.js +90 -0
  4. package/dist/auth-commands.js +102 -0
  5. package/dist/cli.js +106 -4874
  6. package/dist/commands.js +41 -0
  7. package/dist/download.js +45 -0
  8. package/dist/errors.js +7 -0
  9. package/dist/input.js +89 -0
  10. package/dist/internal/contracts/artifact.js +103 -0
  11. package/dist/internal/contracts/collection-control.js +69 -0
  12. package/dist/internal/contracts/collection.js +174 -0
  13. package/dist/internal/contracts/common.js +16 -0
  14. package/dist/internal/contracts/currentness.js +221 -0
  15. package/dist/internal/contracts/event.js +35 -0
  16. package/dist/internal/contracts/evidence.js +171 -0
  17. package/dist/internal/contracts/index.js +55 -0
  18. package/dist/internal/contracts/material-capability.js +8 -0
  19. package/dist/internal/contracts/material-carrier.js +15 -0
  20. package/dist/internal/contracts/material-context.js +9 -0
  21. package/dist/internal/contracts/material-read.js +12 -0
  22. package/dist/internal/contracts/observation.js +39 -0
  23. package/dist/internal/contracts/persistence.js +239 -0
  24. package/dist/internal/contracts/project-intent.js +121 -0
  25. package/dist/internal/contracts/projection.js +194 -0
  26. package/dist/internal/contracts/public-reference.js +87 -0
  27. package/dist/internal/contracts/reference-record.js +55 -0
  28. package/dist/internal/contracts/remote-file.js +159 -0
  29. package/dist/internal/contracts/resource-scope.js +38 -0
  30. package/dist/internal/contracts/resource-transfer.js +11 -0
  31. package/dist/internal/contracts/review-packet.js +61 -0
  32. package/dist/internal/contracts/review-plane.js +89 -0
  33. package/dist/internal/contracts/rich-document.js +184 -0
  34. package/dist/internal/contracts/runtime-compatibility.js +55 -0
  35. package/dist/internal/contracts/runtime.js +68 -0
  36. package/dist/internal/contracts/scheduler.js +256 -0
  37. package/dist/internal/contracts/signal.js +77 -0
  38. package/dist/internal/contracts/source-governance.js +194 -0
  39. package/dist/internal/contracts/source.js +32 -0
  40. package/dist/internal/contracts/tenant-budget.js +34 -0
  41. package/dist/internal/contracts/tenant-collection-request.js +58 -0
  42. package/dist/internal/contracts/tenant-collection.js +66 -0
  43. package/dist/internal/contracts/tenant-currentness.js +26 -0
  44. package/dist/internal/contracts/tenant-management.js +27 -0
  45. package/dist/internal/contracts/tenant-material-api.js +46 -0
  46. package/dist/internal/contracts/tenant-material-json-schema.js +16 -0
  47. package/dist/internal/contracts/tenant-pdf-text.js +13 -0
  48. package/dist/internal/contracts/tenant-project-control.js +19 -0
  49. package/dist/internal/contracts/tenant-public-reference.js +26 -0
  50. package/dist/internal/contracts/tenant-resource-api.js +70 -0
  51. package/dist/internal/contracts/tenant-resource.js +118 -0
  52. package/dist/internal/contracts/tenant-rich-document.js +17 -0
  53. package/dist/internal/contracts/tenant-source-binding.js +33 -0
  54. package/dist/internal/contracts/tenant-source-bridge.js +9 -0
  55. package/dist/internal/contracts/tenant-source-cutover.js +26 -0
  56. package/dist/internal/contracts/tenant-source-document.js +38 -0
  57. package/dist/internal/contracts/tenant-source-lease.js +5 -0
  58. package/dist/internal/contracts/tenant-source.js +83 -0
  59. package/dist/internal/contracts/tenant-web-collection.js +118 -0
  60. package/dist/internal/contracts/tenant-web-content-plan.js +39 -0
  61. package/dist/internal/contracts/tenant-web-content-result.js +15 -0
  62. package/dist/internal/contracts/tenant-web-content.js +16 -0
  63. package/dist/internal/contracts/tenant-web-crawl.js +55 -0
  64. package/dist/internal/contracts/tenant-web-dynamic.js +68 -0
  65. package/dist/internal/contracts/tenant-web-results.js +41 -0
  66. package/dist/internal/contracts/tenant-web-workflow.js +29 -0
  67. package/dist/internal/serving-contracts/agent-catalog.js +56 -0
  68. package/dist/internal/serving-contracts/agent-entry.js +57 -0
  69. package/dist/internal/serving-contracts/agent-guidance-shapes.js +12 -0
  70. package/dist/internal/serving-contracts/agent-input-hints.js +104 -0
  71. package/dist/internal/serving-contracts/body-search.js +16 -0
  72. package/dist/internal/serving-contracts/consumer.js +45 -0
  73. package/dist/internal/serving-contracts/core.js +76 -0
  74. package/dist/internal/serving-contracts/http.js +64 -0
  75. package/dist/internal/serving-contracts/index.js +25 -0
  76. package/dist/internal/serving-contracts/management.js +15 -0
  77. package/dist/internal/serving-contracts/material-capabilities.js +14 -0
  78. package/dist/internal/serving-contracts/material-index-status.js +5 -0
  79. package/dist/internal/serving-contracts/mcp.js +137 -0
  80. package/dist/internal/serving-contracts/tenant-governance.js +13 -0
  81. package/dist/internal/serving-contracts/tenant-management.js +89 -0
  82. package/dist/internal/serving-contracts/tenant-project.js +39 -0
  83. package/dist/internal/serving-contracts/tenant-runtime.js +37 -0
  84. package/dist/internal/serving-contracts/tenant-session.js +8 -0
  85. package/dist/internal/serving-contracts/tenant-web-content.js +20 -0
  86. package/dist/internal/serving-contracts/tenant-web-crawl.js +13 -0
  87. package/dist/internal/serving-contracts/tenant-web-dynamic.js +20 -0
  88. package/dist/internal/serving-contracts/tenant-web-workflow.js +36 -0
  89. package/dist/internal/serving-contracts/tenant-web.js +53 -0
  90. package/dist/internal/serving-contracts/tenant-workspace.js +28 -0
  91. package/dist/internal/serving-contracts/workflow.js +37 -0
  92. package/dist/profile-store.js +142 -0
  93. package/package.json +7 -17
  94. package/README.md +0 -28
@@ -0,0 +1,41 @@
1
+ /** 固定别名映射到原生工具;不存在可回退本地执行的分支。 */
2
+ export const commands = {
3
+ 'agent guide': 'tenant_workflow_guide', 'agent plan': 'tenant_workflow_plan', 'agent advance': 'tenant_workflow_advance', 'agent submit': 'tenant_material_submit', 'agent status': 'tenant_workflow_plan',
4
+ 'source list': 'tenant_source_list', 'source get': 'tenant_source_get', 'source define': 'tenant_source_define', 'source configure': 'tenant_source_configure',
5
+ 'collection request': 'tenant_collection_request', 'collection status': 'tenant_collection_status',
6
+ 'project list': 'tenant_project_list', 'project get': 'tenant_project_get', 'project materials': 'tenant_project_materials', 'project scope-set': 'tenant_project_scope_set', 'project lifecycle-set': 'tenant_project_lifecycle_set',
7
+ 'runtime status': 'tenant_runtime_status', 'work queue': 'tenant_work_queue',
8
+ 'artifact draft': 'tenant_artifact_draft', 'artifact review': 'tenant_knowledge_review', 'artifact promote': 'tenant_artifact_promote', 'knowledge compose': 'tenant_knowledge_compose', 'insight compose': 'tenant_knowledge_compose', 'knowledge read': 'tenant_knowledge_read',
9
+ 'material share': 'tenant_material_share', 'material revoke': 'tenant_material_revoke', 'material routes': 'tenant_material_routes', 'snapshot declare': 'tenant_snapshot_declare', 'snapshot status': 'tenant_snapshot_status',
10
+ };
11
+ for (const op of ['list', 'describe', 'read', 'search', 'counts', 'window', 'changes', 'capabilities', 'media', 'index-status'])
12
+ commands['resource ' + op] = 'knowledge_resource_' + op.replaceAll('-', '_');
13
+ for (const op of ['preview', 'submit', 'status', 'results', 'cycles', 'read', 'pause', 'resume', 'cancel', 'refresh', 'index-status'])
14
+ commands['web-collect ' + op] = 'tenant_web_collect_' + (['pause', 'resume', 'cancel'].includes(op) ? 'control' : op.replaceAll('-', '_'));
15
+ export const localFlags = new Set(['data-root', 'identity-database', 'node-id', 'tenant', 'user', 'agent', 'cursor-secret-env', 'file-roots', 'browser-fallback', 'projection-mode', 'consumer-profile-file']);
16
+ export const retired = new Set(['project-intent put', 'project-intent get', 'source put', 'source preview', 'source add-url', 'source add-file', 'source add-executor', 'source reproject', 'source drift', 'source rebind', 'source due', 'batch create', 'batch create-due', 'batch get', 'batch collector-view', 'batch run', 'batch run-get', 'material page', 'material describe', 'material scope get', 'material scope set', 'material status', 'material publish', 'material location select', 'material put', 'artifact create', 'artifact get', 'artifact list', 'artifact index', 'artifact support', 'reference gap-audit', 'delivery content-factory project', 'delivery content-factory stage', 'delivery content-factory outbox-list', 'delivery content-factory record']);
17
+ export const help = { name: 'native-knowledge', alias: 'knowledge-v2', schemaVersion: 1, transport: 'REMOTE_OAUTH_TENANT', commands: ['auth login|status|whoami|logout', 'profile list|use|remove', 'discover', 'tool list|describe|call', 'resource file', 'agent find', 'agent tool', 'agent call', 'agent task', 'agent input', ...Object.keys(commands)], input: '--input-file <JSON> | --input-json <JSON>; --content-file <file> for submit/draft', connection: '--server <API-root> --profile <name> --token-file <file> --tenant-id <expected-Tenant>', login: 'auth login --server <API-root> --client-id <registered-native-client> [--scope <scope> ...] [--authority-version 1|2] [--no-browser] [--callback-port <port>]', rules: ['Tenant identity comes from the server session. Project is an explicit narrowing.', 'No database, local collection or Host maintenance access.', 'Noninteractive business calls never open a browser. stdout is one JSON result.'] };
18
+ /** Local command names also drive offline help; live schemas remain server-owned. */
19
+ export const localCommands = [
20
+ 'auth login', 'auth status', 'auth whoami', 'auth logout', 'profile list', 'profile use', 'profile remove',
21
+ 'discover', 'tool list', 'tool describe', 'tool call', 'resource file',
22
+ 'agent find', 'agent tool', 'agent call', 'agent task', 'agent input',
23
+ ];
24
+ export const commandNames = [...localCommands, ...Object.keys(commands)];
25
+ export function commandHelp(topic = '') {
26
+ const groups = [...new Set(commandNames.filter(name => name.includes(' ')).map(name => name.split(' ')[0]))];
27
+ if (!topic)
28
+ return { ...help, groups, help: 'native-knowledge help [<group> [<command>]]' };
29
+ const selected = commandNames.filter(name => name === topic || name.startsWith(topic + ' '));
30
+ if (!selected.length)
31
+ return undefined;
32
+ return {
33
+ name: help.name, alias: help.alias, schemaVersion: help.schemaVersion, transport: help.transport,
34
+ topic, commands: selected, input: help.input, connection: help.connection, rules: help.rules,
35
+ ...(topic === 'auth' || topic === 'auth login' ? { login: help.login } : {}),
36
+ ...(commands[topic] && topic !== 'agent status' ? {
37
+ tool: commands[topic], schema: `native-knowledge tool describe --tool ${commands[topic]}`,
38
+ } : {}),
39
+ help: 'Use a command with --help; authenticated tool describe returns the current schema and availability.',
40
+ };
41
+ }
@@ -0,0 +1,45 @@
1
+ import { open, unlink } from 'node:fs/promises';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { Readable, Transform } from 'node:stream';
4
+ import { pipeline } from 'node:stream/promises';
5
+ import { createHash } from 'node:crypto';
6
+ import { AuthError } from '@native-knowledge/knowledge-sdk';
7
+ import { one } from './input.js';
8
+ export async function download(client, f, input) {
9
+ if (typeof input.ref !== 'string')
10
+ throw new AuthError('CLI_INPUT_REQUIRED');
11
+ const output = one(f, 'output-file') ?? one(f, 'output');
12
+ if (!f.has('head') && !output)
13
+ throw new AuthError('CLI_INPUT_REQUIRED');
14
+ const response = await client.resources.stream(input.ref, { ...(f.has('head') ? { head: true } : {}), ...(one(f, 'range') ? { range: one(f, 'range') } : {}), ...(one(f, 'expected-content-hash') ? { expectedContentHash: one(f, 'expected-content-hash') } : {}), ...(typeof input.projectRef === 'string' ? { projectRef: input.projectRef } : {}) });
15
+ const { body, ...metadata } = response;
16
+ if (f.has('head'))
17
+ return metadata;
18
+ if (!body)
19
+ throw new AuthError('CLI_TRANSFER_FAILED');
20
+ let handle;
21
+ try {
22
+ handle = await open(output, 'wx', 0o600);
23
+ }
24
+ catch {
25
+ await body.cancel();
26
+ throw new AuthError('CLI_OUTPUT_EXISTS_OR_UNAVAILABLE');
27
+ }
28
+ const hash = createHash('sha256');
29
+ let bytes = 0;
30
+ try {
31
+ await pipeline(Readable.fromWeb(body), new Transform({ transform(chunk, _encoding, done) { bytes += chunk.length; if (bytes > response.contentLength)
32
+ return done(new AuthError('CLI_TRANSFER_FAILED')); hash.update(chunk); done(null, chunk); } }), createWriteStream(output, { fd: handle.fd, autoClose: false }));
33
+ if (bytes !== response.contentLength || response.status !== 206 && 'sha256:' + hash.digest('hex') !== response.contentHash)
34
+ throw new AuthError('CLI_TRANSFER_INTEGRITY');
35
+ await handle.sync();
36
+ return { ...metadata, bytes, verification: response.status === 206 ? 'RANGE_BYTES_RECEIVED' : 'CLIENT_FULL_HASH_VERIFIED' };
37
+ }
38
+ catch {
39
+ await unlink(output).catch(() => { });
40
+ throw new AuthError('CLI_TRANSFER_FAILED');
41
+ }
42
+ finally {
43
+ await handle.close();
44
+ }
45
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,7 @@
1
+ import { AuthError, TenantManagementSdkError, ResourceSdkError, TenantOperationUnknownError } from '@native-knowledge/knowledge-sdk';
2
+ import { agentEntryFailure } from './agent-entry-cli.js';
3
+ /** A CLI failure never authorizes replay; retained phases remain partial completion. */
4
+ export function cliFailure(e) {
5
+ const status = e instanceof AuthError || e instanceof TenantManagementSdkError || e instanceof ResourceSdkError ? e.status : 0, raw = e instanceof AuthError ? e.code : e instanceof TenantManagementSdkError || e instanceof ResourceSdkError ? e.code : e instanceof Error && e.name === 'ZodError' ? 'CLI_INPUT_INVALID' : e instanceof Error && /^[A-Z][A-Z0-9_]{1,100}$/.test(e.message) ? e.message : 'CLI_OPERATION_FAILED', code = /^[A-Z][A-Z0-9_]{1,100}$/.test(raw) ? raw : 'CLI_OPERATION_FAILED', exit = (e instanceof TenantManagementSdkError || e instanceof TenantOperationUnknownError) && e.completedPhase ? 7 : code.includes('UNKNOWN') || code.includes('INCOMPLETE') ? 7 : status === 403 || code.includes('FORBIDDEN') ? 4 : status === 409 || code.includes('CONFLICT') ? 5 : status >= 500 || code.includes('TRANSPORT') ? 6 : code.includes('AUTH_') ? 3 : 2;
6
+ return { exit, error: { code, status, ...(agentEntryFailure(e) ?? {}), retryable: false, nextActions: code === 'AUTH_LOGOUT_REQUIRED' ? [{ command: 'native-knowledge auth logout', automatic: false }] : code === 'AUTH_CREDENTIAL_SOURCE_CONFLICT' ? [{ command: 'native-knowledge help', automatic: false }] : e instanceof TenantOperationUnknownError && e.requestId ? [{ tool: 'tenant_operation_status', arguments: { requestId: e.requestId }, automatic: false, requiredScopes: ['knowledge.workflow.read'] }] : exit === 3 ? [{ command: 'native-knowledge auth login', automatic: false }] : code === 'CLI_LOCAL_ACCESS_REMOVED' ? [{ command: 'native-knowledge help', automatic: false, documentation: 'docs/operations/tenant-cli-migration.md' }] : [], ...((e instanceof TenantManagementSdkError || e instanceof TenantOperationUnknownError) ? { completedPhase: e.completedPhase, ref: e.ref } : {}), ...(e instanceof TenantOperationUnknownError ? { requestId: e.requestId } : {}) } };
7
+ }
package/dist/input.js ADDED
@@ -0,0 +1,89 @@
1
+ import { open } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { AuthError } from '@native-knowledge/knowledge-sdk';
4
+ import { localFlags, retired } from './commands.js';
5
+ const values = ['server', 'profile', 'config-dir', 'client-id', 'scope', 'authority-version', 'callback-port', 'timeout-seconds', 'token-file', 'tenant-id', 'input-file', 'input-json', 'arguments-file', 'content-file', 'tool', 'name', 'ref', 'query', 'after', 'limit', 'project-key', 'project-ref', 'source-id', 'request-id', 'job-ref', 'expected-epoch', 'expected-version', 'expected-generation', 'action', 'output-file', 'output', 'range', 'expected-content-hash', 'kind', 'index', 'state-index', 'resource-type', 'source-project', 'ref-prefix', 'after-ordinal', 'document-ref', 'category', 'goal'];
6
+ const booleans = ['human', 'json', 'help', 'non-interactive', 'no-browser', 'allow-insecure-http', 'head', 'all', 'guided'];
7
+ export function parse(argv) { const words = [], flags = new Map(); for (let i = 0; i < argv.length; i++) {
8
+ const s = argv[i];
9
+ if (!s.startsWith('--')) {
10
+ words.push(s);
11
+ continue;
12
+ }
13
+ const k = s.slice(2);
14
+ if (retired.has(words.join(' ')) || localFlags.has(k))
15
+ throw new AuthError('CLI_LOCAL_ACCESS_REMOVED');
16
+ if (!values.includes(k) && !booleans.includes(k))
17
+ throw new AuthError('CLI_INPUT_INVALID');
18
+ const v = booleans.includes(k) ? 'true' : argv[++i];
19
+ if (!v || v.startsWith('--') || flags.has(k) && k !== 'scope')
20
+ throw new AuthError('CLI_INPUT_INVALID');
21
+ flags.set(k, [...(flags.get(k) ?? []), v]);
22
+ } return { command: words.join(' '), flags }; }
23
+ export function one(f, k, required = false) { const v = f.get(k)?.[0]; if (required && !v)
24
+ throw new AuthError('CLI_INPUT_REQUIRED'); return v; }
25
+ export function integer(f, k, fallback, min, max) { const raw = one(f, k), n = raw === undefined ? fallback : Number(raw); if (!Number.isSafeInteger(n) || n < min || n > max)
26
+ throw new AuthError('CLI_INPUT_INVALID'); return n; }
27
+ export async function smallFile(file, max) { const h = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW); try {
28
+ const s = await h.stat();
29
+ if (!s.isFile() || s.size > max)
30
+ throw new AuthError('CLI_INPUT_BUDGET');
31
+ const b = Buffer.alloc(max + 1);
32
+ let n = 0;
33
+ while (n < b.length) {
34
+ const p = await h.read(b, n, b.length - n, n);
35
+ if (!p.bytesRead)
36
+ break;
37
+ n += p.bytesRead;
38
+ }
39
+ if (n > max)
40
+ throw new AuthError('CLI_INPUT_BUDGET');
41
+ return b.subarray(0, n);
42
+ }
43
+ finally {
44
+ await h.close();
45
+ } }
46
+ export async function argumentsFor(f, command) {
47
+ const file = one(f, 'input-file') ?? one(f, 'arguments-file'), json = one(f, 'input-json');
48
+ if (file && json || f.has('input-file') && f.has('arguments-file'))
49
+ throw new AuthError('CLI_INPUT_CONFLICT');
50
+ let input = {};
51
+ if (file || json) {
52
+ const text = file ? (await smallFile(file, 131072)).toString('utf8') : json;
53
+ if (Buffer.byteLength(text) > 131072)
54
+ throw new AuthError('CLI_INPUT_BUDGET');
55
+ try {
56
+ input = JSON.parse(text);
57
+ }
58
+ catch {
59
+ throw new AuthError('CLI_INPUT_INVALID');
60
+ }
61
+ if (!input || typeof input !== 'object' || Array.isArray(input))
62
+ throw new AuthError('CLI_INPUT_INVALID');
63
+ }
64
+ for (const k of ['ref', 'query', 'after', 'limit', 'project-key', 'project-ref', 'source-id', 'request-id', 'job-ref', 'expected-epoch', 'expected-version', 'expected-generation', 'action', 'kind', 'index', 'state-index', 'resource-type', 'source-project', 'ref-prefix', 'after-ordinal', 'document-ref']) {
65
+ const v = one(f, k);
66
+ if (v === undefined)
67
+ continue;
68
+ const camel = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
69
+ if (Object.hasOwn(input, camel))
70
+ throw new AuthError('CLI_INPUT_CONFLICT');
71
+ input[camel] = ['limit', 'expected-epoch', 'expected-version', 'expected-generation', 'index', 'state-index', 'after-ordinal'].includes(k) ? integer(f, k, 0, 0, Number.MAX_SAFE_INTEGER) : v;
72
+ }
73
+ if (command.startsWith('web-collect ') && ['pause', 'resume', 'cancel'].includes(command.split(' ')[1])) {
74
+ if (input.action !== undefined)
75
+ throw new AuthError('CLI_INPUT_CONFLICT');
76
+ input.action = command.split(' ')[1].toUpperCase();
77
+ }
78
+ if (command === 'insight compose') {
79
+ if (input.outputKind !== undefined && input.outputKind !== 'INSIGHT')
80
+ throw new AuthError('CLI_INPUT_CONFLICT');
81
+ input.outputKind = 'INSIGHT';
82
+ }
83
+ if (f.has('content-file')) {
84
+ if (!['agent submit', 'artifact draft', 'tool call'].includes(command) || Object.hasOwn(input, 'contentBase64'))
85
+ throw new AuthError('CLI_INPUT_CONFLICT');
86
+ input.contentBase64 = (await smallFile(one(f, 'content-file', true), 65536)).toString('base64');
87
+ }
88
+ return input;
89
+ }
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ import { accessClassificationSchema, isoDateTimeSchema, projectKeySchema, schemaVersionSchema, sha256Schema, stableUriSchema, } from './common.js';
3
+ export const artifactIdSchema = z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
4
+ export const artifactKindSchema = z.enum([
5
+ 'SIGNAL_GENERATION',
6
+ 'RESEARCH',
7
+ 'REVIEW',
8
+ 'KNOWLEDGE',
9
+ 'INSIGHT',
10
+ ]);
11
+ export const artifactStatusSchema = z.enum([
12
+ 'GENERATED',
13
+ 'DRAFT',
14
+ 'READY_FOR_REVIEW',
15
+ 'REWORK_REQUIRED',
16
+ 'APPROVED',
17
+ 'REJECTED',
18
+ 'SUPERSEDED',
19
+ ]);
20
+ export const artifactManifestSchema = z.object({
21
+ schemaVersion: schemaVersionSchema,
22
+ artifactId: artifactIdSchema,
23
+ kind: artifactKindSchema,
24
+ projectKey: projectKeySchema,
25
+ projectRef: stableUriSchema.optional(),
26
+ version: z.number().int().positive(),
27
+ ref: stableUriSchema,
28
+ identityRef: stableUriSchema,
29
+ status: artifactStatusSchema,
30
+ access: accessClassificationSchema.default('INTERNAL'),
31
+ title: z.string().trim().min(1).max(500).optional(),
32
+ createdAt: isoDateTimeSchema,
33
+ observedAt: isoDateTimeSchema.nullable().default(null),
34
+ validUntil: isoDateTimeSchema.nullable().default(null),
35
+ contentHash: sha256Schema,
36
+ mediaType: z.string().trim().min(1).max(256),
37
+ byteLength: z.number().int().nonnegative(),
38
+ evidenceRefs: z.array(stableUriSchema).max(256).default([]),
39
+ relatedRefs: z.array(stableUriSchema).max(256).default([]),
40
+ supersedesRef: stableUriSchema.nullable().default(null),
41
+ }).strict().superRefine((value, ctx) => {
42
+ if (value.projectRef) {
43
+ try {
44
+ const url = new URL(value.projectRef);
45
+ if (url.protocol !== 'knowledge-project:' || url.hostname.toLowerCase() !== value.projectKey.toLowerCase())
46
+ ctx.addIssue({ code: 'custom', path: ['projectRef'], message: 'projectRef project must match Artifact project' });
47
+ }
48
+ catch {
49
+ ctx.addIssue({ code: 'custom', path: ['projectRef'], message: 'Invalid projectRef' });
50
+ }
51
+ }
52
+ if (value.validUntil && value.observedAt && Date.parse(value.validUntil) < Date.parse(value.observedAt)) {
53
+ ctx.addIssue({ code: 'custom', path: ['validUntil'], message: 'validUntil must not precede observedAt' });
54
+ }
55
+ if (value.kind === 'SIGNAL_GENERATION' && (value.version !== 1 || value.status !== 'GENERATED')) {
56
+ ctx.addIssue({ code: 'custom', path: ['version'], message: 'Signal generations are immutable generated artifacts at version 1' });
57
+ }
58
+ if (['KNOWLEDGE', 'INSIGHT'].includes(value.kind) && value.status === 'GENERATED') {
59
+ ctx.addIssue({ code: 'custom', path: ['status'], message: 'Knowledge/Insight cannot use GENERATED status' });
60
+ }
61
+ });
62
+ export const artifactWriteMetadataSchema = z.object({
63
+ schemaVersion: schemaVersionSchema,
64
+ artifactId: artifactIdSchema,
65
+ kind: artifactKindSchema,
66
+ projectKey: projectKeySchema,
67
+ projectRef: stableUriSchema.optional(),
68
+ version: z.number().int().positive(),
69
+ status: artifactStatusSchema,
70
+ access: accessClassificationSchema.default('INTERNAL'),
71
+ title: z.string().trim().min(1).max(500).optional(),
72
+ createdAt: isoDateTimeSchema,
73
+ observedAt: isoDateTimeSchema.nullable().default(null),
74
+ validUntil: isoDateTimeSchema.nullable().default(null),
75
+ mediaType: z.string().trim().min(1).max(256),
76
+ evidenceRefs: z.array(stableUriSchema).max(256).default([]),
77
+ relatedRefs: z.array(stableUriSchema).max(256).default([]),
78
+ supersedesRef: stableUriSchema.nullable().default(null),
79
+ }).strict().superRefine((value, ctx) => {
80
+ if (value.validUntil && value.observedAt && Date.parse(value.validUntil) < Date.parse(value.observedAt)) {
81
+ ctx.addIssue({ code: 'custom', path: ['validUntil'], message: 'validUntil must not precede observedAt' });
82
+ }
83
+ if (value.kind === 'SIGNAL_GENERATION') {
84
+ ctx.addIssue({ code: 'custom', path: ['kind'], message: 'Signal generations are produced only by the signal composer' });
85
+ }
86
+ if (value.kind === 'REVIEW') {
87
+ ctx.addIssue({ code: 'custom', path: ['kind'], message: 'Review artifacts are produced only by the review lifecycle' });
88
+ }
89
+ if (['KNOWLEDGE', 'INSIGHT'].includes(value.kind) && value.status !== 'DRAFT') {
90
+ ctx.addIssue({ code: 'custom', path: ['status'], message: 'Knowledge/Insight manual writes must enter as DRAFT' });
91
+ }
92
+ if (value.kind === 'RESEARCH' && value.status !== 'DRAFT') {
93
+ ctx.addIssue({ code: 'custom', path: ['status'], message: 'Research manual writes must enter as DRAFT' });
94
+ }
95
+ });
96
+ export const reviewDecisionSchema = z.object({
97
+ schemaVersion: schemaVersionSchema,
98
+ targetRef: stableUriSchema,
99
+ decision: z.enum(['PASS', 'REWORK', 'REJECT']),
100
+ reviewedAt: isoDateTimeSchema,
101
+ summary: z.string().trim().min(1).max(16000),
102
+ evidenceRefs: z.array(stableUriSchema).max(256).default([]),
103
+ }).strict();
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import { idSchema, isoDateTimeSchema, projectKeySchema, schemaVersionSchema, stableUriSchema } from './common.js';
3
+ export const knowledgeControlScopeSchema = z.enum([
4
+ 'knowledge.read',
5
+ 'knowledge.collect',
6
+ 'knowledge.source.manage',
7
+ 'knowledge.review',
8
+ 'knowledge.publish',
9
+ ]);
10
+ export const collectionControlRequestIdSchema = idSchema.regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/);
11
+ export const collectionControlCreateRequestSchema = z.object({
12
+ projectKey: projectKeySchema,
13
+ requestId: collectionControlRequestIdSchema,
14
+ sourceIds: z.array(idSchema).min(1).max(1_000),
15
+ }).strict().transform((value) => ({
16
+ ...value,
17
+ sourceIds: [...new Set(value.sourceIds)].sort(),
18
+ }));
19
+ export const collectionControlAuditSchema = z.object({
20
+ schemaVersion: schemaVersionSchema,
21
+ projectKey: projectKeySchema,
22
+ requestId: collectionControlRequestIdSchema,
23
+ batchId: idSchema,
24
+ batchRef: stableUriSchema,
25
+ tenantId: idSchema,
26
+ clientId: idSchema,
27
+ requestedAt: isoDateTimeSchema,
28
+ sourceIds: z.array(idSchema).min(1).max(1_000),
29
+ sourceRevisionRefs: z.array(stableUriSchema).min(1).max(1_000),
30
+ admission: z.literal('ACCEPTED'),
31
+ }).strict().superRefine((value, ctx) => {
32
+ if (value.sourceIds.length !== value.sourceRevisionRefs.length)
33
+ ctx.addIssue({
34
+ code: 'custom',
35
+ path: ['sourceRevisionRefs'],
36
+ message: 'sourceRevisionRefs must align with sourceIds',
37
+ });
38
+ });
39
+ export const collectionControlStatusSchema = z.enum(['PENDING', 'SUCCEEDED', 'PARTIAL', 'FAILED']);
40
+ export const collectionControlRunSummarySchema = z.object({
41
+ runId: idSchema,
42
+ runtimeNodeId: idSchema,
43
+ startedAt: isoDateTimeSchema,
44
+ finishedAt: isoDateTimeSchema,
45
+ status: z.enum(['SUCCEEDED', 'PARTIAL', 'FAILED']),
46
+ }).strict();
47
+ export const collectionControlRequestProjectionSchema = z.object({
48
+ schemaVersion: schemaVersionSchema,
49
+ projectKey: projectKeySchema,
50
+ requestId: collectionControlRequestIdSchema,
51
+ batchId: idSchema,
52
+ batchRef: stableUriSchema,
53
+ trigger: z.literal('MANUAL'),
54
+ status: collectionControlStatusSchema,
55
+ requestedAt: isoDateTimeSchema,
56
+ requestedBy: z.object({ tenantId: idSchema, clientId: idSchema }).strict(),
57
+ sourceIds: z.array(idSchema).min(1).max(1_000),
58
+ sourceRevisionRefs: z.array(stableUriSchema).min(1).max(1_000),
59
+ run: collectionControlRunSummarySchema.nullable(),
60
+ }).strict();
61
+ export const collectionControlCreateResponseSchema = z.object({
62
+ schemaVersion: schemaVersionSchema,
63
+ inserted: z.boolean(),
64
+ request: collectionControlRequestProjectionSchema,
65
+ }).strict();
66
+ export const collectionControlGetResponseSchema = z.object({
67
+ schemaVersion: schemaVersionSchema,
68
+ request: collectionControlRequestProjectionSchema,
69
+ }).strict();
@@ -0,0 +1,174 @@
1
+ import { z } from 'zod';
2
+ import { idSchema, isoDateTimeSchema, projectKeySchema, schemaVersionSchema, sha256Schema, stableUriSchema, } from './common.js';
3
+ import { acquisitionTargetSchema, collectionRefreshSchema } from './project-intent.js';
4
+ export const collectionBatchSourceBindingSchema = z.object({
5
+ sourceId: idSchema,
6
+ sourceRef: stableUriSchema,
7
+ sourceRevisionRef: stableUriSchema,
8
+ targetId: sha256Schema,
9
+ }).strict();
10
+ /** The collector-facing task deliberately contains no project/source-family semantics. */
11
+ export const collectionTaskSchema = z.object({
12
+ taskId: idSchema,
13
+ targetId: sha256Schema,
14
+ target: acquisitionTargetSchema,
15
+ }).strict();
16
+ export const collectionBatchTriggerSchema = z.enum(['MANUAL', 'DUE']);
17
+ export const collectionBatchSchema = z.object({
18
+ schemaVersion: schemaVersionSchema,
19
+ projectKey: projectKeySchema,
20
+ projectRef: stableUriSchema,
21
+ projectIntentRef: stableUriSchema,
22
+ requestId: idSchema,
23
+ batchId: idSchema,
24
+ batchRef: stableUriSchema,
25
+ trigger: collectionBatchTriggerSchema.default('MANUAL'),
26
+ createdAt: isoDateTimeSchema,
27
+ sourceBindings: z.array(collectionBatchSourceBindingSchema).min(1).max(10_000),
28
+ tasks: z.array(collectionTaskSchema).min(1).max(10_000),
29
+ }).strict().superRefine((value, ctx) => {
30
+ const sourceIds = new Set(), bindingTargets = new Set(), taskIds = new Set(), taskTargets = new Set();
31
+ for (let i = 0; i < value.sourceBindings.length; i += 1) {
32
+ const row = value.sourceBindings[i];
33
+ if (sourceIds.has(row.sourceId))
34
+ ctx.addIssue({ code: 'custom', path: ['sourceBindings', i, 'sourceId'], message: 'sourceId must be unique in a batch' });
35
+ if (bindingTargets.has(row.targetId))
36
+ ctx.addIssue({ code: 'custom', path: ['sourceBindings', i, 'targetId'], message: 'targetId must be unique in project source bindings' });
37
+ sourceIds.add(row.sourceId);
38
+ bindingTargets.add(row.targetId);
39
+ }
40
+ for (let i = 0; i < value.tasks.length; i += 1) {
41
+ const row = value.tasks[i];
42
+ if (taskIds.has(row.taskId))
43
+ ctx.addIssue({ code: 'custom', path: ['tasks', i, 'taskId'], message: 'taskId must be unique in a batch' });
44
+ if (taskTargets.has(row.targetId))
45
+ ctx.addIssue({ code: 'custom', path: ['tasks', i, 'targetId'], message: 'targetId must be unique in a batch' });
46
+ taskIds.add(row.taskId);
47
+ taskTargets.add(row.targetId);
48
+ }
49
+ for (let i = 0; i < value.tasks.length; i += 1)
50
+ if (!bindingTargets.has(value.tasks[i].targetId))
51
+ ctx.addIssue({ code: 'custom', path: ['tasks', i, 'targetId'], message: 'task target must have a source binding' });
52
+ for (let i = 0; i < value.sourceBindings.length; i += 1)
53
+ if (!taskTargets.has(value.sourceBindings[i].targetId))
54
+ ctx.addIssue({ code: 'custom', path: ['sourceBindings', i, 'targetId'], message: 'source binding must have a collector task' });
55
+ });
56
+ export const collectorBatchViewSchema = z.object({
57
+ schemaVersion: schemaVersionSchema,
58
+ batchId: idSchema,
59
+ tasks: z.array(collectionTaskSchema).min(1).max(10_000),
60
+ }).strict();
61
+ export const sourceCollectionDueReasonSchema = z.enum([
62
+ 'DISABLED',
63
+ 'MANUAL',
64
+ 'PENDING_BATCH',
65
+ 'SPECIALIZED_EXECUTOR',
66
+ 'SPECIALIZED_COLLECTOR',
67
+ 'NEVER_ATTEMPTED',
68
+ 'INTERVAL_WAIT',
69
+ 'INTERVAL_ELAPSED',
70
+ ]);
71
+ export const sourceCollectionDueEntrySchema = z.object({
72
+ sourceId: idSchema,
73
+ sourceRef: stableUriSchema,
74
+ sourceRevisionRef: stableUriSchema,
75
+ enabled: z.boolean(),
76
+ refresh: collectionRefreshSchema,
77
+ lastAttemptAt: isoDateTimeSchema.nullable(),
78
+ lastState: z.enum(['ACQUIRED', 'ACCESS_CONTROLLED', 'HTTP_TERMINAL', 'FAILED']).nullable(),
79
+ lastBatchRef: stableUriSchema.nullable(),
80
+ lastRunId: idSchema.nullable(),
81
+ pendingBatchRef: stableUriSchema.nullable(),
82
+ nextDueAt: isoDateTimeSchema.nullable(),
83
+ due: z.boolean(),
84
+ reason: sourceCollectionDueReasonSchema,
85
+ }).strict().superRefine((value, ctx) => {
86
+ if (value.reason === 'PENDING_BATCH' && !value.pendingBatchRef)
87
+ ctx.addIssue({ code: 'custom', path: ['pendingBatchRef'], message: 'PENDING_BATCH requires pendingBatchRef' });
88
+ if (value.reason !== 'PENDING_BATCH' && value.pendingBatchRef)
89
+ ctx.addIssue({ code: 'custom', path: ['pendingBatchRef'], message: 'pendingBatchRef is only valid for PENDING_BATCH' });
90
+ if (value.due && !['NEVER_ATTEMPTED', 'INTERVAL_ELAPSED'].includes(value.reason))
91
+ ctx.addIssue({ code: 'custom', path: ['due'], message: 'due source must be never attempted or interval elapsed' });
92
+ if (value.refresh.mode === 'MANUAL' && value.nextDueAt !== null)
93
+ ctx.addIssue({ code: 'custom', path: ['nextDueAt'], message: 'MANUAL source has no automatic nextDueAt' });
94
+ });
95
+ export const projectCollectionDuePlanSchema = z.object({
96
+ schemaVersion: schemaVersionSchema,
97
+ projectKey: projectKeySchema,
98
+ projectRef: stableUriSchema,
99
+ projectIntentRef: stableUriSchema,
100
+ assessedAt: isoDateTimeSchema,
101
+ sourceCount: z.number().int().nonnegative(),
102
+ dueSourceCount: z.number().int().nonnegative(),
103
+ dueSourceIds: z.array(idSchema).max(10_000),
104
+ sources: z.array(sourceCollectionDueEntrySchema).max(10_000),
105
+ }).strict().superRefine((value, ctx) => {
106
+ const due = value.sources.filter((row) => row.due).map((row) => row.sourceId);
107
+ if (value.sourceCount !== value.sources.length)
108
+ ctx.addIssue({ code: 'custom', path: ['sourceCount'], message: 'sourceCount must match sources' });
109
+ if (value.dueSourceCount !== due.length)
110
+ ctx.addIssue({ code: 'custom', path: ['dueSourceCount'], message: 'dueSourceCount must match due sources' });
111
+ if (JSON.stringify(value.dueSourceIds) !== JSON.stringify(due))
112
+ ctx.addIssue({ code: 'custom', path: ['dueSourceIds'], message: 'dueSourceIds must match ordered due sources' });
113
+ });
114
+ export const collectionTaskRunResultSchema = z.object({
115
+ taskId: idSchema,
116
+ targetId: sha256Schema,
117
+ sourceId: idSchema,
118
+ sourceRevisionRef: stableUriSchema,
119
+ observedAt: isoDateTimeSchema,
120
+ state: z.enum(['ACQUIRED', 'ACCESS_CONTROLLED', 'HTTP_TERMINAL', 'FAILED']),
121
+ finalLocator: stableUriSchema.nullable(),
122
+ httpStatus: z.number().int().min(100).max(599).nullable(),
123
+ evidenceRef: stableUriSchema.nullable(),
124
+ contentHash: sha256Schema.nullable(),
125
+ byteLength: z.number().int().nonnegative().nullable(),
126
+ mediaType: z.string().trim().min(1).max(256).nullable(),
127
+ reusedEvidence: z.boolean(),
128
+ relatedEvidenceRefs: z.array(stableUriSchema).max(512).default([]),
129
+ richDocumentRef: stableUriSchema.nullable().default(null),
130
+ projectionJobId: idSchema.optional(),
131
+ projectionState: z.enum(['PENDING', 'SUCCEEDED', 'PARTIAL', 'FAILED', 'NOT_REQUIRED']).optional(),
132
+ errorCode: z.string().trim().min(1).max(256).nullable(),
133
+ }).strict().superRefine((value, ctx) => {
134
+ const acquired = value.state === 'ACQUIRED';
135
+ if (acquired && (!value.evidenceRef || !value.contentHash || value.byteLength === null || !value.mediaType))
136
+ ctx.addIssue({ code: 'custom', path: ['evidenceRef'], message: 'ACQUIRED result requires persisted Evidence identity' });
137
+ if (!acquired && (value.evidenceRef || value.contentHash || value.byteLength !== null || value.mediaType || value.reusedEvidence || value.relatedEvidenceRefs.length || value.richDocumentRef))
138
+ ctx.addIssue({ code: 'custom', path: ['evidenceRef'], message: 'non-ACQUIRED result cannot claim persisted Evidence' });
139
+ if (value.projectionState === 'PENDING' && (!value.projectionJobId || value.richDocumentRef || value.relatedEvidenceRefs.length || value.errorCode))
140
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'pending projection requires a job and no derived result' });
141
+ if (value.projectionState === 'SUCCEEDED' && !value.richDocumentRef)
142
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'successful projection requires a document' });
143
+ if (value.projectionState === 'PARTIAL' && (!acquired || !value.errorCode || !value.richDocumentRef))
144
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'partial projection requires acquired Evidence, document and error' });
145
+ if (value.projectionState === 'NOT_REQUIRED' && value.richDocumentRef)
146
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'unrequired projection cannot claim a document' });
147
+ if (value.projectionState === 'FAILED' && (!acquired || !value.errorCode || value.richDocumentRef))
148
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'failed projection requires acquired raw Evidence, error and no document' });
149
+ if (!acquired && value.projectionState)
150
+ ctx.addIssue({ code: 'custom', path: ['projectionState'], message: 'projection requires acquired raw Evidence' });
151
+ if (value.state === 'FAILED' && !value.errorCode)
152
+ ctx.addIssue({ code: 'custom', path: ['errorCode'], message: 'FAILED result requires errorCode' });
153
+ });
154
+ export const collectionRunReceiptSchema = z.object({
155
+ schemaVersion: schemaVersionSchema,
156
+ projectKey: projectKeySchema,
157
+ projectRef: stableUriSchema,
158
+ batchId: idSchema,
159
+ batchRef: stableUriSchema,
160
+ runId: idSchema,
161
+ runtimeNodeId: idSchema,
162
+ startedAt: isoDateTimeSchema,
163
+ finishedAt: isoDateTimeSchema,
164
+ status: z.enum(['SUCCEEDED', 'PARTIAL', 'FAILED']),
165
+ results: z.array(collectionTaskRunResultSchema).min(1).max(10_000),
166
+ }).strict().superRefine((value, ctx) => {
167
+ if (Date.parse(value.finishedAt) < Date.parse(value.startedAt))
168
+ ctx.addIssue({ code: 'custom', path: ['finishedAt'], message: 'finishedAt must not precede startedAt' });
169
+ const acquired = value.results.filter((row) => row.state === 'ACQUIRED').length;
170
+ const incomplete = value.results.some(row => row.projectionState === 'FAILED' || row.projectionState === 'PARTIAL');
171
+ const expected = acquired === 0 ? 'FAILED' : acquired === value.results.length && !incomplete ? 'SUCCEEDED' : 'PARTIAL';
172
+ if (value.status !== expected)
173
+ ctx.addIssue({ code: 'custom', path: ['status'], message: `status must be ${expected} for task results` });
174
+ });
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export const schemaVersionSchema = z.literal(1);
3
+ export const idSchema = z.string().trim().min(1).max(256);
4
+ export const projectKeySchema = z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
5
+ export const isoDateTimeSchema = z.iso.datetime({ offset: true });
6
+ export const sha256Schema = z.string().regex(/^sha256:[a-f0-9]{64}$/);
7
+ export const stableUriSchema = z.string().trim().min(1).max(2048).regex(/^[a-z][a-z0-9+.-]*:\/\/.+/i);
8
+ export const accessClassificationSchema = z.enum([
9
+ 'PUBLIC',
10
+ 'PUBLIC_METADATA',
11
+ 'TENANT',
12
+ 'INTERNAL',
13
+ 'FDE_INTERNAL',
14
+ 'ACCESS_CONTROLLED',
15
+ 'UNKNOWN',
16
+ ]);