@arnilo/prism 0.7.0 → 0.9.0
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 +73 -0
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +15 -2
- package/dist/agent-approval.js +5 -1
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +91 -10
- package/dist/agent-run-state.d.ts +34 -2
- package/dist/agent-run-state.js +68 -6
- package/dist/agent-session/helpers.js +20 -1
- package/dist/agent-session/session/assemble.js +250 -27
- package/dist/agent-session/session/persist.d.ts +27 -0
- package/dist/agent-session/session/persist.js +94 -12
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +197 -25
- package/dist/agent-session/session/tool-round.js +24 -2
- package/dist/agent-session/session/types.d.ts +36 -2
- package/dist/agent-session/session.d.ts +40 -4
- package/dist/agent-session/session.js +78 -5
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/checkpoints.js +7 -11
- package/dist/context-budget.d.ts +2 -1
- package/dist/context-budget.js +24 -2
- package/dist/contracts-core/agent.d.ts +30 -0
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +15 -0
- package/dist/contracts-core/guardrail-packs.d.ts +41 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/provider.d.ts +25 -0
- package/dist/contracts-core/run-limits.d.ts +21 -0
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +90 -4
- package/dist/contracts-run-state.d.ts +82 -6
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +16 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +53 -0
- package/dist/guardrails.d.ts +20 -1
- package/dist/guardrails.js +268 -0
- package/dist/host-composition.d.ts +13 -0
- package/dist/host-composition.js +33 -2
- package/dist/index.d.ts +19 -10
- package/dist/index.js +11 -6
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +11 -3
- package/dist/provider-events.js +62 -4
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +150 -0
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +46 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +30 -0
- package/dist/testing/prefix-stability-conformance.js +104 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +11 -3
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/ag-ui.md +5 -0
- package/docs/agent-events.md +68 -1
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +5 -3
- package/docs/attention-compiler.md +89 -8
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +1 -0
- package/docs/coding-tools.md +0 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-observational-memory.md +34 -7
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +13 -0
- package/docs/core.md +1 -1
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +9 -9
- package/docs/documents.md +32 -11
- package/docs/durable-runs.md +129 -0
- package/docs/embeddings.md +5 -0
- package/docs/enterprise-postgres-state.md +4 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +84 -1
- package/docs/guardrails.md +71 -2
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/release-handoffs.md +38 -0
- package/docs/host-compositions.md +8 -6
- package/docs/host-security.md +2 -2
- package/docs/index.md +66 -29
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/knowledge-sync.md +4 -0
- package/docs/live-testing.md +5 -3
- package/docs/mcp-tools.md +1 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/middleware-hooks.md +38 -2
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +43 -0
- package/docs/model-registry.md +12 -2
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +1 -3
- package/docs/options-index.md +36 -3
- package/docs/peer-dependencies.md +6 -6
- package/docs/policy-and-audit.md +13 -1
- package/docs/postgres-persistence.md +1 -1
- package/docs/prefix-stability-conformance.md +93 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +20 -20
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/public-contracts.md +2 -2
- package/docs/rag.md +102 -4
- package/docs/release-and-install.md +55 -47
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +57 -6
- package/docs/scoped-agent-memory.md +262 -0
- package/docs/server.md +2 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +5 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +32 -12
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/tools.md +17 -0
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +1 -1
- package/docs/work-connectors.md +9 -9
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +38 -16
- package/docs/workflows.md +5 -0
- package/package.json +9 -3
- package/templates/business-worker/manifest.json +2 -1
- package/templates/business-worker/src/agent.ts.tmpl +1 -1
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Built-in guardrail pack registry (plan 092 Task 2). Definitions are pure data + pure factories. */
|
|
2
|
+
import { codingStandardPack } from "./coding-standard.js";
|
|
3
|
+
import { destructiveCommandsPack } from "./destructive-commands.js";
|
|
4
|
+
import { secretsHygienePack } from "./secrets-hygiene.js";
|
|
5
|
+
import { validationRespectPack } from "./validation-respect.js";
|
|
6
|
+
const DEFINITIONS = [
|
|
7
|
+
codingStandardPack,
|
|
8
|
+
destructiveCommandsPack,
|
|
9
|
+
validationRespectPack,
|
|
10
|
+
secretsHygienePack,
|
|
11
|
+
];
|
|
12
|
+
export const BUILT_IN_GUARDRAIL_PACKS = new Map(DEFINITIONS.map((definition) => [definition.id, definition]));
|
|
13
|
+
/** Built-in pack ids, in registry order. */
|
|
14
|
+
export const BUILT_IN_GUARDRAIL_PACK_IDS = DEFINITIONS.map((definition) => definition.id);
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known credential shapes, all quantifier-bounded so a hostile argument string cannot blow up the regex.
|
|
3
|
+
* Prism redaction matches exact known values only (plan 092 Task 1), so these patterns ship with the pack.
|
|
4
|
+
*/
|
|
5
|
+
const SECRET_PATTERN = /(?:sk-[A-Za-z0-9]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.|xox[baprs]-[A-Za-z0-9-]{10,})/;
|
|
6
|
+
/** Canned secret hygiene: any tool argument carrying credential-shaped material is denied. */
|
|
7
|
+
export const secretsHygienePack = {
|
|
8
|
+
id: "secrets-hygiene",
|
|
9
|
+
version: 1,
|
|
10
|
+
description: "Blocks tool calls whose arguments carry secret-shaped material (API keys, tokens, private keys).",
|
|
11
|
+
build() {
|
|
12
|
+
return {
|
|
13
|
+
rules: [
|
|
14
|
+
{
|
|
15
|
+
id: "no-secret-material-in-arguments",
|
|
16
|
+
pattern: SECRET_PATTERN,
|
|
17
|
+
reason: "Tool arguments contain secret-shaped material",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=secrets-hygiene.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Internal guardrail-pack definition types (plan 092 Task 2). Not SDK surface; hosts configure packs via `AgentSessionConfig.guardrailPacks`. */
|
|
2
|
+
import type { GuardrailRule, GuardrailRuleContext } from "../contracts-core/guardrail-packs.js";
|
|
3
|
+
import type { ToolResult } from "../contracts-protocol.js";
|
|
4
|
+
/** What a pack's `build` returns: restrictive rules plus an optional pure result observer. */
|
|
5
|
+
export interface GuardrailPackRules {
|
|
6
|
+
readonly rules: readonly GuardrailRule[];
|
|
7
|
+
/** Records tool results into pack-local state (never denies); a throw fails closed as a guardrail tripwire. */
|
|
8
|
+
readonly observe?: (state: Record<string, unknown>, result: ToolResult, context: GuardrailRuleContext) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface GuardrailPackDefinition {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly version: number;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
/** Pure factory: options in, rules out. No session access, no I/O. */
|
|
15
|
+
readonly build: (options: Readonly<Record<string, unknown>>) => GuardrailPackRules;
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GuardrailPackError } from "./errors.js";
|
|
2
|
+
const MUTATING_TOOLS = ["write", "edit", "delete", "move"];
|
|
3
|
+
/** Validation-style tool names. `shell` is opt-in (`validationTools`): a non-zero shell exit is a failure signal too. */
|
|
4
|
+
const DEFAULT_VALIDATION_TOOLS = ["test", "run_tests", "validate", "validation", "lint", "typecheck", "check"];
|
|
5
|
+
const MAX_VALIDATION_TOOLS = 16;
|
|
6
|
+
/** A tool result failed when it carries an error, or reported a non-zero `exitCode` (the shell tool's shape). */
|
|
7
|
+
function resultFailed(result) {
|
|
8
|
+
if (result.error !== undefined)
|
|
9
|
+
return true;
|
|
10
|
+
const value = result.value;
|
|
11
|
+
if (!value || typeof value !== "object" || !("exitCode" in value))
|
|
12
|
+
return false;
|
|
13
|
+
const exitCode = value.exitCode;
|
|
14
|
+
return typeof exitCode === "number" && exitCode !== 0;
|
|
15
|
+
}
|
|
16
|
+
function readValidationTools(value) {
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
return DEFAULT_VALIDATION_TOOLS;
|
|
19
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_VALIDATION_TOOLS) {
|
|
20
|
+
throw new GuardrailPackError(`validation-respect options.validationTools must be a non-empty string array (max ${MAX_VALIDATION_TOOLS})`);
|
|
21
|
+
}
|
|
22
|
+
return value.map((name) => {
|
|
23
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
24
|
+
throw new GuardrailPackError("validation-respect options.validationTools entries must be non-empty strings");
|
|
25
|
+
}
|
|
26
|
+
return name;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Canned "respect failed validation": once a validation tool result fails, file mutations are denied until one passes. */
|
|
30
|
+
export const validationRespectPack = {
|
|
31
|
+
id: "validation-respect",
|
|
32
|
+
version: 1,
|
|
33
|
+
description: "Denies file mutations after a failed validation tool result until a later validation passes.",
|
|
34
|
+
build(options) {
|
|
35
|
+
const validationTools = readValidationTools(options.validationTools);
|
|
36
|
+
return {
|
|
37
|
+
observe(state, result, context) {
|
|
38
|
+
if (!validationTools.includes(context.toolName))
|
|
39
|
+
return;
|
|
40
|
+
state.validationFailed = resultFailed(result) ? context.toolName : undefined;
|
|
41
|
+
},
|
|
42
|
+
rules: [
|
|
43
|
+
{
|
|
44
|
+
id: "no-mutation-after-failed-validation",
|
|
45
|
+
tool: MUTATING_TOOLS,
|
|
46
|
+
reason: "A previous validation result failed; fix it before mutating files",
|
|
47
|
+
deny: (_args, context) => context.state.validationFailed !== undefined,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=validation-respect.js.map
|
package/dist/guardrails.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { AgentEvent, GuardrailContext, GuardrailRecord, GuardrailStage, Guardrails, GuardrailValue } from "./contracts.js";
|
|
1
|
+
import type { AgentEvent, GuardrailContext, GuardrailPackRef, GuardrailRecord, GuardrailStage, Guardrails, GuardrailValue } from "./contracts.js";
|
|
2
|
+
export { GuardrailPackError } from "./guardrail-packs/errors.js";
|
|
2
3
|
import type { SecretRedactor } from "./redaction.js";
|
|
3
4
|
export declare const MAX_GUARDRAIL_CONCURRENCY = 16;
|
|
5
|
+
/** Guardrail pack compile bounds (plan 092 Task 2). All ceilings are config-shape limits, not runtime budgets. */
|
|
6
|
+
export declare const MAX_GUARDRAIL_PACKS = 8;
|
|
7
|
+
export declare const MAX_GUARDRAIL_PACK_RULES = 64;
|
|
4
8
|
export declare class GuardrailError extends Error {
|
|
5
9
|
readonly code: string;
|
|
6
10
|
readonly record: GuardrailRecord;
|
|
@@ -23,3 +27,18 @@ export interface GuardrailRunResult {
|
|
|
23
27
|
/** Evaluate one typed stage. Default is declaration-order sequential; bounded parallel mode still reports declaration order. */
|
|
24
28
|
export declare function runGuardrails<S extends GuardrailStage>(options: RunGuardrailsOptions<S>): Promise<GuardrailRunResult>;
|
|
25
29
|
export declare function assertGuardrailsAllowed(result: GuardrailRunResult): void;
|
|
30
|
+
/** One `guardrail:<stage>` identity row of a compiled pack, for run-bundle fingerprints. */
|
|
31
|
+
export interface GuardrailPackRow {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly stage: "tool_input" | "tool_output";
|
|
34
|
+
readonly revision: string | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compiles `guardrailPacks` config onto the existing tool interception seams: one `tool_input`
|
|
38
|
+
* guardrail per rule (`name = pack:<pack>/<rule>`), plus one `tool_output` recorder for packs that
|
|
39
|
+
* observe results. Compiled once per session — patterns are compiled here, never per tool call.
|
|
40
|
+
* Throws `GuardrailPackError` on malformed config (fail closed); returns `undefined` when unset.
|
|
41
|
+
*/
|
|
42
|
+
export declare function compileGuardrailPacks(refs: readonly GuardrailPackRef[] | undefined, registry?: ReadonlyMap<string, import("./guardrail-packs/types.js").GuardrailPackDefinition>): Guardrails | undefined;
|
|
43
|
+
/** Stable identity rows for the same config `compileGuardrailPacks` accepts (no state, no guardrails built). */
|
|
44
|
+
export declare function describeGuardrailPacks(refs: readonly GuardrailPackRef[] | undefined, registry?: ReadonlyMap<string, import("./guardrail-packs/types.js").GuardrailPackDefinition>): readonly GuardrailPackRow[];
|
package/dist/guardrails.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
import { GuardrailPackError } from "./guardrail-packs/errors.js";
|
|
2
|
+
export { GuardrailPackError } from "./guardrail-packs/errors.js";
|
|
3
|
+
import { BUILT_IN_GUARDRAIL_PACKS } from "./guardrail-packs/index.js";
|
|
1
4
|
export const MAX_GUARDRAIL_CONCURRENCY = 16;
|
|
2
5
|
const MAX_REASON_BYTES = 4 * 1024;
|
|
3
6
|
const MAX_METADATA_BYTES = 16 * 1024;
|
|
7
|
+
/** Guardrail pack compile bounds (plan 092 Task 2). All ceilings are config-shape limits, not runtime budgets. */
|
|
8
|
+
export const MAX_GUARDRAIL_PACKS = 8;
|
|
9
|
+
export const MAX_GUARDRAIL_PACK_RULES = 64;
|
|
10
|
+
const MAX_PACK_ID_BYTES = 96;
|
|
11
|
+
const MAX_RULE_ID_BYTES = 96;
|
|
12
|
+
const MAX_RULE_REASON_BYTES = 512;
|
|
13
|
+
const MAX_RULE_ARG_PATHS = 8;
|
|
14
|
+
const MAX_PATTERN_BYTES = 4 * 1024;
|
|
15
|
+
const MAX_ARG_SCAN_DEPTH = 8;
|
|
16
|
+
const MAX_ARG_SCAN_STRINGS = 64;
|
|
17
|
+
const MAX_ARG_STRING_BYTES = 16 * 1024;
|
|
18
|
+
const MAX_PACK_NAME_BYTES = 128;
|
|
19
|
+
const OBSERVE_RULE_ID = "observe";
|
|
4
20
|
export class GuardrailError extends Error {
|
|
5
21
|
code;
|
|
6
22
|
record;
|
|
@@ -60,6 +76,258 @@ export function assertGuardrailsAllowed(result) {
|
|
|
60
76
|
if (result.terminal)
|
|
61
77
|
throw new GuardrailError(result.terminal);
|
|
62
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Compiles `guardrailPacks` config onto the existing tool interception seams: one `tool_input`
|
|
81
|
+
* guardrail per rule (`name = pack:<pack>/<rule>`), plus one `tool_output` recorder for packs that
|
|
82
|
+
* observe results. Compiled once per session — patterns are compiled here, never per tool call.
|
|
83
|
+
* Throws `GuardrailPackError` on malformed config (fail closed); returns `undefined` when unset.
|
|
84
|
+
*/
|
|
85
|
+
export function compileGuardrailPacks(refs, registry = BUILT_IN_GUARDRAIL_PACKS) {
|
|
86
|
+
const packs = resolveGuardrailPacks(refs, registry);
|
|
87
|
+
if (packs.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
const toolInput = [];
|
|
90
|
+
const toolOutput = [];
|
|
91
|
+
for (const pack of packs) {
|
|
92
|
+
const state = {};
|
|
93
|
+
if (pack.observe)
|
|
94
|
+
toolOutput.push(observeGuardrail(pack, pack.observe, state));
|
|
95
|
+
for (const resolved of pack.rules)
|
|
96
|
+
toolInput.push(ruleGuardrail(pack, resolved, state));
|
|
97
|
+
}
|
|
98
|
+
return toolOutput.length > 0 ? { toolInput, toolOutput } : { toolInput };
|
|
99
|
+
}
|
|
100
|
+
/** Stable identity rows for the same config `compileGuardrailPacks` accepts (no state, no guardrails built). */
|
|
101
|
+
export function describeGuardrailPacks(refs, registry = BUILT_IN_GUARDRAIL_PACKS) {
|
|
102
|
+
const rows = [];
|
|
103
|
+
for (const pack of resolveGuardrailPacks(refs, registry)) {
|
|
104
|
+
if (pack.observe)
|
|
105
|
+
rows.push({ name: packRuleName(pack.id, OBSERVE_RULE_ID), stage: "tool_output", revision: packRevision(pack) });
|
|
106
|
+
for (const resolved of pack.rules)
|
|
107
|
+
rows.push({ name: packRuleName(pack.id, resolved.rule.id), stage: "tool_input", revision: packRevision(pack) });
|
|
108
|
+
}
|
|
109
|
+
return rows;
|
|
110
|
+
}
|
|
111
|
+
function packRevision(pack) {
|
|
112
|
+
return `${pack.id}@${pack.version}`;
|
|
113
|
+
}
|
|
114
|
+
function packRuleName(packId, ruleId) {
|
|
115
|
+
return `pack:${packId}/${ruleId}`;
|
|
116
|
+
}
|
|
117
|
+
function resolveGuardrailPacks(refs, registry) {
|
|
118
|
+
if (refs === undefined)
|
|
119
|
+
return [];
|
|
120
|
+
if (!Array.isArray(refs))
|
|
121
|
+
throw new GuardrailPackError("guardrailPacks must be an array of pack ids or pack input objects");
|
|
122
|
+
if (refs.length > MAX_GUARDRAIL_PACKS)
|
|
123
|
+
throw new GuardrailPackError(`guardrailPacks accepts at most ${MAX_GUARDRAIL_PACKS} packs`);
|
|
124
|
+
const seenPacks = new Set();
|
|
125
|
+
return refs.map((ref) => {
|
|
126
|
+
const input = (typeof ref === "string" ? { id: ref } : ref) ?? {};
|
|
127
|
+
if (typeof input.id !== "string" || !input.id.trim() || byteLength(input.id) > MAX_PACK_ID_BYTES) {
|
|
128
|
+
throw new GuardrailPackError(`guardrail pack ids must be non-empty strings of at most ${MAX_PACK_ID_BYTES} bytes`);
|
|
129
|
+
}
|
|
130
|
+
if (seenPacks.has(input.id))
|
|
131
|
+
throw new GuardrailPackError(`duplicate guardrail pack id "${input.id}"`);
|
|
132
|
+
seenPacks.add(input.id);
|
|
133
|
+
if (input.version !== undefined && (!Number.isSafeInteger(input.version) || input.version < 1)) {
|
|
134
|
+
throw new GuardrailPackError(`guardrail pack "${input.id}" version must be a positive integer`);
|
|
135
|
+
}
|
|
136
|
+
const definition = registry.get(input.id);
|
|
137
|
+
if (definition === undefined && input.rules === undefined) {
|
|
138
|
+
throw new GuardrailPackError(`unknown guardrail pack "${input.id}"; known packs: ${[...registry.keys()].join(", ") || "none"}`);
|
|
139
|
+
}
|
|
140
|
+
let built;
|
|
141
|
+
if (input.rules !== undefined) {
|
|
142
|
+
if (input.options !== undefined)
|
|
143
|
+
throw new GuardrailPackError(`inline guardrail pack "${input.id}" cannot set options`);
|
|
144
|
+
built = { rules: input.rules };
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
built = definition.build(Object.freeze({ ...input.options }));
|
|
148
|
+
}
|
|
149
|
+
if (!built || !Array.isArray(built.rules) || built.rules.length === 0) {
|
|
150
|
+
throw new GuardrailPackError(`guardrail pack "${input.id}" must declare at least one rule`);
|
|
151
|
+
}
|
|
152
|
+
if (built.rules.length > MAX_GUARDRAIL_PACK_RULES) {
|
|
153
|
+
throw new GuardrailPackError(`guardrail pack "${input.id}" accepts at most ${MAX_GUARDRAIL_PACK_RULES} rules`);
|
|
154
|
+
}
|
|
155
|
+
if (built.observe !== undefined && typeof built.observe !== "function") {
|
|
156
|
+
throw new GuardrailPackError(`guardrail pack "${input.id}" observe must be a function`);
|
|
157
|
+
}
|
|
158
|
+
const seenRules = new Set();
|
|
159
|
+
const rules = built.rules.map((rule) => resolveRule(input.id, rule, seenRules));
|
|
160
|
+
if (built.observe && byteLength(packRuleName(input.id, OBSERVE_RULE_ID)) > MAX_PACK_NAME_BYTES) {
|
|
161
|
+
throw new GuardrailPackError(`guardrail pack "${input.id}" name exceeds ${MAX_PACK_NAME_BYTES} bytes`);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
id: input.id,
|
|
165
|
+
version: input.version ?? definition?.version ?? 1,
|
|
166
|
+
rules,
|
|
167
|
+
...(built.observe ? { observe: built.observe } : {}),
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function resolveRule(packId, rule, seenRules) {
|
|
172
|
+
const where = `guardrail pack "${packId}"`;
|
|
173
|
+
if (!rule || typeof rule.id !== "string" || !rule.id.trim() || byteLength(rule.id) > MAX_RULE_ID_BYTES) {
|
|
174
|
+
throw new GuardrailPackError(`${where} rule ids must be non-empty strings of at most ${MAX_RULE_ID_BYTES} bytes`);
|
|
175
|
+
}
|
|
176
|
+
if (seenRules.has(rule.id))
|
|
177
|
+
throw new GuardrailPackError(`${where} has a duplicate rule id "${rule.id}"`);
|
|
178
|
+
seenRules.add(rule.id);
|
|
179
|
+
const action = rule.action ?? "deny";
|
|
180
|
+
if (action !== "deny" && action !== "tripwire") {
|
|
181
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" action must be "deny" or "tripwire" ("ask" has no deterministic seam)`);
|
|
182
|
+
}
|
|
183
|
+
const hasPattern = rule.pattern !== undefined;
|
|
184
|
+
const hasDeny = rule.deny !== undefined;
|
|
185
|
+
if (hasPattern === hasDeny) {
|
|
186
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" requires exactly one of "pattern" or "deny"`);
|
|
187
|
+
}
|
|
188
|
+
if (hasDeny && typeof rule.deny !== "function")
|
|
189
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" deny must be a function`);
|
|
190
|
+
if (rule.reason !== undefined && (typeof rule.reason !== "string" || byteLength(rule.reason) > MAX_RULE_REASON_BYTES)) {
|
|
191
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" reason must be a string of at most ${MAX_RULE_REASON_BYTES} bytes`);
|
|
192
|
+
}
|
|
193
|
+
let tools;
|
|
194
|
+
if (rule.tool !== undefined) {
|
|
195
|
+
tools = typeof rule.tool === "string" ? [rule.tool] : [...rule.tool];
|
|
196
|
+
if (tools.length === 0 || tools.length > MAX_GUARDRAIL_PACK_RULES || tools.some((name) => typeof name !== "string" || !name.trim())) {
|
|
197
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" tool must be a non-empty tool name or array of names`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (rule.argPath !== undefined) {
|
|
201
|
+
const paths = typeof rule.argPath === "string" ? [rule.argPath] : [...rule.argPath];
|
|
202
|
+
if (paths.length === 0 || paths.length > MAX_RULE_ARG_PATHS || paths.some((path) => typeof path !== "string" || !path.trim())) {
|
|
203
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" argPath must be a non-empty dot path or array of paths`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (byteLength(packRuleName(packId, rule.id)) > MAX_PACK_NAME_BYTES) {
|
|
207
|
+
throw new GuardrailPackError(`${where} rule "${rule.id}" name exceeds ${MAX_PACK_NAME_BYTES} bytes`);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
rule,
|
|
211
|
+
action,
|
|
212
|
+
reason: rule.reason ?? `guardrail pack rule ${packId}/${rule.id}`,
|
|
213
|
+
...(tools ? { tools } : {}),
|
|
214
|
+
...(hasPattern ? { regex: compileRulePattern(packId, rule.id, rule.pattern) } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function compileRulePattern(packId, ruleId, pattern) {
|
|
218
|
+
const where = `guardrail pack "${packId}" rule "${ruleId}"`;
|
|
219
|
+
if (pattern instanceof RegExp)
|
|
220
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
221
|
+
if (typeof pattern !== "string" || !pattern)
|
|
222
|
+
throw new GuardrailPackError(`${where} pattern must be a non-empty string or RegExp`);
|
|
223
|
+
if (byteLength(pattern) > MAX_PATTERN_BYTES)
|
|
224
|
+
throw new GuardrailPackError(`${where} pattern exceeds ${MAX_PATTERN_BYTES} bytes`);
|
|
225
|
+
try {
|
|
226
|
+
return new RegExp(pattern);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new GuardrailPackError(`${where} has an invalid pattern`, { cause: error });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function ruleGuardrail(pack, resolved, state) {
|
|
233
|
+
return {
|
|
234
|
+
name: packRuleName(pack.id, resolved.rule.id),
|
|
235
|
+
revision: packRevision(pack),
|
|
236
|
+
stage: "tool_input",
|
|
237
|
+
evaluate(context) {
|
|
238
|
+
if (resolved.tools && !resolved.tools.includes(context.toolName ?? ""))
|
|
239
|
+
return { action: "allow" };
|
|
240
|
+
const args = context.value.arguments;
|
|
241
|
+
const matched = resolved.rule.deny
|
|
242
|
+
? Boolean(resolved.rule.deny(args, ruleContext(context, state)))
|
|
243
|
+
: patternMatches(resolved.regex, argumentStrings(args, resolved.rule.argPath));
|
|
244
|
+
if (!matched)
|
|
245
|
+
return { action: "allow" };
|
|
246
|
+
return {
|
|
247
|
+
// Pack vocabulary is `deny`; the core guardrail action for a denial is `block`.
|
|
248
|
+
action: resolved.action === "deny" ? "block" : "tripwire",
|
|
249
|
+
reason: resolved.reason,
|
|
250
|
+
metadata: { pack: pack.id, rule: resolved.rule.id, version: pack.version },
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function observeGuardrail(pack, observe, state) {
|
|
256
|
+
return {
|
|
257
|
+
name: packRuleName(pack.id, OBSERVE_RULE_ID),
|
|
258
|
+
revision: packRevision(pack),
|
|
259
|
+
stage: "tool_output",
|
|
260
|
+
evaluate(context) {
|
|
261
|
+
observe(state, context.value, ruleContext(context, state));
|
|
262
|
+
return { action: "allow" };
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function ruleContext(context, state) {
|
|
267
|
+
return {
|
|
268
|
+
toolName: context.toolName ?? "",
|
|
269
|
+
toolCallId: context.toolCallId ?? "",
|
|
270
|
+
sessionId: context.sessionId,
|
|
271
|
+
runId: context.runId,
|
|
272
|
+
metadata: context.metadata,
|
|
273
|
+
state,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function patternMatches(regex, values) {
|
|
277
|
+
for (const value of values) {
|
|
278
|
+
if (regex.test(value.length > MAX_ARG_STRING_BYTES ? value.slice(0, MAX_ARG_STRING_BYTES) : value))
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
function argumentStrings(args, argPath) {
|
|
284
|
+
if (argPath === undefined) {
|
|
285
|
+
const all = [];
|
|
286
|
+
for (const value of Object.values(args))
|
|
287
|
+
pushStrings(value, all, 0);
|
|
288
|
+
return all;
|
|
289
|
+
}
|
|
290
|
+
const out = [];
|
|
291
|
+
for (const path of typeof argPath === "string" ? [argPath] : argPath) {
|
|
292
|
+
walkPath(args, path.split("."), out);
|
|
293
|
+
if (out.length >= MAX_ARG_SCAN_STRINGS)
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function walkPath(value, segments, out) {
|
|
299
|
+
if (out.length >= MAX_ARG_SCAN_STRINGS)
|
|
300
|
+
return;
|
|
301
|
+
if (segments.length === 0) {
|
|
302
|
+
pushStrings(value, out, 0);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(value)) {
|
|
306
|
+
for (const item of value)
|
|
307
|
+
walkPath(item, segments, out);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (value && typeof value === "object") {
|
|
311
|
+
walkPath(value[segments[0]], segments.slice(1), out);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function pushStrings(value, out, depth) {
|
|
315
|
+
if (out.length >= MAX_ARG_SCAN_STRINGS || depth > MAX_ARG_SCAN_DEPTH)
|
|
316
|
+
return;
|
|
317
|
+
if (typeof value === "string") {
|
|
318
|
+
out.push(value);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(value)) {
|
|
322
|
+
for (const item of value)
|
|
323
|
+
pushStrings(item, out, depth + 1);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (value && typeof value === "object") {
|
|
327
|
+
for (const item of Object.values(value))
|
|
328
|
+
pushStrings(item, out, depth + 1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
63
331
|
function stageGuards(guardrails, stage) {
|
|
64
332
|
if (!guardrails)
|
|
65
333
|
return [];
|
|
@@ -24,6 +24,10 @@ export interface HostCompositionOptions {
|
|
|
24
24
|
readonly workspaceRoot?: string;
|
|
25
25
|
readonly sandboxRoots?: readonly string[];
|
|
26
26
|
readonly credentialRefs?: readonly string[];
|
|
27
|
+
readonly connectedApps?: {
|
|
28
|
+
readonly appIds: readonly string[];
|
|
29
|
+
readonly serverIds: readonly string[];
|
|
30
|
+
};
|
|
27
31
|
readonly governance?: HostCompositionGovernance;
|
|
28
32
|
readonly redactor?: SecretRedactor;
|
|
29
33
|
/** Explicit opt-in for live network checks; inert by default. */
|
|
@@ -38,6 +42,10 @@ export interface HostCompositionReport {
|
|
|
38
42
|
readonly profile: "personal" | "business";
|
|
39
43
|
readonly effectiveTools: readonly HostCompositionToolReport[];
|
|
40
44
|
readonly credentialReferences: readonly string[];
|
|
45
|
+
readonly connectedApps?: {
|
|
46
|
+
readonly appIds: readonly string[];
|
|
47
|
+
readonly serverIds: readonly string[];
|
|
48
|
+
};
|
|
41
49
|
readonly ownership: {
|
|
42
50
|
readonly tenantId?: string;
|
|
43
51
|
readonly accountId?: string;
|
|
@@ -66,6 +74,11 @@ export interface HostCompositionReport {
|
|
|
66
74
|
readonly warnings: readonly string[];
|
|
67
75
|
};
|
|
68
76
|
}
|
|
77
|
+
/** Classifies a store by declared kind/durability and constructor name — never reads contents or connection strings (plan 084 Task 4 reuses it for run-bundle snapshots). */
|
|
78
|
+
export declare function describeStorage(store: unknown, checkpoints: unknown): {
|
|
79
|
+
kind: string;
|
|
80
|
+
durable: boolean;
|
|
81
|
+
};
|
|
69
82
|
/**
|
|
70
83
|
* Inspects a host composition in an inert, bounded, and secret-redacting manner.
|
|
71
84
|
* Performs zero network calls by default.
|
package/dist/host-composition.js
CHANGED
|
@@ -56,11 +56,37 @@ function sanitizeCredentialRefs(refs = [], redactor) {
|
|
|
56
56
|
}
|
|
57
57
|
return Object.freeze(sanitized);
|
|
58
58
|
}
|
|
59
|
+
const HARD_MAX_CONNECTED_APPS = 32;
|
|
60
|
+
const MAX_CONNECTED_APP_IDENTIFIER_BYTES = 128;
|
|
61
|
+
function copyConnectedApps(value) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!value || typeof value !== "object")
|
|
65
|
+
throw new HostCompositionError("Connected apps must provide identifier arrays");
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
appIds: copyConnectedAppIdentifiers(value.appIds, "appIds"),
|
|
68
|
+
serverIds: copyConnectedAppIdentifiers(value.serverIds, "serverIds"),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function copyConnectedAppIdentifiers(value, name) {
|
|
72
|
+
if (!Array.isArray(value) || value.length > HARD_MAX_CONNECTED_APPS) {
|
|
73
|
+
throw new HostCompositionError(`Connected app ${name} must contain at most ${HARD_MAX_CONNECTED_APPS} identifiers`);
|
|
74
|
+
}
|
|
75
|
+
return Object.freeze(value.map((identifier) => {
|
|
76
|
+
if (typeof identifier !== "string" ||
|
|
77
|
+
!identifier.trim() ||
|
|
78
|
+
Buffer.byteLength(identifier, "utf8") > MAX_CONNECTED_APP_IDENTIFIER_BYTES) {
|
|
79
|
+
throw new HostCompositionError(`Connected app ${name} must contain 1..${MAX_CONNECTED_APP_IDENTIFIER_BYTES} byte identifiers`);
|
|
80
|
+
}
|
|
81
|
+
return identifier;
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
59
84
|
function isContainedPath(parent, child) {
|
|
60
85
|
const rel = relative(parent, child);
|
|
61
86
|
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
62
87
|
}
|
|
63
|
-
|
|
88
|
+
/** Classifies a store by declared kind/durability and constructor name — never reads contents or connection strings (plan 084 Task 4 reuses it for run-bundle snapshots). */
|
|
89
|
+
export function describeStorage(store, checkpoints) {
|
|
64
90
|
const target = store ?? checkpoints;
|
|
65
91
|
if (!target || typeof target !== "object") {
|
|
66
92
|
return { kind: "none", durable: false };
|
|
@@ -111,6 +137,7 @@ export function inspectHostComposition(options) {
|
|
|
111
137
|
}));
|
|
112
138
|
// 2. Credential references (identifier references only, raw values redacted)
|
|
113
139
|
const credRefs = sanitizeCredentialRefs(options.credentialRefs, redactor);
|
|
140
|
+
const connectedApps = copyConnectedApps(options.connectedApps);
|
|
114
141
|
// 3. Ownership
|
|
115
142
|
const rawOwnership = config?.ownership;
|
|
116
143
|
const ownership = {
|
|
@@ -119,7 +146,7 @@ export function inspectHostComposition(options) {
|
|
|
119
146
|
userId: typeof rawOwnership?.userId === "string" ? rawOwnership.userId.trim() : undefined,
|
|
120
147
|
};
|
|
121
148
|
// 4. Storage durability
|
|
122
|
-
const storage =
|
|
149
|
+
const storage = describeStorage(options.store, options.checkpoints ?? config?.runState?.checkpoints);
|
|
123
150
|
// 5. Sandbox capabilities
|
|
124
151
|
const workspaceRoot = options.workspaceRoot;
|
|
125
152
|
const sandboxRoots = options.sandboxRoots ?? [];
|
|
@@ -180,6 +207,9 @@ export function inspectHostComposition(options) {
|
|
|
180
207
|
}
|
|
181
208
|
// Verified identity verification
|
|
182
209
|
const identity = config?.identity;
|
|
210
|
+
if (connectedApps !== undefined && identity?.verified !== true) {
|
|
211
|
+
errors.push("Business connected apps require a verified identity");
|
|
212
|
+
}
|
|
183
213
|
if (identity) {
|
|
184
214
|
if (!identity.principal?.id || !identity.tenantId) {
|
|
185
215
|
errors.push("Business worker identity cannot be empty or fabricated");
|
|
@@ -219,6 +249,7 @@ export function inspectHostComposition(options) {
|
|
|
219
249
|
profile: normalizedProfile,
|
|
220
250
|
effectiveTools: Object.freeze(effectiveTools),
|
|
221
251
|
credentialReferences: credRefs,
|
|
252
|
+
...(connectedApps === undefined ? {} : { connectedApps }),
|
|
222
253
|
ownership: Object.freeze(ownership),
|
|
223
254
|
storage: Object.freeze(storage),
|
|
224
255
|
sandbox: Object.freeze({
|