graphban-cli 0.6.0__tar.gz → 0.7.1__tar.gz
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.
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/.gitignore +6 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/PKG-INFO +27 -1
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/README.md +26 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/pyproject.toml +1 -1
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/cli.py +87 -2
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/client.py +4 -0
- graphban_cli-0.7.1/src/gban/data/stack_sdks.toml +177 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/skills/graphban-delegation/SKILL.md +7 -0
- graphban_cli-0.7.1/src/gban/stackmap.py +1446 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_acts.py +3 -2
- graphban_cli-0.7.1/tests/test_stackmap.py +720 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/LICENSE +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/__init__.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/config.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/doctor.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/gitignore.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/setup.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/skills/watch-wave/SKILL.md +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/src/gban/swamp.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_doctor.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_login_projects.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_packaging.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_session.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_setup.py +0 -0
- {graphban_cli-0.6.0 → graphban_cli-0.7.1}/tests/test_swamp.py +0 -0
|
@@ -10,6 +10,7 @@ backend/.pytest.db
|
|
|
10
10
|
# `test_no_tracked_control_bytes` on main and therefore on every PR built against it.
|
|
11
11
|
.pytest.db
|
|
12
12
|
.pytest.db-journal
|
|
13
|
+
*.db-journal
|
|
13
14
|
# The concurrency lock GRPH-554 added sits beside the database it guards, and xdist gives
|
|
14
15
|
# each worker its own — so the pattern has to cover `.pytest_gw3.db.lock` as well as the
|
|
15
16
|
# bare one. Untracked litter in `git status` is how a real change gets missed in the noise.
|
|
@@ -31,6 +32,11 @@ web/node_modules/
|
|
|
31
32
|
web/dist/
|
|
32
33
|
web/.vite/
|
|
33
34
|
web/*.tsbuildinfo
|
|
35
|
+
web/test-results/
|
|
36
|
+
web/playwright-report/
|
|
37
|
+
web/blob-report/
|
|
38
|
+
# Playwright MCP browser session output (console logs, snapshots)
|
|
39
|
+
.playwright-mcp/
|
|
34
40
|
|
|
35
41
|
# Env
|
|
36
42
|
.env
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: graphban-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: gban — a Graphban client for the human at a terminal
|
|
5
5
|
Project-URL: Homepage, https://github.com/asc-me/graphban
|
|
6
6
|
Project-URL: Repository, https://github.com/asc-me/graphban
|
|
@@ -187,6 +187,32 @@ script into a shell. And it never writes the gate key into an MCP config: `gban
|
|
|
187
187
|
key must not carry `gate`, or the agent doing the work attests its own completion — which
|
|
188
188
|
fails silently, since a gate that always says yes looks exactly like a gate that held.
|
|
189
189
|
|
|
190
|
+
## `gban stack map`
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
gban stack map --dry-run
|
|
194
|
+
gban stack map --project <id>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Reads this checkout and posts one app-topology record to `POST /api/platform/stack/app`.
|
|
198
|
+
`--dry-run` prints that record and posts nothing; it needs no server and no key.
|
|
199
|
+
Two runs of the same commit print the same JSON. There is no timestamp in it.
|
|
200
|
+
|
|
201
|
+
What it reads:
|
|
202
|
+
|
|
203
|
+
- Compose files (`compose.yml`, `docker-compose.yml`, and the `compose.*` / `docker-compose.*` variants): a service with `build` is a deployable, a service with only `image` is infrastructure, and `depends_on` is an edge. `environment` contributes variable **names** and, when a value is a literal URL, the bare host.
|
|
204
|
+
- Dockerfiles (`Dockerfile`, `Dockerfile.*`, `*.Dockerfile`), for the deployable they sit in and for a `FROM` image the table knows.
|
|
205
|
+
- Dependency manifests (`package.json`, `pyproject.toml`, `requirements*.txt`, `go.mod`, `Cargo.toml`, `Gemfile`), matched against the known-SDK table and attributed to the deployable whose directory contains them.
|
|
206
|
+
- `.env.example` only. A real `.env` is not a template and is not opened. Source files are not scanned.
|
|
207
|
+
|
|
208
|
+
The known-SDK table ships at `cli/src/gban/data/stack_sdks.toml` and is versioned with `gban`. A checkout may add rows in `.graphban/stack-sdks.toml` (same shape); those rows are consulted first.
|
|
209
|
+
|
|
210
|
+
Compose is parsed by a subset, not by PyYAML: block mappings and lists, single-line `[...]` / `{...}` flow, and `|` / `>` scalars. Anchors and aliases are not expanded (`*name` stays text). A `<<` merge key is ignored. A file the subset cannot read is skipped with a warning on stderr; the rest of the tree is still mapped.
|
|
211
|
+
|
|
212
|
+
Env values are never stored and never posted. A host taken from a URL is the bare host (`api.stripe.com`), because the posted shape rejects any string containing `://`. An image the table does not know is infrastructure named after the **compose service**, kind `unclassified image` — the image string is not stored. A `depends_on`, `external: true`, or `extends.file` that names something this file does not define becomes an `unresolved` node. It is not dropped and it is not a failure.
|
|
213
|
+
|
|
214
|
+
A real post needs a git commit (there is nothing to send for a repo with no HEAD) and a project-write API key: `$GBFLEET_API_KEY`, then `$GRAPHBAN_API_KEY`, then the key `gban setup` stored for this project. The login session is a different credential and is not sent. Pass `--project` (or `$GRAPHBAN_PROJECT`) so the record names the project; without one, `--dry-run` still prints and a post is refused.
|
|
215
|
+
|
|
190
216
|
## `gban login` wants a real terminal
|
|
191
217
|
|
|
192
218
|
It refuses without one, rather than prompting. `getpass` falls back to a plain **echoing**
|
|
@@ -172,6 +172,32 @@ script into a shell. And it never writes the gate key into an MCP config: `gban
|
|
|
172
172
|
key must not carry `gate`, or the agent doing the work attests its own completion — which
|
|
173
173
|
fails silently, since a gate that always says yes looks exactly like a gate that held.
|
|
174
174
|
|
|
175
|
+
## `gban stack map`
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
gban stack map --dry-run
|
|
179
|
+
gban stack map --project <id>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Reads this checkout and posts one app-topology record to `POST /api/platform/stack/app`.
|
|
183
|
+
`--dry-run` prints that record and posts nothing; it needs no server and no key.
|
|
184
|
+
Two runs of the same commit print the same JSON. There is no timestamp in it.
|
|
185
|
+
|
|
186
|
+
What it reads:
|
|
187
|
+
|
|
188
|
+
- Compose files (`compose.yml`, `docker-compose.yml`, and the `compose.*` / `docker-compose.*` variants): a service with `build` is a deployable, a service with only `image` is infrastructure, and `depends_on` is an edge. `environment` contributes variable **names** and, when a value is a literal URL, the bare host.
|
|
189
|
+
- Dockerfiles (`Dockerfile`, `Dockerfile.*`, `*.Dockerfile`), for the deployable they sit in and for a `FROM` image the table knows.
|
|
190
|
+
- Dependency manifests (`package.json`, `pyproject.toml`, `requirements*.txt`, `go.mod`, `Cargo.toml`, `Gemfile`), matched against the known-SDK table and attributed to the deployable whose directory contains them.
|
|
191
|
+
- `.env.example` only. A real `.env` is not a template and is not opened. Source files are not scanned.
|
|
192
|
+
|
|
193
|
+
The known-SDK table ships at `cli/src/gban/data/stack_sdks.toml` and is versioned with `gban`. A checkout may add rows in `.graphban/stack-sdks.toml` (same shape); those rows are consulted first.
|
|
194
|
+
|
|
195
|
+
Compose is parsed by a subset, not by PyYAML: block mappings and lists, single-line `[...]` / `{...}` flow, and `|` / `>` scalars. Anchors and aliases are not expanded (`*name` stays text). A `<<` merge key is ignored. A file the subset cannot read is skipped with a warning on stderr; the rest of the tree is still mapped.
|
|
196
|
+
|
|
197
|
+
Env values are never stored and never posted. A host taken from a URL is the bare host (`api.stripe.com`), because the posted shape rejects any string containing `://`. An image the table does not know is infrastructure named after the **compose service**, kind `unclassified image` — the image string is not stored. A `depends_on`, `external: true`, or `extends.file` that names something this file does not define becomes an `unresolved` node. It is not dropped and it is not a failure.
|
|
198
|
+
|
|
199
|
+
A real post needs a git commit (there is nothing to send for a repo with no HEAD) and a project-write API key: `$GBFLEET_API_KEY`, then `$GRAPHBAN_API_KEY`, then the key `gban setup` stored for this project. The login session is a different credential and is not sent. Pass `--project` (or `$GRAPHBAN_PROJECT`) so the record names the project; without one, `--dry-run` still prints and a post is refused.
|
|
200
|
+
|
|
175
201
|
## `gban login` wants a real terminal
|
|
176
202
|
|
|
177
203
|
It refuses without one, rather than prompting. `getpass` falls back to a plain **echoing**
|
|
@@ -15,7 +15,7 @@ import subprocess
|
|
|
15
15
|
import sys
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
-
from gban import config, doctor as doctor_mod, setup as setup_mod, swamp as swamp_mod
|
|
18
|
+
from gban import config, doctor as doctor_mod, setup as setup_mod, stackmap, swamp as swamp_mod
|
|
19
19
|
from gban.client import (EXIT_NO_SESSION, EXIT_NO_SUPERVISOR, EXIT_REFUSED, EXIT_UNREACHABLE,
|
|
20
20
|
Client, NoSession,
|
|
21
21
|
Refused, Unreachable, authenticated, login)
|
|
@@ -91,6 +91,24 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
91
91
|
swamp_setup.add_argument("--no-init", action="store_true",
|
|
92
92
|
help="refuse rather than run `swamp repo init` here")
|
|
93
93
|
|
|
94
|
+
stack_cmd = sub.add_parser(
|
|
95
|
+
"stack", help="map this checkout and post the app topology",
|
|
96
|
+
description=("Reads compose files, Dockerfiles, dependency manifests and "
|
|
97
|
+
".env.example, and posts one app-topology record. Env values "
|
|
98
|
+
"are not stored. --dry-run prints the record and posts nothing."))
|
|
99
|
+
stack_do = stack_cmd.add_subparsers(dest="act")
|
|
100
|
+
stack_map = stack_do.add_parser(
|
|
101
|
+
"map", help="build the record from this repo",
|
|
102
|
+
description=("Deterministic. Two runs on the same commit print the same "
|
|
103
|
+
"JSON. A real post uses a project-write API key "
|
|
104
|
+
f"(${doctor_mod.SUPERVISOR_KEY_ENV} or ${config.API_KEY_ENV}), "
|
|
105
|
+
"never the login session, and goes to "
|
|
106
|
+
f"POST {stackmap._POST_PATH}."))
|
|
107
|
+
stack_map.add_argument("--dry-run", action="store_true",
|
|
108
|
+
help="print the record and do not post it")
|
|
109
|
+
stack_map.add_argument("--repo", default=None,
|
|
110
|
+
help="repository to map (default: the git checkout of the current directory)")
|
|
111
|
+
|
|
94
112
|
fleet = sub.add_parser(
|
|
95
113
|
"fleet", help="hand off to gbfleet (the supervisor)",
|
|
96
114
|
description=("Passes everything through to `gbfleet` and returns its exit code "
|
|
@@ -407,6 +425,73 @@ def cmd_swamp(args) -> int:
|
|
|
407
425
|
return code
|
|
408
426
|
|
|
409
427
|
|
|
428
|
+
def cmd_stack(args) -> int:
|
|
429
|
+
"""Build the app-topology record and, unless --dry-run, post it (GRPH-P55 §3)."""
|
|
430
|
+
if args.act != "map":
|
|
431
|
+
print(f"{PROG}: usage: {PROG} stack map [--dry-run] [--repo DIR]", file=sys.stderr)
|
|
432
|
+
return EXIT_REFUSED
|
|
433
|
+
start = Path(args.repo).expanduser() if args.repo else Path.cwd()
|
|
434
|
+
project = (config.resolve(args.project, config.PROJECT_ENV, "project") or "").strip()
|
|
435
|
+
try:
|
|
436
|
+
record, warnings = stackmap.build_record(start, project_id=project)
|
|
437
|
+
except stackmap.MapError as exc:
|
|
438
|
+
print(f"{PROG}: {exc}", file=sys.stderr)
|
|
439
|
+
return EXIT_REFUSED
|
|
440
|
+
for warning in warnings:
|
|
441
|
+
print(f"{PROG}: {warning}", file=sys.stderr)
|
|
442
|
+
text = stackmap.canonical(record)
|
|
443
|
+
if args.dry_run:
|
|
444
|
+
sys.stdout.write(text)
|
|
445
|
+
print(f"{PROG}: dry run, nothing posted", file=sys.stderr)
|
|
446
|
+
return 0
|
|
447
|
+
if not project:
|
|
448
|
+
print(f"{PROG}: `{PROG} stack map` needs a project to post. Pass --project, set "
|
|
449
|
+
f"${config.PROJECT_ENV}, or add --dry-run to print the record only.",
|
|
450
|
+
file=sys.stderr)
|
|
451
|
+
return EXIT_REFUSED
|
|
452
|
+
url = config.resolve(args.server, config.URL_ENV, "url")
|
|
453
|
+
if not url:
|
|
454
|
+
print(f"{PROG}: no server. Pass --server, set ${config.URL_ENV}, or run `gban login "
|
|
455
|
+
f"--server …` once.", file=sys.stderr)
|
|
456
|
+
return EXIT_REFUSED
|
|
457
|
+
# The supervisor injects GBFLEET_API_KEY. resolve_supervisor_key does not read
|
|
458
|
+
# it, so a seat that only has that variable would post with nothing.
|
|
459
|
+
key = (os.environ.get(doctor_mod.SUPERVISOR_KEY_ENV) or "").strip()
|
|
460
|
+
if not key:
|
|
461
|
+
key = doctor_mod.resolve_supervisor_key(project, start)
|
|
462
|
+
if not key:
|
|
463
|
+
print(f"{PROG}: no project-write API key. Set ${doctor_mod.SUPERVISOR_KEY_ENV} or "
|
|
464
|
+
f"${config.API_KEY_ENV}, or run `{PROG} setup`. The login session is not a "
|
|
465
|
+
f"key and is not sent.", file=sys.stderr)
|
|
466
|
+
return EXIT_REFUSED
|
|
467
|
+
# The record goes to stdout only after the server took it: a refused post must not
|
|
468
|
+
# leave behind something that reads like a record that was sent (GRPH-1103).
|
|
469
|
+
try:
|
|
470
|
+
receipt = stackmap.post_record(url, key, record)
|
|
471
|
+
except Refused as exc:
|
|
472
|
+
try:
|
|
473
|
+
reasons = json.loads(exc.detail)
|
|
474
|
+
except ValueError:
|
|
475
|
+
reasons = None
|
|
476
|
+
if not isinstance(reasons, list):
|
|
477
|
+
raise
|
|
478
|
+
print(f"{PROG}: the server refused the app map (HTTP {exc.status}):", file=sys.stderr)
|
|
479
|
+
for reason in reasons:
|
|
480
|
+
if isinstance(reason, dict):
|
|
481
|
+
print(f" {reason.get('path', '?')}: {reason.get('reason', '')}",
|
|
482
|
+
file=sys.stderr)
|
|
483
|
+
else:
|
|
484
|
+
print(f" {reason}", file=sys.stderr)
|
|
485
|
+
return EXIT_REFUSED
|
|
486
|
+
sys.stdout.write(text)
|
|
487
|
+
receipt = receipt if isinstance(receipt, dict) else {}
|
|
488
|
+
sha = str(receipt.get("mapped_sha") or record.get("mapped_sha") or "")[:7]
|
|
489
|
+
said = [f"{k} {receipt[k]}" for k in ("mapped_at", "posted_by") if receipt.get(k)]
|
|
490
|
+
print(f"{PROG}: posted {receipt.get('project_id') or project} app map at {sha} to {url}"
|
|
491
|
+
+ (f" ({', '.join(said)})" if said else ""), file=sys.stderr)
|
|
492
|
+
return 0
|
|
493
|
+
|
|
494
|
+
|
|
410
495
|
def cmd_fleet(args) -> int:
|
|
411
496
|
"""A subprocess, never an import (D5).
|
|
412
497
|
|
|
@@ -560,7 +645,7 @@ def cmd_keys(args) -> int:
|
|
|
560
645
|
|
|
561
646
|
COMMANDS = {"login": cmd_login, "logout": cmd_logout, "whoami": cmd_whoami,
|
|
562
647
|
"doctor": cmd_doctor, "setup": cmd_setup, "fleet": cmd_fleet, "seats": cmd_seats,
|
|
563
|
-
"agents": cmd_agents, "keys": cmd_keys, "swamp": cmd_swamp}
|
|
648
|
+
"agents": cmd_agents, "keys": cmd_keys, "swamp": cmd_swamp, "stack": cmd_stack}
|
|
564
649
|
|
|
565
650
|
|
|
566
651
|
def main(argv: list[str] | None = None) -> int:
|
|
@@ -115,6 +115,10 @@ class Client:
|
|
|
115
115
|
detail = payload.get("detail") or payload.get("message") or f"HTTP {status}"
|
|
116
116
|
if isinstance(detail, dict):
|
|
117
117
|
detail, hint = (detail.get("message") or str(detail)), detail.get("hint", "")
|
|
118
|
+
elif isinstance(detail, list):
|
|
119
|
+
# A list of reasons (e.g. `{path, reason}` from a refused app map) stays JSON,
|
|
120
|
+
# so a caller can read it back rather than parse a Python repr.
|
|
121
|
+
detail, hint = json.dumps(detail), payload.get("hint", "")
|
|
118
122
|
else:
|
|
119
123
|
hint = payload.get("hint", "")
|
|
120
124
|
raise Refused(status, str(detail), str(hint))
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Known-SDK table shipped with gban (GRPH-P55 §3). Versioned with the package.
|
|
2
|
+
# A project adds rows in `.graphban/stack-sdks.toml` using the same shape.
|
|
3
|
+
# New shipped rows arrive by PR. cli/tests/test_stackmap.py exercises every row.
|
|
4
|
+
# `domains` on an external row: a URL host equal to or under one of them is that
|
|
5
|
+
# service, so `api.stripe.com` and the `stripe` package draw one node, not two.
|
|
6
|
+
|
|
7
|
+
[[image]]
|
|
8
|
+
id = "postgres"
|
|
9
|
+
names = ["postgres", "pgvector/pgvector"]
|
|
10
|
+
kind = "postgres"
|
|
11
|
+
label = "Postgres"
|
|
12
|
+
protocol = "postgres"
|
|
13
|
+
|
|
14
|
+
[[image]]
|
|
15
|
+
id = "redis"
|
|
16
|
+
names = ["redis", "bitnami/redis"]
|
|
17
|
+
kind = "redis"
|
|
18
|
+
label = "Redis"
|
|
19
|
+
protocol = "redis"
|
|
20
|
+
|
|
21
|
+
[[image]]
|
|
22
|
+
id = "rabbitmq"
|
|
23
|
+
names = ["rabbitmq", "bitnami/rabbitmq"]
|
|
24
|
+
kind = "rabbitmq"
|
|
25
|
+
label = "RabbitMQ"
|
|
26
|
+
protocol = "amqp"
|
|
27
|
+
|
|
28
|
+
[[image]]
|
|
29
|
+
id = "mysql"
|
|
30
|
+
names = ["mysql", "mariadb"]
|
|
31
|
+
kind = "mysql"
|
|
32
|
+
label = "MySQL"
|
|
33
|
+
protocol = "mysql"
|
|
34
|
+
|
|
35
|
+
[[image]]
|
|
36
|
+
id = "mongo"
|
|
37
|
+
names = ["mongo", "mongodb"]
|
|
38
|
+
kind = "mongodb"
|
|
39
|
+
label = "MongoDB"
|
|
40
|
+
protocol = "mongodb"
|
|
41
|
+
|
|
42
|
+
[[sdk]]
|
|
43
|
+
id = "psycopg"
|
|
44
|
+
packages = ["psycopg", "psycopg2", "psycopg2-binary", "psycopg-binary"]
|
|
45
|
+
node = "postgres"
|
|
46
|
+
group = "infrastructure"
|
|
47
|
+
kind = "postgres"
|
|
48
|
+
label = "Postgres"
|
|
49
|
+
protocol = "postgres"
|
|
50
|
+
|
|
51
|
+
[[sdk]]
|
|
52
|
+
id = "pg"
|
|
53
|
+
packages = ["pg", "postgres"]
|
|
54
|
+
node = "postgres"
|
|
55
|
+
group = "infrastructure"
|
|
56
|
+
kind = "postgres"
|
|
57
|
+
label = "Postgres"
|
|
58
|
+
protocol = "postgres"
|
|
59
|
+
|
|
60
|
+
[[sdk]]
|
|
61
|
+
id = "redis"
|
|
62
|
+
packages = ["redis", "ioredis"]
|
|
63
|
+
node = "redis"
|
|
64
|
+
group = "infrastructure"
|
|
65
|
+
kind = "redis"
|
|
66
|
+
label = "Redis"
|
|
67
|
+
protocol = "redis"
|
|
68
|
+
|
|
69
|
+
[[sdk]]
|
|
70
|
+
id = "stripe"
|
|
71
|
+
packages = ["stripe"]
|
|
72
|
+
node = "stripe"
|
|
73
|
+
group = "external"
|
|
74
|
+
kind = ""
|
|
75
|
+
label = "Stripe"
|
|
76
|
+
protocol = "https"
|
|
77
|
+
domains = ["stripe.com"]
|
|
78
|
+
|
|
79
|
+
[[sdk]]
|
|
80
|
+
id = "boto3"
|
|
81
|
+
packages = ["boto3", "botocore"]
|
|
82
|
+
node = "aws"
|
|
83
|
+
group = "external"
|
|
84
|
+
kind = ""
|
|
85
|
+
label = "AWS"
|
|
86
|
+
protocol = "https"
|
|
87
|
+
domains = ["amazonaws.com"]
|
|
88
|
+
|
|
89
|
+
[[sdk]]
|
|
90
|
+
id = "aws-sdk"
|
|
91
|
+
packages = ["aws-sdk"]
|
|
92
|
+
prefix = "@aws-sdk/"
|
|
93
|
+
node = "aws"
|
|
94
|
+
group = "external"
|
|
95
|
+
kind = ""
|
|
96
|
+
label = "AWS"
|
|
97
|
+
protocol = "https"
|
|
98
|
+
domains = ["amazonaws.com"]
|
|
99
|
+
|
|
100
|
+
[[sdk]]
|
|
101
|
+
id = "openai"
|
|
102
|
+
packages = ["openai"]
|
|
103
|
+
node = "openai"
|
|
104
|
+
group = "external"
|
|
105
|
+
kind = ""
|
|
106
|
+
label = "OpenAI"
|
|
107
|
+
protocol = "https"
|
|
108
|
+
domains = ["openai.com"]
|
|
109
|
+
|
|
110
|
+
[[sdk]]
|
|
111
|
+
id = "anthropic"
|
|
112
|
+
packages = ["anthropic", "@anthropic-ai/sdk"]
|
|
113
|
+
node = "anthropic"
|
|
114
|
+
group = "external"
|
|
115
|
+
kind = ""
|
|
116
|
+
label = "Anthropic"
|
|
117
|
+
protocol = "https"
|
|
118
|
+
domains = ["anthropic.com"]
|
|
119
|
+
|
|
120
|
+
[[sdk]]
|
|
121
|
+
id = "sentry"
|
|
122
|
+
packages = ["sentry-sdk", "@sentry/node", "@sentry/browser", "@sentry/react"]
|
|
123
|
+
prefix = "@sentry/"
|
|
124
|
+
node = "sentry"
|
|
125
|
+
group = "external"
|
|
126
|
+
kind = ""
|
|
127
|
+
label = "Sentry"
|
|
128
|
+
protocol = "https"
|
|
129
|
+
domains = ["sentry.io"]
|
|
130
|
+
|
|
131
|
+
[[sdk]]
|
|
132
|
+
id = "twilio"
|
|
133
|
+
packages = ["twilio"]
|
|
134
|
+
node = "twilio"
|
|
135
|
+
group = "external"
|
|
136
|
+
kind = ""
|
|
137
|
+
label = "Twilio"
|
|
138
|
+
protocol = "https"
|
|
139
|
+
domains = ["twilio.com"]
|
|
140
|
+
|
|
141
|
+
[[sdk]]
|
|
142
|
+
id = "sendgrid"
|
|
143
|
+
packages = ["sendgrid", "@sendgrid/mail"]
|
|
144
|
+
node = "sendgrid"
|
|
145
|
+
group = "external"
|
|
146
|
+
kind = ""
|
|
147
|
+
label = "SendGrid"
|
|
148
|
+
protocol = "https"
|
|
149
|
+
domains = ["sendgrid.com", "sendgrid.net"]
|
|
150
|
+
|
|
151
|
+
[[sdk]]
|
|
152
|
+
id = "supabase"
|
|
153
|
+
packages = ["supabase", "@supabase/supabase-js"]
|
|
154
|
+
node = "supabase"
|
|
155
|
+
group = "external"
|
|
156
|
+
kind = ""
|
|
157
|
+
label = "Supabase"
|
|
158
|
+
protocol = "https"
|
|
159
|
+
domains = ["supabase.co", "supabase.com"]
|
|
160
|
+
|
|
161
|
+
[[env]]
|
|
162
|
+
id = "database-url"
|
|
163
|
+
names = ["DATABASE_URL"]
|
|
164
|
+
node = "postgres"
|
|
165
|
+
group = "infrastructure"
|
|
166
|
+
kind = "postgres"
|
|
167
|
+
label = "Postgres"
|
|
168
|
+
protocol = "postgres"
|
|
169
|
+
|
|
170
|
+
[[env]]
|
|
171
|
+
id = "redis-url"
|
|
172
|
+
names = ["REDIS_URL"]
|
|
173
|
+
node = "redis"
|
|
174
|
+
group = "infrastructure"
|
|
175
|
+
kind = "redis"
|
|
176
|
+
label = "Redis"
|
|
177
|
+
protocol = "redis"
|
|
@@ -107,6 +107,13 @@ willing to pay for.
|
|
|
107
107
|
`seat=true` mints a worker seat **bound to the item**, which is what makes the child claim it
|
|
108
108
|
at registration rather than racing for it.
|
|
109
109
|
|
|
110
|
+
`bound_only=true` (with `seat=true`) is the opt-in that stops the child taking anything else.
|
|
111
|
+
Once its bound item leaves `in_progress`, `claim_review`, `claim_next`, `claim_item`,
|
|
112
|
+
`claim_cluster` and `next_cluster` refuse and name the binding, and the child is told to
|
|
113
|
+
exit. Omit the flag and the child may review other items after it finishes — that is today's
|
|
114
|
+
behaviour, and `gbfleet until` does not set the flag, because a wave relies on finished bound
|
|
115
|
+
workers reviewing each other.
|
|
116
|
+
|
|
110
117
|
Delegate whole items, never fragments. If it has no title, touchpoints and acceptance, it is
|
|
111
118
|
not ready to hand over — the touchpoints become the area reservations that stop the child
|
|
112
119
|
colliding with other work. File it properly or do it inline.
|