@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,342 @@
|
|
|
1
|
+
import { BearerAuthenticator, ownerPrincipal } from '#auth';
|
|
2
|
+
import type { SecretStore } from '#secrets';
|
|
3
|
+
import type { AuditReader } from '#audit';
|
|
4
|
+
import type { RuntimeState } from '#stores/state';
|
|
5
|
+
import type { BlobStore } from '#stores/blobs';
|
|
6
|
+
import type { AnyConnector, ProviderManifest } from '#connectivity';
|
|
7
|
+
import { RateLimiter, allowedConnections } from '#policy';
|
|
8
|
+
import {
|
|
9
|
+
ConfigError,
|
|
10
|
+
layout,
|
|
11
|
+
listProfiles,
|
|
12
|
+
workspacePath,
|
|
13
|
+
type Config,
|
|
14
|
+
type Resolution,
|
|
15
|
+
} from '#profile';
|
|
16
|
+
import { ProviderRegistry, toPolicyDocument } from '#registry';
|
|
17
|
+
import { Dispatcher, createConsoleLogger } from '#dispatch';
|
|
18
|
+
import { PROVIDER_MANIFESTS } from '#providers/index.ts';
|
|
19
|
+
import {
|
|
20
|
+
createBlobVaultStore,
|
|
21
|
+
createFileVaultStore,
|
|
22
|
+
createSecretVaultStore,
|
|
23
|
+
type VaultStore,
|
|
24
|
+
} from '#providers/owner.ts';
|
|
25
|
+
import {
|
|
26
|
+
openAuditSinks,
|
|
27
|
+
openState,
|
|
28
|
+
openSecrets,
|
|
29
|
+
openStorage,
|
|
30
|
+
type StorageFactory,
|
|
31
|
+
type TargetInput,
|
|
32
|
+
} from '#deployments/target.ts';
|
|
33
|
+
import { connectorFactory } from '#connectivity/transports';
|
|
34
|
+
import { requestAuthorizer } from '#connectivity/auth/index.ts';
|
|
35
|
+
import { resolveProfile, type GlobalFlags } from './select.ts';
|
|
36
|
+
import { buildRegistryWithWorkspace, reloadSkills, skillFingerprint } from './registry.ts';
|
|
37
|
+
import { discoveryProbe } from './discovery.ts';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Assembling a profile's runtime from its declared target.
|
|
41
|
+
*
|
|
42
|
+
* The same config can run in more than one place; a target names an adapter
|
|
43
|
+
* set. Connections, providers, policy, and limits are declared once and apply
|
|
44
|
+
* to every target — only the adapters differ, which is what lets M2 change
|
|
45
|
+
* nothing at the application layer.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
export interface Runtime {
|
|
49
|
+
readonly resolution: Resolution;
|
|
50
|
+
readonly config: Config;
|
|
51
|
+
readonly target: string;
|
|
52
|
+
readonly state: RuntimeState;
|
|
53
|
+
/** The durable log, for reading. Copies, if any, are write-only and not here. */
|
|
54
|
+
readonly audit: AuditReader;
|
|
55
|
+
readonly credentials: SecretStore;
|
|
56
|
+
readonly storage: BlobStore;
|
|
57
|
+
/** Where skills are kept — `data/<profile>/skills.d/` locally. */
|
|
58
|
+
readonly skills: BlobStore;
|
|
59
|
+
/** The vault's own store, so `lanes link vault` reaches the same bytes MCP does. */
|
|
60
|
+
readonly vault: VaultStore;
|
|
61
|
+
readonly registry: ProviderRegistry;
|
|
62
|
+
/**
|
|
63
|
+
* Re-read the skills into the registry when they have changed on the store.
|
|
64
|
+
*
|
|
65
|
+
* Cheap and idempotent — one `list()`, and a rebuild only when the listing
|
|
66
|
+
* differs from the last one. The caller decides how often to ask.
|
|
67
|
+
*/
|
|
68
|
+
refreshSkills(): Promise<void>;
|
|
69
|
+
readonly dispatcher: Dispatcher;
|
|
70
|
+
readonly authenticator: BearerAuthenticator;
|
|
71
|
+
/** Same factory the dispatcher uses, exposed for commands that probe upstream. */
|
|
72
|
+
connectorFor(providerId: string, connectionId: string): AnyConnector | undefined;
|
|
73
|
+
/** A provider's manifest, so an omitted `credential_ref` can be derived from it. */
|
|
74
|
+
manifestFor(providerId: string): ProviderManifest | undefined;
|
|
75
|
+
close(): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Where this profile's skills live, in either target.
|
|
80
|
+
*
|
|
81
|
+
* Locally `data/<profile>/skills.d/`; deployed, the same key under the bucket
|
|
82
|
+
* prefix. Going through the store rather than a filesystem path is what gives a
|
|
83
|
+
* deployment skills at all — a path is baked into a container image at build
|
|
84
|
+
* time and an object key is not, so before ADR-014 a deployed instance could
|
|
85
|
+
* only ever serve the skills that existed when its image was built.
|
|
86
|
+
*
|
|
87
|
+
* **Per profile**, which reverses ADR-012 §1. Policy gating `skills.<name>`
|
|
88
|
+
* was the whole isolation story while the bytes were shared, and it is a weak
|
|
89
|
+
* one: it decides who may *run* a procedure, not who may read that it exists or
|
|
90
|
+
* what it says. A skill written for work names work's accounts and work's
|
|
91
|
+
* people. ADR-030.
|
|
92
|
+
*
|
|
93
|
+
* An explicit area, matching `openState` and `openAudit` — a profile that
|
|
94
|
+
* declares its own `storage.path` moves its provider blobs, not the reserved
|
|
95
|
+
* roots beside them.
|
|
96
|
+
*/
|
|
97
|
+
function skillStore(storage: StorageFactory, profile: string): BlobStore {
|
|
98
|
+
return storage(layout.skills(profile));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The vault's encrypted document, wherever this target keeps it.
|
|
103
|
+
*
|
|
104
|
+
* Defaults to `file`, so a profile written before ADR-014 needs no change and a
|
|
105
|
+
* local run needs no vault configuration at all. `blob` exists because the file
|
|
106
|
+
* adapter was unconditional before: a deployed instance wrote its vault to a
|
|
107
|
+
* container filesystem, and every item in it was discarded by the next
|
|
108
|
+
* revision without an error to say so.
|
|
109
|
+
*/
|
|
110
|
+
function openVault(
|
|
111
|
+
input: TargetInput,
|
|
112
|
+
storage: StorageFactory,
|
|
113
|
+
credentials: SecretStore,
|
|
114
|
+
): VaultStore {
|
|
115
|
+
const { declared, config, root } = input;
|
|
116
|
+
const vault = declared.vault ?? { adapter: 'file' as const };
|
|
117
|
+
|
|
118
|
+
switch (vault.adapter) {
|
|
119
|
+
case 'file':
|
|
120
|
+
return createFileVaultStore({
|
|
121
|
+
path: workspacePath(root, vault.path ?? layout.vault(config.instance.profile)),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
case 'secret':
|
|
125
|
+
// The document is sealed under LANES_LINK_VAULT_KEY before it gets here,
|
|
126
|
+
// so the credential store holds ciphertext it cannot read. Separate
|
|
127
|
+
// document, separate key, separate environment variable — the backend
|
|
128
|
+
// was never what kept the two stores apart. ADR-022.
|
|
129
|
+
return createSecretVaultStore({
|
|
130
|
+
store: credentials,
|
|
131
|
+
...(vault.ref !== undefined ? { ref: vault.ref } : {}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
case 'blob':
|
|
135
|
+
return createBlobVaultStore({
|
|
136
|
+
store: storage(),
|
|
137
|
+
...(vault.path !== undefined ? { key: vault.path } : {}),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function openRuntime(flags: GlobalFlags): Promise<Runtime> {
|
|
143
|
+
const { resolution, config, target } = await resolveProfile(flags);
|
|
144
|
+
const declared = config.targets[target];
|
|
145
|
+
if (!declared) throw new ConfigError(`Target "${target}" is not declared`);
|
|
146
|
+
|
|
147
|
+
const root = resolution.workspaceRoot;
|
|
148
|
+
const adapters: TargetInput = { declared, config, root, target };
|
|
149
|
+
|
|
150
|
+
// Credentials first: an S3 key pair is itself a credential reference, so the
|
|
151
|
+
// credential store has to exist before the blob store that names it. State
|
|
152
|
+
// and the log then ride that blob store rather than opening backends of
|
|
153
|
+
// their own, which is why there is no migrate step here any more — there is
|
|
154
|
+
// no schema left to migrate.
|
|
155
|
+
const credentials = await openSecrets(adapters);
|
|
156
|
+
const storageFor = await openStorage(adapters, credentials);
|
|
157
|
+
const storage = storageFor();
|
|
158
|
+
const state = openState(storageFor, config.instance.profile);
|
|
159
|
+
|
|
160
|
+
// The durable log, plus any copies the target declares. `sink` is what
|
|
161
|
+
// dispatch writes to; `audit` is what `tail` and `verify` read, and those are
|
|
162
|
+
// not the same object once a fan-out exists — a copy is not the log.
|
|
163
|
+
const logger = createConsoleLogger(flags.quiet ? 'error' : 'info');
|
|
164
|
+
const { sink: auditSink, reader: audit } = await openAuditSinks(
|
|
165
|
+
adapters,
|
|
166
|
+
storageFor,
|
|
167
|
+
credentials,
|
|
168
|
+
(message) => logger.warn(message),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const skills = skillStore(storageFor, config.instance.profile);
|
|
172
|
+
|
|
173
|
+
// The vault's own store, beside the credential store and never it: a separate
|
|
174
|
+
// document, a separate key, and a separate environment variable
|
|
175
|
+
// (`LANES_LINK_VAULT_KEY`). One master secret reused across purposes turns any
|
|
176
|
+
// single compromise into a total one — `docs/detailed/security.md`, and the boundary
|
|
177
|
+
// test that has existed since M1.
|
|
178
|
+
//
|
|
179
|
+
// Opened before the registry because each stored item becomes its own
|
|
180
|
+
// `vault.get.<id>` capability, which is what makes per-item policy
|
|
181
|
+
// expressible; an item written since the last start is therefore not readable
|
|
182
|
+
// until the next one, deliberately (ADR-012 §3).
|
|
183
|
+
const vaultStore = openVault(adapters, storageFor, credentials);
|
|
184
|
+
|
|
185
|
+
// `refreshSkills` is declared before the registry it mutates because the
|
|
186
|
+
// provider has to be handed it at construction: a `skills.manage.write` says
|
|
187
|
+
// that something changed, and this is what that means. It is only ever
|
|
188
|
+
// *called* after the registry exists.
|
|
189
|
+
let registry: ProviderRegistry;
|
|
190
|
+
let fingerprint = '';
|
|
191
|
+
const refreshSkills = async (): Promise<void> => {
|
|
192
|
+
fingerprint = await reloadSkills(registry, skills, fingerprint, refreshSkills);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Computed before the registry because the setup surface is handed a way to
|
|
196
|
+
// ask what this principal may reach — through the *same* `allowedConnections`
|
|
197
|
+
// the dispatcher enforces with and `mergeCapabilities` builds the connection
|
|
198
|
+
// enum from. Computing visibility a second way is how discovery and
|
|
199
|
+
// enforcement drift, and a leak in discovery is still a leak.
|
|
200
|
+
const policy = toPolicyDocument(config);
|
|
201
|
+
// The owner, explicitly, rather than assumed: when a delegated principal
|
|
202
|
+
// arrives this becomes the seam, and `reachable` grows a parameter instead of
|
|
203
|
+
// being rewritten.
|
|
204
|
+
const principal = ownerPrincipal(config.instance.profile).id;
|
|
205
|
+
|
|
206
|
+
// Lazy for the same reason `refreshSkills` is: it reads the registry it is
|
|
207
|
+
// registered into. Per call rather than a snapshot, so a policy the runtime
|
|
208
|
+
// was opened with is re-evaluated rather than remembered.
|
|
209
|
+
const reachable = (): ReadonlyArray<{ key: string; provider: string; account: string }> =>
|
|
210
|
+
config.connections
|
|
211
|
+
.filter((connection) =>
|
|
212
|
+
registry
|
|
213
|
+
.capabilities()
|
|
214
|
+
.some(
|
|
215
|
+
({ id }) =>
|
|
216
|
+
id.startsWith(`${connection.provider}.`) &&
|
|
217
|
+
allowedConnections(
|
|
218
|
+
id,
|
|
219
|
+
[`${connection.provider}.${connection.id}`],
|
|
220
|
+
principal,
|
|
221
|
+
policy,
|
|
222
|
+
).length > 0,
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
.map((connection) => ({
|
|
226
|
+
key: `${connection.provider}.${connection.id}`,
|
|
227
|
+
provider: connection.provider,
|
|
228
|
+
account: connection.account,
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
registry = await buildRegistryWithWorkspace(root, config.instance.profile, {
|
|
232
|
+
skillStore: skills,
|
|
233
|
+
onSkillsChanged: refreshSkills,
|
|
234
|
+
vault: { store: vaultStore, items: await vaultStore.ids() },
|
|
235
|
+
setup: {
|
|
236
|
+
profile: config.instance.profile,
|
|
237
|
+
profiles: await listProfiles(root),
|
|
238
|
+
catalogue: PROVIDER_MANIFESTS,
|
|
239
|
+
ownClients: Object.keys(config.oauth_apps),
|
|
240
|
+
reachable,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
// Seeded from the build that just happened, so the first refresh compares
|
|
244
|
+
// against what is registered rather than rebuilding once to find out.
|
|
245
|
+
fingerprint = await skillFingerprint(skills);
|
|
246
|
+
|
|
247
|
+
// Discovered capabilities never come from a live call on the dispatch path —
|
|
248
|
+
// that is what keeps the server stateless. But "not live" is not the same as
|
|
249
|
+
// "cached", and conflating the two was a bug: `connect` was the only writer of
|
|
250
|
+
// this cache, so an operator who upgraded without re-authorising kept whatever
|
|
251
|
+
// their last consent screen happened to discover. Drive shipped nine
|
|
252
|
+
// operations and served six; Gmail served a `drafts.create` this repository
|
|
253
|
+
// had deleted, because the spec is read by the tests and the cache is read by
|
|
254
|
+
// the endpoint.
|
|
255
|
+
//
|
|
256
|
+
// So: derive it where deriving is free, and cache it only where it is not. An
|
|
257
|
+
// `http` provider's capabilities are a pure function of a document committed
|
|
258
|
+
// here — reviewed in a diff, not fetched from a vendor — which is the property
|
|
259
|
+
// that makes re-deriving safe as well as cheap.
|
|
260
|
+
for (const entry of registry.list()) {
|
|
261
|
+
if (entry.manifest.connector.kind === 'local') continue;
|
|
262
|
+
|
|
263
|
+
const probe = discoveryProbe(entry.manifest);
|
|
264
|
+
if (probe?.cost === 'offline') {
|
|
265
|
+
try {
|
|
266
|
+
registry.setDiscovered(entry.manifest.id, await probe.run());
|
|
267
|
+
continue;
|
|
268
|
+
} catch {
|
|
269
|
+
// A malformed committed spec is a build problem, not a reason to refuse
|
|
270
|
+
// to start — fall through to whatever the cache last held.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const cached = await state.kv.get('discovery', entry.manifest.id);
|
|
275
|
+
if (cached) {
|
|
276
|
+
try {
|
|
277
|
+
registry.setDiscovered(entry.manifest.id, JSON.parse(cached));
|
|
278
|
+
} catch {
|
|
279
|
+
// A corrupt cache entry means "not discovered yet", which `plan`
|
|
280
|
+
// reports and `connect` fixes — never a reason to fail startup.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// One factory for the whole runtime, so its cache actually holds. The
|
|
286
|
+
// dispatcher and the CLI share it deliberately: a stateful connector must be
|
|
287
|
+
// the *same instance* whichever side asks for it, or a held session is held
|
|
288
|
+
// twice.
|
|
289
|
+
const connectorFor = connectorFactory({ registry, credentials });
|
|
290
|
+
let closed = false;
|
|
291
|
+
|
|
292
|
+
const dispatcher = new Dispatcher({
|
|
293
|
+
config,
|
|
294
|
+
registry,
|
|
295
|
+
connectorFor,
|
|
296
|
+
authorizeRequest: requestAuthorizer(registry, credentials),
|
|
297
|
+
policy,
|
|
298
|
+
state,
|
|
299
|
+
audit: auditSink,
|
|
300
|
+
credentials,
|
|
301
|
+
storage,
|
|
302
|
+
limiter: new RateLimiter(),
|
|
303
|
+
log: logger,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
resolution,
|
|
308
|
+
config,
|
|
309
|
+
target,
|
|
310
|
+
state,
|
|
311
|
+
audit,
|
|
312
|
+
credentials,
|
|
313
|
+
storage,
|
|
314
|
+
skills,
|
|
315
|
+
vault: vaultStore,
|
|
316
|
+
registry,
|
|
317
|
+
refreshSkills,
|
|
318
|
+
dispatcher,
|
|
319
|
+
authenticator: new BearerAuthenticator({
|
|
320
|
+
profile: config.instance.profile,
|
|
321
|
+
tokenRef: config.auth.token_ref,
|
|
322
|
+
credentials,
|
|
323
|
+
}),
|
|
324
|
+
connectorFor,
|
|
325
|
+
manifestFor: (providerId: string) => registry.manifest(providerId),
|
|
326
|
+
// Sessions first, then the state: a connector may still want to log out
|
|
327
|
+
// cleanly, and LOGOUT is worth more than the microsecond it costs.
|
|
328
|
+
//
|
|
329
|
+
// Idempotent, because a command that finishes early closes explicitly and
|
|
330
|
+
// its `finally` then closes again — and closing a SQLite handle twice is an
|
|
331
|
+
// error that would surface as a crash after the work already succeeded.
|
|
332
|
+
close: async () => {
|
|
333
|
+
if (closed) return;
|
|
334
|
+
closed = true;
|
|
335
|
+
await connectorFor.closeAll();
|
|
336
|
+
// Closing the log is not releasing a handle: it writes the marker that
|
|
337
|
+
// lets `audit verify` tell a run that ended from one whose tail was cut
|
|
338
|
+
// off. A crash leaves the run open, which is the honest outcome.
|
|
339
|
+
await auditSink.close();
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { BlobStore } from '#stores/blobs';
|
|
2
|
+
import type { ProviderDefinition } from '#connectivity';
|
|
3
|
+
import { ConfigError } from '#profile';
|
|
4
|
+
import { ProviderRegistry } from '#registry';
|
|
5
|
+
import { loadProfileProviders } from '#providers/custom/index.ts';
|
|
6
|
+
import { loadProfileSkills, type LoadedSkill } from '#providers/skills/store.ts';
|
|
7
|
+
import { exampleProvider } from '#providers/example/provider.ts';
|
|
8
|
+
import {
|
|
9
|
+
createMemoryVaultStore,
|
|
10
|
+
createSetupProvider,
|
|
11
|
+
createSkillsProvider,
|
|
12
|
+
createVaultProvider,
|
|
13
|
+
memoryProvider,
|
|
14
|
+
type SetupProviderOptions,
|
|
15
|
+
type VaultStore,
|
|
16
|
+
} from '#providers/owner.ts';
|
|
17
|
+
import { PROVIDERS, PROVIDER_MANIFESTS } from '#providers/index.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Which providers exist, and keeping the skills among them current.
|
|
21
|
+
*
|
|
22
|
+
* Separate from `./open.ts` because building a registry is synchronous and
|
|
23
|
+
* needs no adapters: `lanes link deploy` reads manifests and never opens a database.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** What the owner layer needs from its store before it can be built. */
|
|
27
|
+
export interface OwnerLayerOptions {
|
|
28
|
+
/** Skills already loaded. Each becomes an MCP prompt. */
|
|
29
|
+
readonly skills?: readonly LoadedSkill[];
|
|
30
|
+
/**
|
|
31
|
+
* Where to load them from, and where authoring writes.
|
|
32
|
+
*
|
|
33
|
+
* Its presence is what gives the provider its `skills.manage.*` half: a
|
|
34
|
+
* registry built without one — `lanes link deploy` reads manifests and nothing else —
|
|
35
|
+
* gets the prompts and no way to author them, rather than four capabilities
|
|
36
|
+
* that would fail.
|
|
37
|
+
*/
|
|
38
|
+
readonly skillStore?: BlobStore;
|
|
39
|
+
/** Called after a skill is written or removed, so the registry can catch up. */
|
|
40
|
+
readonly onSkillsChanged?: () => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* The vault store, and the items already in it.
|
|
43
|
+
*
|
|
44
|
+
* Both are resolved by the caller because opening an encrypted file is
|
|
45
|
+
* asynchronous and building a registry is not. The item list has to be known
|
|
46
|
+
* *here*: each item is its own `vault.get.<id>` capability, which is what
|
|
47
|
+
* makes per-item policy expressible without teaching the policy engine about
|
|
48
|
+
* arguments (ADR-012 §3).
|
|
49
|
+
*/
|
|
50
|
+
readonly vault?: {
|
|
51
|
+
readonly store: VaultStore;
|
|
52
|
+
readonly items: ReadonlyArray<{ id: string; description?: string }>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* What the read-only setup surface describes.
|
|
56
|
+
*
|
|
57
|
+
* `reachable` is a function evaluated per call, and the caller supplies it
|
|
58
|
+
* because filtering it is a policy decision: `#providers` may not import
|
|
59
|
+
* `#policy`, and computing visibility anywhere but through
|
|
60
|
+
* `allowedConnections` is how discovery and enforcement drift apart.
|
|
61
|
+
*
|
|
62
|
+
* Absent for a registry built to read manifests — `lanes link deploy` — which
|
|
63
|
+
* then gets a surface describing the catalogue and no accounts, rather than
|
|
64
|
+
* two capabilities that would report an empty profile as the truth.
|
|
65
|
+
*/
|
|
66
|
+
readonly setup?: SetupProviderOptions;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Built-in providers: the local reference, the owner layer, plus the manifests.
|
|
71
|
+
*
|
|
72
|
+
* Statically imported for now; the registry does not care where a manifest came
|
|
73
|
+
* from, which is what lets workspace YAML register alongside these.
|
|
74
|
+
*
|
|
75
|
+
* `allowReserved` is what admits `memory`, `skills`, and `vault`. The guard
|
|
76
|
+
* stays rather than being retired: it exists so a *third-party* provider cannot
|
|
77
|
+
* claim a namespace whose policy rules would then silently mean something else,
|
|
78
|
+
* and that reason survives the owner layer shipping. Only this one construction
|
|
79
|
+
* site opts in.
|
|
80
|
+
*/
|
|
81
|
+
export function buildRegistry(owner: OwnerLayerOptions = {}): ProviderRegistry {
|
|
82
|
+
const registry = new ProviderRegistry({ allowReserved: true });
|
|
83
|
+
|
|
84
|
+
registry.register(exampleProvider);
|
|
85
|
+
registry.register(memoryProvider);
|
|
86
|
+
registry.register(skillsProviderFor(owner));
|
|
87
|
+
registry.register(
|
|
88
|
+
createVaultProvider({
|
|
89
|
+
// No store means no reads to register; `vault.put` still works, and the
|
|
90
|
+
// items it writes become readable at the next start.
|
|
91
|
+
store: owner.vault?.store ?? createMemoryVaultStore(),
|
|
92
|
+
items: owner.vault?.items ?? [],
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
registry.register(
|
|
97
|
+
createSetupProvider(
|
|
98
|
+
owner.setup ?? { profile: '', catalogue: PROVIDER_MANIFESTS },
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
for (const manifest of PROVIDERS) registry.register(manifest);
|
|
103
|
+
return registry;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Built-ins plus whatever the operator has dropped into this profile.
|
|
108
|
+
*
|
|
109
|
+
* A manifest registering under a built-in id is refused rather than silently
|
|
110
|
+
* overriding it — an operator shadowing `gmail` by accident would be very hard
|
|
111
|
+
* to diagnose from the outside.
|
|
112
|
+
*
|
|
113
|
+
* The profile is a parameter rather than read from a config here because two of
|
|
114
|
+
* the three callers do not have one: `deploy` walks every profile in turn, and
|
|
115
|
+
* `profile remove` is holding the name of the one being deleted. Passing it
|
|
116
|
+
* makes "which profile's manifests" a question the caller has already answered.
|
|
117
|
+
*/
|
|
118
|
+
export async function buildRegistryWithWorkspace(
|
|
119
|
+
root: string,
|
|
120
|
+
profile: string,
|
|
121
|
+
owner: OwnerLayerOptions = {},
|
|
122
|
+
): Promise<ProviderRegistry> {
|
|
123
|
+
const skills =
|
|
124
|
+
owner.skills ?? (owner.skillStore ? await loadProfileSkills(owner.skillStore) : []);
|
|
125
|
+
const registry = buildRegistry({ ...owner, skills });
|
|
126
|
+
|
|
127
|
+
for (const { manifest, path } of await loadProfileProviders(root, profile)) {
|
|
128
|
+
if (registry.has(manifest.id)) {
|
|
129
|
+
throw new ConfigError(
|
|
130
|
+
`${path}: provider "${manifest.id}" is already built in. Rename it, or remove the file to use the built-in.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
registry.register(manifest, 'workspace');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return registry;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function skillsProviderFor(owner: OwnerLayerOptions): ProviderDefinition {
|
|
140
|
+
return createSkillsProvider({
|
|
141
|
+
skills: owner.skills ?? [],
|
|
142
|
+
...(owner.skillStore ? { store: owner.skillStore } : {}),
|
|
143
|
+
...(owner.onSkillsChanged ? { onChange: owner.onSkillsChanged } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Re-read the skills and swap them into the registry, if anything changed.
|
|
149
|
+
*
|
|
150
|
+
* Each skill is its own capability, so both halves of ADR-014's promise need
|
|
151
|
+
* this: a skill written over MCP has to appear without a restart, and so does
|
|
152
|
+
* one written by `lanes link skills add` in another terminal while the endpoint runs.
|
|
153
|
+
*
|
|
154
|
+
* The listing is the change check as well as the source. `BlobMetadata` carries
|
|
155
|
+
* size and mtime, so a fingerprint costs one `list()` and no reads — which
|
|
156
|
+
* matters on S3, where the alternative is fetching every skill on every poll.
|
|
157
|
+
*
|
|
158
|
+
* Returns the fingerprint it settled on, so the caller can decide when to ask
|
|
159
|
+
* again rather than this deciding for it.
|
|
160
|
+
*/
|
|
161
|
+
export async function reloadSkills(
|
|
162
|
+
registry: ProviderRegistry,
|
|
163
|
+
store: BlobStore,
|
|
164
|
+
previous: string,
|
|
165
|
+
onChange: () => Promise<void>,
|
|
166
|
+
): Promise<string> {
|
|
167
|
+
const fingerprint = await skillFingerprint(store);
|
|
168
|
+
if (fingerprint === previous) return previous;
|
|
169
|
+
|
|
170
|
+
registry.replace(
|
|
171
|
+
skillsProviderFor({
|
|
172
|
+
skills: await loadProfileSkills(store),
|
|
173
|
+
skillStore: store,
|
|
174
|
+
onSkillsChanged: onChange,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
return fingerprint;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function skillFingerprint(store: BlobStore): Promise<string> {
|
|
181
|
+
return (await store.list())
|
|
182
|
+
.map((blob) => `${blob.key}:${blob.size}:${blob.modifiedAt.getTime()}`)
|
|
183
|
+
.sort()
|
|
184
|
+
.join('\n');
|
|
185
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { generateProfileToken, ownerPrincipal } from '#auth';
|
|
2
|
+
import type { SecretStore } from '#secrets';
|
|
3
|
+
import type { BlobStore } from '#stores/blobs';
|
|
4
|
+
import {
|
|
5
|
+
loadProfileConfig,
|
|
6
|
+
resolveSelection,
|
|
7
|
+
resolveTarget,
|
|
8
|
+
undeclaredTarget,
|
|
9
|
+
type Config,
|
|
10
|
+
type Resolution,
|
|
11
|
+
} from '#profile';
|
|
12
|
+
import { openSecrets, openStorage } from '#deployments/target.ts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Which profile and target a command acts on, and the credentials that go with
|
|
16
|
+
* them — everything that can be settled without opening a database.
|
|
17
|
+
*
|
|
18
|
+
* Kept apart from `./open.ts` because two callers need exactly this much and
|
|
19
|
+
* nothing more: `secrets push` holds two targets open at once, and `deploy`
|
|
20
|
+
* checks a cloud store only reachable from inside Google's
|
|
21
|
+
* network. A full runtime would fail on the part neither of them uses.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export interface GlobalFlags {
|
|
25
|
+
readonly profile?: string | undefined;
|
|
26
|
+
readonly target?: string | undefined;
|
|
27
|
+
readonly quiet?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function resolveProfile(
|
|
31
|
+
flags: GlobalFlags,
|
|
32
|
+
options: {
|
|
33
|
+
/** `deploy` creates the target it is given; see `resolveTarget`. */
|
|
34
|
+
allowUndeclaredTarget?: boolean;
|
|
35
|
+
/** Injected by tests. Both resolutions must read the same one. */
|
|
36
|
+
env?: Record<string, string | undefined>;
|
|
37
|
+
} = {},
|
|
38
|
+
): Promise<{
|
|
39
|
+
resolution: Resolution;
|
|
40
|
+
config: Config;
|
|
41
|
+
target: string;
|
|
42
|
+
}> {
|
|
43
|
+
// Spread rather than assigned: `exactOptionalPropertyTypes` makes an explicit
|
|
44
|
+
// `env: undefined` a different type from an absent one, and the absent one is
|
|
45
|
+
// what means "read the real environment".
|
|
46
|
+
const env = options.env !== undefined ? { env: options.env } : {};
|
|
47
|
+
|
|
48
|
+
const selection = await resolveSelection({
|
|
49
|
+
profileFlag: flags.profile,
|
|
50
|
+
targetFlag: flags.target,
|
|
51
|
+
...env,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { config } = await loadProfileConfig(selection.workspaceRoot, selection.profile);
|
|
55
|
+
const { target, source } = resolveTarget(config, flags.target, {
|
|
56
|
+
allowUndeclared: options.allowUndeclaredTarget === true,
|
|
57
|
+
...env,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
resolution: { ...selection, target, targetSource: source },
|
|
62
|
+
config,
|
|
63
|
+
target,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* One target's secret store, without opening its database.
|
|
69
|
+
*
|
|
70
|
+
* `secrets push` holds two targets open at once and touches neither state,
|
|
71
|
+
* and `deploy` checks the cloud store while its Postgres is only reachable from
|
|
72
|
+
* inside Google's network. Opening a full runtime for either would fail on the
|
|
73
|
+
* part that is not needed.
|
|
74
|
+
*/
|
|
75
|
+
export async function openSecretStoreFor(
|
|
76
|
+
config: Config,
|
|
77
|
+
root: string,
|
|
78
|
+
target: string,
|
|
79
|
+
): Promise<SecretStore> {
|
|
80
|
+
const declared = config.targets[target];
|
|
81
|
+
if (!declared) throw undeclaredTarget(target, config);
|
|
82
|
+
return openSecrets({ declared, config, root, target });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One target's blob store, for a caller with no use for a runtime.
|
|
87
|
+
*
|
|
88
|
+
* The sibling of `openSecretStoreFor` above, and here for the reason this file
|
|
89
|
+
* already gives: removal enumerates a target's objects and never dispatches a
|
|
90
|
+
* call, so a registry and a reconcile would only add parts that can fail.
|
|
91
|
+
*
|
|
92
|
+
* `area` reaches a root other than the profile's own. The default is the
|
|
93
|
+
* profile's blob tree; `profiles` is where a deployed revision reads its config
|
|
94
|
+
* from (ADR-023), which lives outside that tree and still belongs to the
|
|
95
|
+
* profile being removed.
|
|
96
|
+
*/
|
|
97
|
+
export async function openBlobStoreFor(
|
|
98
|
+
config: Config,
|
|
99
|
+
root: string,
|
|
100
|
+
target: string,
|
|
101
|
+
area?: string,
|
|
102
|
+
): Promise<BlobStore> {
|
|
103
|
+
const declared = config.targets[target];
|
|
104
|
+
if (!declared) throw undeclaredTarget(target, config);
|
|
105
|
+
|
|
106
|
+
const input = { declared, config, root, target };
|
|
107
|
+
const storage = await openStorage(input, await openSecrets(input));
|
|
108
|
+
return area === undefined ? storage() : storage(area);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Mint the profile token if it does not exist yet. Returns it either way. */
|
|
112
|
+
export async function ensureProfileToken(
|
|
113
|
+
credentials: SecretStore,
|
|
114
|
+
tokenRef: string,
|
|
115
|
+
): Promise<{ token: string; created: boolean }> {
|
|
116
|
+
const existing = await credentials.get(tokenRef);
|
|
117
|
+
if (existing) return { token: existing, created: false };
|
|
118
|
+
|
|
119
|
+
const token = generateProfileToken();
|
|
120
|
+
await credentials.set(tokenRef, token);
|
|
121
|
+
return { token, created: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { ownerPrincipal };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's composition root — what a command gets handed before it runs.
|
|
3
|
+
*
|
|
4
|
+
* Three files, split along what each caller actually needs:
|
|
5
|
+
*
|
|
6
|
+
* select.ts which profile and target, and that target's secrets — the
|
|
7
|
+
* part that opens no database
|
|
8
|
+
* registry.ts which providers exist, and keeping the skills among them
|
|
9
|
+
* current; synchronous, and needs no adapters
|
|
10
|
+
* open.ts the whole runtime: database, storage, vault, dispatcher
|
|
11
|
+
*
|
|
12
|
+
* The seam is not cosmetic. `secrets push` holds two targets open at once and
|
|
13
|
+
* `deploy` reads manifests against a bucket it cannot reach, so both stop at
|
|
14
|
+
* `select.ts` and would fail on what `open.ts` does. This file stays a barrel
|
|
15
|
+
* because `#cli/runtime.ts` is the spelling `#server` and `#deployments` bind,
|
|
16
|
+
* and two test files import it directly.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
ensureProfileToken,
|
|
21
|
+
openBlobStoreFor,
|
|
22
|
+
openSecretStoreFor,
|
|
23
|
+
ownerPrincipal,
|
|
24
|
+
resolveProfile,
|
|
25
|
+
type GlobalFlags,
|
|
26
|
+
} from './runtime/select.ts';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
buildRegistry,
|
|
30
|
+
buildRegistryWithWorkspace,
|
|
31
|
+
type OwnerLayerOptions,
|
|
32
|
+
} from './runtime/registry.ts';
|
|
33
|
+
|
|
34
|
+
export { openRuntime, type Runtime } from './runtime/open.ts';
|