@lanes-sh/link 0.6.6 → 0.6.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanes-sh/link",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "A self-hostable MCP gateway for all your connections, memory, tasks, files, and secrets",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://lanes.sh/link",
@@ -6,6 +6,7 @@ import {
6
6
  openTarget,
7
7
  readRegistry,
8
8
  resolveWorkspaceRoot,
9
+ type ResolvedTarget,
9
10
  type WorkspaceTarget,
10
11
  } from '#profile';
11
12
  import { ConfigError } from '#profile';
@@ -182,6 +183,27 @@ function summarise(name: string, entry: WorkspaceTarget, isSelected: boolean): T
182
183
  };
183
184
  }
184
185
 
186
+ /**
187
+ * The entry `show` renders: the declaration on the far end, plus the deploy record.
188
+ *
189
+ * **Without the `workspace:` key**, and that is the whole of it. `openTarget`
190
+ * merges the local pointer over the remote declaration so a redeploy from a
191
+ * second machine does not lose the first one's record — which leaves an entry
192
+ * carrying `workspace:` *and* adapters, and `isPointer` answers yes to anything
193
+ * with a `workspace:`. So `summarise` took the pointer branch and returned nulls
194
+ * for every adapter and `deployed: false`, and `target show cloud` said "No
195
+ * deployment — this target runs wherever the CLI does" about a target with a
196
+ * live Cloud Run service in front of it.
197
+ *
198
+ * This is the command that *follows* the pointer, so the pointer is the one
199
+ * thing it is not reporting: `resolved.workspaceRoot` is already printed on the
200
+ * line above, from the same object.
201
+ */
202
+ export function resolvedEntry(resolved: ResolvedTarget): WorkspaceTarget {
203
+ const { workspace: _followed, ...record } = resolved.entry;
204
+ return { ...resolved.declared, ...record };
205
+ }
206
+
185
207
  /** What `--json` and the tests want, without the rendering. */
186
208
  export async function readTargets(
187
209
  flags: TargetFlags,
@@ -310,7 +332,7 @@ export async function targetShow(name: string | undefined, flags: TargetFlags):
310
332
  }
311
333
 
312
334
  const resolved = await openTarget(root, wanted);
313
- const summary = summarise(wanted, { ...resolved.declared, ...resolved.entry }, true);
335
+ const summary = summarise(wanted, resolvedEntry(resolved), true);
314
336
  const profiles = await listProfiles(resolved.workspaceRoot);
315
337
 
316
338
  const url = summary.deployment
@@ -50,6 +50,31 @@ export function bucketGrants(bucket: string, profiles: readonly string[]): Condi
50
50
  `resource.name.startsWith("projects/_/buckets/${bucket}/objects/${path}")`;
51
51
  const objectIs = (path: string): string =>
52
52
  `resource.name == "projects/_/buckets/${bucket}/objects/${path}"`;
53
+ /**
54
+ * The bucket itself, which is a different resource from anything in it.
55
+ *
56
+ * **`storage.objects.list` is checked against the bucket, never against an
57
+ * object**, so no condition written in terms of `objects/…` can ever grant it
58
+ * — including a prefixed listing, which is one call with a filter rather than
59
+ * a walk of matching resources. Google says so outright: IAM conditions cannot
60
+ * restrict listing by prefix.
61
+ *
62
+ * This was invisible until the day the narrowing actually applied. The
63
+ * `reads-its-config` binding had been sitting at `expression=true` since the
64
+ * first deploy, and `true` matches the bucket resource as readily as an object
65
+ * — so listing worked, and every deploy that thought it had replaced that
66
+ * binding had in fact only added one beside it. The first deploy that removed
67
+ * the old one rolled a revision that could not list its own workspace, exited
68
+ * 1, and took the endpoint's listing with it: `objects.list` had never been
69
+ * granted by anything else.
70
+ *
71
+ * Only `storage.objects.list` can come of it. Every other permission in
72
+ * `objectViewer` is either evaluated against an object — where the prefixes
73
+ * below still decide — or is a project-level permission a binding on a bucket
74
+ * does not reach. So the concession is the names of the objects in this
75
+ * bucket, and reads stay scoped to the config.
76
+ */
77
+ const theBucket = `resource.name == "projects/_/buckets/${bucket}"`;
53
78
 
54
79
  const manifestPrefixes = profiles.map((profile) =>
55
80
  objectsUnder(`${layout.providers(profile)}/`),
@@ -73,7 +98,7 @@ export function bucketGrants(bucket: string, profiles: readonly string[]): Condi
73
98
  // and each profile's own manifests, so name exactly those.
74
99
  role: 'roles/storage.objectViewer',
75
100
  title: 'reads-its-config',
76
- expression: `${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')}${manifests ? ` || ${manifests}` : ''}`,
101
+ expression: `${theBucket} || ${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')}${manifests ? ` || ${manifests}` : ''}`,
77
102
  },
78
103
  ];
79
104
  }
@@ -108,6 +133,17 @@ export async function bucketSteps(input: {
108
133
  // served publicly; uniform access removes per-object ACLs as a way to
109
134
  // get that wrong.
110
135
  '--uniform-bucket-level-access',
136
+ // Autoclass rather than a lifecycle rule, because no one fixed class is
137
+ // right for this bucket: it holds the config the endpoint reads on every
138
+ // boot next to assets and audit rows nobody opens again. Inside an
139
+ // Autoclass bucket there are no retrieval and no early-deletion fees,
140
+ // which is what makes ARCHIVE safe as the floor rather than a bet on
141
+ // never reading the thing again — a read pulls the object back to
142
+ // Standard at no charge. Objects under 128 KiB never leave Standard, so
143
+ // this costs the config and the log nothing and saves on attachments.
144
+ '--enable-autoclass',
145
+ '--autoclass-terminal-storage-class',
146
+ 'ARCHIVE',
111
147
  ],
112
148
  tolerateFailure: true,
113
149
  },