@noodleseed/one 0.168.0 → 0.170.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.
Files changed (142) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +3 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/capabilities.d.ts +19 -0
  5. package/dist/commands/capabilities.d.ts.map +1 -0
  6. package/dist/commands/capabilities.js +128 -0
  7. package/dist/commands/capabilities.js.map +1 -0
  8. package/dist/commands/deployment-delete-ops.d.ts +12 -0
  9. package/dist/commands/deployment-delete-ops.d.ts.map +1 -0
  10. package/dist/commands/deployment-delete-ops.js +230 -0
  11. package/dist/commands/deployment-delete-ops.js.map +1 -0
  12. package/dist/commands/deployment-delete-recovery.d.ts +35 -0
  13. package/dist/commands/deployment-delete-recovery.d.ts.map +1 -0
  14. package/dist/commands/deployment-delete-recovery.js +99 -0
  15. package/dist/commands/deployment-delete-recovery.js.map +1 -0
  16. package/dist/commands/deployments-ops.d.ts +2 -1
  17. package/dist/commands/deployments-ops.d.ts.map +1 -1
  18. package/dist/commands/deployments-ops.js +5 -1
  19. package/dist/commands/deployments-ops.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugin-mode/plugin-operation-contract.d.ts +2 -2
  25. package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +4 -0
  26. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  27. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
  28. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +3 -2
  29. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  30. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.d.ts +1 -0
  31. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.js +4 -1
  32. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +2 -2
  33. package/node_modules/@noodle-borg/authoring/dist/assistant.js +3 -1
  34. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
  35. package/node_modules/@noodle-borg/authoring/dist/index.js +1 -0
  36. package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
  37. package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +2 -1
  38. package/node_modules/@noodle-borg/authoring/dist/recording.js +29 -1
  39. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
  40. package/node_modules/@noodle-borg/authoring/dist/server.js +5 -1
  41. package/node_modules/@noodle-borg/authoring/dist/web-capabilities.d.ts +17 -0
  42. package/node_modules/@noodle-borg/authoring/dist/web-capabilities.js +15 -0
  43. package/node_modules/@noodle-borg/authoring/package.json +1 -0
  44. package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
  45. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-observability.js +102 -0
  46. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-tenant-resources.js +27 -1
  47. package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +3 -0
  48. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
  49. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  50. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  51. package/node_modules/@noodle-borg/compiler/dist/compile.js +2 -0
  52. package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +18 -3
  53. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +34 -0
  54. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +9 -2
  55. package/node_modules/@noodle-borg/compiler/dist/web-capabilities.js +55 -0
  56. package/node_modules/@noodle-borg/compiler/package.json +1 -0
  57. package/node_modules/@noodle-borg/connector-http/dist/ssrf.js +5 -0
  58. package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.d.ts +44 -0
  59. package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.js +105 -0
  60. package/node_modules/@noodle-borg/control-plane/dist/deployment-deletion-contracts.d.ts +20 -0
  61. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
  62. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  63. package/node_modules/@noodle-borg/knowledge-crawl/dist/bounded-body.js +38 -0
  64. package/node_modules/@noodle-borg/knowledge-crawl/dist/first-party.js +2 -3
  65. package/node_modules/@noodle-borg/knowledge-crawl/dist/html-text.js +10 -2
  66. package/node_modules/@noodle-borg/knowledge-crawl/dist/index.d.ts +1 -0
  67. package/node_modules/@noodle-borg/knowledge-crawl/dist/index.js +1 -0
  68. package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.d.ts +26 -0
  69. package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.js +127 -0
  70. package/node_modules/@noodle-borg/knowledge-crawl/dist/robots.js +48 -23
  71. package/node_modules/@noodle-borg/knowledge-crawl/package.json +1 -0
  72. package/node_modules/@noodle-borg/managed-capabilities/dist/budget.d.ts +29 -0
  73. package/node_modules/@noodle-borg/managed-capabilities/dist/budget.js +124 -0
  74. package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.d.ts +212 -0
  75. package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.js +113 -0
  76. package/node_modules/@noodle-borg/managed-capabilities/dist/errors.d.ts +8 -0
  77. package/node_modules/@noodle-borg/managed-capabilities/dist/errors.js +21 -0
  78. package/node_modules/@noodle-borg/managed-capabilities/dist/executor.d.ts +26 -0
  79. package/node_modules/@noodle-borg/managed-capabilities/dist/executor.js +156 -0
  80. package/node_modules/@noodle-borg/managed-capabilities/dist/limits.d.ts +15 -0
  81. package/node_modules/@noodle-borg/managed-capabilities/dist/limits.js +15 -0
  82. package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.d.ts +159 -0
  83. package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.js +69 -0
  84. package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.d.ts +5 -0
  85. package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.js +160 -0
  86. package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.d.ts +34 -0
  87. package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.js +61 -0
  88. package/node_modules/@noodle-borg/managed-capabilities/dist/portable.d.ts +10 -0
  89. package/node_modules/@noodle-borg/managed-capabilities/dist/portable.js +10 -0
  90. package/node_modules/@noodle-borg/managed-capabilities/dist/service.d.ts +83 -0
  91. package/node_modules/@noodle-borg/managed-capabilities/dist/service.js +139 -0
  92. package/node_modules/@noodle-borg/managed-capabilities/dist/urls.d.ts +3 -0
  93. package/node_modules/@noodle-borg/managed-capabilities/dist/urls.js +30 -0
  94. package/node_modules/@noodle-borg/managed-capabilities/package.json +49 -0
  95. package/node_modules/@noodle-borg/module/dist/contract.d.ts +11 -0
  96. package/node_modules/@noodle-borg/runtime/dist/confirmation.js +29 -0
  97. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +2 -0
  98. package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -0
  99. package/node_modules/@noodle-borg/runtime/dist/execute.js +6 -1
  100. package/node_modules/@noodle-borg/runtime/dist/index.d.ts +2 -1
  101. package/node_modules/@noodle-borg/runtime/dist/index.js +2 -1
  102. package/node_modules/@noodle-borg/runtime/dist/interactive.js +10 -1
  103. package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +3 -0
  104. package/node_modules/@noodle-borg/runtime/dist/result-meta.d.ts +2 -0
  105. package/node_modules/@noodle-borg/runtime/dist/result-meta.js +6 -0
  106. package/node_modules/@noodle-borg/runtime/dist/result.d.ts +4 -0
  107. package/node_modules/@noodle-borg/runtime/dist/web-capabilities.d.ts +71 -0
  108. package/node_modules/@noodle-borg/runtime/dist/web-capabilities.js +41 -0
  109. package/node_modules/@noodle-borg/runtime/package.json +1 -0
  110. package/node_modules/@noodle-borg/service/dist/deployment-deletion.js +40 -0
  111. package/node_modules/@noodle-borg/service/dist/local-options.d.ts +1 -0
  112. package/node_modules/@noodle-borg/service/dist/local-web-capabilities.js +13 -0
  113. package/node_modules/@noodle-borg/service/dist/mcp-protocol-runtime.js +21 -0
  114. package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +13 -0
  115. package/node_modules/@noodle-borg/service/dist/registry-compile.js +9 -0
  116. package/node_modules/@noodle-borg/service/dist/registry-deletion.js +75 -0
  117. package/node_modules/@noodle-borg/service/dist/registry-state.js +4 -2
  118. package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -0
  119. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -0
  120. package/node_modules/@noodle-borg/service/dist/registry.d.ts +2 -0
  121. package/node_modules/@noodle-borg/service/dist/registry.js +10 -23
  122. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +5 -2
  123. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +7 -4
  124. package/node_modules/@noodle-borg/service/dist/routes/deploy-dispatch.js +15 -1
  125. package/node_modules/@noodle-borg/service/dist/routes/deployment-delete.js +53 -0
  126. package/node_modules/@noodle-borg/service/dist/serve-local.js +14 -0
  127. package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +10 -0
  128. package/node_modules/@noodle-borg/service/dist/service.js +7 -1
  129. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -1
  130. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +8 -0
  131. package/node_modules/@noodle-borg/service/dist/store/json-file-deletion.js +64 -0
  132. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -1
  133. package/node_modules/@noodle-borg/service/dist/store/json-file.js +23 -5
  134. package/node_modules/@noodle-borg/service/dist/store.d.ts +5 -1
  135. package/node_modules/@noodle-borg/service/dist/web-capabilities.js +39 -0
  136. package/node_modules/@noodle-borg/service/package.json +3 -1
  137. package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.d.ts +29 -0
  138. package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.js +33 -0
  139. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
  140. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
  141. package/node_modules/@noodle-borg/wire-contracts/package.json +1 -0
  142. package/package.json +3 -2
