@lanes-sh/link 0.1.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/LICENSE +202 -0
- package/README.md +144 -0
- package/bin/lanes +42 -0
- package/instructions/agents/lanes-link-scout.md +73 -0
- package/instructions/skills/lanes-link/SKILL.md +187 -0
- package/package.json +95 -0
- package/src/audit/chain.ts +278 -0
- package/src/audit/conformance.ts +374 -0
- package/src/audit/fanout.ts +97 -0
- package/src/audit/index.ts +218 -0
- package/src/audit/stdout.ts +60 -0
- package/src/auth/index.ts +220 -0
- package/src/auth/oauth/metadata.ts +75 -0
- package/src/auth/oauth/server.ts +385 -0
- package/src/auth/oauth/store.ts +215 -0
- package/src/auth/oidc.ts +206 -0
- package/src/auth/remote.ts +72 -0
- package/src/cli/argv.ts +94 -0
- package/src/cli/callback-page.ts +256 -0
- package/src/cli/commands/connect/accounts.ts +94 -0
- package/src/cli/commands/connect/authorise.ts +298 -0
- package/src/cli/commands/connect/client.ts +284 -0
- package/src/cli/commands/connect/index.ts +398 -0
- package/src/cli/commands/connect/outcome.ts +119 -0
- package/src/cli/commands/connect/requirements.ts +103 -0
- package/src/cli/commands/connect/scopes-gate.ts +146 -0
- package/src/cli/commands/connect/settle.ts +136 -0
- package/src/cli/commands/connect/setup.ts +276 -0
- package/src/cli/commands/mcp/assets.ts +189 -0
- package/src/cli/commands/mcp/harnesses.ts +143 -0
- package/src/cli/commands/mcp/list.ts +70 -0
- package/src/cli/commands/mcp/register.ts +199 -0
- package/src/cli/commands/mcp/stdio.ts +57 -0
- package/src/cli/commands/mcp.ts +22 -0
- package/src/cli/commands/operate/attach.ts +121 -0
- package/src/cli/commands/operate/audit.ts +119 -0
- package/src/cli/commands/operate/inspect.ts +355 -0
- package/src/cli/commands/operate/outputs.ts +206 -0
- package/src/cli/commands/operate/policy.ts +80 -0
- package/src/cli/commands/operate/serve.ts +55 -0
- package/src/cli/commands/operate/status.ts +133 -0
- package/src/cli/commands/operate/token.ts +70 -0
- package/src/cli/commands/operate.ts +27 -0
- package/src/cli/commands/owner/memory.ts +110 -0
- package/src/cli/commands/owner/shared.ts +125 -0
- package/src/cli/commands/owner/skills.ts +92 -0
- package/src/cli/commands/owner/vault.ts +140 -0
- package/src/cli/commands/owner.ts +46 -0
- package/src/cli/commands/profile/removal.ts +278 -0
- package/src/cli/commands/profile/remove.ts +315 -0
- package/src/cli/commands/profile.ts +156 -0
- package/src/cli/commands/secrets.ts +176 -0
- package/src/cli/commands/setup.ts +150 -0
- package/src/cli/commands/target.ts +310 -0
- package/src/cli/config-edit.ts +397 -0
- package/src/cli/endpoint-url.ts +77 -0
- package/src/cli/identity.ts +109 -0
- package/src/cli/lanes.ts +78 -0
- package/src/cli/main.ts +333 -0
- package/src/cli/oauth-error.ts +13 -0
- package/src/cli/oauth-exchange.ts +146 -0
- package/src/cli/oauth.ts +354 -0
- package/src/cli/output.ts +184 -0
- package/src/cli/prompt.ts +180 -0
- package/src/cli/publish.ts +185 -0
- package/src/cli/runtime/discovery.ts +123 -0
- package/src/cli/runtime/open.ts +342 -0
- package/src/cli/runtime/registry.ts +185 -0
- package/src/cli/runtime/select.ts +124 -0
- package/src/cli/runtime.ts +34 -0
- package/src/cli/scopes.ts +63 -0
- package/src/cli/usage.ts +111 -0
- package/src/cli/version.ts +25 -0
- package/src/connectivity/auth/README.md +36 -0
- package/src/connectivity/auth/api-key/index.ts +43 -0
- package/src/connectivity/auth/authorize.ts +49 -0
- package/src/connectivity/auth/basic/index.ts +68 -0
- package/src/connectivity/auth/bearer/index.ts +13 -0
- package/src/connectivity/auth/credential.ts +19 -0
- package/src/connectivity/auth/header/index.ts +18 -0
- package/src/connectivity/auth/index.ts +35 -0
- package/src/connectivity/auth/none/index.ts +12 -0
- package/src/connectivity/auth/oauth-authcode/broker.ts +261 -0
- package/src/connectivity/auth/oauth-authcode/index.ts +64 -0
- package/src/connectivity/auth/oauth-authcode/provider.ts +279 -0
- package/src/connectivity/auth/oauth-authcode/refresh.ts +118 -0
- package/src/connectivity/auth/resolve.ts +61 -0
- package/src/connectivity/auth/strategy/index.ts +14 -0
- package/src/connectivity/capability.ts +164 -0
- package/src/connectivity/connector.ts +175 -0
- package/src/connectivity/context.ts +77 -0
- package/src/connectivity/index.ts +94 -0
- package/src/connectivity/mail/attachments.ts +368 -0
- package/src/connectivity/mail/compose.ts +73 -0
- package/src/connectivity/mail/index.ts +40 -0
- package/src/connectivity/mail/message.ts +82 -0
- package/src/connectivity/mail/nodemailer.d.ts +83 -0
- package/src/connectivity/mail/staging.ts +143 -0
- package/src/connectivity/mail/url.ts +301 -0
- package/src/connectivity/manifest/auth.ts +135 -0
- package/src/connectivity/manifest/bundles.ts +23 -0
- package/src/connectivity/manifest/connector.ts +168 -0
- package/src/connectivity/manifest/credential-ref.ts +73 -0
- package/src/connectivity/manifest/identity.ts +43 -0
- package/src/connectivity/manifest/index.ts +46 -0
- package/src/connectivity/manifest/primitives.ts +20 -0
- package/src/connectivity/manifest/provider.ts +208 -0
- package/src/connectivity/manifest/requirements.ts +146 -0
- package/src/connectivity/manifest/setup.ts +55 -0
- package/src/connectivity/provider.ts +163 -0
- package/src/connectivity/transports/README.md +33 -0
- package/src/connectivity/transports/composite/index.ts +68 -0
- package/src/connectivity/transports/dav/calendar.ts +217 -0
- package/src/connectivity/transports/dav/capabilities.ts +151 -0
- package/src/connectivity/transports/dav/client.ts +226 -0
- package/src/connectivity/transports/dav/contacts.ts +75 -0
- package/src/connectivity/transports/dav/ical.ts +412 -0
- package/src/connectivity/transports/dav/index.ts +143 -0
- package/src/connectivity/transports/dav/operations.ts +43 -0
- package/src/connectivity/transports/dav/request.ts +161 -0
- package/src/connectivity/transports/dav/xml.ts +123 -0
- package/src/connectivity/transports/factory.ts +181 -0
- package/src/connectivity/transports/fs/capabilities.ts +88 -0
- package/src/connectivity/transports/fs/commands.ts +258 -0
- package/src/connectivity/transports/fs/index.ts +121 -0
- package/src/connectivity/transports/fs/operations.ts +45 -0
- package/src/connectivity/transports/fs/paths.ts +120 -0
- package/src/connectivity/transports/fs/result.ts +12 -0
- package/src/connectivity/transports/http/index.ts +255 -0
- package/src/connectivity/transports/imap/attachment.ts +166 -0
- package/src/connectivity/transports/imap/capabilities.ts +158 -0
- package/src/connectivity/transports/imap/client.ts +398 -0
- package/src/connectivity/transports/imap/commands.ts +385 -0
- package/src/connectivity/transports/imap/index.ts +152 -0
- package/src/connectivity/transports/imap/operations.ts +64 -0
- package/src/connectivity/transports/imap/parse.ts +130 -0
- package/src/connectivity/transports/imap/parser.ts +272 -0
- package/src/connectivity/transports/imap/result.ts +15 -0
- package/src/connectivity/transports/imap/send.ts +92 -0
- package/src/connectivity/transports/imap/socket.ts +111 -0
- package/src/connectivity/transports/imap/utf7.ts +136 -0
- package/src/connectivity/transports/index.ts +20 -0
- package/src/connectivity/transports/local/index.ts +173 -0
- package/src/connectivity/transports/mcp/index.ts +215 -0
- package/src/deployments/README.md +63 -0
- package/src/deployments/adapters/audit-blob.ts +203 -0
- package/src/deployments/adapters/filesystem.ts +184 -0
- package/src/deployments/adapters/gcp-secret-manager.ts +492 -0
- package/src/deployments/adapters/gcs.ts +191 -0
- package/src/deployments/adapters/otlp.ts +128 -0
- package/src/deployments/adapters/s3.ts +195 -0
- package/src/deployments/azure/README.md +21 -0
- package/src/deployments/bootstrap.ts +177 -0
- package/src/deployments/deploy.ts +290 -0
- package/src/deployments/driver.ts +157 -0
- package/src/deployments/drivers.ts +35 -0
- package/src/deployments/gcp/Dockerfile +70 -0
- package/src/deployments/gcp/cloudbuild.yaml +31 -0
- package/src/deployments/gcp/driver.ts +175 -0
- package/src/deployments/gcp/gcloud.ts +178 -0
- package/src/deployments/gcp/provision.ts +290 -0
- package/src/deployments/gcp/survey.ts +319 -0
- package/src/deployments/local/README.md +12 -0
- package/src/deployments/prepare.ts +257 -0
- package/src/deployments/steps.ts +137 -0
- package/src/deployments/target.ts +295 -0
- package/src/deployments/upload.ts +207 -0
- package/src/dispatch/context.ts +195 -0
- package/src/dispatch/dispatch.ts +350 -0
- package/src/dispatch/index.ts +32 -0
- package/src/dispatch/staging.ts +102 -0
- package/src/policy/index.ts +179 -0
- package/src/policy/limits.ts +77 -0
- package/src/profile/authorization.ts +81 -0
- package/src/profile/files.ts +71 -0
- package/src/profile/index.ts +76 -0
- package/src/profile/layout.ts +123 -0
- package/src/profile/load.ts +199 -0
- package/src/profile/primitives.ts +45 -0
- package/src/profile/schema.ts +347 -0
- package/src/profile/secret-detection.ts +162 -0
- package/src/profile/targets.ts +152 -0
- package/src/profile/workspace.ts +262 -0
- package/src/providers/custom/index.ts +21 -0
- package/src/providers/custom/load.ts +115 -0
- package/src/providers/custom/template.ts +156 -0
- package/src/providers/example/provider.ts +207 -0
- package/src/providers/google/calendar/index.ts +66 -0
- package/src/providers/google/calendar/redact.ts +40 -0
- package/src/providers/google/contacts/index.ts +50 -0
- package/src/providers/google/contacts/redact.ts +21 -0
- package/src/providers/google/docs/index.ts +45 -0
- package/src/providers/google/drive/hints.ts +28 -0
- package/src/providers/google/drive/index.ts +34 -0
- package/src/providers/google/drive/redact.ts +39 -0
- package/src/providers/google/drive-mcp/index.ts +21 -0
- package/src/providers/google/gmail/api.ts +42 -0
- package/src/providers/google/gmail/attachment.ts +142 -0
- package/src/providers/google/gmail/hints.ts +55 -0
- package/src/providers/google/gmail/index.ts +112 -0
- package/src/providers/google/gmail/redact.ts +56 -0
- package/src/providers/google/gmail/send.ts +365 -0
- package/src/providers/google/gmail-mcp/index.ts +35 -0
- package/src/providers/google/index.ts +10 -0
- package/src/providers/google/shared/oauth.ts +122 -0
- package/src/providers/google/shared/scopes.ts +99 -0
- package/src/providers/google/shared/setup.ts +80 -0
- package/src/providers/google/sheets/hints.ts +45 -0
- package/src/providers/google/sheets/index.ts +70 -0
- package/src/providers/google/sheets/redact.ts +45 -0
- package/src/providers/google/specs/calendar.v3.json +1829 -0
- package/src/providers/google/specs/docs.v1.json +381 -0
- package/src/providers/google/specs/drive.v3.json +2208 -0
- package/src/providers/google/specs/gmail.v1.json +2578 -0
- package/src/providers/google/specs/people.v1.json +506 -0
- package/src/providers/google/specs/sheets.v4.json +1269 -0
- package/src/providers/google/specs/tasks.v1.json +840 -0
- package/src/providers/google/specs/vendor.ts +661 -0
- package/src/providers/google/tasks/index.ts +53 -0
- package/src/providers/google/tasks/redact.ts +34 -0
- package/src/providers/harness.ts +95 -0
- package/src/providers/icloud/calendar/index.ts +27 -0
- package/src/providers/icloud/contacts/index.ts +17 -0
- package/src/providers/icloud/drive/index.ts +47 -0
- package/src/providers/icloud/index.ts +8 -0
- package/src/providers/icloud/mail/index.ts +37 -0
- package/src/providers/icloud/shared/setup.ts +66 -0
- package/src/providers/index.ts +93 -0
- package/src/providers/linear/index.ts +11 -0
- package/src/providers/linear/scopes.ts +7 -0
- package/src/providers/memory/provider.ts +429 -0
- package/src/providers/notion/index.ts +19 -0
- package/src/providers/owner.ts +49 -0
- package/src/providers/scopes.ts +26 -0
- package/src/providers/setup/plan.ts +141 -0
- package/src/providers/setup/provider.ts +323 -0
- package/src/providers/shared/frontmatter.ts +119 -0
- package/src/providers/skills/provider.ts +283 -0
- package/src/providers/skills/store.ts +252 -0
- package/src/providers/vault/provider.ts +194 -0
- package/src/registry/index.ts +36 -0
- package/src/registry/policy-bridge.ts +32 -0
- package/src/registry/reconcile.ts +313 -0
- package/src/registry/registry.ts +240 -0
- package/src/secrets/document.ts +293 -0
- package/src/secrets/index.ts +154 -0
- package/src/secrets/system.ts +173 -0
- package/src/secrets/vault.ts +336 -0
- package/src/server/attachments.ts +197 -0
- package/src/server/container.ts +96 -0
- package/src/server/edge.ts +53 -0
- package/src/server/endpoint.ts +352 -0
- package/src/server/generations.ts +362 -0
- package/src/server/harness.ts +400 -0
- package/src/server/index.ts +331 -0
- package/src/server/logging.ts +41 -0
- package/src/server/mcp/build.ts +68 -0
- package/src/server/mcp/icon.ts +145 -0
- package/src/server/mcp/index.ts +32 -0
- package/src/server/mcp/instructions.ts +245 -0
- package/src/server/mcp/naming.ts +39 -0
- package/src/server/mcp/prompts.ts +78 -0
- package/src/server/mcp/resources.ts +106 -0
- package/src/server/mcp/routing.ts +117 -0
- package/src/server/mcp/schema.ts +78 -0
- package/src/server/mcp/tools.ts +186 -0
- package/src/server/mcp/visibility.ts +132 -0
- package/src/server/oauth.ts +222 -0
- package/src/server/rebinding.ts +53 -0
- package/src/server/stdio.ts +192 -0
- package/src/stores/blobs/conformance.ts +223 -0
- package/src/stores/blobs/index.ts +125 -0
- package/src/stores/blobs/testing.ts +49 -0
- package/src/stores/state/index.ts +247 -0
- package/src/stores/state/keys.ts +68 -0
- package/src/stores/state/testing.ts +41 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
SUPPORTED_CONTRACT,
|
|
5
|
+
configSchema,
|
|
6
|
+
type Config,
|
|
7
|
+
type PolicyRuleConfig,
|
|
8
|
+
} from './schema.ts';
|
|
9
|
+
import { findSecrets, formatSecretFindings } from './secret-detection.ts';
|
|
10
|
+
|
|
11
|
+
export class ConfigError extends Error {
|
|
12
|
+
readonly findings: readonly string[];
|
|
13
|
+
|
|
14
|
+
constructor(message: string, findings: readonly string[] = []) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'ConfigError';
|
|
17
|
+
this.findings = findings;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoadedConfig {
|
|
22
|
+
readonly config: Config;
|
|
23
|
+
readonly source: string;
|
|
24
|
+
/** `provider.id` for every declared connection, in declaration order. */
|
|
25
|
+
readonly connectionKeys: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate a raw parsed config.
|
|
30
|
+
*
|
|
31
|
+
* Order matters and is not arbitrary:
|
|
32
|
+
*
|
|
33
|
+
* 1. Contract major, before anything else. An unrecognised major means we do
|
|
34
|
+
* not know what the rest of the document means, so no further rule can be
|
|
35
|
+
* trusted to be reading what the operator wrote.
|
|
36
|
+
* 2. Secret detection, on the RAW object rather than the parsed one. Zod
|
|
37
|
+
* strips unknown keys, so a credential parked under a misspelled key would
|
|
38
|
+
* survive schema validation invisibly.
|
|
39
|
+
* 3. Schema shape.
|
|
40
|
+
* 4. Referential integrity, which needs a well-formed document to check.
|
|
41
|
+
*/
|
|
42
|
+
export function validateConfig(raw: unknown, source = '<config>'): Config {
|
|
43
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
44
|
+
throw new ConfigError(`${source}: expected a YAML mapping at the top level`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
assertSupportedContract(raw, source);
|
|
48
|
+
|
|
49
|
+
const secrets = findSecrets(raw);
|
|
50
|
+
if (secrets.length > 0) {
|
|
51
|
+
throw new ConfigError(
|
|
52
|
+
`${source}: ${formatSecretFindings(secrets)}`,
|
|
53
|
+
secrets.map((finding) => finding.path),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const parsed = configSchema.safeParse(raw);
|
|
58
|
+
if (!parsed.success) {
|
|
59
|
+
throw new ConfigError(`${source}:\n${formatZodIssues(parsed.error)}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
assertReferentialIntegrity(parsed.data, source);
|
|
63
|
+
return parsed.data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* An unknown contract major is rejected outright, never loaded best-effort.
|
|
68
|
+
*
|
|
69
|
+
* This file governs authorization. Guessing at a schema we do not implement
|
|
70
|
+
* risks reading a document as more permissive than the operator wrote it, and
|
|
71
|
+
* refusing to start is always the safer failure.
|
|
72
|
+
*/
|
|
73
|
+
function assertSupportedContract(raw: object, source: string): void {
|
|
74
|
+
const contract = (raw as { contract?: unknown }).contract;
|
|
75
|
+
|
|
76
|
+
if (typeof contract !== 'number' || !Number.isInteger(contract)) {
|
|
77
|
+
throw new ConfigError(
|
|
78
|
+
`${source}: "contract" is required and must be an integer. This binary implements contract ${SUPPORTED_CONTRACT}.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (contract !== SUPPORTED_CONTRACT) {
|
|
83
|
+
const direction = contract > SUPPORTED_CONTRACT ? 'newer than' : 'older than';
|
|
84
|
+
throw new ConfigError(
|
|
85
|
+
`${source}: contract ${contract} is ${direction} the contract this binary implements (${SUPPORTED_CONTRACT}). ` +
|
|
86
|
+
`Refusing to load rather than guessing at what the document means. ` +
|
|
87
|
+
(contract > SUPPORTED_CONTRACT
|
|
88
|
+
? 'Upgrade lanes-link.'
|
|
89
|
+
: 'Migrate the config, or use a matching lanes-link version.'),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function formatZodIssues(error: z.ZodError): string {
|
|
95
|
+
return error.issues
|
|
96
|
+
.map((issue) => {
|
|
97
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '<root>';
|
|
98
|
+
return ` ${path}: ${issue.message}`;
|
|
99
|
+
})
|
|
100
|
+
.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Cross-references, all of which fail rather than degrade.
|
|
105
|
+
*
|
|
106
|
+
* A policy rule naming a connection that does not exist is the important one:
|
|
107
|
+
* left to resolve silently it grants nothing, which looks identical to a
|
|
108
|
+
* working rule until the day someone relies on it.
|
|
109
|
+
*/
|
|
110
|
+
function assertReferentialIntegrity(config: Config, source: string): void {
|
|
111
|
+
const problems: string[] = [];
|
|
112
|
+
|
|
113
|
+
const targetNames = new Set(Object.keys(config.targets));
|
|
114
|
+
if (targetNames.size === 0) {
|
|
115
|
+
problems.push('targets: at least one target must be declared');
|
|
116
|
+
}
|
|
117
|
+
if (!targetNames.has(config.instance.default_target)) {
|
|
118
|
+
problems.push(
|
|
119
|
+
`instance.default_target: "${config.instance.default_target}" is not a declared target (have: ${[...targetNames].join(', ') || 'none'})`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Only what holds for every platform. What one platform needs and the next
|
|
124
|
+
// has no concept of — a GCP project, an AWS role ARN — is refused by the
|
|
125
|
+
// driver that needs it, the way an adapter-specific field is refused by the
|
|
126
|
+
// code that opens the adapter rather than by this file.
|
|
127
|
+
for (const [name, target] of Object.entries(config.targets)) {
|
|
128
|
+
if (!target.deploy) continue;
|
|
129
|
+
for (const field of ['region', 'service'] as const) {
|
|
130
|
+
if (!target.deploy[field]) {
|
|
131
|
+
problems.push(`targets.${name}.deploy.${field}: required for a deployable target`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Connection ids are unique per provider, so `gmail.main` and
|
|
137
|
+
// `icloud_mail.main` can coexist.
|
|
138
|
+
const connectionKeys = new Set<string>();
|
|
139
|
+
const providerNames = new Set(config.connections.map((connection) => connection.provider));
|
|
140
|
+
|
|
141
|
+
config.connections.forEach((connection, index) => {
|
|
142
|
+
const key = `${connection.provider}.${connection.id}`;
|
|
143
|
+
if (connectionKeys.has(key)) {
|
|
144
|
+
problems.push(`connections[${index}]: duplicate connection "${key}"`);
|
|
145
|
+
}
|
|
146
|
+
connectionKeys.add(key);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const checkRules = (rules: readonly PolicyRuleConfig[], field: 'allow' | 'deny'): void => {
|
|
150
|
+
rules.forEach((rule, index) => {
|
|
151
|
+
const where = `policy.${field}[${index}]`;
|
|
152
|
+
if (rule.capability === '*') return;
|
|
153
|
+
|
|
154
|
+
// A rule naming a provider with no connection is almost always a typo,
|
|
155
|
+
// and one that fails open-looking: it reads as a grant while matching
|
|
156
|
+
// nothing. Worth saying, but not fatal for a `deny` — denying something
|
|
157
|
+
// you have not connected yet is a perfectly reasonable thing to write
|
|
158
|
+
// ahead of time.
|
|
159
|
+
const providerOfCapability = rule.capability.split('.')[0] ?? '';
|
|
160
|
+
if (field === 'allow' && !providerNames.has(providerOfCapability)) {
|
|
161
|
+
problems.push(
|
|
162
|
+
`${where}: "${rule.capability}" names provider "${providerOfCapability}", which has no connection` +
|
|
163
|
+
(providerNames.size > 0 ? ` (have: ${[...providerNames].join(', ')})` : ''),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
checkRules(config.policy.allow, 'allow');
|
|
170
|
+
checkRules(config.policy.deny, 'deny');
|
|
171
|
+
|
|
172
|
+
if (problems.length > 0) {
|
|
173
|
+
throw new ConfigError(`${source}:\n${problems.map((p) => ` ${p}`).join('\n')}`, problems);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function parseConfig(text: string, source = '<config>'): LoadedConfig {
|
|
178
|
+
let raw: unknown;
|
|
179
|
+
try {
|
|
180
|
+
raw = parseYaml(text);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new ConfigError(`${source}: could not parse YAML — ${(error as Error).message}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const config = validateConfig(raw, source);
|
|
186
|
+
return {
|
|
187
|
+
config,
|
|
188
|
+
source,
|
|
189
|
+
connectionKeys: config.connections.map((c) => `${c.provider}.${c.id}`),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function loadConfigFile(path: string): Promise<LoadedConfig> {
|
|
194
|
+
const file = Bun.file(path);
|
|
195
|
+
if (!(await file.exists())) {
|
|
196
|
+
throw new ConfigError(`${path}: no such config file`);
|
|
197
|
+
}
|
|
198
|
+
return parseConfig(await file.text(), path);
|
|
199
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The three string shapes the config contract is built out of.
|
|
5
|
+
*
|
|
6
|
+
* Their own file because more than one schema module needs them, and the
|
|
7
|
+
* alternative was either a circular import or a second copy of a regex that
|
|
8
|
+
* governs authorization. A duplicated `credentialRef` that drifted would let a
|
|
9
|
+
* literal secret through in whichever copy was looser.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const identifier = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^[a-z][a-z0-9_]*$/, 'must be lowercase letters, digits, and underscores, starting with a letter');
|
|
15
|
+
|
|
16
|
+
export const credentialRef = z
|
|
17
|
+
.string()
|
|
18
|
+
.regex(
|
|
19
|
+
/^[a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)+$/,
|
|
20
|
+
'must be a credential reference like "gmail/main", not a literal value',
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `*`, `gmail.*`, `gmail.users.*`, `gmail.users.drafts.send`, `notion.get-comments`.
|
|
25
|
+
*
|
|
26
|
+
* The provider half is ours and stays strict. The capability half is **not**:
|
|
27
|
+
* it comes from an upstream MCP server or an OpenAPI document, and vendors name
|
|
28
|
+
* things however they like — Notion uses hyphens throughout, and an OpenAPI
|
|
29
|
+
* operationId is routinely dotted (`users.drafts.send`). Constraining it would
|
|
30
|
+
* mean refusing to express a policy rule for a tool that demonstrably exists,
|
|
31
|
+
* which is worse than a slightly looser grammar. Dots were rejected here until
|
|
32
|
+
* the HTTP connector shipped, which made `lanes link policy deny
|
|
33
|
+
* gmail.users.drafts.send` — the command `connect` itself prints — impossible
|
|
34
|
+
* to write.
|
|
35
|
+
*
|
|
36
|
+
* A trailing `.*` is the only wildcard, and it may appear at any depth, so
|
|
37
|
+
* `gmail.users.*` narrows without naming every operation. Do not build a policy
|
|
38
|
+
* expression language beyond that.
|
|
39
|
+
*/
|
|
40
|
+
export const capabilityPattern = z
|
|
41
|
+
.string()
|
|
42
|
+
.regex(
|
|
43
|
+
/^(?:\*|[a-z][a-z0-9_]*\.(?:\*|[A-Za-z0-9][A-Za-z0-9_-]*(?:\.[A-Za-z0-9][A-Za-z0-9_-]*)*(?:\.\*)?))$/,
|
|
44
|
+
'must be "*", "gmail.*", "gmail.users.*", or "gmail.users.drafts.send"',
|
|
45
|
+
);
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { capabilityPattern, credentialRef, identifier } from './primitives.ts';
|
|
3
|
+
import { authorizationSchema } from './authorization.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration is declarative desired state. This file is the source of truth
|
|
7
|
+
* for what exists; the credential store holds values, and the database holds
|
|
8
|
+
* only runtime state.
|
|
9
|
+
*
|
|
10
|
+
* That split exists because the deployed target scales to zero: administering a
|
|
11
|
+
* remote instance by shelling into a container or connecting to its database is
|
|
12
|
+
* not workable. Declared config plus a reconcile step on boot removes that
|
|
13
|
+
* problem and makes the whole setup diffable and reproducible.
|
|
14
|
+
*
|
|
15
|
+
* The CLI's mutating commands edit this file rather than the database.
|
|
16
|
+
* Declarative config and an imperative CLI are not opposites — `lanes link connect` is
|
|
17
|
+
* a convenience that produces a correct file, so nobody has to hand-edit YAML,
|
|
18
|
+
* while the file remains the single source of truth.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The config contract major version.
|
|
23
|
+
*
|
|
24
|
+
* A config declaring a major this binary does not implement is rejected
|
|
25
|
+
* outright — never a best-effort load. Guessing at an unknown schema on a file
|
|
26
|
+
* that governs authorization is how you end up granting something the operator
|
|
27
|
+
* did not write.
|
|
28
|
+
*/
|
|
29
|
+
export const SUPPORTED_CONTRACT = 1;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* There is no `database:` block any more.
|
|
33
|
+
*
|
|
34
|
+
* State — connections, provider state, cursors — is one object per key in the
|
|
35
|
+
* blob store `storage:` already names, and the log is objects beside it
|
|
36
|
+
* (ADR-020). There was nothing left for a second declaration to choose. A
|
|
37
|
+
* profile written before this keeps its `database:` key and it is ignored:
|
|
38
|
+
* zod strips unknown keys, so an old file loads unchanged rather than failing
|
|
39
|
+
* on a field that no longer means anything.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export const credentialsTargetSchema = z.object({
|
|
43
|
+
adapter: z.enum(['file', 'gcp-secret-manager']),
|
|
44
|
+
path: z.string().optional(),
|
|
45
|
+
project: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const storageTargetSchema = z.object({
|
|
49
|
+
adapter: z.enum(['filesystem', 'gcs', 's3']),
|
|
50
|
+
path: z.string().optional(),
|
|
51
|
+
bucket: z.string().optional(),
|
|
52
|
+
/**
|
|
53
|
+
* The S3-compatible service endpoint. Named for the protocol rather than a
|
|
54
|
+
* vendor (ADR-013) — Supabase Storage, R2, MinIO, and AWS differ only in this
|
|
55
|
+
* URL. `gcs` needs none of this: it addresses Google's own API and
|
|
56
|
+
* authenticates as the identity already present.
|
|
57
|
+
*/
|
|
58
|
+
endpoint: z.string().optional(),
|
|
59
|
+
region: z.string().optional(),
|
|
60
|
+
/** Bucket-relative key prefix; the S3 equivalent of `path`. */
|
|
61
|
+
prefix: z.string().optional(),
|
|
62
|
+
/**
|
|
63
|
+
* Two refs rather than one, because `SecretStore` holds strings and a key
|
|
64
|
+
* pair is two of them. Packing both into one value would mean encoding a
|
|
65
|
+
* secret that routinely contains `/` and `+`, and an encoding bug in a
|
|
66
|
+
* credential path fails at the least convenient moment.
|
|
67
|
+
*
|
|
68
|
+
* The access key id is a ref too, though it is closer to a username than a
|
|
69
|
+
* secret: Supabase issues 32-character hex ids, which the config secret
|
|
70
|
+
* detector reads as a high-entropy blob and rejects inline.
|
|
71
|
+
*/
|
|
72
|
+
access_key_id_ref: credentialRef.optional(),
|
|
73
|
+
secret_access_key_ref: credentialRef.optional(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extra places the log is copied to.
|
|
78
|
+
*
|
|
79
|
+
* Never a replacement: the durable log is objects in the blob store, written
|
|
80
|
+
* and awaited, and these are copies written best-effort behind a bounded
|
|
81
|
+
* queue. A collector being down must not fail a capability call, so a sink
|
|
82
|
+
* that can fail cannot be the one the guarantee rests on.
|
|
83
|
+
*/
|
|
84
|
+
export const auditSinkSchema = z.discriminatedUnion('kind', [
|
|
85
|
+
z.object({ kind: z.literal('stdout') }),
|
|
86
|
+
z.object({
|
|
87
|
+
kind: z.literal('otlp'),
|
|
88
|
+
/** The logs endpoint, e.g. `https://collector.example/v1/logs`. */
|
|
89
|
+
endpoint: z.string().url(),
|
|
90
|
+
/** Static headers, as a credential ref: an API key belongs in the store. */
|
|
91
|
+
headers_ref: credentialRef.optional(),
|
|
92
|
+
service_name: z.string().optional(),
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
export const auditTargetSchema = z.object({
|
|
97
|
+
/** Copies, in addition to the durable log. Empty or absent means no copies. */
|
|
98
|
+
sinks: z.array(auditSinkSchema).default([]),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Where the vault's encrypted document lives.
|
|
103
|
+
*
|
|
104
|
+
* Its own block rather than a reuse of `credentials`, because the vault must
|
|
105
|
+
* never be the credential store — the two hold different things and collapsing
|
|
106
|
+
* them is the single most damaging mistake available here (`docs/detailed/security.md`).
|
|
107
|
+
*
|
|
108
|
+
* Optional, defaulting to `file`: every profile written before ADR-014 keeps
|
|
109
|
+
* working unchanged, and a local run needs no vault configuration at all.
|
|
110
|
+
*
|
|
111
|
+
* `secret` is what a deployment uses: the whole sealed document as one entry in
|
|
112
|
+
* the target's own credential store. Not a merge of the two stores — it is
|
|
113
|
+
* ciphertext by the time it arrives, under a different key from a different
|
|
114
|
+
* environment variable, and the bucket arrangement it replaces was protecting
|
|
115
|
+
* the smaller asset with the taller fence (ADR-022).
|
|
116
|
+
*
|
|
117
|
+
* One entry rather than one per item, because the document is encrypted whole:
|
|
118
|
+
* item *names* are inside it, and a secret-per-item mapping would publish
|
|
119
|
+
* those names into a cloud IAM console to gain nothing this does not have.
|
|
120
|
+
*
|
|
121
|
+
* `blob` remains, for a target that would rather keep it beside the other
|
|
122
|
+
* objects.
|
|
123
|
+
*/
|
|
124
|
+
export const vaultTargetSchema = z.object({
|
|
125
|
+
adapter: z.enum(['file', 'blob', 'secret']),
|
|
126
|
+
/** File path, or blob key. Defaults to `./data/<profile>.vault.enc` / `vault.enc`. */
|
|
127
|
+
path: z.string().optional(),
|
|
128
|
+
/** `secret` only: where the sealed document lives. Defaults to `vault/document`. */
|
|
129
|
+
ref: credentialRef.optional(),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Where a target is rolled out, and who may reach it once it is.
|
|
134
|
+
*
|
|
135
|
+
* `platform` is the discriminator, and it is the only thing that decides which
|
|
136
|
+
* driver runs — the block used to be named `cloudrun`, which meant the *name of
|
|
137
|
+
* a key* selected a vendor and a second host could only ever be a second
|
|
138
|
+
* optional block that the loader had to disambiguate. Adapters are named for the
|
|
139
|
+
* protocol and deployments for the vendor (ADR-013); `platform` is how a target
|
|
140
|
+
* says which vendor without the schema growing a key per vendor.
|
|
141
|
+
*
|
|
142
|
+
* `project` is optional here for the same reason `credentials.project` is: it
|
|
143
|
+
* means something to one platform and nothing to the next, so the driver that
|
|
144
|
+
* needs it is the thing that refuses without it. The alternative — a
|
|
145
|
+
* discriminated union per platform — buys precision this file cannot use and
|
|
146
|
+
* costs a schema edit on every field any host ever adds.
|
|
147
|
+
*/
|
|
148
|
+
export const deployTargetSchema = z.object({
|
|
149
|
+
platform: z.enum(['cloudrun']),
|
|
150
|
+
region: z.string(),
|
|
151
|
+
service: z.string(),
|
|
152
|
+
/**
|
|
153
|
+
* `iam` puts the platform's own identity check in front of the service;
|
|
154
|
+
* `public` leaves the door open and relies on this application's gate.
|
|
155
|
+
*
|
|
156
|
+
* Defaults to `iam`, so a target that says nothing gets the closed one. Note
|
|
157
|
+
* what `iam` costs: it admits only callers who can mint an identity token for
|
|
158
|
+
* the host's own IAM, which no agent harness can do. A target reached by
|
|
159
|
+
* Claude or ChatGPT wants `public` plus an `auth.authorization` block, and
|
|
160
|
+
* that combination is the one `deploy` proposes.
|
|
161
|
+
*/
|
|
162
|
+
access: z.enum(['iam', 'public']).default('iam'),
|
|
163
|
+
/** Required by the `cloudrun` driver; meaningless to a platform without projects. */
|
|
164
|
+
project: z.string().optional(),
|
|
165
|
+
/**
|
|
166
|
+
* The billing account to attach `project` to, when this deploy created it.
|
|
167
|
+
*
|
|
168
|
+
* Present only when the project did not exist at survey time, and it is what
|
|
169
|
+
* makes the difference between a deploy that provisions a project and one that
|
|
170
|
+
* deploys into a project you already had. Absent is the ordinary case, and
|
|
171
|
+
* means "assume the project is there and billed".
|
|
172
|
+
*
|
|
173
|
+
* Not a credential: a billing account id names an account, it does not
|
|
174
|
+
* authorise a charge against it — spending is authorised by the IAM role your
|
|
175
|
+
* own login holds on it. So it belongs in config rather than the secret store,
|
|
176
|
+
* and `secret-detection.ts` has no reason to refuse it.
|
|
177
|
+
*/
|
|
178
|
+
billing_account: z.string().optional(),
|
|
179
|
+
/** The identity the running revision assumes. Needs read access to the credential store. */
|
|
180
|
+
service_account: z.string().optional(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The pre-`deploy` spelling, still accepted.
|
|
185
|
+
*
|
|
186
|
+
* Normalised into `deploy` below rather than read anywhere, so exactly one shape
|
|
187
|
+
* reaches the rest of the codebase. It gains `access: iam` in the process, which
|
|
188
|
+
* is a deliberate change of default for a config that predates the field — a
|
|
189
|
+
* deploy that was open stays open only by saying so.
|
|
190
|
+
*/
|
|
191
|
+
const legacyCloudRunSchema = z.object({
|
|
192
|
+
project: z.string(),
|
|
193
|
+
region: z.string(),
|
|
194
|
+
service: z.string(),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export const targetSchema = z
|
|
198
|
+
.object({
|
|
199
|
+
credentials: credentialsTargetSchema,
|
|
200
|
+
audit: auditTargetSchema.optional(),
|
|
201
|
+
storage: storageTargetSchema,
|
|
202
|
+
vault: vaultTargetSchema.optional(),
|
|
203
|
+
deploy: deployTargetSchema.optional(),
|
|
204
|
+
/** @deprecated Write `deploy` with `platform: cloudrun`. */
|
|
205
|
+
cloudrun: legacyCloudRunSchema.optional(),
|
|
206
|
+
})
|
|
207
|
+
.superRefine((target, ctx) => {
|
|
208
|
+
// Both present is refused rather than resolved by precedence: a second place
|
|
209
|
+
// to say where this deploys could only ever disagree with the first, and
|
|
210
|
+
// silently preferring one would roll a revision to the project the operator
|
|
211
|
+
// was not reading.
|
|
212
|
+
if (target.deploy && target.cloudrun) {
|
|
213
|
+
ctx.addIssue({
|
|
214
|
+
code: 'custom',
|
|
215
|
+
path: ['cloudrun'],
|
|
216
|
+
message:
|
|
217
|
+
'both "deploy" and "cloudrun" are declared — remove "cloudrun", which "deploy" replaces',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
.transform(({ cloudrun, ...target }) =>
|
|
222
|
+
target.deploy || !cloudrun
|
|
223
|
+
? target
|
|
224
|
+
: { ...target, deploy: { ...cloudrun, platform: 'cloudrun' as const, access: 'iam' as const } },
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A rule is a pattern, and usually just a string.
|
|
229
|
+
*
|
|
230
|
+
* The object form exists only for `expires_at`; writing `gmail.*` should not
|
|
231
|
+
* require learning a record shape. Both parse to the same thing.
|
|
232
|
+
*/
|
|
233
|
+
export const policyRuleSchema = z.union([
|
|
234
|
+
capabilityPattern.transform((capability) => ({ capability })),
|
|
235
|
+
z.object({
|
|
236
|
+
capability: capabilityPattern,
|
|
237
|
+
expires_at: z.iso.datetime({ offset: true }).optional(),
|
|
238
|
+
}),
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* One policy block for the whole profile.
|
|
243
|
+
*
|
|
244
|
+
* Rules name capabilities, never connections. Every account of a provider in a
|
|
245
|
+
* profile is governed identically, and granularity comes from running a
|
|
246
|
+
* narrower profile — profiles already share no database, no credential store,
|
|
247
|
+
* and no URL, which is a stronger boundary than a policy row ever was. The
|
|
248
|
+
* agent still names which account it is calling; policy simply does not
|
|
249
|
+
* discriminate between them.
|
|
250
|
+
*
|
|
251
|
+
* Default deny still holds where it counts: an absent or empty `policy` grants
|
|
252
|
+
* nothing at all. `connect` writes `allow: ['*']` because a connection you just
|
|
253
|
+
* authorised and cannot use is a bad first impression — but that is a default
|
|
254
|
+
* written into your file, visible and editable, not a behaviour of the engine.
|
|
255
|
+
*/
|
|
256
|
+
export const policySchema = z.object({
|
|
257
|
+
allow: z.array(policyRuleSchema).default([]),
|
|
258
|
+
deny: z.array(policyRuleSchema).default([]),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* One authorised account.
|
|
263
|
+
*
|
|
264
|
+
* `account` is the identity as the provider knows it — an email address, a
|
|
265
|
+
* workspace name — resolved at connect time rather than invented. A file that
|
|
266
|
+
* says `Gmail main2` cannot answer the only question anyone asks of it, which
|
|
267
|
+
* is *whose mailbox is this*. `id` is the stable key that `credential_ref` and
|
|
268
|
+
* the agent's `connection` argument point at, and it is derived from `account`
|
|
269
|
+
* so it means something too.
|
|
270
|
+
*/
|
|
271
|
+
export const connectionSchema = z.object({
|
|
272
|
+
id: z.string().regex(/^[a-z0-9][a-z0-9_]*$/, 'must be lowercase alphanumeric with underscores'),
|
|
273
|
+
provider: identifier,
|
|
274
|
+
account: z.string().min(1),
|
|
275
|
+
credential_ref: credentialRef.optional(),
|
|
276
|
+
/** Provider-specific, validated later against that provider's own schema. */
|
|
277
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
export const oauthAppSchema = z.object({
|
|
281
|
+
client_id_ref: credentialRef,
|
|
282
|
+
client_secret_ref: credentialRef,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
export const configSchema = z.object({
|
|
287
|
+
contract: z.number().int().positive(),
|
|
288
|
+
|
|
289
|
+
instance: z.object({
|
|
290
|
+
profile: identifier,
|
|
291
|
+
default_target: z.string().min(1),
|
|
292
|
+
port: z.number().int().min(1).max(65535).default(7337),
|
|
293
|
+
/**
|
|
294
|
+
* Loopback by default. Binding elsewhere is possible but the server
|
|
295
|
+
* refuses to combine it with anonymous auth.
|
|
296
|
+
*/
|
|
297
|
+
host: z.string().default('127.0.0.1'),
|
|
298
|
+
}),
|
|
299
|
+
|
|
300
|
+
targets: z.record(z.string(), targetSchema),
|
|
301
|
+
|
|
302
|
+
auth: z
|
|
303
|
+
.object({
|
|
304
|
+
mode: z.literal('bearer').default('bearer'),
|
|
305
|
+
token_ref: credentialRef.default('profile/token'),
|
|
306
|
+
/**
|
|
307
|
+
* Additive. `mode` above still describes what the endpoint accepts on the
|
|
308
|
+
* wire — a bearer token — and this describes where a *remote* client can
|
|
309
|
+
* get one. Omitting it leaves every existing profile behaving identically.
|
|
310
|
+
*/
|
|
311
|
+
authorization: authorizationSchema.optional(),
|
|
312
|
+
})
|
|
313
|
+
.default({ mode: 'bearer', token_ref: 'profile/token' }),
|
|
314
|
+
|
|
315
|
+
limits: z
|
|
316
|
+
.object({
|
|
317
|
+
requests_per_minute: z.number().int().positive().default(120),
|
|
318
|
+
upstream_calls_per_minute: z.number().int().positive().default(60),
|
|
319
|
+
})
|
|
320
|
+
.default({ requests_per_minute: 120, upstream_calls_per_minute: 60 }),
|
|
321
|
+
|
|
322
|
+
oauth_apps: z.record(identifier, oauthAppSchema).default({}),
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* There is no `providers` block. A provider is enabled by having a connection
|
|
326
|
+
* to it — a second place to say so could only ever disagree with the first,
|
|
327
|
+
* and everything else a provider needs is in its manifest.
|
|
328
|
+
*/
|
|
329
|
+
connections: z.array(connectionSchema).default([]),
|
|
330
|
+
policy: policySchema.default({ allow: [], deny: [] }),
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
export type Config = z.infer<typeof configSchema>;
|
|
334
|
+
export type ConnectionConfig = z.infer<typeof connectionSchema>;
|
|
335
|
+
export type PolicyRuleConfig = z.infer<typeof policyRuleSchema>;
|
|
336
|
+
export type TargetConfig = z.infer<typeof targetSchema>;
|
|
337
|
+
export type DeployConfig = z.infer<typeof deployTargetSchema>;
|
|
338
|
+
export type AuthorizationConfig = z.infer<typeof authorizationSchema>;
|
|
339
|
+
export { authorizationSchema };
|
|
340
|
+
|
|
341
|
+
/** The workspace file: `lanes-link.yaml`, alongside a `profiles/` directory. */
|
|
342
|
+
export const workspaceSchema = z.object({
|
|
343
|
+
contract: z.number().int().positive(),
|
|
344
|
+
default_profile: identifier.optional(),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
export type WorkspaceConfig = z.infer<typeof workspaceSchema>;
|