@lanes-sh/link 0.2.2 → 0.3.1
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/README.md +22 -8
- package/instructions/skills/lanes-link/SKILL.md +42 -14
- package/package.json +1 -1
- package/src/cli/argv.ts +50 -0
- package/src/cli/brand.ts +178 -0
- package/src/cli/callback-page.ts +108 -128
- package/src/cli/commands/connect/accounts.ts +5 -0
- package/src/cli/commands/connect/assertion.ts +187 -0
- package/src/cli/commands/connect/authorise.ts +61 -17
- package/src/cli/commands/connect/client.ts +37 -9
- package/src/cli/commands/connect/discover.ts +94 -0
- package/src/cli/commands/connect/family.ts +72 -0
- package/src/cli/commands/connect/index.ts +113 -115
- package/src/cli/commands/connect/method.ts +237 -0
- package/src/cli/commands/connect/outcome.ts +42 -1
- package/src/cli/commands/connect/pasted-token.ts +66 -0
- package/src/cli/commands/connect/requirements.ts +60 -8
- package/src/cli/commands/connect/setup.ts +16 -5
- package/src/cli/commands/connect/target-note.ts +34 -0
- package/src/cli/commands/identity.ts +258 -0
- package/src/cli/commands/knowledge/index.ts +390 -0
- package/src/cli/commands/knowledge/migrate.ts +180 -0
- package/src/cli/commands/knowledge/setup.ts +144 -0
- package/src/cli/commands/knowledge.ts +10 -0
- package/src/cli/commands/mcp/harnesses.ts +16 -2
- package/src/cli/commands/mcp/register.ts +9 -1
- package/src/cli/commands/mcp/stdio.ts +21 -0
- package/src/cli/commands/operate/dashboard.ts +107 -0
- package/src/cli/commands/operate/findings.ts +151 -0
- package/src/cli/commands/operate/inspect.ts +63 -164
- package/src/cli/commands/operate/outputs.ts +38 -11
- package/src/cli/commands/operate/policy.ts +7 -7
- package/src/cli/commands/operate/serve.ts +3 -0
- package/src/cli/commands/operate/status.ts +108 -1
- package/src/cli/commands/operate/token.ts +1 -1
- package/src/cli/commands/operate.ts +2 -0
- package/src/cli/commands/profile/declare.ts +154 -0
- package/src/cli/commands/profile/removal.ts +17 -0
- package/src/cli/commands/profile/remove.ts +5 -5
- package/src/cli/commands/profile.ts +83 -35
- package/src/cli/commands/secrets.ts +6 -6
- package/src/cli/commands/setup.ts +22 -6
- package/src/cli/commands/sync.ts +262 -0
- package/src/cli/commands/target.ts +65 -83
- package/src/cli/config-edit.ts +53 -144
- package/src/cli/config-repair.ts +186 -0
- package/src/cli/dashboard-page.ts +284 -0
- package/src/cli/dashboard-shell.ts +125 -0
- package/src/cli/dispatch-owner.ts +93 -0
- package/src/cli/identity.ts +12 -1
- package/src/cli/main.ts +90 -61
- package/src/cli/nearest.ts +45 -0
- package/src/cli/oauth-callback.ts +187 -0
- package/src/cli/oauth-exchange.ts +57 -15
- package/src/cli/oauth.ts +67 -177
- package/src/cli/output.ts +21 -5
- package/src/cli/provider-marks.ts +45 -0
- package/src/cli/runtime/open.ts +74 -51
- package/src/cli/runtime/registry.ts +60 -2
- package/src/cli/runtime/select.ts +26 -13
- package/src/cli/runtime/vault.ts +61 -0
- package/src/cli/runtime.ts +2 -1
- package/src/cli/selection.ts +365 -0
- package/src/cli/usage.ts +42 -11
- package/src/connectivity/auth/README.md +7 -1
- package/src/connectivity/auth/basic/index.ts +1 -1
- package/src/connectivity/auth/index.ts +14 -0
- package/src/connectivity/auth/oauth-authcode/broker.ts +26 -0
- package/src/connectivity/auth/oauth-authcode/index.ts +16 -2
- package/src/connectivity/auth/oauth-authcode/provider.ts +1 -1
- package/src/connectivity/auth/oauth-authcode/refresh.ts +3 -3
- package/src/connectivity/auth/oauth-jwt/README.md +33 -0
- package/src/connectivity/auth/oauth-jwt/index.ts +237 -0
- package/src/connectivity/auth/oauth-jwt/key.ts +148 -0
- package/src/connectivity/auth/resolve.ts +1 -1
- package/src/connectivity/auth/token.ts +11 -0
- package/src/connectivity/index.ts +2 -0
- package/src/connectivity/manifest/auth.ts +99 -2
- package/src/connectivity/manifest/identity.ts +12 -0
- package/src/connectivity/manifest/index.ts +3 -1
- package/src/connectivity/manifest/provider.ts +37 -8
- package/src/connectivity/manifest/requirements.ts +109 -6
- package/src/deployments/adapters/filesystem.ts +10 -1
- package/src/deployments/adapters/github-api.ts +106 -0
- package/src/deployments/adapters/github-commit.ts +103 -0
- package/src/deployments/adapters/github-repo.ts +356 -0
- package/src/deployments/adapters/github-testing.ts +258 -0
- package/src/deployments/adapters/github.ts +125 -0
- package/src/deployments/deploy.ts +94 -114
- package/src/deployments/discover.ts +103 -0
- package/src/deployments/driver.ts +8 -1
- package/src/deployments/gcp/driver.ts +3 -1
- package/src/deployments/knowledge.ts +119 -0
- package/src/deployments/prepare.ts +12 -6
- package/src/deployments/report.ts +117 -0
- package/src/deployments/servable.ts +82 -0
- package/src/deployments/serving.ts +165 -0
- package/src/deployments/sync-apply.ts +276 -0
- package/src/deployments/sync.ts +136 -0
- package/src/deployments/target.ts +3 -2
- package/src/deployments/upload.ts +19 -12
- package/src/dispatch/dispatch.ts +1 -1
- package/src/profile/deployments.ts +80 -0
- package/src/profile/identity.ts +60 -0
- package/src/profile/index.ts +23 -5
- package/src/profile/knowledge.ts +124 -0
- package/src/profile/load.ts +17 -5
- package/src/profile/primitives.ts +24 -1
- package/src/profile/schema.ts +81 -3
- package/src/profile/targets.ts +122 -109
- package/src/profile/workspace.ts +139 -79
- package/src/providers/google/calendar/index.ts +2 -0
- package/src/providers/google/contacts/index.ts +2 -0
- package/src/providers/google/docs/index.ts +2 -0
- package/src/providers/google/drive/index.ts +2 -0
- package/src/providers/google/gmail/index.ts +2 -0
- package/src/providers/google/gmail-imap/index.ts +125 -0
- package/src/providers/google/index.ts +2 -1
- package/src/providers/google/shared/oauth.ts +18 -6
- package/src/providers/google/shared/service-account.ts +110 -0
- package/src/providers/google/shared/setup.ts +21 -3
- package/src/providers/google/sheets/index.ts +2 -0
- package/src/providers/google/tasks/index.ts +2 -0
- package/src/providers/identity/provider.ts +166 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/owner.ts +10 -2
- package/src/providers/scopes.ts +2 -0
- package/src/providers/setup/plan.ts +31 -9
- package/src/providers/setup/provider.ts +23 -0
- package/src/providers/slack/index.ts +81 -33
- package/src/providers/slack/oauth.ts +103 -0
- package/src/providers/slack/scopes.ts +37 -0
- package/src/server/container.ts +18 -1
- package/src/server/cors.ts +252 -0
- package/src/server/dashboard.ts +208 -0
- package/src/server/endpoint.ts +45 -1
- package/src/server/generations.ts +11 -2
- package/src/server/harness.ts +7 -0
- package/src/server/index.ts +45 -6
- package/src/server/mcp/index.ts +1 -0
- package/src/server/mcp/instructions.ts +28 -1
- package/src/server/mcp/visibility.ts +33 -0
- package/src/stores/blobs/route.ts +123 -0
package/src/profile/index.ts
CHANGED
|
@@ -14,16 +14,28 @@
|
|
|
14
14
|
export {
|
|
15
15
|
SUPPORTED_CONTRACT,
|
|
16
16
|
configSchema,
|
|
17
|
+
deploymentRecordSchema,
|
|
17
18
|
workspaceSchema,
|
|
18
19
|
type AuthorizationConfig,
|
|
19
20
|
type Config,
|
|
20
21
|
type ConnectionConfig,
|
|
21
22
|
type DeployConfig,
|
|
23
|
+
type DeploymentRecord,
|
|
24
|
+
type IdentityEntry,
|
|
22
25
|
type PolicyRuleConfig,
|
|
23
26
|
type TargetConfig,
|
|
24
27
|
type WorkspaceConfig,
|
|
25
28
|
} from './schema.ts';
|
|
26
29
|
|
|
30
|
+
export {
|
|
31
|
+
KNOWLEDGE_LAYOUT,
|
|
32
|
+
knowledgeRoot,
|
|
33
|
+
knowledgeTargetSchema,
|
|
34
|
+
parseRepository,
|
|
35
|
+
type KnowledgeArea,
|
|
36
|
+
type KnowledgeConfig,
|
|
37
|
+
} from './knowledge.ts';
|
|
38
|
+
|
|
27
39
|
export {
|
|
28
40
|
ConfigError,
|
|
29
41
|
loadConfigFile,
|
|
@@ -41,23 +53,28 @@ export {
|
|
|
41
53
|
|
|
42
54
|
export {
|
|
43
55
|
WORKSPACE_FILE,
|
|
44
|
-
describeSelection,
|
|
45
56
|
installRoot,
|
|
46
57
|
listProfiles,
|
|
47
58
|
loadProfileConfig,
|
|
59
|
+
loadWorkspaceProfiles,
|
|
60
|
+
noProfileNamed,
|
|
48
61
|
profilePath,
|
|
49
62
|
readWorkspace,
|
|
50
63
|
resolveSelection,
|
|
51
64
|
resolveWorkspaceRoot,
|
|
65
|
+
targetsByName,
|
|
52
66
|
workspacePath,
|
|
67
|
+
type LoadedProfile,
|
|
68
|
+
type WorkspaceProfiles,
|
|
53
69
|
} from './workspace.ts';
|
|
54
70
|
export {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
LEGACY_TARGET_ENV,
|
|
72
|
+
noTargetInWorkspace,
|
|
73
|
+
noTargetNamed,
|
|
74
|
+
requireTarget,
|
|
59
75
|
undeclaredTarget,
|
|
60
76
|
} from './targets.ts';
|
|
77
|
+
export { findDeployment, readDeployments, recordDeployment } from './deployments.ts';
|
|
61
78
|
export {
|
|
62
79
|
isRemoteWorkspace,
|
|
63
80
|
readWorkspaceFile,
|
|
@@ -65,6 +82,7 @@ export {
|
|
|
65
82
|
writeWorkspaceFile,
|
|
66
83
|
} from './files.ts';
|
|
67
84
|
export {
|
|
85
|
+
type ProfileSelection,
|
|
68
86
|
type Resolution,
|
|
69
87
|
type ResolveOptions,
|
|
70
88
|
} from './workspace.ts';
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { credentialRef } from './primitives.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where a profile's memory and skills live, when that is not where everything
|
|
6
|
+
* else lives.
|
|
7
|
+
*
|
|
8
|
+
* A target's `storage:` block names one backend for everything a profile holds
|
|
9
|
+
* — runtime state, the audit log, memory entries, skills, cached attachments.
|
|
10
|
+
* That is right for most of it and wrong for two: state and the log are
|
|
11
|
+
* artefacts of one installation, and memory entries and skills are documents
|
|
12
|
+
* the owner wrote. Documents want history, review, and to be readable from more
|
|
13
|
+
* than one machine, and the backend that suits an object nobody reads is not
|
|
14
|
+
* the backend that suits those.
|
|
15
|
+
*
|
|
16
|
+
* So this block moves exactly those two, and it is the whole of what it can
|
|
17
|
+
* move. There is no `credentials` or `vault` value here and there will not be:
|
|
18
|
+
* a repository is a place to publish, and those two hold the material whose
|
|
19
|
+
* entire value is that it is not published (`docs/detailed/security.md`). The
|
|
20
|
+
* exclusion is structural — a field that does not exist cannot be set by
|
|
21
|
+
* accident, by a flag, or by an operator following an example.
|
|
22
|
+
*
|
|
23
|
+
* `adapter` is an enum of one, the same shape `vaultTargetSchema` uses, so a
|
|
24
|
+
* second host is a case rather than a schema rewrite.
|
|
25
|
+
*
|
|
26
|
+
* Absent means what has always happened, which is why every field that could
|
|
27
|
+
* carry a default does. A profile written before this block existed loads
|
|
28
|
+
* unchanged and stores its memory and skills exactly where it did.
|
|
29
|
+
*
|
|
30
|
+
* ADR-041.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `owner/name`, as GitHub spells it everywhere a person types one.
|
|
35
|
+
*
|
|
36
|
+
* A URL is deliberately not accepted. The browser URL, the SSH remote, and this
|
|
37
|
+
* are three spellings of one value, and a schema that takes all three has to
|
|
38
|
+
* normalise them somewhere — at which point the file no longer says what the
|
|
39
|
+
* operator wrote. The CLI accepts a pasted URL and reduces it to this before
|
|
40
|
+
* writing, which puts the leniency where a person is typing rather than in the
|
|
41
|
+
* contract.
|
|
42
|
+
*/
|
|
43
|
+
const repository = z
|
|
44
|
+
.string()
|
|
45
|
+
.regex(
|
|
46
|
+
/^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+$/,
|
|
47
|
+
'must be "owner/name" — not a URL, and not a local path',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A directory inside the repository, for one that holds other things too.
|
|
52
|
+
*
|
|
53
|
+
* Normalised to no leading or trailing slash so `knowledge`, `/knowledge` and
|
|
54
|
+
* `knowledge/` are one prefix rather than three key spaces that look identical
|
|
55
|
+
* in a diff and address different objects.
|
|
56
|
+
*/
|
|
57
|
+
const repositoryPath = z
|
|
58
|
+
.string()
|
|
59
|
+
.refine((value) => !value.split('/').includes('..'), {
|
|
60
|
+
message: 'must not contain ".." — it is a prefix inside the repository, not a path to walk',
|
|
61
|
+
})
|
|
62
|
+
.transform((value) => value.replace(/^\/+/, '').replace(/\/+$/, ''));
|
|
63
|
+
|
|
64
|
+
export const knowledgeTargetSchema = z.object({
|
|
65
|
+
adapter: z.literal('github'),
|
|
66
|
+
repo: repository,
|
|
67
|
+
/**
|
|
68
|
+
* Defaults at open time rather than here, to the repository's own default
|
|
69
|
+
* branch. Writing `main` into every generated config would be a guess, and a
|
|
70
|
+
* wrong one for anybody whose default is still `master` — a guess that then
|
|
71
|
+
* fails as "branch not found" rather than as "you did not say".
|
|
72
|
+
*/
|
|
73
|
+
branch: z.string().min(1).optional(),
|
|
74
|
+
path: repositoryPath.optional(),
|
|
75
|
+
/**
|
|
76
|
+
* A reference, never the token.
|
|
77
|
+
*
|
|
78
|
+
* Its own ref rather than a reuse of the `github` provider connection's,
|
|
79
|
+
* which holds a token for GitHub's MCP server. That one needs Contents:
|
|
80
|
+
* *read*; this one needs Contents: *write*, has a different lifetime, and
|
|
81
|
+
* should be revocable on its own — revoking an MCP connection must not
|
|
82
|
+
* silently empty somebody's memory.
|
|
83
|
+
*/
|
|
84
|
+
token_ref: credentialRef.default('knowledge/token'),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type KnowledgeConfig = z.infer<typeof knowledgeTargetSchema>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Where each area sits inside the repository.
|
|
91
|
+
*
|
|
92
|
+
* Two directories, named after the two things that move. `memory` is also the
|
|
93
|
+
* memory provider's own blob namespace — the prefix core scopes it into under
|
|
94
|
+
* the profile's blob root — which is why the same word does both jobs: the
|
|
95
|
+
* route that redirects it and the directory it lands in are the same fact, and
|
|
96
|
+
* spelling them separately is how they would come to disagree.
|
|
97
|
+
*
|
|
98
|
+
* Declared here, beside the schema, so the runtime that opens these stores and
|
|
99
|
+
* the command that migrates into them read one definition.
|
|
100
|
+
*/
|
|
101
|
+
export const KNOWLEDGE_LAYOUT = {
|
|
102
|
+
memory: 'memory',
|
|
103
|
+
skills: 'skills',
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
export type KnowledgeArea = keyof typeof KNOWLEDGE_LAYOUT;
|
|
107
|
+
|
|
108
|
+
/** The directory one area occupies, under the profile's optional path prefix. */
|
|
109
|
+
export function knowledgeRoot(knowledge: KnowledgeConfig, area: KnowledgeArea): string {
|
|
110
|
+
const directory = KNOWLEDGE_LAYOUT[area];
|
|
111
|
+
return knowledge.path ? `${knowledge.path}/${directory}` : directory;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** `owner/name`, out of whatever a person pasted. Null when it is not one. */
|
|
115
|
+
export function parseRepository(given: string): string | null {
|
|
116
|
+
const trimmed = given
|
|
117
|
+
.trim()
|
|
118
|
+
.replace(/^https?:\/\/(?:www\.)?github\.com\//i, '')
|
|
119
|
+
.replace(/^(?:ssh:\/\/)?git@[^:]+[:/]/i, '')
|
|
120
|
+
.replace(/\.git$/i, '')
|
|
121
|
+
.replace(/\/+$/, '');
|
|
122
|
+
|
|
123
|
+
return repository.safeParse(trimmed).success ? trimmed : null;
|
|
124
|
+
}
|
package/src/profile/load.ts
CHANGED
|
@@ -114,11 +114,9 @@ function assertReferentialIntegrity(config: Config, source: string): void {
|
|
|
114
114
|
if (targetNames.size === 0) {
|
|
115
115
|
problems.push('targets: at least one target must be declared');
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
121
|
-
}
|
|
117
|
+
// `instance.default_target` is deliberately not checked. Nothing reads it
|
|
118
|
+
// (ADR-037), so validating it would be validating a comment — and failing
|
|
119
|
+
// `check` on a stale value would teach that the key still matters.
|
|
122
120
|
|
|
123
121
|
// Only what holds for every platform. What one platform needs and the next
|
|
124
122
|
// has no concept of — a GCP project, an AWS role ARN — is refused by the
|
|
@@ -146,6 +144,20 @@ function assertReferentialIntegrity(config: Config, source: string): void {
|
|
|
146
144
|
connectionKeys.add(key);
|
|
147
145
|
});
|
|
148
146
|
|
|
147
|
+
// Same reason as a duplicate connection: two entries with the same kind and
|
|
148
|
+
// value cannot both be meant, and the one that loses is invisible. It matters
|
|
149
|
+
// more here than it looks, because the two would usually differ only in their
|
|
150
|
+
// `note` — so the discarded one is precisely the guidance someone wrote down
|
|
151
|
+
// to stop an agent picking wrong.
|
|
152
|
+
const identityKeys = new Set<string>();
|
|
153
|
+
config.identity.forEach((entry, index) => {
|
|
154
|
+
const key = `${entry.kind}=${entry.value}`;
|
|
155
|
+
if (identityKeys.has(key)) {
|
|
156
|
+
problems.push(`identity[${index}]: duplicate entry "${entry.kind}: ${entry.value}"`);
|
|
157
|
+
}
|
|
158
|
+
identityKeys.add(key);
|
|
159
|
+
});
|
|
160
|
+
|
|
149
161
|
const checkRules = (rules: readonly PolicyRuleConfig[], field: 'allow' | 'deny'): void => {
|
|
150
162
|
rules.forEach((rule, index) => {
|
|
151
163
|
const where = `policy.${field}[${index}]`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
4
|
+
* The four string shapes the config contract is built out of.
|
|
5
5
|
*
|
|
6
6
|
* Their own file because more than one schema module needs them, and the
|
|
7
7
|
* alternative was either a circular import or a second copy of a regex that
|
|
@@ -43,3 +43,26 @@ export const capabilityPattern = z
|
|
|
43
43
|
/^(?:\*|[a-z][a-z0-9_]*\.(?:\*|[A-Za-z0-9][A-Za-z0-9_-]*(?:\.[A-Za-z0-9][A-Za-z0-9_-]*)*(?:\.\*)?))$/,
|
|
44
44
|
'must be "*", "gmail.*", "gmail.users.*", or "gmail.users.drafts.send"',
|
|
45
45
|
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A browser origin, exactly — scheme, host, and port, with nothing after it. Or
|
|
49
|
+
* `*`, which is what an absent list means anyway and is accepted so that saying
|
|
50
|
+
* it explicitly is not an error.
|
|
51
|
+
*
|
|
52
|
+
* Checked by round trip rather than by a regex, because `URL` already knows what
|
|
53
|
+
* an origin is and the failure this catches is a trailing slash or a path that
|
|
54
|
+
* looks harmless and matches nothing: a browser sends `Origin: https://x.example`
|
|
55
|
+
* and a configured `https://x.example/` would compare unequal, silently refusing
|
|
56
|
+
* the origin its owner believed they had allowed.
|
|
57
|
+
*/
|
|
58
|
+
export const browserOrigin = z.string().refine(
|
|
59
|
+
(value) => {
|
|
60
|
+
if (value === '*') return true;
|
|
61
|
+
try {
|
|
62
|
+
return new URL(value).origin === value;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
'must be "*" or an origin with no trailing slash or path, e.g. "https://chat.example"',
|
|
68
|
+
);
|
package/src/profile/schema.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { capabilityPattern, credentialRef, identifier } from './primitives.ts';
|
|
2
|
+
import { browserOrigin, capabilityPattern, credentialRef, identifier } from './primitives.ts';
|
|
3
3
|
import { authorizationSchema } from './authorization.ts';
|
|
4
|
+
import { identitySchema } from './identity.ts';
|
|
5
|
+
import { knowledgeTargetSchema } from './knowledge.ts';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Configuration is declarative desired state. This file is the source of truth
|
|
@@ -214,6 +216,15 @@ export const targetSchema = z
|
|
|
214
216
|
audit: auditTargetSchema.optional(),
|
|
215
217
|
storage: storageTargetSchema,
|
|
216
218
|
vault: vaultTargetSchema.optional(),
|
|
219
|
+
/**
|
|
220
|
+
* Memory and skills, somewhere other than `storage` above.
|
|
221
|
+
*
|
|
222
|
+
* Optional and absent by default, so every profile written before it keeps
|
|
223
|
+
* storing both exactly where it did. See `knowledge.ts` for why these two
|
|
224
|
+
* are separable from the rest and why the credential store and the vault
|
|
225
|
+
* are not.
|
|
226
|
+
*/
|
|
227
|
+
knowledge: knowledgeTargetSchema.optional(),
|
|
217
228
|
deploy: deployTargetSchema.optional(),
|
|
218
229
|
/** @deprecated Write `deploy` with `platform: cloudrun`. */
|
|
219
230
|
cloudrun: legacyCloudRunSchema.optional(),
|
|
@@ -313,7 +324,22 @@ export const configSchema = z.object({
|
|
|
313
324
|
|
|
314
325
|
instance: z.object({
|
|
315
326
|
profile: identifier,
|
|
316
|
-
|
|
327
|
+
/**
|
|
328
|
+
* @deprecated Parsed, never read. See ADR-037.
|
|
329
|
+
*
|
|
330
|
+
* Every command names its target on the command line now, so nothing
|
|
331
|
+
* consults this. It stays *declared* rather than being dropped, and that is
|
|
332
|
+
* the whole point: an undeclared key is stripped silently by the schema,
|
|
333
|
+
* which would leave `check` and `doctor` with nothing to report and an
|
|
334
|
+
* operator staring at a line they reasonably believe still selects
|
|
335
|
+
* something. Declaring it is what lets them be told it is inert.
|
|
336
|
+
*
|
|
337
|
+
* Optional, so a profile written today needs no such line, and unvalidated,
|
|
338
|
+
* so a stale value naming a target that no longer exists is harmless rather
|
|
339
|
+
* than a failure on a key nothing reads. The `database:` note above records
|
|
340
|
+
* the same decision for the same reason.
|
|
341
|
+
*/
|
|
342
|
+
default_target: z.string().min(1).optional(),
|
|
317
343
|
port: z.number().int().min(1).max(65535).default(7337),
|
|
318
344
|
/**
|
|
319
345
|
* Loopback by default. Binding elsewhere is possible but the server
|
|
@@ -334,6 +360,15 @@ export const configSchema = z.object({
|
|
|
334
360
|
* get one. Omitting it leaves every existing profile behaving identically.
|
|
335
361
|
*/
|
|
336
362
|
authorization: authorizationSchema.optional(),
|
|
363
|
+
/**
|
|
364
|
+
* Browser origins that may call a *deployment's* MCP endpoint. Absent
|
|
365
|
+
* means `*`, so naming any is a narrowing and nothing needs setting.
|
|
366
|
+
*
|
|
367
|
+
* A deployment only, and this cannot widen that: a loopback endpoint
|
|
368
|
+
* refuses every cross-origin request and must keep doing so. Why the
|
|
369
|
+
* default is a wildcard is `src/server/cors.ts` and ADR-039.
|
|
370
|
+
*/
|
|
371
|
+
allowed_origins: z.array(browserOrigin).optional(),
|
|
337
372
|
})
|
|
338
373
|
.default({ mode: 'bearer', token_ref: 'profile/token' }),
|
|
339
374
|
|
|
@@ -353,6 +388,13 @@ export const configSchema = z.object({
|
|
|
353
388
|
*/
|
|
354
389
|
connections: z.array(connectionSchema).default([]),
|
|
355
390
|
policy: policySchema.default({ allow: [], deny: [] }),
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Who the owner is, for anything written as them. Optional and additive, so
|
|
394
|
+
* every profile written before it keeps loading unchanged — the same reasoning
|
|
395
|
+
* as `auth.authorization` above, and the reason `contract` does not move.
|
|
396
|
+
*/
|
|
397
|
+
identity: identitySchema.default([]),
|
|
356
398
|
});
|
|
357
399
|
|
|
358
400
|
export type Config = z.infer<typeof configSchema>;
|
|
@@ -361,12 +403,48 @@ export type PolicyRuleConfig = z.infer<typeof policyRuleSchema>;
|
|
|
361
403
|
export type TargetConfig = z.infer<typeof targetSchema>;
|
|
362
404
|
export type DeployConfig = z.infer<typeof deployTargetSchema>;
|
|
363
405
|
export type AuthorizationConfig = z.infer<typeof authorizationSchema>;
|
|
364
|
-
export { authorizationSchema };
|
|
406
|
+
export { authorizationSchema, identitySchema };
|
|
407
|
+
export type { IdentityEntry } from './identity.ts';
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Where a deployment lives — an index, not configuration.
|
|
411
|
+
*
|
|
412
|
+
* **Nothing resolves from this.** A target is still declared by the profile,
|
|
413
|
+
* and a command still acts on what the profile says. This exists because the
|
|
414
|
+
* profile file was the *only* record that a deployment existed, so rewriting
|
|
415
|
+
* one erased the sole pointer to a live service, a bucket holding every byte
|
|
416
|
+
* the endpoint remembered, and a credential store — none of which had gone
|
|
417
|
+
* anywhere. There was no command that could find them again.
|
|
418
|
+
*
|
|
419
|
+
* It lives in `lanes-link.yaml`, outside any profile, for exactly that reason:
|
|
420
|
+
* a record kept inside the thing it describes cannot survive the thing being
|
|
421
|
+
* lost. `lanes link sync targets` reads it, and reads it *only* to know where
|
|
422
|
+
* to look (ADR-044).
|
|
423
|
+
*
|
|
424
|
+
* Written by `deploy`, after an upload has succeeded — so it never names a
|
|
425
|
+
* workspace nothing was ever put in.
|
|
426
|
+
*/
|
|
427
|
+
export const deploymentRecordSchema = z.object({
|
|
428
|
+
target: identifier,
|
|
429
|
+
/** Where the deployed endpoint reads its config: `gs://bucket[/prefix]`. */
|
|
430
|
+
workspace: z.string().min(1),
|
|
431
|
+
/**
|
|
432
|
+
* Whose bearer token opens this endpoint (ADR-009).
|
|
433
|
+
*
|
|
434
|
+
* Recorded rather than inferred. One endpoint serves every profile in the
|
|
435
|
+
* bucket under one token, and which profile's token that is decides who gets
|
|
436
|
+
* in — the one question about a deployment that must not be guessed at.
|
|
437
|
+
*/
|
|
438
|
+
primary: identifier.optional(),
|
|
439
|
+
last_deploy: z.string().optional(),
|
|
440
|
+
});
|
|
365
441
|
|
|
366
442
|
/** The workspace file: `lanes-link.yaml`, alongside a `profiles/` directory. */
|
|
367
443
|
export const workspaceSchema = z.object({
|
|
368
444
|
contract: z.number().int().positive(),
|
|
369
445
|
default_profile: identifier.optional(),
|
|
446
|
+
deployments: z.array(deploymentRecordSchema).default([]),
|
|
370
447
|
});
|
|
371
448
|
|
|
372
449
|
export type WorkspaceConfig = z.infer<typeof workspaceSchema>;
|
|
450
|
+
export type DeploymentRecord = z.infer<typeof deploymentRecordSchema>;
|