@@ -0,0 +1,139 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { CapabilityError } from './errors.js';
3
+ import { effectiveWebPolicy, executeWebExtract } from './executor.js';
4
+ import { WEB_EXTRACT_LIMITS } from './limits.js';
5
+ import { capabilityScopeKey, } from './policy-store.js';
6
+ /** Policy/admission authority shared by normal tools and operator diagnostics. Never resolves a model. */
7
+ export class CapabilityService {
8
+ options;
9
+ constructor(options) {
10
+ this.options = options;
11
+ if (options.profile === 'hosted' && (!options.policies.durable || !options.counters.durable)) {
12
+ throw new Error('capability_durable_authority_required');
13
+ }
14
+ }
15
+ enabled(tenant) {
16
+ return (this.options.profile === 'development' ||
17
+ (this.options.enabledScopes ?? []).some((scope) => scope.org === tenant.org && scope.app === tenant.app && scope.env === tenant.env));
18
+ }
19
+ async inspect(tenant, declaration) {
20
+ const record = await this.options.policies.get({ ...tenant, name: declaration.name });
21
+ const operator = record === undefined ? {} : policyBounds(record.policy);
22
+ return {
23
+ name: declaration.name,
24
+ class: declaration.class,
25
+ title: declaration.title,
26
+ serviceKey: `capability.web-extract.${declaration.name}.provider`,
27
+ available: this.enabled(tenant) && record?.policy.enabled === true,
28
+ profile: this.options.profile === 'hosted' ? 'managed-learning-cohort' : 'local-first-party',
29
+ revision: record?.revision ?? 0,
30
+ developerPolicy: declaration.policy ?? {},
31
+ operatorPolicy: record?.policy ?? { enabled: false, dailyCalls: 100 },
32
+ effectivePolicy: effectiveWebPolicy(declaration.policy, operator),
33
+ ...(record === undefined ? {} : { actor: record.actor, updatedAt: record.updatedAt }),
34
+ };
35
+ }
36
+ async configure(tenant, declaration, update) {
37
+ const effective = effectiveWebPolicy(declaration.policy, policyBounds(update.policy));
38
+ for (const key of ['maxUrls', 'maxCalls', 'timeoutMs', 'maxTextBytes']) {
39
+ if (update.policy[key] !== undefined && update.policy[key] !== effective[key])
40
+ throw new CapabilityError('capability_policy_denied');
41
+ }
42
+ if (update.policy.domains?.some((host) => !effective.domains?.includes(host)))
43
+ throw new CapabilityError('capability_policy_denied');
44
+ return this.options.policies.replace({ ...tenant, name: declaration.name }, update);
45
+ }
46
+ async execute(declaration, input, context, reader = this.options.reader) {
47
+ const scope = { ...context.tenant, name: declaration.name };
48
+ if (!this.enabled(context.tenant))
49
+ throw new CapabilityError('capability_unavailable');
50
+ let record;
51
+ try {
52
+ record = await this.options.policies.get(scope);
53
+ }
54
+ catch {
55
+ throw new CapabilityError('capability_unavailable');
56
+ }
57
+ if (record?.policy.enabled !== true)
58
+ throw new CapabilityError('capability_unavailable');
59
+ if (context.anonymous && !context.network)
60
+ throw new CapabilityError('capability_policy_denied');
61
+ if (!context.anonymous && !context.subject)
62
+ throw new CapabilityError('capability_policy_denied');
63
+ const scopeId = opaque(capabilityScopeKey(scope));
64
+ const amount = WEB_EXTRACT_LIMITS.maxHttpAttempts;
65
+ const attribution = context.anonymous
66
+ ? `network:${context.network}`
67
+ : `subject:${context.subject}`;
68
+ const started = Date.now();
69
+ try {
70
+ const result = await executeWebExtract(declaration, input, {
71
+ reader,
72
+ enabled: true,
73
+ authorized: context.authorized,
74
+ budget: context.budget,
75
+ operatorPolicy: policyBounds(record.policy),
76
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
77
+ admit: async () => {
78
+ const requests = [
79
+ { key: 'capability:web-extract:fleet', amount, limit: 1000 * amount },
80
+ { key: `capability:${scopeId}`, amount, limit: record.policy.dailyCalls * amount },
81
+ {
82
+ key: `capability:caller:${opaque(`${scopeId}:${attribution}`)}`,
83
+ amount,
84
+ limit: 10 * amount,
85
+ },
86
+ ...(context.network === undefined
87
+ ? []
88
+ : [
89
+ {
90
+ key: `capability:network:${opaque(context.network)}`,
91
+ amount,
92
+ limit: 30 * amount,
93
+ },
94
+ ]),
95
+ ];
96
+ const attempt = {
97
+ key: `capability:attempt:${opaque(`${scopeId}:${context.deploymentId}:${context.executionId}`)}`,
98
+ fingerprint: opaque(JSON.stringify(input)),
99
+ };
100
+ const outcome = await this.options.counters.consumeAllOnce(requests, attempt, new Date());
101
+ // A replay is not a cache hit: there is no retained source body, so it cannot authorize more I/O.
102
+ return outcome.kind === 'consumed';
103
+ },
104
+ });
105
+ await this.options.audit?.({
106
+ ...context.tenant,
107
+ class: declaration.class,
108
+ scope: scopeId,
109
+ policyRevision: record.revision,
110
+ status: result.status,
111
+ pages: result.items.length,
112
+ elapsedMs: Date.now() - started,
113
+ });
114
+ return result;
115
+ }
116
+ catch (error) {
117
+ const safe = error instanceof CapabilityError ? error : new CapabilityError('capability_unavailable');
118
+ await Promise.resolve()
119
+ .then(() => this.options.audit?.({
120
+ ...context.tenant,
121
+ class: declaration.class,
122
+ scope: scopeId,
123
+ policyRevision: record.revision,
124
+ status: safe.code,
125
+ elapsedMs: Date.now() - started,
126
+ }))
127
+ .catch(() => { });
128
+ throw safe;
129
+ }
130
+ }
131
+ }
132
+ function opaque(value) {
133
+ return createHash('sha256').update(value).digest('hex');
134
+ }
135
+ function policyBounds(policy) {
136
+ const { enabled: _enabled, dailyCalls: _daily, ...bounds } = policy;
137
+ return bounds;
138
+ }
139
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1,3 @@
1
+ /** Syntactic policy only. The adapter must additionally pin socket DNS to public unicast addresses. */
2
+ export declare function publicPageUrl(value: string, domains?: readonly string[]): URL;
3
+ //# sourceMappingURL=urls.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { isIP } from 'node:net';
2
+ import { CapabilityError } from './errors.js';
3
+ import { WEB_EXTRACT_LIMITS } from './limits.js';
4
+ /** Syntactic policy only. The adapter must additionally pin socket DNS to public unicast addresses. */
5
+ export function publicPageUrl(value, domains) {
6
+ let url;
7
+ try {
8
+ url = new URL(value);
9
+ }
10
+ catch {
11
+ throw new CapabilityError('capability_source_rejected');
12
+ }
13
+ const host = url.hostname;
14
+ if (value.length > WEB_EXTRACT_LIMITS.maxUrlCharacters ||
15
+ url.protocol !== 'https:' ||
16
+ url.username !== '' ||
17
+ url.password !== '' ||
18
+ url.port !== '' ||
19
+ url.search !== '' ||
20
+ isIP(host.replace(/^\[|\]$/g, '')) !== 0 ||
21
+ !host.includes('.') ||
22
+ host.endsWith('.') ||
23
+ /(?:^|\.)(?:localhost|local|internal|test|invalid)$/.test(host) ||
24
+ (domains !== undefined && !domains.includes(host))) {
25
+ throw new CapabilityError('capability_source_rejected');
26
+ }
27
+ url.hash = '';
28
+ return url;
29
+ }
30
+ //# sourceMappingURL=urls.js.map
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@noodle-borg/managed-capabilities",
3
+ "version": "0.0.0",
4
+ "license": "Apache-2.0",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=24"
8
+ },
9
+ "main": "./dist/portable.js",
10
+ "types": "./dist/portable.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/portable.d.ts",
14
+ "import": "./dist/portable.js"
15
+ },
16
+ "./portable": {
17
+ "types": "./dist/portable.d.ts",
18
+ "import": "./dist/portable.js"
19
+ },
20
+ "./contracts": {
21
+ "types": "./dist/operator-contracts.d.ts",
22
+ "import": "./dist/operator-contracts.js"
23
+ },
24
+ "./testing": {
25
+ "types": "./dist/conformance.d.ts",
26
+ "import": "./dist/conformance.js"
27
+ },
28
+ "./postgres": {
29
+ "types": "./dist/postgres.d.ts",
30
+ "import": "./dist/postgres.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "typecheck": "tsc -p tsconfig.json --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "@noodle-borg/module": "0.0.0",
42
+ "@noodle-borg/admission-limits": "0.0.0",
43
+ "zod": "^4.4.3"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.13.1",
47
+ "@types/pg": "^8.20.0"
48
+ }
49
+ }
@@ -25,6 +25,17 @@ export interface ModuleRouteContext {
25
25
  readonly tenantControl?: TenantControlAuthorizer;
26
26
  /** Narrow deployment/App Package projection for hosted package operations. */
27
27
  readonly deploymentPackages?: DeploymentPackageView;
28
+ /** Read-only exact active capability declarations, available after same-request tenant authorization. */
29
+ readonly capabilityDeployments?: {
30
+ get(req: IncomingMessage, scope: {
31
+ readonly org: string;
32
+ readonly app: string;
33
+ readonly env: string;
34
+ }): Promise<{
35
+ readonly deploymentId: string;
36
+ readonly declarations: readonly unknown[];
37
+ } | undefined>;
38
+ };
28
39
  /** Minimal live deployment/package identity used only after an opaque delivery lookup. */
29
40
  readonly distributionDelivery?: DistributionDeliveryView;
30
41
  /** Host audit sink for route-emitted audit events; absent means the host has no audit trail. */
@@ -1,3 +1,4 @@
1
+ import { CapabilityBudget } from '@noodle-borg/managed-capabilities';
1
2
  import { resolveVariableEnvironment, validateVariableContinuation } from './business-variables.js';
2
3
  import { analyzeEligibleActions, countTrailingEligibleOperations, isActionOperation, validateConfirmationFlow, } from './confirmation-analysis.js';
3
4
  import { artifactIdentity, confirmationRequired, sameArtifact, } from './confirmation-continuation.js';
@@ -10,12 +11,32 @@ import { createHost, evalExprMap, prepareOperationAction, resolveEnv, runOperati
10
11
  import { AllowAllPolicy } from './policy/allow-all.js';
11
12
  import { attachResultMeta, splitResultMeta } from './result-meta.js';
12
13
  import { admitToolDispatch, latchToolDispatchAdmission } from './tool-dispatch.js';
14
+ function carryBudget(result, budget) {
15
+ if (result.status === 'input_required')
16
+ return {
17
+ ...result,
18
+ continuation: { ...result.continuation, capabilityBudget: budget.snapshot() },
19
+ };
20
+ if (result.status === 'confirmation_required')
21
+ return {
22
+ ...result,
23
+ continuation: { ...result.continuation, capabilityBudget: budget.snapshot() },
24
+ };
25
+ return result;
26
+ }
13
27
  /**
14
28
  * Evaluate the pure prefix of a confirmable tool. Connector operations are an absolute suspension
15
29
  * boundary: this function either requests input, returns a prepared confirmation, or fails before
16
30
  * invoking one.
17
31
  */
18
32
  export async function prepareToolForConfirmation(artifact, toolName, input, deps) {
33
+ const capabilityBudget = deps.capabilityBudget ?? new CapabilityBudget();
34
+ return carryBudget(await prepareToolForConfirmationInternal(artifact, toolName, input, {
35
+ ...deps,
36
+ capabilityBudget,
37
+ }), capabilityBudget);
38
+ }
39
+ async function prepareToolForConfirmationInternal(artifact, toolName, input, deps) {
19
40
  if (artifact.resolution !== 'resolved') {
20
41
  return failPreparation('shape_only_artifact', 'runtime refuses to serve a shape-only artifact');
21
42
  }
@@ -52,6 +73,13 @@ export async function prepareToolForConfirmation(artifact, toolName, input, deps
52
73
  }
53
74
  /** Resume the pure preparation prefix with one accepted, declined, or cancelled input response. */
54
75
  export async function resumeToolPreparation(artifact, continuation, response, deps) {
76
+ const capabilityBudget = new CapabilityBudget(continuation.capabilityBudget);
77
+ return carryBudget(await resumeToolPreparationInternal(artifact, continuation, response, {
78
+ ...deps,
79
+ capabilityBudget,
80
+ }), capabilityBudget);
81
+ }
82
+ async function resumeToolPreparationInternal(artifact, continuation, response, deps) {
55
83
  if (artifact.resolution !== 'resolved') {
56
84
  return failPreparation('shape_only_artifact', 'runtime refuses to serve a shape-only artifact');
57
85
  }
@@ -94,6 +122,7 @@ export async function resumeToolPreparation(artifact, continuation, response, de
94
122
  * after performing a side effect.
95
123
  */
96
124
  export async function executePreparedTool(artifact, continuation, deps) {
125
+ deps = { ...deps, capabilityBudget: new CapabilityBudget(continuation.capabilityBudget) };
97
126
  if (artifact.resolution !== 'resolved') {
98
127
  return failInteractive('shape_only_artifact', 'runtime refuses to serve a shape-only artifact');
99
128
  }
@@ -1,4 +1,5 @@
1
1
  import type { CredentialProfile, OperationSignature, ResolvedOperationRef } from '@noodle-borg/compiler';
2
+ import type { CapabilityBudget } from '@noodle-borg/managed-capabilities';
2
3
  import type { DownstreamCredential } from '../broker/types.js';
3
4
  import type { CustomerConnectorRoute } from '../customer-routing.js';
4
5
  import type { OperationCoordinationDeclaration, OperationCoordinationSnapshot } from '../operation-coordination.js';
@@ -81,6 +82,7 @@ export interface ConnectorCallHost {
81
82
  }
82
83
  /** A single connector-operation invocation, fully resolved and credentialed by the runtime. */
83
84
  export interface ConnectorCall {
85
+ readonly capabilityBudget?: CapabilityBudget;
84
86
  readonly operation: string;
85
87
  /** Runtime-generated, deployment/step-bound identity. Not a business input or recovery authority. */
86
88
  readonly execution?: Readonly<{
@@ -1,4 +1,5 @@
1
1
  import type { ArtifactFulfilment, RuntimeArtifact } from '@noodle-borg/compiler';
2
+ import { CapabilityBudget } from '@noodle-borg/managed-capabilities';
2
3
  import type { CredentialBroker } from './broker/types.js';
3
4
  import type { CallerIdentity, ConnectorRegistry, ExecutionTraceSink } from './connector/types.js';
4
5
  import type { FrozenCustomerRoutes } from './customer-routing.js';
@@ -18,6 +19,8 @@ export { evalExprMap, validateAgainstSchema, validateAgainstSchemaWithDefaults,
18
19
  * [ADR 0005](../../../docs/decisions/0005-runtime-execution-boundary.md)).
19
20
  */
20
21
  export interface ExecuteDeps {
22
+ /** Trusted shared assistant-turn / MCP-invocation budget; never part of the expression scope. */
23
+ readonly capabilityBudget?: CapabilityBudget;
21
24
  readonly connectors: ConnectorRegistry;
22
25
  readonly broker: CredentialBroker;
23
26
  readonly policy?: PolicyGate;
@@ -1,3 +1,4 @@
1
+ import { CapabilityBudget } from '@noodle-borg/managed-capabilities';
1
2
  import { resolveVariableEnvironment } from './business-variables.js';
2
3
  import { preflightFulfilmentSignatures } from './connector-snapshot.js';
3
4
  import { preflightFulfilmentCustomerRoutes } from './customer-routing.js';
@@ -29,7 +30,11 @@ export async function executeTool(artifact, toolName, input, deps) {
29
30
  const variables = resolveVariableEnvironment(artifact.server.variables ?? [], await resolveEnv(deps), toolName);
30
31
  if (!variables.ok)
31
32
  return variables;
32
- return runFulfilment(tool.fulfilment, input, toolName, { ...deps, env: variables.env }, deps.beforeDispatch);
33
+ return runFulfilment(tool.fulfilment, input, toolName, {
34
+ ...deps,
35
+ env: variables.env,
36
+ capabilityBudget: deps.capabilityBudget ?? new CapabilityBudget(),
37
+ }, deps.beforeDispatch);
33
38
  }
34
39
  /**
35
40
  * Execute a resource read against a resolved artifact. `input` is the resource's variable scope — the
@@ -20,8 +20,9 @@ export type { OperationEvidence, OperationEvidenceIntent, OperationEvidencePort,
20
20
  export { AllowAllPolicy } from './policy/allow-all.js';
21
21
  export type { PolicyContext, PolicyDecision, PolicyGate } from './policy/types.js';
22
22
  export { type ConfirmationActionReview, type ConfirmationPreparationResult, type ConfirmationReview, type ConnectorFailureAttribution, type ElicitationRequest, type ElicitationResponse, type ExecutionError, type ExecutionErrorCode, type ExecutionResult, type InteractiveExecutionResult, isConfirmationRequired, isInputRequired, isInputRequiredForConfirmation, type PreparedOperationAction, type PreparedToolContinuation, type ToolContinuation, type ToolPreparationContinuation, } from './result.js';
23
- export { splitResultMeta } from './result-meta.js';
23
+ export { hasEphemeralEvidence, splitResultMeta } from './result-meta.js';
24
24
  export { type CallerStateAdoptionInput, type CallerStateAdoptionResult, claimableStateHandleNames, } from './state-handle-ownership.js';
25
25
  export { assertExpectedStateRevision, assertNoSecretValue, assertSchemaValue, assertStateHandleNotCompleted, COMPLETE_STATE_OPERATION, createMutableStateHandleRecord, createStateConnector, InMemoryStateHandleStore, type MutableStateHandleRecord, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, type StateHandleRecord, type StateHandleStore, type StateInput, type StateMutationInput, type StatePatchInput, stateHandleRecordForMutation, toPublicStateHandleRecord, } from './state-handles.js';
26
26
  export type { ToolDispatchContext, ToolDispatchDecision, ToolDispatchHook, } from './tool-dispatch.js';
27
+ export { WebCapabilityConnector, type WebCapabilityExecutionPort } from './web-capabilities.js';
27
28
  //# sourceMappingURL=index.d.ts.map
@@ -15,7 +15,8 @@ export { executeToolInteractive, resumeTool } from './interactive.js';
15
15
  export { resolveManagedOrigins, } from './managed-origins.js';
16
16
  export { AllowAllPolicy } from './policy/allow-all.js';
17
17
  export { isConfirmationRequired, isInputRequired, isInputRequiredForConfirmation, } from './result.js';
18
- export { splitResultMeta } from './result-meta.js';
18
+ export { hasEphemeralEvidence, splitResultMeta } from './result-meta.js';
19
19
  export { claimableStateHandleNames, } from './state-handle-ownership.js';
20
20
  export { assertExpectedStateRevision, assertNoSecretValue, assertSchemaValue, assertStateHandleNotCompleted, COMPLETE_STATE_OPERATION, createMutableStateHandleRecord, createStateConnector, InMemoryStateHandleStore, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, stateHandleRecordForMutation, toPublicStateHandleRecord, } from './state-handles.js';
21
+ export { WebCapabilityConnector } from './web-capabilities.js';
21
22
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
+ import { CapabilityBudget } from '@noodle-borg/managed-capabilities';
1
2
  import { resolveVariableEnvironment, validateVariableContinuation } from './business-variables.js';
2
3
  import { preflightFulfilmentCustomerRoutes } from './customer-routing.js';
3
4
  import { validateElicitationContent } from './elicitation-response.js';
@@ -17,7 +18,11 @@ export async function executeToolInteractive(artifact, toolName, input, deps) {
17
18
  const variables = resolveVariableEnvironment(artifact.server.variables ?? [], await resolveEnv(deps), toolName);
18
19
  if (!variables.ok)
19
20
  return { status: 'failed', error: variables.error };
20
- deps = { ...deps, env: variables.env };
21
+ deps = {
22
+ ...deps,
23
+ env: variables.env,
24
+ capabilityBudget: deps.capabilityBudget ?? new CapabilityBudget(),
25
+ };
21
26
  if (tool.fulfilment.kind !== 'flow' || !tool.fulfilment.steps.some((s) => s.kind === 'elicit')) {
22
27
  return toInteractive(await runFulfilment(tool.fulfilment, input, toolName, deps, deps.beforeDispatch));
23
28
  }
@@ -32,6 +37,7 @@ export async function executeToolInteractive(artifact, toolName, input, deps) {
32
37
  }
33
38
  /** Resume one server-held continuation with the user's reviewed response. */
34
39
  export async function resumeTool(artifact, continuation, response, deps) {
40
+ deps = { ...deps, capabilityBudget: new CapabilityBudget(continuation.capabilityBudget) };
35
41
  if (!sameArtifact(artifact, continuation) || continuation.version !== 1) {
36
42
  return interactiveFail('invalid_continuation', 'tool continuation does not match the artifact');
37
43
  }
@@ -84,6 +90,9 @@ async function runInteractiveFlow(artifact, toolName, flow, input, startIndex, s
84
90
  status: 'input_required',
85
91
  request,
86
92
  continuation: {
93
+ ...(deps.capabilityBudget === undefined
94
+ ? {}
95
+ : { capabilityBudget: deps.capabilityBudget.snapshot() }),
87
96
  version: 1,
88
97
  artifact: artifactIdentity(artifact),
89
98
  toolName,
@@ -224,6 +224,9 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
224
224
  if (signal?.aborted)
225
225
  return fail('execution_cancelled', 'Execution deadline elapsed before connector dispatch.');
226
226
  output = await connector.invoke({
227
+ ...(deps.capabilityBudget === undefined
228
+ ? {}
229
+ : { capabilityBudget: deps.capabilityBudget }),
227
230
  operation: ref.operation,
228
231
  execution,
229
232
  reportOutcome: (value) => {
@@ -1,3 +1,5 @@
1
+ /** Ephemeral extraction evidence is usable in the current turn but never replay/persistence data. */
2
+ export declare function hasEphemeralEvidence(output: unknown): boolean;
1
3
  /** @internal Shared with suspension-aware execution and surface adapters. */
2
4
  export declare function splitResultMeta(output: unknown): {
3
5
  readonly visible: unknown;
@@ -1,4 +1,8 @@
1
1
  const RESULT_META_KEY = '__noodleResultMeta';
2
+ /** Ephemeral extraction evidence is usable in the current turn but never replay/persistence data. */
3
+ export function hasEphemeralEvidence(output) {
4
+ return splitResultMeta(output).meta?.['noodle/ephemeralEvidence'] === true;
5
+ }
2
6
  /** @internal Shared with suspension-aware execution and surface adapters. */
3
7
  export function splitResultMeta(output) {
4
8
  if (output === null || typeof output !== 'object' || Array.isArray(output)) {
@@ -31,6 +35,8 @@ export function attachResultMeta(output, metas) {
31
35
  }
32
36
  function mergeRecords(target, source) {
33
37
  for (const [key, value] of Object.entries(source)) {
38
+ if (key === 'noodle/ephemeralEvidence' && target[key] === true)
39
+ continue;
34
40
  const existing = target[key];
35
41
  if (isPlainRecord(existing) && isPlainRecord(value)) {
36
42
  mergeRecords(existing, value);
@@ -1,4 +1,5 @@
1
1
  import type { CredentialProfile } from '@noodle-borg/compiler';
2
+ import type { CapabilityBudgetSnapshot } from '@noodle-borg/managed-capabilities';
2
3
  import type { ConnectorFailureCategory } from './connector/types.js';
3
4
  import type { CustomerRouteBinding } from './customer-routing.js';
4
5
  /** Why a tool call could not be fulfilled. Protocol and embedded adapters map this internal contract. */
@@ -60,6 +61,7 @@ export type ElicitationResponse = {
60
61
  * never be serialized into MCP or embedded-assistant client payloads.
61
62
  */
62
63
  export interface ToolContinuation {
64
+ readonly capabilityBudget?: CapabilityBudgetSnapshot;
63
65
  readonly version: 1;
64
66
  readonly artifact: {
65
67
  readonly manifestName: string;
@@ -86,6 +88,7 @@ interface ContinuationArtifactIdentity {
86
88
  * can evaluate pure maps and request more input, but cannot execute a connector operation.
87
89
  */
88
90
  export interface ToolPreparationContinuation {
91
+ readonly capabilityBudget?: CapabilityBudgetSnapshot;
89
92
  readonly kind: 'confirmation_preparation';
90
93
  readonly version: 1;
91
94
  readonly artifact: ContinuationArtifactIdentity;
@@ -102,6 +105,7 @@ export interface ToolPreparationContinuation {
102
105
  * behind an opaque interaction id and release it to {@code executePreparedTool} only after approval.
103
106
  */
104
107
  export interface PreparedToolContinuation {
108
+ readonly capabilityBudget?: CapabilityBudgetSnapshot;
105
109
  readonly kind: 'prepared_confirmation';
106
110
  /** Private hosting snapshot; never projected into a model-visible confirmation. */
107
111
  readonly executionRevision?: string;
@@ -0,0 +1,71 @@
1
+ import type { WebCapability, WebExtractResult } from '@noodle-borg/managed-capabilities';
2
+ import { type Connector, type ConnectorCall } from './connector/types.js';
3
+ export type WebCapabilityExecutionPort = (declaration: WebCapability, request: unknown, call: ConnectorCall) => Promise<WebExtractResult>;
4
+ /** Thin ordinary connector; deployment binding and policy authority are injected by the service. */
5
+ export declare class WebCapabilityConnector implements Connector {
6
+ private readonly declarations;
7
+ private readonly execute;
8
+ readonly id = "noodle_web";
9
+ readonly version = "1.0.0";
10
+ constructor(declarations: readonly WebCapability[], execute: WebCapabilityExecutionPort);
11
+ signature(operation: string): {
12
+ type: "read";
13
+ input: import("zod/v4/core").ZodStandardJSONSchemaPayload<import("zod").ZodObject<{
14
+ name: import("zod").ZodString;
15
+ request: import("zod").ZodObject<{
16
+ urls: import("zod").ZodArray<import("zod").ZodString>;
17
+ domains: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
18
+ }, import("zod/v4/core").$strict>;
19
+ }, import("zod/v4/core").$strict>>;
20
+ output: import("zod/v4/core").ZodStandardJSONSchemaPayload<import("zod").ZodObject<{
21
+ status: import("zod").ZodEnum<{
22
+ complete: "complete";
23
+ partial: "partial";
24
+ }>;
25
+ items: import("zod").ZodArray<import("zod").ZodObject<{
26
+ requestIndex: import("zod").ZodNumber;
27
+ sourceRef: import("zod").ZodString;
28
+ content: import("zod").ZodObject<{
29
+ format: import("zod").ZodLiteral<"text">;
30
+ text: import("zod").ZodString;
31
+ }, import("zod/v4/core").$strict>;
32
+ links: import("zod").ZodArray<import("zod").ZodObject<{
33
+ url: import("zod").ZodString;
34
+ label: import("zod").ZodOptional<import("zod").ZodString>;
35
+ }, import("zod/v4/core").$strict>>;
36
+ truncated: import("zod").ZodBoolean;
37
+ }, import("zod/v4/core").$strict>>;
38
+ sources: import("zod").ZodArray<import("zod").ZodObject<{
39
+ ref: import("zod").ZodString;
40
+ url: import("zod").ZodString;
41
+ title: import("zod").ZodOptional<import("zod").ZodString>;
42
+ retrievedAt: import("zod").ZodISODateTime;
43
+ publishedAt: import("zod").ZodOptional<import("zod").ZodISODateTime>;
44
+ observedAt: import("zod").ZodOptional<import("zod").ZodISODateTime>;
45
+ expiresAt: import("zod").ZodOptional<import("zod").ZodISODateTime>;
46
+ attribution: import("zod").ZodOptional<import("zod").ZodString>;
47
+ rights: import("zod").ZodOptional<import("zod").ZodString>;
48
+ }, import("zod/v4/core").$strict>>;
49
+ warnings: import("zod").ZodArray<import("zod").ZodObject<{
50
+ requestIndex: import("zod").ZodNumber;
51
+ code: import("zod").ZodEnum<{
52
+ capability_unavailable: "capability_unavailable";
53
+ capability_binding_invalid: "capability_binding_invalid";
54
+ capability_requirement_unsupported: "capability_requirement_unsupported";
55
+ capability_policy_denied: "capability_policy_denied";
56
+ capability_budget_exhausted: "capability_budget_exhausted";
57
+ capability_source_rejected: "capability_source_rejected";
58
+ capability_provider_failed: "capability_provider_failed";
59
+ capability_result_invalid: "capability_result_invalid";
60
+ capability_cancelled: "capability_cancelled";
61
+ content_truncated: "content_truncated";
62
+ }>;
63
+ }, import("zod/v4/core").$strict>>;
64
+ }, import("zod/v4/core").$strict>>;
65
+ } | undefined;
66
+ executionBoundMs(): number;
67
+ invoke(call: ConnectorCall): Promise<WebExtractResult & {
68
+ readonly __noodleResultMeta: Readonly<Record<string, unknown>>;
69
+ }>;
70
+ }
71
+ //# sourceMappingURL=web-capabilities.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { CapabilityError, WEB_CONNECTOR_ID, WEB_CONNECTOR_VERSION, WEB_OPERATION_SIGNATURE, webOperationInputSchema, } from '@noodle-borg/managed-capabilities';
2
+ import { ConnectorInvocationError } from './connector/types.js';
3
+ /** Thin ordinary connector; deployment binding and policy authority are injected by the service. */
4
+ export class WebCapabilityConnector {
5
+ declarations;
6
+ execute;
7
+ id = WEB_CONNECTOR_ID;
8
+ version = WEB_CONNECTOR_VERSION;
9
+ constructor(declarations, execute) {
10
+ this.declarations = declarations;
11
+ this.execute = execute;
12
+ }
13
+ signature(operation) {
14
+ return operation === 'extract' ? WEB_OPERATION_SIGNATURE : undefined;
15
+ }
16
+ executionBoundMs() {
17
+ return 30_000;
18
+ }
19
+ async invoke(call) {
20
+ const input = webOperationInputSchema.safeParse(call.args);
21
+ const declaration = input.success
22
+ ? this.declarations.find((value) => value.name === input.data.name)
23
+ : undefined;
24
+ if (call.operation !== 'extract' ||
25
+ declaration === undefined ||
26
+ !input.success ||
27
+ call.capabilityBudget === undefined) {
28
+ throw new ConnectorInvocationError('capability_policy_denied', { retryable: false });
29
+ }
30
+ try {
31
+ return {
32
+ ...(await this.execute(declaration, input.data.request, call)),
33
+ __noodleResultMeta: { 'noodle/ephemeralEvidence': true },
34
+ };
35
+ }
36
+ catch (error) {
37
+ throw new ConnectorInvocationError(error instanceof CapabilityError ? error.code : 'capability_unavailable', { retryable: false });
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=web-capabilities.js.map
@@ -26,6 +26,7 @@
26
26
  "typecheck": "tsc -p tsconfig.json --noEmit"
27
27
  },
28
28
  "dependencies": {
29
+ "@noodle-borg/managed-capabilities": "0.0.0",
29
30
  "@noodle-borg/compiler": "0.0.0",
30
31
  "@noodle-borg/module": "0.0.0"
31
32
  },
@@ -0,0 +1,40 @@
1
+ import { normalizeServerVersion } from '@noodle-borg/module';
2
+ import { sameTenantRecord } from './deployment-versioning.js';
3
+ import { validateTenantRef } from './store/validate.js';
4
+ /** Shared, side-effect-free decision made inside each adapter's mutation boundary. */
5
+ export function planDeploymentDeletion(records, ref, selection) {
6
+ const safe = validateTenantRef(ref);
7
+ const version = selection.kind === 'version' && selection.serverVersion !== undefined
8
+ ? normalizeServerVersion(selection.serverVersion)
9
+ : undefined;
10
+ const deleted = records.filter((record) => sameTenantRecord(record, safe) &&
11
+ (selection.kind === 'deployment'
12
+ ? record.deploymentId === selection.deploymentId
13
+ : record.serverVersion === version));
14
+ if (deleted.length === 0)
15
+ return {
16
+ ok: false,
17
+ code: selection.kind === 'deployment' ? 'deployment_not_found' : 'version_not_found',
18
+ };
19
+ if (records.some((record) => record.orgSlug === safe.org &&
20
+ record.appSlug === safe.app &&
21
+ record.archivedAt !== undefined)) {
22
+ return { ok: false, code: 'app_archived' };
23
+ }
24
+ if (selection.kind === 'deployment') {
25
+ if (deleted.some((record) => record.active))
26
+ return { ok: false, code: 'active_deployment' };
27
+ }
28
+ else {
29
+ if (deleted.some((record) => record.active && record.deploymentLock !== undefined))
30
+ return { ok: false, code: 'deployment_locked' };
31
+ const expected = new Set(selection.expectedDeploymentIds);
32
+ if (expected.size !== selection.expectedDeploymentIds.length ||
33
+ expected.size !== deleted.length ||
34
+ deleted.some((record) => !expected.has(record.deploymentId))) {
35
+ return { ok: false, code: 'deployment_delete_conflict' };
36
+ }
37
+ }
38
+ return { ok: true, deleted };
39
+ }
40
+ //# sourceMappingURL=deployment-deletion.js.map