@hasna/loops 0.4.28 → 0.4.29
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/CHANGELOG.md +84 -1
- package/README.md +226 -49
- package/dist/api/index.d.ts +20 -19
- package/dist/api/index.js +6846 -1087
- package/dist/cli/index.js +2471 -885
- package/dist/cli/safe-error-context.d.ts +1 -0
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1786 -493
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/mode.d.ts +6 -12
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4778 -1016
- package/dist/lib/advancement.d.ts +52 -0
- package/dist/lib/agent-adapter.d.ts +20 -2
- package/dist/lib/auth/route-policy.d.ts +14 -0
- package/dist/lib/auth/tenant-auth.d.ts +38 -0
- package/dist/lib/cloud/mode.d.ts +2 -8
- package/dist/lib/cloud/storage.d.ts +1 -2
- package/dist/lib/cloud/transport.d.ts +4 -18
- package/dist/lib/errors.d.ts +43 -1
- package/dist/lib/format.d.ts +2 -2
- package/dist/lib/goal/runner.d.ts +36 -3
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/labels.d.ts +4 -0
- package/dist/lib/loop-status.d.ts +4 -0
- package/dist/lib/migration.d.ts +4 -17
- package/dist/lib/mode.d.ts +2 -5
- package/dist/lib/mode.js +27 -36
- package/dist/lib/route/index.d.ts +2 -2
- package/dist/lib/route/throttle.d.ts +7 -0
- package/dist/lib/route/types.d.ts +3 -0
- package/dist/lib/run-completion.d.ts +18 -0
- package/dist/lib/scheduler.d.ts +5 -11
- package/dist/lib/storage/contract.d.ts +15 -1
- package/dist/lib/storage/index.d.ts +1 -1
- package/dist/lib/storage/index.js +3864 -457
- package/dist/lib/storage/pg-executor.d.ts +10 -5
- package/dist/lib/storage/postgres-loop-storage.d.ts +50 -24
- package/dist/lib/storage/postgres-schema.d.ts +8 -0
- package/dist/lib/storage/postgres-schema.js +1323 -1
- package/dist/lib/storage/postgres.d.ts +3 -1
- package/dist/lib/storage/postgres.js +1353 -27
- package/dist/lib/storage/provider-credentials.d.ts +84 -0
- package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
- package/dist/lib/storage/sqlite.d.ts +15 -2
- package/dist/lib/storage/sqlite.js +1299 -217
- package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
- package/dist/lib/storage/tenant-backfill.d.ts +40 -0
- package/dist/lib/store/index.d.ts +11 -8
- package/dist/lib/store.d.ts +52 -7
- package/dist/lib/store.js +1266 -217
- package/dist/lib/templates.d.ts +11 -0
- package/dist/lib/workflow-events.d.ts +6 -0
- package/dist/lib/workflow-provenance.d.ts +8 -0
- package/dist/lib/workflow-runner.d.ts +52 -4
- package/dist/mcp/index.js +1961 -651
- package/dist/runner/index.d.ts +20 -2
- package/dist/runner/index.js +2113 -177
- package/dist/sdk/http.d.ts +211 -3
- package/dist/sdk/http.js +301 -0
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +1959 -711
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.js +12323 -1743
- package/dist/types.d.ts +64 -3
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
- package/docs/CUTOVER-RUNBOOK.md +158 -31
- package/docs/DEPLOYMENT_MODES.md +78 -56
- package/docs/RUNTIME_BOUNDARY.md +26 -26
- package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
- package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
- package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
- package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
- package/docs/USAGE.md +143 -52
- package/docs/workflows/transcript-feedback-to-loops.json +2 -2
- package/package.json +7 -3
- package/dist/lib/storage/pg-runner-claim.d.ts +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,83 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to
|
|
3
|
+
All notable changes to Loops (npm `@hasna/loops`, repo `hasna/loops`) are
|
|
4
4
|
documented in this file. Version entries are generated from the
|
|
5
5
|
conventional-commit git history; one commit maps to one released patch version
|
|
6
6
|
unless noted.
|
|
7
7
|
|
|
8
|
+
## 0.4.29 (2026-07-21)
|
|
9
|
+
|
|
10
|
+
This source release gives the post-`npm/loops/v0.4.28` code line a new,
|
|
11
|
+
unambiguous package, OpenAPI, and generated HTTP SDK identity.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Tenant-bound authentication and PostgreSQL row isolation:** migrations
|
|
16
|
+
`0008_tenant_prepare`, `0009_tenant_backfill`, and
|
|
17
|
+
`0010_tenant_enforce` add tenant/principal/membership/API-key state, require
|
|
18
|
+
an explicit complete tenant assignment before enforcement, and install the
|
|
19
|
+
closed-world owner/migrator/runtime/authenticator role model with
|
|
20
|
+
tenant-scoped privileges and RLS. The API now validates tenant-bound keys,
|
|
21
|
+
roles, scopes, and token kinds and emits structured denial evidence.
|
|
22
|
+
- **Tenant migration operations:** encrypted tenant-backfill delivery,
|
|
23
|
+
provider credential reconciliation, PostgreSQL 16 bootstrap membership
|
|
24
|
+
gates, and protected shared-database transfer tooling now support rehearsed,
|
|
25
|
+
auditable cutovers without command overrides or unverified TLS targets.
|
|
26
|
+
- **Runner and workflow execution:** long-running runner readiness and workflow
|
|
27
|
+
execution are implemented, with operator recovery, replayable advancement,
|
|
28
|
+
workflow-definition provenance in `0011_workflow_run_provenance`, and loop
|
|
29
|
+
labels in `0012_loop_labels`.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Product identity:** user-facing product and documentation naming is now
|
|
34
|
+
**Loops**, while published compatibility identifiers and paths such as
|
|
35
|
+
the legacy uppercase environment-variable namespace, `open_loops`,
|
|
36
|
+
`.openloops`, `openloops`, `runtimeOwner`, MCP identifiers, project slugs,
|
|
37
|
+
and the `loops-api` compatibility binary/waiver remain stable.
|
|
38
|
+
- **Runtime and supply chain:** the runtime image now uses pinned Bun Alpine
|
|
39
|
+
packages, runs the runner as non-root, pins CI actions, adds a protected ECR
|
|
40
|
+
candidate workflow, and fails closed on high-severity or malformed scan
|
|
41
|
+
results. Packed artifacts are checked for private-host leakage, branding
|
|
42
|
+
regressions, storage-kit drift, and contract conformance.
|
|
43
|
+
- **Public contracts:** the OpenAPI document and generated HTTP SDK cover the
|
|
44
|
+
expanded tenant, runner, workflow, recovery, labels, count, archive, and
|
|
45
|
+
operational surfaces. MCP output is bounded, and public or persisted
|
|
46
|
+
workflow output is scrubbed before truncation.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **Agent boundaries:** Codewith auth profiles are resolved from exact
|
|
51
|
+
JSON-first inventory, unusable or malformed profiles and extra arguments
|
|
52
|
+
fail closed, session creation is atomic, root agent directories are rejected,
|
|
53
|
+
route agent allowlists propagate correctly, and Cursor provider/retry
|
|
54
|
+
handling no longer weakens trust boundaries.
|
|
55
|
+
- **API and claim safety:** tenant error compatibility is preserved while
|
|
56
|
+
runner endpoints, route admission, loop updates, runner clocks, local claims,
|
|
57
|
+
recovery snapshots, and persisted workflow/session contracts reject
|
|
58
|
+
ambiguous, stale, or cross-boundary state.
|
|
59
|
+
- **Archive and finalization:** ambiguous archive names fail closed and resolve
|
|
60
|
+
against target state; PostgreSQL finalization conflicts are enforced
|
|
61
|
+
directly; SQLite generated-route finalization matches PostgreSQL behavior;
|
|
62
|
+
and public CLI errors and pagination avoid leaking unsafe context.
|
|
63
|
+
- **Recovery and advancement:** workflow recovery ownership is fenced,
|
|
64
|
+
advancement is unified across schedulers, replay is idempotent, successor and
|
|
65
|
+
completion parity gaps are closed, and recovery cannot double-claim local
|
|
66
|
+
work or regenerate already-finalized routes.
|
|
67
|
+
- **Security boundaries:** private and encoded infrastructure hostnames are
|
|
68
|
+
removed from publishable output, encoded separator bypasses are rejected,
|
|
69
|
+
trusted contracts and legacy events are validated, and the shared transfer
|
|
70
|
+
gate accepts only verified TLS DSNs.
|
|
71
|
+
|
|
72
|
+
### Upgrade warning
|
|
73
|
+
|
|
74
|
+
- **`0010_tenant_enforce` is a rollback boundary.** After this migration is
|
|
75
|
+
applied, the immutable published `0.4.28` image cannot pass readiness because
|
|
76
|
+
it does not contain the `0008`-`0010` tenant migration lineage. Do not roll
|
|
77
|
+
the binary back in place. Recovery requires rolling forward to `0.4.29` or a
|
|
78
|
+
later compatible image, or restoring the rehearsed pre-cutover database
|
|
79
|
+
target before starting the older image.
|
|
80
|
+
|
|
8
81
|
## 0.4.26
|
|
9
82
|
|
|
10
83
|
### Fixed
|
|
@@ -19,6 +92,16 @@ unless noted.
|
|
|
19
92
|
|
|
20
93
|
## Unreleased
|
|
21
94
|
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **Codewith auth-profile preflight:** use JSON-first exact profile matching for
|
|
98
|
+
local and remote runs. Root `data`/`profiles` entries are authoritative:
|
|
99
|
+
`usable: false` is rejected, legacy entries without `usable` remain accepted,
|
|
100
|
+
and `currentProfile` does not add inventory membership. Human-table parsing,
|
|
101
|
+
including active `*` rows, is used only when JSON mode is unsupported or its
|
|
102
|
+
inventory is structurally unavailable. NUL-containing names, missing
|
|
103
|
+
profiles, and non-fallback profile-list failures fail closed.
|
|
104
|
+
|
|
22
105
|
### Added
|
|
23
106
|
|
|
24
107
|
- `loops-mcp` now serves a shared Streamable HTTP transport (default
|
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Loops
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Loops is a local CLI and daemon for persistent loops and workflows: scheduled or recurring work that survives process restarts and records every run.
|
|
4
4
|
|
|
5
|
-
Naming: the product is **
|
|
5
|
+
Naming: the product is **Loops**, published on npm as
|
|
6
6
|
[`@hasna/loops`](https://www.npmjs.com/package/@hasna/loops) and developed in the
|
|
7
7
|
[`hasna/loops`](https://github.com/hasna/loops) repository. The installed
|
|
8
8
|
binaries are `loops`, `loops-daemon`, `loops-api`, `loops-serve`,
|
|
9
9
|
`loops-runner`, and `loops-mcp`.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Loops is the **runtime, scheduler, and workflow engine** for automations —
|
|
12
12
|
not the automation domain model. Specs, triggers, queue ownership, approvals,
|
|
13
13
|
DLQ/replay, and audit live in `@hasna/automations` and `@hasna/actions`; see
|
|
14
14
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for ownership split and external
|
|
@@ -25,7 +25,7 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
|
|
|
25
25
|
|
|
26
26
|
## Deployment Modes
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Loops has three deployment modes:
|
|
29
29
|
|
|
30
30
|
- `local`: SQLite in `LOOPS_DATA_DIR` is authoritative and `loops-daemon` executes scheduled work.
|
|
31
31
|
- `self_hosted`: the Hasna-owned AWS/RDS control-plane deployment, served by
|
|
@@ -33,23 +33,20 @@ OpenLoops has three deployment modes:
|
|
|
33
33
|
shared by serve, SDK, and tests. This release exposes status, storage-backed
|
|
34
34
|
`/v1` loop CRUD and run listing, runner claim/heartbeat/finalize protocol
|
|
35
35
|
endpoints, a one-shot `loops-runner run-once` execution path for embedded
|
|
36
|
-
control-plane hosts, id-preserving local export/import,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- `cloud`: a hosted control-plane contract. This release exposes client/runner status only; hosted tenant auth and infrastructure live outside this package.
|
|
36
|
+
control-plane hosts, id-preserving local export/import, tenant-bound API-key
|
|
37
|
+
authentication, and forced Postgres row-level tenant isolation.
|
|
38
|
+
- `cloud`: a hosted control-plane contract using the same tenant-bound API.
|
|
40
39
|
|
|
41
40
|
`local` is the default and requires no network, token, Postgres, or hosted
|
|
42
|
-
service. Set `
|
|
43
|
-
`cloud` to choose explicitly. Without an explicit mode,
|
|
44
|
-
|
|
45
|
-
`self_hosted`.
|
|
41
|
+
service. Set `HASNA_LOOPS_STORAGE_MODE` to `local`, `self_hosted`, or
|
|
42
|
+
`cloud` to choose explicitly. Without an explicit mode,
|
|
43
|
+
`HASNA_LOOPS_API_URL` or `HASNA_LOOPS_DATABASE_URL` selects `self_hosted`.
|
|
46
44
|
|
|
47
45
|
The public `@hasna/loops` package owns the local runtime, mode resolver,
|
|
48
|
-
self-hosted API contract,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`LOOPS_CLOUD_TOKEN` or `HASNA_LOOPS_CLOUD_TOKEN`.
|
|
46
|
+
self-hosted API contract, tenant authentication and authorization, runner
|
|
47
|
+
contract, SDK, MCP server, and CLI. Account provisioning and infrastructure
|
|
48
|
+
deployment remain operator responsibilities. Cloud clients use
|
|
49
|
+
`HASNA_LOOPS_API_URL` plus `HASNA_LOOPS_API_KEY`.
|
|
53
50
|
|
|
54
51
|
Scheduler state is explicit in status JSON. `schedulerState.localStore` is
|
|
55
52
|
SQLite plus local run artifact files: authoritative in `local`, cache/spool in
|
|
@@ -73,7 +70,6 @@ loops self-hosted status
|
|
|
73
70
|
loops self-hosted migrate --dry-run
|
|
74
71
|
loops self-hosted push --dry-run
|
|
75
72
|
loops self-hosted pull --dry-run
|
|
76
|
-
loops self-hosted runner-register --runner-id <id> --machine-id <machine> --apply
|
|
77
73
|
loops export --file ./loops-export.json --dry-run
|
|
78
74
|
loops export --file ./loops-export.json
|
|
79
75
|
loops import ./loops-export.json
|
|
@@ -81,7 +77,9 @@ loops import ./loops-export.json --apply
|
|
|
81
77
|
loops cloud status
|
|
82
78
|
loops-api status
|
|
83
79
|
loops-serve version
|
|
84
|
-
|
|
80
|
+
HASNA_LOOPS_MIGRATOR_DATABASE_URL=... loops-serve migrate --dry-run
|
|
81
|
+
loops-serve db-credentials reconcile
|
|
82
|
+
HASNA_LOOPS_DATABASE_URL=... HASNA_LOOPS_AUTH_DATABASE_URL=... loops-serve serve
|
|
85
83
|
loops-runner status
|
|
86
84
|
```
|
|
87
85
|
|
|
@@ -94,9 +92,40 @@ preserve flags only for an intentional activation.
|
|
|
94
92
|
See [Deployment Modes](docs/DEPLOYMENT_MODES.md) for the full package boundary
|
|
95
93
|
and machine-placement contract.
|
|
96
94
|
|
|
95
|
+
### Provider database credentials
|
|
96
|
+
|
|
97
|
+
`loops-serve db-credentials reconcile` is the fixed in-cluster reconciler for
|
|
98
|
+
provider-managed RDS app credentials. It reads the RDS-managed master secret
|
|
99
|
+
with the AWS Secrets Manager SDK, builds `sslmode=verify-full` DSNs only in
|
|
100
|
+
memory, and writes each app DSN to that app's Secrets Manager secret through
|
|
101
|
+
`AWSPENDING` before promoting `AWSCURRENT`. It never accepts database URLs,
|
|
102
|
+
passwords, access keys, profiles, or secret material as command flags.
|
|
103
|
+
|
|
104
|
+
Required environment is intentionally strict: `AWS_REGION`,
|
|
105
|
+
`AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`, `HASNA_LOOPS_RDS_MASTER_SECRET_ARN`,
|
|
106
|
+
`HASNA_LOOPS_MIGRATOR_DATABASE_URL_SECRET_ARN`,
|
|
107
|
+
`HASNA_LOOPS_RUNTIME_DATABASE_URL_SECRET_ARN`,
|
|
108
|
+
`HASNA_LOOPS_AUTH_DATABASE_URL_SECRET_ARN`, `HASNA_LOOPS_RDS_INSTANCE_ID`,
|
|
109
|
+
`HASNA_LOOPS_RDS_ENDPOINT`, `HASNA_LOOPS_RDS_PORT`,
|
|
110
|
+
`HASNA_LOOPS_RDS_DATABASE`, and `HASNA_LOOPS_RDS_MASTER_USERNAME`. Static AWS
|
|
111
|
+
credential environment variables, profile/web-identity inputs, full credential
|
|
112
|
+
URIs, non-RDS-style endpoints, duplicate secret ARNs, cross-region secret ARNs,
|
|
113
|
+
and malformed identifiers fail closed before any secret or database operation.
|
|
114
|
+
|
|
115
|
+
The command normalizes the fixed NOLOGIN database roles
|
|
116
|
+
`open_loops_owner`, `open_loops_migrator`, `open_loops_runtime`, and
|
|
117
|
+
`open_loops_authenticator`; manages exactly three LOGIN users
|
|
118
|
+
`open_loops_migrator_login`, `open_loops_runtime_login`, and
|
|
119
|
+
`open_loops_authenticator_login`; grants only the migrator login exact
|
|
120
|
+
owner/migrator membership plus `CREATEROLE`; and keeps runtime/authenticator
|
|
121
|
+
without memberships until migration `0010_tenant_enforce` has landed. After
|
|
122
|
+
`0010`, runtime and authenticator are attached only to their matching roles.
|
|
123
|
+
If PostgreSQL 16 implicit creator memberships cannot be normalized, the
|
|
124
|
+
reconciler fails before publishing app credentials.
|
|
125
|
+
|
|
97
126
|
## Install
|
|
98
127
|
|
|
99
|
-
**
|
|
128
|
+
**Loops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
|
|
100
129
|
`loops`, `loops-daemon`, `loops-api`, `loops-serve`, `loops-runner`, and
|
|
101
130
|
`loops-mcp` binaries run with a `#!/usr/bin/env bun` shebang, so Bun must be on
|
|
102
131
|
`PATH` even when the package is installed with npm. Node.js is not a supported
|
|
@@ -132,7 +161,7 @@ The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to iso
|
|
|
132
161
|
|
|
133
162
|
## MCP Server
|
|
134
163
|
|
|
135
|
-
|
|
164
|
+
Loops ships a stdio MCP server for safe loop and workflow inspection from
|
|
136
165
|
MCP-capable agents:
|
|
137
166
|
|
|
138
167
|
```bash
|
|
@@ -149,6 +178,7 @@ import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
|
149
178
|
Available read tools include `loops_list`, `loops_show`, `loops_runs`,
|
|
150
179
|
`loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
|
|
151
180
|
`loops_workflow_validate`.
|
|
181
|
+
`loops_list` and `loops_runs` accept label arrays with AND semantics.
|
|
152
182
|
Resources are available at `loops://runtime` and `loops://tools`.
|
|
153
183
|
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
154
184
|
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
@@ -158,12 +188,16 @@ Mutation tools are disabled by default. Start the server with
|
|
|
158
188
|
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
159
189
|
allowed to change loop state. The guarded mutation tools use canonical names:
|
|
160
190
|
`loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
|
|
161
|
-
`loops_unarchive`, `loops_create_command`, and
|
|
191
|
+
`loops_unarchive`, `loops_labels_update`, `loops_create_command`, and
|
|
192
|
+
`loops_create_workflow`.
|
|
162
193
|
Deprecated `loop_*` aliases are still registered where compatibility needs them,
|
|
163
194
|
but callers should use the `loops_*` names. Mutation tools do not require or
|
|
164
195
|
accept confirmation-string parameters; the server-side environment opt-in is the
|
|
165
196
|
gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
|
|
166
197
|
execution remains CLI-only.
|
|
198
|
+
When `showOutput` is enabled, `loops_runs` caps each stdout/stderr field at
|
|
199
|
+
32,000 characters, caps output-bearing pages at 25 runs, and caps the aggregate
|
|
200
|
+
MCP JSON response at 128,000 characters.
|
|
167
201
|
|
|
168
202
|
Keep host-affecting or long-running operations on the CLI: daemon
|
|
169
203
|
start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
|
|
@@ -223,8 +257,41 @@ loops create agent supply-chain-watch \
|
|
|
223
257
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
224
258
|
```
|
|
225
259
|
|
|
260
|
+
Codewith `--auth-profile` is provider-native, not an OpenAccounts selector.
|
|
261
|
+
Local and remote preflight first request `codewith profile list --json` and
|
|
262
|
+
match the requested name exactly against the root `data` or `profiles`
|
|
263
|
+
inventory. That inventory is authoritative: `usable: false` rejects a profile,
|
|
264
|
+
legacy entries without `usable` remain usable, and `currentProfile` does not add
|
|
265
|
+
inventory membership. Loops falls back to the human-readable table,
|
|
266
|
+
including active `*` rows, only when JSON mode is unsupported or its inventory
|
|
267
|
+
is structurally unavailable. Embedded NUL bytes, missing profiles, and
|
|
268
|
+
non-fallback profile-list failures fail closed.
|
|
269
|
+
|
|
270
|
+
## Labels
|
|
271
|
+
|
|
272
|
+
Command, agent, and workflow loops accept repeatable labels:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
loops create command repo-status --every 1m --cmd "git status --short" \
|
|
276
|
+
--label BrowserPlan --label maintenance
|
|
277
|
+
loops list --label browserplan --label maintenance
|
|
278
|
+
loops runs --label browserplan
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Labels are normalized to lowercase, deduplicated, limited to 32 per loop, and
|
|
282
|
+
must match `[a-z0-9][a-z0-9._-]{0,63}` after normalization. Repeated filters use
|
|
283
|
+
AND semantics. Run filtering uses the loop's current labels rather than a
|
|
284
|
+
historical snapshot on each run.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
loops labels add repo-status urgent
|
|
288
|
+
loops labels remove repo-status maintenance
|
|
289
|
+
loops labels set repo-status browserplan nightly
|
|
290
|
+
loops labels clear repo-status
|
|
291
|
+
```
|
|
292
|
+
|
|
226
293
|
Run an OpenCode loop with an explicit provider/model. OpenCode reads
|
|
227
|
-
`~/.config/opencode/config.json` when no model is supplied, so
|
|
294
|
+
`~/.config/opencode/config.json` when no model is supplied, so Loops rejects
|
|
228
295
|
OpenCode agent targets without `--model` instead of inheriting a stale or
|
|
229
296
|
machine-specific default.
|
|
230
297
|
|
|
@@ -237,6 +304,32 @@ loops create agent opencode-smoke \
|
|
|
237
304
|
--prompt "Reply with exactly OK."
|
|
238
305
|
```
|
|
239
306
|
|
|
307
|
+
Direct agent loops can persist an auditable session contract:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
loops create agent repo-check \
|
|
311
|
+
--provider codewith \
|
|
312
|
+
--every 15m \
|
|
313
|
+
--cwd /path/to/repo \
|
|
314
|
+
--sandbox workspace-write \
|
|
315
|
+
--prompt "Check the repo and report concrete failures." \
|
|
316
|
+
--allow-tool functions.exec_command \
|
|
317
|
+
--allow-command git,bun \
|
|
318
|
+
--safety-reason "isolated repository status inspection"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Tool and command lists are advisory metadata, not provider-enforced policy.
|
|
322
|
+
Loops stores the reason with the direct loop target, adds an explicit
|
|
323
|
+
advisory contract to provider stdin, and exports `LOOPS_AGENT_SESSION_CONTRACT`.
|
|
324
|
+
A direct agent loop has no workflow run, so it does not create a workflow event.
|
|
325
|
+
Agent steps inside workflows that carry an audit contract additionally receive
|
|
326
|
+
one server-derived `agent_session_contract` event per step and workflow run.
|
|
327
|
+
|
|
328
|
+
Codewith/Codex `danger-full-access`, Cursor `sandbox=disabled`, and provider
|
|
329
|
+
bypass modes (Claude, Cursor, AI Copilot, and OpenCode) require both a non-empty
|
|
330
|
+
safety reason and `--manual-break-glass`. A reason cannot replace explicit
|
|
331
|
+
operator approval.
|
|
332
|
+
|
|
240
333
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
241
334
|
|
|
242
335
|
```bash
|
|
@@ -280,7 +373,7 @@ resolve from the workflow JSON file's directory:
|
|
|
280
373
|
}
|
|
281
374
|
```
|
|
282
375
|
|
|
283
|
-
|
|
376
|
+
Loops records `promptSource` metadata and redacts prompt bodies in public
|
|
284
377
|
CLI output by default, including `templates render`. Use
|
|
285
378
|
`~/.hasna/loops/prompts/<stable-name>.md` as the default prompt store for
|
|
286
379
|
production loops.
|
|
@@ -291,7 +384,7 @@ non-secret routing/configuration data.
|
|
|
291
384
|
|
|
292
385
|
## Goals
|
|
293
386
|
|
|
294
|
-
Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer.
|
|
387
|
+
Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer. Loops asks the configured model to create a flat DAG plan, then executes ready plan nodes by re-running the underlying target once per node. Each node run is parameterized with that node's objective: agent prompts get the goal and node objective appended, and every wrapped process receives `LOOPS_GOAL_NODE_KEY` and `LOOPS_GOAL_NODE_OBJECTIVE` in its environment. An adversarial achievement audit runs before the goal is marked complete.
|
|
295
388
|
|
|
296
389
|
The goal `autoExecute` mode is honored: `off` plans and persists the DAG without executing any nodes (the run reports the persisted plan), while `readyOnly` (the default) and `aiDirected` run the dependency-driven execution loop over ready nodes.
|
|
297
390
|
|
|
@@ -342,7 +435,7 @@ Workflow JSON can also embed goals at the workflow or step level:
|
|
|
342
435
|
For **workflow loops** (`loops create workflow ...`), prefer a single loop-level
|
|
343
436
|
`--goal` wrapper around the scheduled workflow. Do not also define a top-level
|
|
344
437
|
`"goal"` on the workflow JSON spec: nested loop-level and workflow-level goal
|
|
345
|
-
wrappers deadlocked execution because each layer waited on the other.
|
|
438
|
+
wrappers deadlocked execution because each layer waited on the other. Loops
|
|
346
439
|
now rejects new workflow loops that combine both wrappers at creation time, and
|
|
347
440
|
blocks retargeting a loop-level goal loop onto a workflow that also carries a
|
|
348
441
|
top-level goal.
|
|
@@ -421,7 +514,7 @@ Use `shell: true` only when you intentionally want shell parsing:
|
|
|
421
514
|
## Templates And Task Events
|
|
422
515
|
|
|
423
516
|
Built-in templates turn common orchestration flows into reusable workflow JSON.
|
|
424
|
-
Todos-task routes are
|
|
517
|
+
Todos-task routes are Loops-native runtime admission — see
|
|
425
518
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for how this differs from
|
|
426
519
|
OpenAutomations queue ownership.
|
|
427
520
|
`todos-task-worker-verifier` performs one todos task and then verifies it.
|
|
@@ -477,7 +570,7 @@ loops templates render routing-remediation \
|
|
|
477
570
|
--var idempotencyKey=routing-health:repo:shard0
|
|
478
571
|
```
|
|
479
572
|
|
|
480
|
-
Custom reusable workflow templates live under the
|
|
573
|
+
Custom reusable workflow templates live under the Loops app data directory:
|
|
481
574
|
`~/.hasna/loops/templates` by default, or `$LOOPS_DATA_DIR/templates` when
|
|
482
575
|
`LOOPS_DATA_DIR` is set. Store templates as declarative JSON files; listing,
|
|
483
576
|
showing, and rendering templates never executes workflow steps or mutates the
|
|
@@ -581,6 +674,8 @@ arguments, and implicit Codewith/Codex full-access defaults. If a custom
|
|
|
581
674
|
Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
|
|
582
675
|
`sandbox` to `workspace-write` or `read-only`. Use built-in templates with
|
|
583
676
|
explicit break-glass handling for emergency workflows that need full access.
|
|
677
|
+
Claude, Cursor, AI Copilot, and OpenCode bypass modes always require explicit
|
|
678
|
+
break-glass plus a non-empty safety reason.
|
|
584
679
|
|
|
585
680
|
For event-driven task automation, `loops routes create todos-task` reads a
|
|
586
681
|
Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
|
|
@@ -682,10 +777,10 @@ cat event.json | loops routes create generic \
|
|
|
682
777
|
```
|
|
683
778
|
|
|
684
779
|
This is the intended deterministic-to-agentic path: a producer creates a todos
|
|
685
|
-
task, `@hasna/events` delivers `task.created`,
|
|
686
|
-
and admission item,
|
|
780
|
+
task, `@hasna/events` delivers `task.created`, Loops records the invocation
|
|
781
|
+
and admission item, Loops creates a worker/verifier workflow when admitted,
|
|
687
782
|
and the workflow updates todos with evidence. Use account pools so worker and
|
|
688
|
-
verifier steps do not burn the same profile. For live Codewith drains,
|
|
783
|
+
verifier steps do not burn the same profile. For live Codewith drains, Loops
|
|
689
784
|
spreads each route to the **least-loaded** pool account (counting running steps
|
|
690
785
|
per account) and still keeps the verifier on a different profile than the worker;
|
|
691
786
|
the deterministic hash is the tie-break, so a cold store reproduces the previous
|
|
@@ -698,9 +793,9 @@ Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
|
|
|
698
793
|
loop input without storing anything.
|
|
699
794
|
|
|
700
795
|
Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
|
|
701
|
-
is requested without `manualBreakGlass=true
|
|
702
|
-
unattended task/event routes. Full
|
|
703
|
-
not a default automation mode.
|
|
796
|
+
is requested without both `manualBreakGlass=true` and a non-empty
|
|
797
|
+
`safetyReason`. Use `workspace-write` for unattended task/event routes. Full
|
|
798
|
+
access is an explicit manual emergency path, not a default automation mode.
|
|
704
799
|
|
|
705
800
|
When a sandboxed Codewith/Codex worker must update app stores outside the repo
|
|
706
801
|
worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
@@ -792,7 +887,21 @@ anti-hog caps counted over all routes. Raise a router's `--max-active`
|
|
|
792
887
|
deliberately once counting is per-route; keep `--max-per-profile` set so the
|
|
793
888
|
extra concurrency spreads across subscription accounts.
|
|
794
889
|
|
|
795
|
-
|
|
890
|
+
Route flags or an expanded named policy are authoritative ceilings. Positive
|
|
891
|
+
integer task/event fields (`max_active`, `max_active_per_project`, and
|
|
892
|
+
`max_active_per_project_group`, including camel-case aliases) may only tighten
|
|
893
|
+
an already configured ceiling for that admission attempt; metadata cannot
|
|
894
|
+
create a cap, raise it, or override an explicit `--project-group`. Generated
|
|
895
|
+
workflow prompts and invocation manifests include the resolved route scope,
|
|
896
|
+
project group, and throttle limits so the admission decision is auditable from
|
|
897
|
+
the same evidence the agent sees.
|
|
898
|
+
|
|
899
|
+
These caps are local-store admission controls, not a distributed semaphore. The
|
|
900
|
+
count and admission write are serialized in one transaction for routers sharing
|
|
901
|
+
the same local `loops.db`; different `LOOPS_DATA_DIR` databases or machines do
|
|
902
|
+
not share project-group counts.
|
|
903
|
+
|
|
904
|
+
`--max-active` and related route throttles count Loops routed workflow work
|
|
796
905
|
items; they do not know whether Codewith is already at its live background-agent
|
|
797
906
|
admission limit. Add `--provider-active-cap <n>` (or the Codewith-specific alias
|
|
798
907
|
`--codewith-active-cap <n>`) to make the route run `codewith agent diagnostics
|
|
@@ -817,7 +926,7 @@ Workflow run manifests are written under
|
|
|
817
926
|
|
|
818
927
|
## OpenAutomations Runtime Binding
|
|
819
928
|
|
|
820
|
-
|
|
929
|
+
Loops executes automations that external compilers have materialized; it
|
|
821
930
|
does not own the automation product surface. See
|
|
822
931
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for the full ownership table,
|
|
823
932
|
three handoff paths (claim-queue, planned `@hasna/actions` upsert-one-shot, and
|
|
@@ -839,7 +948,7 @@ automations queue complete <action-id> --runner open-loops:<worker-id>
|
|
|
839
948
|
```
|
|
840
949
|
|
|
841
950
|
For explicit event workflow routing, OpenAutomations can export the normalized
|
|
842
|
-
event envelope and
|
|
951
|
+
event envelope and Loops can consume it through the existing generic event
|
|
843
952
|
route:
|
|
844
953
|
|
|
845
954
|
```bash
|
|
@@ -847,13 +956,13 @@ automations --json webhooks event <route> --body-json '<json>' \
|
|
|
847
956
|
| loops --json routes create generic
|
|
848
957
|
```
|
|
849
958
|
|
|
850
|
-
This is not automation materialization in
|
|
959
|
+
This is not automation materialization in Loops. It is an explicit
|
|
851
960
|
event-envelope workflow handoff: OpenAutomations owns deterministic automation
|
|
852
|
-
specs, webhook normalization, queue state, approvals, DLQ, and replay;
|
|
961
|
+
specs, webhook normalization, queue state, approvals, DLQ, and replay; Loops
|
|
853
962
|
owns agent workflow invocation after the operator routes the envelope to
|
|
854
963
|
`loops routes create generic`.
|
|
855
964
|
|
|
856
|
-
Do not store automation specs in
|
|
965
|
+
Do not store automation specs in Loops, infer automation triggers from event
|
|
857
966
|
transport alone, or replace the OpenAutomations queue with loop/workflow rows.
|
|
858
967
|
When a loop or workflow is used for execution, keep `HASNA_AUTOMATIONS_DIR`
|
|
859
968
|
pointing at the owning OpenAutomations data root and preserve the runner id in
|
|
@@ -862,7 +971,7 @@ completion/failure calls so OpenAutomations can enforce action leases.
|
|
|
862
971
|
### Planned Workflow Upsert SDK
|
|
863
972
|
|
|
864
973
|
External compilers such as `@hasna/actions` and `@hasna/automations` should not
|
|
865
|
-
write
|
|
974
|
+
write Loops SQLite rows directly. The stable contract should be an
|
|
866
975
|
idempotent CLI/SDK upsert that accepts a fully rendered one-shot workflow loop
|
|
867
976
|
request and returns durable refs.
|
|
868
977
|
|
|
@@ -875,7 +984,7 @@ does not duplicate that spec.
|
|
|
875
984
|
That design doc also defines the planned `@hasna/actions` target binding. The
|
|
876
985
|
binding reuses `ActionManifest`, `ActionInvocation`, `ActionRunStatus`,
|
|
877
986
|
`ActionQueueStatus`, action-owned idempotency keys, action audit events, and
|
|
878
|
-
dead-letter/replay records.
|
|
987
|
+
dead-letter/replay records. Loops only admits and executes the handed-off
|
|
879
988
|
workflow, then returns workflow refs for action-owned evidence.
|
|
880
989
|
|
|
881
990
|
The same design doc covers the planned DLQ/dead-letter lifecycle, including
|
|
@@ -889,7 +998,7 @@ work.
|
|
|
889
998
|
|
|
890
999
|
## Transcript-Driven Loops
|
|
891
1000
|
|
|
892
|
-
|
|
1001
|
+
Loops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
|
|
893
1002
|
|
|
894
1003
|
```bash
|
|
895
1004
|
loops workflows validate /path/to/repo/.openloops/transcript-feedback-to-loops.json --preflight
|
|
@@ -999,7 +1108,7 @@ Archived loops are hidden from the default `loops list`, excluded from daemon sc
|
|
|
999
1108
|
|
|
1000
1109
|
`loops run-now` reports the manual run source:
|
|
1001
1110
|
|
|
1002
|
-
- `source=ad_hoc`: the loop was not due yet, so
|
|
1111
|
+
- `source=ad_hoc`: the loop was not due yet, so Loops created a one-off manual slot. This is a single immediate attempt and does not schedule retries or consume the future scheduled slot.
|
|
1003
1112
|
- `source=due_slot`: the persisted scheduled slot was already due, so the manual run claims that slot and advances or retries the loop using normal scheduler rules.
|
|
1004
1113
|
- `source=retry_slot`: the loop was waiting on a failed slot retry, so the manual run claims that retry slot and advances the loop using normal retry rules.
|
|
1005
1114
|
|
|
@@ -1048,16 +1157,84 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
1048
1157
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
1049
1158
|
|
|
1050
1159
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
1051
|
-
- Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex.
|
|
1160
|
+
- Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex.
|
|
1052
1161
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
1053
|
-
- Cursor is CLI-first for now via the standalone `agent -p` binary.
|
|
1162
|
+
- Cursor is CLI-first for now via the standalone `agent -p` binary. Loops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
|
|
1054
1163
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
1055
1164
|
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin, including Codewith `exec` (which reads instructions from stdin when no positional prompt is given), so the prompt never lands on argv.
|
|
1056
|
-
- When `--account` or a step `account` is set,
|
|
1057
|
-
- `--auth-profile` and step `authProfile` are provider-native
|
|
1165
|
+
- When `--account` or a step `account` is set, Loops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
|
|
1166
|
+
- `--auth-profile` and step `authProfile` are provider-native Codewith selectors passed as `--auth-profile <name>` on the `exec` invocation; they do not call OpenAccounts. Local and remote preflight share the JSON-first, exact-name contract described above, use human-table parsing only as a compatibility fallback, and fail closed for unusable or missing profiles, NUL-containing names, and non-fallback list failures.
|
|
1058
1167
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
|
1168
|
+
- `--allow-tool` and `--allow-command` declare advisory, metadata-only
|
|
1169
|
+
restrictions and require `--safety-reason`. The exact contract is persisted
|
|
1170
|
+
and emitted for audit, but Loops does not claim provider-side enforcement.
|
|
1171
|
+
Relaxed sandboxes and native provider bypass modes also require explicit
|
|
1172
|
+
`--manual-break-glass`.
|
|
1059
1173
|
- `--permission-mode` maps `plan`, `auto`, and `bypass` where the provider supports it. Claude uses native permission modes, Cursor maps bypass to `--force`, and OpenCode/AICopilot map bypass to `--dangerously-skip-permissions`.
|
|
1174
|
+
- `extraArgs` is fail-closed: every provider currently rejects non-empty
|
|
1175
|
+
passthrough arguments, including unknown options, positional subcommands,
|
|
1176
|
+
split values, `--option=value`, and attached short-option forms. Configure
|
|
1177
|
+
execution, output, permissions, sandboxing, model, cwd, and other supported
|
|
1178
|
+
behavior through the modeled agent-target fields. A passthrough option must
|
|
1179
|
+
be explicitly reviewed and added to the provider allowlist before use.
|
|
1180
|
+
Legacy persisted targets are not silently accepted or rewritten: execution
|
|
1181
|
+
fails validation until `extraArgs` is removed and replaced with modeled
|
|
1182
|
+
fields. API and migration imports reject those targets instead of stripping
|
|
1183
|
+
the arguments; update the source record and retry the import.
|
|
1060
1184
|
- `--variant` is provider-specific reasoning/model effort. Claude maps it to `--effort`, Codewith/Codex map it to `model_reasoning_effort`, and OpenCode/AICopilot pass `--variant`.
|
|
1061
1185
|
- Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
|
|
1062
1186
|
|
|
1187
|
+
For hosted workflow runs, the control plane derives any required agent session
|
|
1188
|
+
contract from the stored workflow step and persists it before execution. Reusing an
|
|
1189
|
+
older workflow run backfills a missing valid contract idempotently. Stored
|
|
1190
|
+
pre-contract workflows with unsafe/invalid agent options, mismatched stored
|
|
1191
|
+
contracts, duplicate contracts, command-step contracts, and client-fabricated
|
|
1192
|
+
contracts fail closed. Direct agent loops continue to expose their contract
|
|
1193
|
+
through prompt/environment metadata only.
|
|
1194
|
+
|
|
1063
1195
|
For production loops that can mutate repos, use disposable worktrees (`--worktree-mode required` / `worktreeMode=required`) and explicit prompts that name allowed write scope. Worktrees are executor-enforced: when a target carries worktree metadata, the executor prepares and enters the git worktree before spawning the child process, records the worktree it entered, and with `mode=required` fails the run closed instead of falling back to the original checkout when preparation fails. Existing managed worktrees are reused only after top-level and git-common-dir checks; a clean detached or stale-branch checkout may be reattached to the expected generated branch, while dirty or unsafe mismatches fail with cleanup evidence. Remote machine runs with a required worktree apply the same checks on the remote side before the target executes.
|
|
1196
|
+
|
|
1197
|
+
## Immutable ECR Candidate Images
|
|
1198
|
+
|
|
1199
|
+
`.github/workflows/ecr-candidate.yml` is the repository workflow for building
|
|
1200
|
+
an AWS ECR release candidate. It is manual and source-only: it does not update
|
|
1201
|
+
`latest`, ECS task definitions, services, or deployments. The job checks out
|
|
1202
|
+
an exact full commit SHA, proves that commit is reachable from `origin/main`,
|
|
1203
|
+
builds the `Dockerfile` `runner` target natively for `linux/arm64`, blocks on
|
|
1204
|
+
local Trivy critical/high findings, and only then assumes the ECR push role.
|
|
1205
|
+
After the immutable push it waits for ECR scanning and blocks on critical/high
|
|
1206
|
+
findings there as well. The retained artifact contains the local scan report,
|
|
1207
|
+
a CycloneDX SBOM, an unsigned in-toto/SLSA provenance statement, and bounded
|
|
1208
|
+
ECR scan counts. The statement is evidence, not a cryptographic attestation.
|
|
1209
|
+
|
|
1210
|
+
Before the first run, repository administrators must create a protected GitHub
|
|
1211
|
+
environment named `ecr-candidate` with required reviewers and no deployment
|
|
1212
|
+
branches other than `main`. Configure these repository variables (not secrets):
|
|
1213
|
+
|
|
1214
|
+
- `AWS_REGION`: AWS region containing the existing candidate repository.
|
|
1215
|
+
- `AWS_ROLE_ARN`: OIDC role dedicated to this workflow.
|
|
1216
|
+
- `ECR_REPOSITORY`: existing ECR repository name. It must use immutable tags
|
|
1217
|
+
and scan-on-push.
|
|
1218
|
+
|
|
1219
|
+
The OIDC role trust must constrain `token.actions.githubusercontent.com:aud`
|
|
1220
|
+
to `sts.amazonaws.com` and `token.actions.githubusercontent.com:sub` to exactly
|
|
1221
|
+
`repo:hasna/loops:environment:ecr-candidate`. Its permissions should allow
|
|
1222
|
+
`ecr:GetAuthorizationToken` on `*`, and only these actions on the configured
|
|
1223
|
+
repository ARN: `ecr:DescribeRepositories`, `ecr:BatchCheckLayerAvailability`,
|
|
1224
|
+
`ecr:GetDownloadUrlForLayer`, `ecr:BatchGetImage`, `ecr:InitiateLayerUpload`,
|
|
1225
|
+
`ecr:UploadLayerPart`, `ecr:CompleteLayerUpload`, `ecr:PutImage`,
|
|
1226
|
+
`ecr:DescribeImages`, and `ecr:DescribeImageScanFindings`. No static AWS access
|
|
1227
|
+
key is used.
|
|
1228
|
+
|
|
1229
|
+
Dispatch only after the source commit is merged to `main`:
|
|
1230
|
+
|
|
1231
|
+
```bash
|
|
1232
|
+
sha="$(git rev-parse origin/main)"
|
|
1233
|
+
gh workflow run ecr-candidate.yml --repo hasna/loops --ref main \
|
|
1234
|
+
-f source_sha="${sha}" -f confirmation="push ${sha}"
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
Dispatch is an AWS mutation and requires the applicable operator approval and
|
|
1238
|
+
fresh release preflight. Do not run it merely because the source workflow has
|
|
1239
|
+
merged. Review the workflow summary and retained evidence; promote only by
|
|
1240
|
+
the reported immutable digest through the separate deployment process.
|