@atollhq/skill-codex 0.4.23 → 0.4.25

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 CHANGED
@@ -6,13 +6,27 @@ Gives your Codex agent the ability to manage tasks, goals, KPIs, initiatives, mi
6
6
 
7
7
  ## Install
8
8
 
9
+ Install or refresh the local skill files without configuring credentials:
10
+
11
+ ```bash
12
+ npx --yes @atollhq/skill-codex@latest
13
+ ```
14
+
15
+ Bare invocation and `--install-only` only update `~/.codex/skills/atoll/`, the profile-neutral routing hint, and compatibility references. They do not inspect or change Atoll credentials, profiles, shell files, or repo configuration, even when `ATOLL_*` variables are present.
16
+
17
+ Configure a named profile explicitly:
18
+
19
+ ```bash
20
+ npx --yes @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org org-uuid --project project-id --team team-id
21
+ ```
22
+
23
+ For intentional environment configuration, use `--configure`:
24
+
9
25
  ```bash
10
- npx @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org your-org-id --project project-id --team team-id
11
- # or
12
- ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=your-org-id npx @atollhq/skill-codex@latest
26
+ ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=org-uuid npx --yes @atollhq/skill-codex@latest --configure
13
27
  ```
14
28
 
15
- Optional defaults: `--project`, `--team`, and `--base-url` are stored with the selected mode. Use `--no-project`, `--no-team`, or `--no-base-url` to clear previously saved defaults. Pass `--profile` to store credentials and defaults only in that named Atoll CLI profile. The installer does not write global `ATOLL_*` exports to `~/.zshrc` or `~/.bashrc` in profile mode. Use `atoll --profile agent-a ...` for profile-scoped commands, or omit `--profile` to use env-var mode.
29
+ Configuration mode supports `--project`, `--team`, and `--base-url`. Use `--no-project`, `--no-team`, or `--no-base-url` to clear saved defaults. Pass `--profile` to store credentials and defaults only in that named Atoll CLI profile. The installer does not write global `ATOLL_*` exports in profile mode. Use `atoll --profile agent-a ...` for profile-scoped commands.
16
30
 
17
31
  For multi-org setups, keep global Codex instructions neutral and bind profiles per repo:
18
32
 
@@ -25,16 +39,16 @@ npx @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org your-
25
39
 
26
40
  Get an agent API key from **Agents** in the Atoll app. Integration keys are managed from **Settings > Integrations**.
27
41
 
28
- This does six things:
42
+ Every invocation installs the local skill assets, routing hint, and compatibility references. Configuration mode additionally:
29
43
 
30
44
  1. Installs the `atoll` skill to `~/.codex/skills/atoll/`
31
45
  2. Appends (or updates) a small profile-neutral Atoll skill routing hint in `~/.codex/AGENTS.md`
32
46
  3. Copies API reference files to `~/.codex/atoll-references/`
33
47
  4. Creates or updates the named Atoll CLI profile when `--profile` is provided
34
- 5. Appends Atoll env var exports to your shell profile (`~/.zshrc` or `~/.bashrc`) only when no profile is provided
48
+ 5. Appends Atoll env var exports to your shell profile (`~/.zshrc` or `~/.bashrc`) when `--configure` is used without a profile
35
49
  6. Optionally writes repo-local profile guidance when `--write-project-instructions` is provided
36
50
 
37
- For profile mode, Codex has the Atoll skill immediately, global Codex guidance points to that skill without embedding the full Atoll guide, and terminal commands can use `atoll --profile agent-a ...`. For env-var mode, the installer writes `ATOLL_ENV_MODE=1` with the credential exports; open a fresh shell or `source` your profile.
51
+ For profile mode, Codex has the Atoll skill immediately, global Codex guidance points to that skill without embedding the full Atoll guide, and terminal commands can use `atoll --profile agent-a ...`. For explicitly requested env-var mode, the installer writes `ATOLL_ENV_MODE=1` with the credential exports; open a fresh shell or `source` your profile.
38
52
 
39
53
  Use `@latest` in the `npx` command so npm does not reuse a stale cached installer. In profile mode, the installer prints its package version and a verification command; run `atoll --profile agent-a agent-context --json` if you need to confirm the profile was created.
40
54
 
package/bin/install.mjs CHANGED
@@ -22,45 +22,86 @@ import { fileURLToPath } from 'node:url'
22
22
 
23
23
  const __dirname = dirname(fileURLToPath(import.meta.url))
24
24
  const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))
25
+ const configurationFlags = new Set([
26
+ '--configure',
27
+ '--profile',
28
+ '--key',
29
+ '--org',
30
+ '--project',
31
+ '--no-project',
32
+ '--team',
33
+ '--no-team',
34
+ '--base-url',
35
+ '--no-base-url',
36
+ '--write-project-instructions',
37
+ '--project-dir',
38
+ '--instruction-files',
39
+ '--force-project-instructions',
40
+ ])
41
+ const valueFlags = {
42
+ '--profile': 'profile',
43
+ '--key': 'key',
44
+ '--org': 'org',
45
+ '--project': 'project',
46
+ '--team': 'team',
47
+ '--base-url': 'baseUrl',
48
+ '--project-dir': 'projectDir',
49
+ '--instruction-files': 'instructionFiles',
50
+ }
51
+
52
+ function hasConfigurationIntent(argv) {
53
+ return argv.slice(2).some((arg) => configurationFlags.has(arg.split('=', 1)[0]))
54
+ }
25
55
 
26
56
  function parseArgs(argv) {
27
57
  const args = {}
28
58
  for (let i = 2; i < argv.length; i++) {
29
- if (argv[i] === '--profile' && argv[i + 1]) args.profile = argv[++i]
30
- else if (argv[i] === '--key' && argv[i + 1]) args.key = argv[++i]
31
- else if (argv[i] === '--org' && argv[i + 1]) args.org = argv[++i]
32
- else if (argv[i] === '--project' && argv[i + 1]) args.project = argv[++i]
33
- else if (argv[i] === '--no-project') args.clearProject = true
34
- else if (argv[i] === '--team' && argv[i + 1]) args.team = argv[++i]
35
- else if (argv[i] === '--no-team') args.clearTeam = true
36
- else if (argv[i] === '--base-url' && argv[i + 1]) args.baseUrl = argv[++i]
37
- else if (argv[i] === '--no-base-url') args.clearBaseUrl = true
38
- else if (argv[i] === '--write-project-instructions') args.writeProjectInstructions = true
39
- else if (argv[i] === '--project-dir' && argv[i + 1]) args.projectDir = argv[++i]
40
- else if (argv[i] === '--instruction-files' && argv[i + 1]) args.instructionFiles = argv[++i]
41
- else if (argv[i] === '--force-project-instructions') args.forceProjectInstructions = true
42
- else if (argv[i] === '--help' || argv[i] === '-h') args.help = true
59
+ const argument = argv[i]
60
+ const separator = argument.indexOf('=')
61
+ const flag = separator === -1 ? argument : argument.slice(0, separator)
62
+ const inlineValue = separator === -1 ? undefined : argument.slice(separator + 1)
63
+ const nextValue = inlineValue ?? argv[i + 1]
64
+ const valueKey = valueFlags[flag]
65
+ if (valueKey) {
66
+ if (!nextValue || nextValue.startsWith('-')) {
67
+ throw new Error(`${flag} requires a non-empty value`)
68
+ }
69
+ args[valueKey] = nextValue
70
+ if (inlineValue === undefined) i++
71
+ } else if (flag === '--no-project') args.clearProject = true
72
+ else if (flag === '--no-team') args.clearTeam = true
73
+ else if (flag === '--no-base-url') args.clearBaseUrl = true
74
+ else if (flag === '--install-only') args.installOnly = true
75
+ else if (flag === '--configure') args.configure = true
76
+ else if (flag === '--write-project-instructions') args.writeProjectInstructions = true
77
+ else if (flag === '--force-project-instructions') args.forceProjectInstructions = true
78
+ else if (flag === '--help' || flag === '-h') args.help = true
79
+ else throw new Error(`Unknown option: ${flag}`)
43
80
  }
44
81
  return args
45
82
  }
46
83
 
47
84
  function printUsage() {
48
85
  console.log(`
49
- Usage: npx @atollhq/skill-codex@latest [--profile <name>] --key <api-key> --org <org-id> [--project <id>] [--team <id-or-slug>] [--base-url <url>]
50
- or: ATOLL_API_KEY=<api-key> ATOLL_ORG_ID=<org-id> npx @atollhq/skill-codex@latest
86
+ Usage: npx @atollhq/skill-codex@latest --install-only
87
+ or: npx @atollhq/skill-codex@latest --configure [--profile <name>] --key <api-key> --org <org-id> [--project <id>] [--team <id-or-slug>] [--base-url <url>]
88
+ or: ATOLL_API_KEY=<api-key> ATOLL_ORG_ID=<org-id> npx @atollhq/skill-codex@latest --configure
51
89
 
52
90
  Options:
53
- --profile Atoll CLI profile name to create/update. Profile mode does not write shell env vars.
54
- --key Atoll API key (sk_atoll_...). Defaults to ATOLL_API_KEY.
55
- --org Organization ID. Defaults to ATOLL_ORG_ID.
56
- --project Default project ID. Defaults to ATOLL_PROJECT.
91
+ --profile Atoll CLI profile name to create/update. Enables configuration mode.
92
+ --key Atoll API key (sk_atoll_...). Used in configuration mode; defaults to ATOLL_API_KEY.
93
+ --org Organization ID. Used in configuration mode; defaults to ATOLL_ORG_ID.
94
+ --project Default project ID. Used in configuration mode; defaults to ATOLL_PROJECT.
57
95
  --no-project Clear any saved default project.
58
- --team Default team ID or slug. Defaults to ATOLL_TEAM.
96
+ --team Default team ID or slug. Used in configuration mode; defaults to ATOLL_TEAM.
59
97
  --no-team Clear any saved default team.
60
- --base-url Atoll base URL. Defaults to ATOLL_BASE_URL.
98
+ --base-url Atoll base URL. Used in configuration mode; defaults to ATOLL_BASE_URL.
61
99
  --no-base-url Clear any saved custom base URL.
100
+ --install-only
101
+ Install or refresh local skill files without reading or changing credentials.
102
+ --configure Explicitly enable profile or environment credential configuration.
62
103
  --write-project-instructions
63
- Write a small repo-local Atoll profile block. Requires --profile.
104
+ Write a small repo-local Atoll profile block. Requires --configure and --profile.
64
105
  --project-dir
65
106
  Directory for repo-local instructions. Defaults to the current directory.
66
107
  --instruction-files
@@ -72,24 +113,35 @@ Options:
72
113
  Installs the Atoll integration for Codex CLI:
73
114
  - Installs the atoll skill to ~/.codex/skills/atoll/
74
115
  - Writes a small AGENTS.md routing hint to ~/.codex/
75
- - Creates/updates an Atoll CLI profile when --profile is provided
116
+ - Leaves authentication and profile configuration unchanged by default
117
+ - Creates/updates credentials only when --configure or another configuration flag is provided
76
118
  - Optionally writes repo-local AGENTS.md/CLAUDE.md profile guidance with --write-project-instructions
77
- - Otherwise writes Atoll env vars to your shell profile for env-var mode
78
119
  `)
79
120
  }
80
121
 
81
122
  const args = parseArgs(process.argv)
82
- args.key ??= process.env.ATOLL_API_KEY
83
- args.org ??= process.env.ATOLL_ORG_ID
84
- if (args.clearProject) delete args.project
85
- else args.project ??= process.env.ATOLL_PROJECT
86
- if (args.clearTeam) delete args.team
87
- else args.team ??= process.env.ATOLL_TEAM
88
- if (args.clearBaseUrl) delete args.baseUrl
89
- else args.baseUrl ??= process.env.ATOLL_BASE_URL
123
+ const configurationIntent = hasConfigurationIntent(process.argv)
124
+
125
+ if (args.installOnly && configurationIntent) {
126
+ console.error('Error: --install-only cannot be combined with credential or profile configuration flags')
127
+ printUsage()
128
+ process.exit(1)
129
+ }
90
130
 
91
131
  if (args.help) { printUsage(); process.exit(0) }
92
132
 
133
+ const configureMode = configurationIntent
134
+ if (configureMode) {
135
+ args.key ??= process.env.ATOLL_API_KEY
136
+ args.org ??= process.env.ATOLL_ORG_ID
137
+ if (args.clearProject) delete args.project
138
+ else args.project ??= process.env.ATOLL_PROJECT
139
+ if (args.clearTeam) delete args.team
140
+ else args.team ??= process.env.ATOLL_TEAM
141
+ if (args.clearBaseUrl) delete args.baseUrl
142
+ else args.baseUrl ??= process.env.ATOLL_BASE_URL
143
+ }
144
+
93
145
  console.log(`Running @atollhq/skill-codex installer v${packageJson.version}`)
94
146
 
95
147
  const instructionFileChoices = (args.instructionFiles ?? 'agents')
@@ -118,13 +170,13 @@ if (args.writeProjectInstructions && !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(args.p
118
170
  process.exit(1)
119
171
  }
120
172
 
121
- if (!args.key || !args.org) {
173
+ if (configureMode && (!args.key || !args.org)) {
122
174
  console.error('Error: provide --key and --org, or set ATOLL_API_KEY and ATOLL_ORG_ID\n')
123
175
  printUsage()
124
176
  process.exit(1)
125
177
  }
126
178
 
127
- if (!args.key.startsWith('sk_atoll_')) {
179
+ if (configureMode && !args.key.startsWith('sk_atoll_')) {
128
180
  console.error('Error: API key must start with sk_atoll_')
129
181
  process.exit(1)
130
182
  }
@@ -164,6 +216,52 @@ function ensurePrivateDirectory(path) {
164
216
  chmodSync(path, 0o700)
165
217
  }
166
218
 
219
+ function prepareSkillDirectory(path, source) {
220
+ const existing = lstatIfExists(path)
221
+ if (existing) {
222
+ if (existing.isSymbolicLink()) {
223
+ throw new Error(`Refusing symbolic link for skill directory: ${path}`)
224
+ }
225
+ if (!existing.isDirectory()) {
226
+ throw new Error(`Refusing non-directory skill path: ${path}`)
227
+ }
228
+ }
229
+
230
+ const parent = dirname(path)
231
+ mkdirSync(parent, { recursive: true })
232
+ const tempPath = join(parent, `.${basename(path)}.atoll-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`)
233
+ const backupPath = `${tempPath}.backup`
234
+ let movedExisting = false
235
+ let installed = false
236
+ try {
237
+ cpSync(source, tempPath, { recursive: true })
238
+
239
+ const current = lstatIfExists(path)
240
+ if (current) {
241
+ if (current.isSymbolicLink()) {
242
+ throw new Error(`Refusing symbolic link for skill directory: ${path}`)
243
+ }
244
+ if (!current.isDirectory()) {
245
+ throw new Error(`Refusing non-directory skill path: ${path}`)
246
+ }
247
+ renameSync(path, backupPath)
248
+ movedExisting = true
249
+ }
250
+
251
+ renameSync(tempPath, path)
252
+ installed = true
253
+ if (movedExisting) rmSync(backupPath, { recursive: true, force: true })
254
+ } catch (error) {
255
+ if (!installed) {
256
+ if (lstatIfExists(tempPath)) rmSync(tempPath, { recursive: true, force: true })
257
+ if (movedExisting && !lstatIfExists(path) && lstatIfExists(backupPath)) {
258
+ renameSync(backupPath, path)
259
+ }
260
+ }
261
+ throw error
262
+ }
263
+ }
264
+
167
265
  function readJson(path) {
168
266
  assertSafeFile(path)
169
267
  if (!existsSync(path)) return {}
@@ -198,10 +296,27 @@ function writePrivateFile(path, content) {
198
296
  }
199
297
  }
200
298
 
201
- function acquireInstallerLock() {
202
- const atollDir = join(homedir(), '.atoll')
203
- const target = join(atollDir, 'installer.lock-target')
204
- ensurePrivateDirectory(atollDir)
299
+ const codexDir = resolve(process.env.CODEX_HOME || join(homedir(), '.codex'))
300
+ const defaultInstallerLockTarget = join(homedir(), '.atoll', 'installer.lock-target')
301
+ const sharedInstallerLockTarget = join(
302
+ homedir(),
303
+ '.atoll-skill-installer.lock-target',
304
+ )
305
+ const codexSkillLockTarget = join(codexDir, '.atoll-skill-installer.lock-target')
306
+
307
+ function ensureLockDirectory(path) {
308
+ const entry = lstatIfExists(path)
309
+ if (entry) {
310
+ const stat = entry.isSymbolicLink() ? statSync(path) : entry
311
+ if (!stat.isDirectory()) throw new Error(`Refusing non-directory installer lock path: ${path}`)
312
+ } else {
313
+ mkdirSync(path, { recursive: true, mode: 0o700 })
314
+ }
315
+ }
316
+
317
+ function acquireInstallerLock(target = defaultInstallerLockTarget) {
318
+ if (target === defaultInstallerLockTarget) ensurePrivateDirectory(dirname(target))
319
+ else ensureLockDirectory(dirname(target))
205
320
  try {
206
321
  writeFileSync(target, '', { flag: 'wx', mode: 0o600 })
207
322
  } catch (error) {
@@ -257,7 +372,23 @@ function acquireInstallerLock() {
257
372
  }
258
373
  }
259
374
 
260
- const releaseInstallerLock = acquireInstallerLock()
375
+ const releaseSharedInstallerLock = acquireInstallerLock(sharedInstallerLockTarget)
376
+ let releaseCodexSkillInstallerLock = null
377
+ let releaseConfigInstallerLock = null
378
+ try {
379
+ releaseCodexSkillInstallerLock = acquireInstallerLock(codexSkillLockTarget)
380
+ if (configureMode) releaseConfigInstallerLock = acquireInstallerLock()
381
+ } catch (error) {
382
+ releaseCodexSkillInstallerLock?.()
383
+ releaseSharedInstallerLock()
384
+ throw error
385
+ }
386
+
387
+ function releaseInstallerLock() {
388
+ releaseConfigInstallerLock?.()
389
+ releaseCodexSkillInstallerLock?.()
390
+ releaseSharedInstallerLock()
391
+ }
261
392
 
262
393
  function writeAtollProfile() {
263
394
  if (!args.profile) return null
@@ -422,14 +553,12 @@ function writeGlobalInstructions(targetPath) {
422
553
  }
423
554
 
424
555
  // 1. Install the Codex skill to ~/.codex/skills/atoll/
425
- const codexDir = join(homedir(), '.codex')
426
556
  mkdirSync(codexDir, { recursive: true })
427
557
 
428
558
  const skillDir = join(__dirname, '..', 'skill')
429
559
  const skillDest = join(codexDir, 'skills', 'atoll')
430
560
  const legacySkillDest = join(codexDir, 'skills', 'atoll-api')
431
- mkdirSync(skillDest, { recursive: true })
432
- cpSync(skillDir, skillDest, { recursive: true })
561
+ prepareSkillDirectory(skillDest, skillDir)
433
562
  console.log(`Installed Atoll skill to ${skillDest}`)
434
563
  if (existsSync(legacySkillDest)) {
435
564
  rmSync(legacySkillDest, { recursive: true, force: true })
@@ -449,6 +578,13 @@ for (const file of ['api-endpoints.md', 'api-fields.md']) {
449
578
  }
450
579
  console.log(`Copied API references to ${refsDir}`)
451
580
 
581
+ if (!configureMode) {
582
+ console.log('Authentication/profile configuration left unchanged.')
583
+ console.log('\nDone! Codex has the Atoll skill installed.')
584
+ releaseInstallerLock()
585
+ process.exit(0)
586
+ }
587
+
452
588
  const shell = process.env.SHELL || '/bin/bash'
453
589
  const profilePath = shell.includes('zsh')
454
590
  ? join(homedir(), '.zshrc')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atollhq/skill-codex",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "description": "Install the Atoll project management integration for Codex CLI",
5
5
  "bin": {
6
6
  "skill-codex": "bin/install.mjs"
package/skill/SKILL.md CHANGED
@@ -364,6 +364,7 @@ atoll dependency bulk-add --file ./dependencies.json --continue-on-error
364
364
 
365
365
  Dependency reads include a target issue `identifier` and `projectSlug` when the target belongs to a project. Inaccessible targets remain `issue: null`; projectless targets have both fields set to `null`.
366
366
  Dependencies persist a release point in the blocking project's ordered board columns. Add `releaseColumnId` when creating an edge, or omit it to default to that project's `done` column. Use the dependency API PATCH route to change the release point; reads include `releaseColumnId`, `releaseColumn`, and `satisfied`.
367
+ Archiving a blocker preserves the dependency edge and configured release column while satisfying the dependency. Restoring it re-evaluates the same release point and can block the dependent again. Configurable release-point and cancelled-blocker behavior are unchanged.
367
368
  The blocking issue must belong to a project because its release point is a board
368
369
  column there; a projectless issue may be the blocked target.
369
370
  The dependency-release migration backfills existing dependencies to the
@@ -429,6 +430,9 @@ CLI JSON conventions:
429
430
  - `atoll heartbeat --json` includes the same structured `cli` update metadata for agents, plus `attention_items`, `attention_summary`, and `recommended_action` when Atoll can propose one concrete strategy-backed next action. `atoll heartbeat --signals-only --json` preserves filtered `signals`, `attention_items`, `attention_summary`, and `recommended_action` for short polling. Handle direct attention items first, then call each handled item's `ack_endpoint`. Follow `recommended_action.usage_guidance`: prefer `suggested_write.operation` when it still matches the board, preserve KPI/initiative/initiative_target/why-now/expected-impact/first-step/success-criteria evidence, and avoid copying deferred busywork into issue or comment payloads. If a `start_work` recommendation uses `issue.update` with a body, update the issue status and preserve that body as an issue comment; `PATCH /issues/{issueId}` accepts `comment_body` for this same-request progress note.
430
431
  - Authorized humans can configure an agent's included heartbeat sections and generated-signal focus in the Atoll **Heartbeats** UI. The saved policy is applied by the API before CLI or MCP request-level narrowing; it never changes project access, and existing heartbeat commands require no new arguments.
431
432
  - GitHub `workflow_run` signals are accepted only when HMAC-signed and completed, then reread and matched exactly by repository, PR, workflow path, run attempt, and head SHA. Workflow verification is disabled by default and observe-only until an owner/admin enables it in **Settings > Integrations > GitHub**. `attention` mode can add one bounded `verification.completed` attention item through authorized REST or CLI heartbeat for exactly one eligible current agent assignee or, when there is no unambiguous assignee, an eligible configured delivery agent. The public MCP heartbeat excludes this private event type. Unresolved recipients and cancelled, obsolete, superseded, mismatched, or unreadable runs create no attention. Owners and admins can configure 1–10 workflow paths of at most 255 characters each; the bounded evidence list defaults to 25 items and accepts a maximum `limit` of 100. Signed pull-request writes and reconciliation bind PR links to the stable GitHub repository ID, so repository renames keep existing workflow evidence linked. Do not expect raw payloads, secrets, logs, or thread IDs in evidence; owner/admin reconciliation retries pending evidence after current GitHub and PR-link readback.
433
+ - Release-added required GitHub hook events mark existing reconciled and already-pending connections pending. The bounded 15-minute service sweep verifies immutable repository identity and upgrades hooks automatically; transient failures remain pending for retry, and owners/admins can reconcile manually.
434
+ - Issue delivery context selects an open PR first, then the latest updated link, then the highest PR number. `pending` review/workflow state with null provenance means no current-head observation and does not by itself set `partial`. Disabled GitHub verification stops new projections. Workflow conclusions map success/neutral to passed, cancelled/stale/skipped to cancelled, and other supported terminal conclusions to failed.
435
+ - Aggregate review state keeps each reviewer's latest exact-head opinion, ignores comments, and removes dismissed opinions. Change requests win. `approved` means at least one effective approval and no effective change request; it does not prove required-review counts or branch protection.
432
436
  - `atoll plan validate/apply` consumes `schemaVersion: "atoll.plan.v1"` files with `milestones`, `issues`, `dependencies`, `initiativeLinks`, and `milestoneLinks`; local `key` values can be referenced by `milestoneKey`, `issueKey`, `dependsOn`, `blockedBy`, or `blocks`.
433
437
 
434
438
  ## KPI HTTP Sync Drafts
@@ -620,6 +624,64 @@ atoll() {
620
624
  atoll "/api/orgs/$ATOLL_ORG_ID/issues?status=todo"
621
625
  ```
622
626
 
627
+ ## Execution and attention CLI workflow
628
+
629
+ Use `atoll execution list|get|create|transition`, `execution evidence list|add`,
630
+ and `atoll attention create|list|get|cancel` with the selected profile and `--json`.
631
+ Creation requires `--issue`, `--agent <member-id|self>`, and an explicit
632
+ `--idempotency-key`; it returns `assigned` at state version 1. Start with a
633
+ separate `execution transition <id> --to running --expected-state-version 1
634
+ --idempotency-key <start-key>`. Atoll records state; it does not start a harness.
635
+
636
+ Generic transition targets are `running|waiting|succeeded|failed|cancelled`.
637
+ For `succeeded`, supply `--outcome-summary` unless the execution already has
638
+ linked evidence. The server validates this requirement.
639
+ Use `attention create` to move `running|waiting` to `needs_human`; generic
640
+ transitions cannot enter or leave `needs_human`. Attention kinds are exactly
641
+ `approval|clarification|access|decision|destructive_action|other`. Supply the
642
+ execution's expected state version, title, request summary, why needed, resume
643
+ condition, exactly one member/team/project-admin target, and an idempotency key.
644
+ Never put credentials, access tokens, private paths, prompts, logs, or other
645
+ secrets in attention text. Server permissions and concealed 404 responses remain
646
+ authoritative; do not try another identity to bypass them.
647
+
648
+ Read `attention get <id>` for the human's resolution and current attention and
649
+ execution versions. Human resolution returns the execution to `waiting`; it
650
+ does not resume a model or harness. Requester `attention cancel` also returns it
651
+ to `waiting` and requires `--expected-attention-version`,
652
+ `--expected-state-version`, and `--idempotency-key`. Human resolve, administrator
653
+ retarget/cancel, and recovery discovery are REST/UI operations, not CLI commands.
654
+ Harness acceptance and the later explicitly fenced `waiting -> running` resume
655
+ remain the separate AH-2122 integration.
656
+
657
+ Every write uses the caller's explicit idempotency key; transitions and attention
658
+ writes use the caller's expected versions. Never silently fetch a new version
659
+ and write against it. After a POST timeout, network failure, or HTTP 5xx, the
660
+ outcome is uncertain and the CLI does not retry. Read `execution get <id>`,
661
+ `attention get <id>` (or `attention list --execution <id>` when create returned no
662
+ attention ID), or `execution evidence list <id>`. Stop if the result is visible.
663
+ For execution create without an ID, replay the identical create command with
664
+ the same key, then read the returned ID. If replay is needed for another write,
665
+ keep the exact body and key. Stop for operator reconciliation if changed state
666
+ or versions make the outcome ambiguous; never use a new key to force progress.
667
+
668
+ Evidence add links only an existing authorized issue object using
669
+ `--type <comment|activity_event|issue_pr_link|attachment> --target-id <uuid>
670
+ --idempotency-key <key>`. It does not upload files, URLs, text, or raw logs.
671
+
672
+ ## Human attention
673
+
674
+ When an execution needs a human, use the attention contract. `POST
675
+ /api/orgs/{id}/attention` records a bounded request and atomically moves the
676
+ execution to `needs_human`; generic execution transitions cannot perform this
677
+ edge. Poll `GET /api/orgs/{id}/attention` or use the exact item endpoint.
678
+ Resolve, cancel, or retarget with both expected versions and an idempotency
679
+ key. Reuse the same key only with the same input. Use `mode=recovery` only as
680
+ an authorized human administrator when the original target is no longer
681
+ eligible. Keep request text concise and never include secrets, credentials,
682
+ logs, prompts, or local paths. The public projection provides current and
683
+ snapshot actor/target fields, execution state, issue, and project context.
684
+
623
685
  ## The Heartbeat Loop
624
686
 
625
687
  The primary pattern for autonomous agents. Prefer `atoll heartbeat --json` when the CLI is available; it wraps `GET /api/orgs/{id}/heartbeat` and returns the same computed briefing:
@@ -791,6 +853,20 @@ Owners/admins can read billing state with `GET /api/orgs/{id}/billing` and start
791
853
 
792
854
  Creation endpoints can return `402` with `code: "PLAN_LIMIT_REACHED"` when an org reaches limits for humans, agents/integrations, active projects, or active issues.
793
855
 
856
+ ## Agent execution REST API
857
+
858
+ Use the canonical org-scoped execution routes for lifecycle management:
859
+ `GET|POST /api/orgs/{id}/executions`, `GET
860
+ /api/orgs/{id}/executions/{executionId}`, `POST
861
+ /api/orgs/{id}/executions/{executionId}/transitions`, and `GET|POST` on the
862
+ matching `/evidence` route. Create starts in `assigned`; transition writes
863
+ require `expected_state_version` and an idempotency key. Generic transitions
864
+ cannot enter or leave `needs_human`; use the attention contract. Reads follow
865
+ the issue's current project access. Non-guest organization members may also read
866
+ projectless executions; setup-scoped agents and guest members cannot. Creation-
867
+ project metadata does not grant access, and unreadable records are concealed.
868
+ Responses are bounded management projections, not logs or harness controls.
869
+
794
870
  ## API Reference
795
871
 
796
872
  Full endpoint tables and field schemas:
@@ -827,6 +903,12 @@ default 50) and `offset`, and returns `hasMore`; removing the final link
827
903
  requires owner or admin access. Linked issues and projects cannot be deleted
828
904
  until the Artifact is unlinked or reassigned.
829
905
 
906
+ Artifact list and detail responses include `can_edit`, which is true when the
907
+ current member can create a revision, and `can_unlink`, which is true when the
908
+ current member can remove a visible link. Members with write access can remove
909
+ a link when another link remains; removing a final link requires owner or admin
910
+ access.
911
+
830
912
  Private CLI issue reads request the opt-in metadata-only manifest. Inspect
831
913
  `.artifacts`, then use `atoll artifact get <id> --issue <issue>` only when the
832
914
  full current body is required. Create and update accept `--body-file -` for
@@ -861,6 +943,17 @@ otherwise the API returns `422` with `code: "github_identity_unavailable"`.
861
943
  Reads return bounded display metadata, provenance, observation timestamps, and
862
944
  resolvability. Reads require project visibility; writes require project
863
945
  `edit`/`admin`, with eligible non-guests allowed for projectless issues.
946
+ For compact implementation evidence, the private REST endpoint
947
+ `GET /api/orgs/{id}/issues/{issueId}/external-operational-signals` returns the
948
+ selected PR, stable repository identity, exact current head SHA, current-head
949
+ review and configured workflow states, bounded provenance, freshness, and a
950
+ safe strongest blocker. Older-head evidence is historical. Configured
951
+ workflows are not GitHub branch-protection required checks. This namespace is
952
+ separate from heartbeat `signals[]` and never changes tasks or dispatches
953
+ agents.
954
+ The selected PR-link state is authoritative. If a same-head PR observation
955
+ disagrees, Atoll clears its observation/provider provenance, falls back to the
956
+ link URL, excludes it from freshness, and sets `partial`.
864
957
  Organization-wide templates are readable by non-guests and manageable only by
865
958
  organization owners/admins; guest/project-scoped agents never receive them.
866
959
  Avatar mutations require both caller and target to belong to the organization
@@ -220,6 +220,7 @@ bodies, projects only declared public issue fields, preserves nullable
220
220
  Add with `{ "blockedByIssueId": "uuid" }` or `{ "blockingIssueId": "uuid" }`; snake_case aliases `{ "blocked_by_issue_id": "uuid" }` and `{ "blocking_issue_id": "uuid" }` are also accepted. The blocking issue must belong to a project; a projectless issue may be the blocked target. Optionally include `releaseColumnId` from the blocking project's board columns. Omit it to use the blocking project's `done` column. PATCH the dependency with `{ "releaseColumnId": "uuid" }`. Circular dependencies rejected (400). Duplicates return 409.
221
221
 
222
222
  Dependency reads include each authorized target issue's canonical `identifier` and `projectSlug` when it belongs to a project. Projectless targets have both fields `null`; inaccessible targets remain `issue: null`. Release fields include `releaseColumnId` and the compatibility alias `release_column_id`; POST and PATCH accept either camelCase or snake_case release-column input. Release metadata is present when the blocking issue is authorized; a `blocking` target projection may still be `issue: null` independently.
223
+ Archiving a blocker preserves the dependency edge and configured release column while satisfying the dependency. Restoring it re-evaluates the same release point and can block the dependent again. Configurable release-point and cancelled-blocker behavior are unchanged.
223
224
  The dependency-release migration backfills existing dependencies to the
224
225
  blocking project's `done` column. During a rolling deployment, compatibility
225
226
  reads may omit release fields from older rows; treat missing release metadata as
@@ -296,10 +297,10 @@ Artifacts.
296
297
 
297
298
  | Method | Endpoint | Description |
298
299
  | --- | --- | --- |
299
- | `GET` | `/api/orgs/{id}/artifacts` | List readable artifact metadata and visible links; revision content is omitted; supports `limit` (1-100, default 50) and `offset` (0-10000), and returns `hasMore` |
300
+ | `GET` | `/api/orgs/{id}/artifacts` | List readable artifact metadata and visible links; revision content is omitted; includes `can_edit` and `can_unlink` capabilities; supports `limit` (1-100, default 50) and `offset` (0-10000), and returns `hasMore` |
300
301
  | `POST` | `/api/orgs/{id}/artifacts` | Create artifact and immutable revision 1 atomically |
301
- | `GET` | `/api/orgs/{id}/artifacts/{artifactId}` | Read artifact metadata and visible links |
302
- | `GET` | `/api/orgs/{id}/artifacts/{artifactId}/revisions` | List immutable revision summaries without content; supports `limit` (1-100, default 50) and `offset`, and returns `hasMore` |
302
+ | `GET` | `/api/orgs/{id}/artifacts/{artifactId}` | Read artifact metadata and visible links, including `can_edit` and `can_unlink` capabilities |
303
+ | `GET` | `/api/orgs/{id}/artifacts/{artifactId}/revisions` | List immutable revision summaries without content; supports `limit` (1-100, default 50) and `offset` (0-10000), and returns `hasMore` |
303
304
  | `POST` | `/api/orgs/{id}/artifacts/{artifactId}/revisions` | Create a content revision or title-aware full snapshot with an expected current revision |
304
305
  | `GET` | `/api/orgs/{id}/artifacts/{artifactId}/revisions/{revisionId}` | Read one sanitized revision including content |
305
306
  | `POST` | `/api/orgs/{id}/artifacts/{artifactId}/links` | Link to an authorized issue or project |
@@ -425,6 +426,20 @@ references return `409`, and resolver failures return `500`.
425
426
 
426
427
  Returns findings only (not the full graph). Use it for a high-level review — orphaned initiatives/KPIs (no goal), goals with no KPI or no initiative, dangling initiative execution links, KPIs missing targets/stale/off-pace, initiatives missing impact/execution or stalled, blocked/overdue work — then remediate with the goal/KPI/initiative write endpoints above. Owners/admins receive organization-wide execution evidence. Other non-guests receive project-bound issues, milestones, target links, and target findings only for readable projects. A restricted member with no readable projects receives no issue or target execution evidence. Forbidden for guests. CLI: `atoll strategy audit [--severity critical|warning|info] [--json]`.
427
428
 
429
+ ## Human attention
430
+
431
+ | Method | Endpoint | Description |
432
+ | --- | --- | --- |
433
+ | `GET` | `/api/orgs/{id}/attention` | List relevant open or closed attention items; supports status, execution, kind, recovery mode, target filters, bounded pagination, and envelope/CLI shape |
434
+ | `POST` | `/api/orgs/{id}/attention` | Request human attention and atomically pause the execution in `needs_human` |
435
+ | `GET` | `/api/orgs/{id}/attention/{attentionId}` | Read one safe attention detail projection |
436
+ | `POST` | `/api/orgs/{id}/attention/{attentionId}/resolve` | Resolve an open item for its eligible human target and leave the execution in `waiting`; a trusted harness performs any later resume |
437
+ | `POST` | `/api/orgs/{id}/attention/{attentionId}/cancel` | Cancel an item as its requesting agent and leave the execution in `waiting`; a trusted harness performs any later resume |
438
+ | `POST` | `/api/orgs/{id}/attention/{attentionId}/admin-cancel` | Cancel an item as an authorized human administrator and leave the execution in `waiting`; a trusted harness performs any later resume |
439
+ | `POST` | `/api/orgs/{id}/attention/{attentionId}/retarget` | Retarget an open item as an authorized human administrator |
440
+
441
+ The create body is strict and requires `execution_id`, `expected_state_version`, `kind`, `title`, `request_summary`, `why_needed`, `resume_condition`, one exact target shape, and `idempotency_key`. Close and retarget bodies require both expected versions. Mutations are idempotent and return `409` for stale versions, invalid lifecycle edges, conflicting keys, or ineligible targets. `mode=recovery` is restricted to authorized human administrators. Text is bounded and secret-safe; public projections omit provenance, hashes, prompts, logs, credentials, and paths.
442
+
428
443
  ## Heartbeat
429
444
 
430
445
  | Method | Endpoint | Description |
@@ -455,6 +470,25 @@ atoll heartbeat --severity critical
455
470
  `atoll heartbeat --signals-only --json` returns filtered `signals`, direct `attention_items`, `attention_summary`, and `recommended_action` for polling agents.
456
471
  KPI stale/off-pace signal metadata includes `linked_initiatives` and `recent_attributed_snapshots`; `--explain-kpi` returns that movement context under `kpi_explanation`.
457
472
 
473
+ ## Agent executions
474
+
475
+ | Method | Endpoint | Description |
476
+ | --- | --- | --- |
477
+ | GET | `/api/orgs/{id}/executions` | List executions in the caller's current issue-project scope; non-guest members may also read projectless executions, except setup agents and guests; filters: `issue_id`, `agent_member_id`, `state`, `active`, `harness_kind`, `updated_after`, `limit`, `offset` (maximum 10,000), `shape` |
478
+ | POST | `/api/orgs/{id}/executions` | Create an execution. Strict body; required `idempotency_key`; starts in `assigned` |
479
+ | GET | `/api/orgs/{id}/executions/{executionId}` | Read safe detail, transitions, and evidence projections |
480
+ | POST | `/api/orgs/{id}/executions/{executionId}/transitions` | Version-fenced transition through the shipped lifecycle RPC |
481
+ | GET/POST | `/api/orgs/{id}/executions/{executionId}/evidence` | List or link existing authorized issue evidence |
482
+
483
+ Use `expected_state_version` for follow-up transitions. Generic transitions cannot enter
484
+ or leave `needs_human`; those edges return `ATTENTION_CONTRACT_REQUIRED` and
485
+ belong to the attention contract. Reads use the issue's current project access;
486
+ non-guest members may also read projectless executions, except setup agents and
487
+ guests. Creation-project metadata does not grant access. Unreadable records are
488
+ concealed as `404`; public projections omit hashes, provenance, logs,
489
+ prompts, credentials, and paths. This API records state and does not start or
490
+ resume an underlying harness. There is no issue-specific execution route.
491
+
458
492
  ## Activity
459
493
 
460
494
  | Method | Endpoint | Description |
@@ -600,6 +634,10 @@ requests also sweep a small due batch. Uploads over 2MB return `413`.
600
634
 
601
635
  Attach PRs manually with a canonical GitHub pull request URL such as `https://github.com/owner/repo/pull/123`; malformed or non-PR URLs return `400`. On attach, Atoll refreshes GitHub metadata when available so title/status/head SHA reflect the PR instead of only the submitted URL. PR links can also be created or refreshed automatically via the GitHub webhook integration.
602
636
 
637
+ GET returns `id`, `pr_number`, `github_repo`, nullable
638
+ `github_repository_id`, nullable `external_reference_id`, `pr_url`, `pr_title`,
639
+ `pr_status`, nullable `head_sha`, and `updated_at` for each link.
640
+
603
641
  For project-bound issues, listing requires project access and attaching requires
604
642
  `edit` or `admin` access. Eligible non-guests may list and attach links for
605
643
  projectless issues. Authorization is bound to the issue's current parent before
@@ -613,12 +651,35 @@ child reads or writes and occurs before URL parsing or GitHub metadata lookup.
613
651
  | POST | `/api/orgs/{id}/issues/{issueId}/external-references` | Resolve and link a GitHub PR |
614
652
  | GET | `/api/orgs/{id}/issues/{issueId}/external-references/{referenceId}` | Inspect a linked reference |
615
653
  | DELETE | `/api/orgs/{id}/issues/{issueId}/external-references/{referenceId}` | Unlink a reference |
654
+ | GET | `/api/orgs/{id}/issues/{issueId}/external-operational-signals` | Read current exact-head GitHub delivery context |
616
655
  | GET | `/api/orgs/{id}/projects/{projectId}/external-references` | List project external references |
617
656
  | POST | `/api/orgs/{id}/projects/{projectId}/external-references` | Resolve and link a GitHub PR |
618
657
  | GET | `/api/orgs/{id}/projects/{projectId}/external-references/{referenceId}` | Inspect a linked reference |
619
658
  | DELETE | `/api/orgs/{id}/projects/{projectId}/external-references/{referenceId}` | Unlink a reference |
620
659
 
621
- External Reference POST requests accept `{ "url": "https://github.com/owner/repo/pull/123" }`. They require an authorized GitHub connection and store a reference only when the live response proves numeric immutable repository and pull-request IDs. Missing proof returns `422` with `code: "github_identity_unavailable"`. URLs and caller owner/repo fields are not identity or authorization inputs. Existing PR-link operations remain independent; CLI/MCP tools are deferred to a later slice.
660
+ External Reference POST requests accept `{ "url": "https://github.com/owner/repo/pull/123" }`. They require an authorized GitHub connection and store a reference only when the live response proves numeric immutable repository and pull-request IDs. Missing proof returns `422` with `code: "github_identity_unavailable"`. URLs and caller owner/repo fields are not identity or authorization inputs. Manual PR-link operations remain available without an External Reference; verified delivery projection creates and binds the immutable PR reference. CLI/MCP tools are deferred to a later slice.
661
+
662
+ The external-operational-signals GET returns `{ deliveryContext }` for the
663
+ selected current PR or `null` when there is no PR link. Selection prefers an
664
+ open link, then the latest `updated_at`, then the highest PR number. It contains stable
665
+ repository identity, exact head SHA, current PR/review/configured-workflow
666
+ state, bounded source and provider-event provenance, freshness, a safe strongest
667
+ blocker, and `partial`. Older-head signals are historical. Configured workflows
668
+ are not GitHub branch-protection required checks and report `required: false`.
669
+ Missing current-head review or configured-workflow evidence appears as
670
+ `pending` with null provenance and does not by itself set `partial`. Disabled
671
+ verification stops new projections. Workflow conclusions map `success` or
672
+ `neutral` to `passed`; `cancelled`, `stale`, or `skipped` to `cancelled`; and
673
+ other supported terminal conclusions to `failed`.
674
+ The selected PR-link state is authoritative. If a same-head PR observation
675
+ disagrees, Atoll clears its observation/provider provenance, uses the link URL
676
+ as `source_url`, excludes it from freshness, and sets `partial`.
677
+ Review aggregation keeps each reviewer's latest exact-head opinion, ignores
678
+ comments, and removes dismissed opinions. Change requests win; `approved`
679
+ means at least one effective approval and no effective change request. It does
680
+ not prove required-review counts or branch protection.
681
+ This read-only namespace is separate from heartbeat `signals[]` and does not
682
+ dispatch agents or change tasks.
622
683
 
623
684
  ## Project Status Updates
624
685
 
@@ -798,6 +859,11 @@ The default new-agent local path (without `setupAgentMemberId`) atomically creat
798
859
  | POST | `/api/integrations/github/connect` | Connect a repo |
799
860
  | POST | `/api/integrations/github/disconnect` | Disconnect a repo |
800
861
 
862
+ Release-added required hook events mark existing reconciled and already-pending connections pending. A bounded
863
+ 15-minute service sweep verifies immutable repository identity and upgrades the
864
+ hooks automatically. Transient failures remain pending for retry; owners and
865
+ admins can also use the reconciliation endpoint.
866
+
801
867
  ## Platform Feedback
802
868
 
803
869
  ### Feedback error contract
@@ -782,6 +782,30 @@ The opt-in issue manifest contains only `id`, `type`, `title`,
782
782
  Implementation Plan links are limited to one slot per issue, and each such
783
783
  Artifact can be authoritative for only one issue.
784
784
 
785
+ Artifact list and detail responses also include `can_edit` and `can_unlink`.
786
+ `can_edit` is true when the current member can create a revision. `can_unlink`
787
+ is true when the current member can remove a visible link; removing a final link
788
+ requires owner or admin access, while a member with write access can remove a
789
+ link when another link remains.
790
+
791
+ ## Agent execution fields
792
+
793
+ Execution projections contain `id`, `issue_id`, `current_project_id`,
794
+ `project_id_at_creation` (provenance only), normalized `state`, `state_version`,
795
+ bounded lifecycle summaries, safe harness/external-run metadata, timestamps,
796
+ and actor objects `{ id, display_name, type, deleted }`. `harness_kind` and
797
+ `external_run_id` reject credentials, tokens, and local filesystem paths;
798
+ legacy unsafe values are redacted as `null` in projections. Deleted agent or actor
799
+ rows use immutable AH-2095 snapshots. Detail adds ordered `transitions` and
800
+ existing `evidence` references `{ id, issue_id, link_type, target_id,
801
+ created_by, created_at }`.
802
+
803
+ Create requires `issue_id`, `agent_member_id`, and `idempotency_key`; it always
804
+ returns state `assigned`. Transition requires `expected_state_version`,
805
+ `to_state`, and `idempotency_key`. HTTP bodies are strict and omit actor
806
+ provenance; the server derives safe OAuth provenance. The generic transition
807
+ enum excludes `needs_human`.
808
+
785
809
  ## Analytics Response
786
810
 
787
811
  ```json
@@ -795,6 +819,28 @@ Artifact can be authoritative for only one issue.
795
819
 
796
820
  ---
797
821
 
822
+ ## Human attention fields
823
+
824
+ Attention list/detail projections contain `id`, `status` (`open`, `resolved`, or
825
+ `cancelled`), `kind` (`approval`, `clarification`, `access`, `decision`,
826
+ `destructive_action`, or `other`), bounded `title`, `request_summary`,
827
+ `why_needed`, `resume_condition`, `requested_at`, `closed_at`,
828
+ `resolution_outcome`, `resolution_summary`, `attention_version`, and the
829
+ execution, issue, and project projections. `target` contains the exact target
830
+ type plus a live member/team projection when it still exists and immutable
831
+ snapshot fields. `requester` and `closed_by` contain `{ id, display_name,
832
+ type, deleted }` snapshots. Detail adds
833
+ `execution_state_version_at_request` and `execution_state_version_at_close`.
834
+
835
+ Create targets are one of `{ target_type: "member", target_member_id }`,
836
+ `{ target_type: "team", target_team_id }`, or
837
+ `{ target_type: "project_admins" }`. Mutation requests use
838
+ `expected_attention_version`, `expected_state_version`, and
839
+ `idempotency_key`; resolve also accepts `resolution_outcome` and an optional
840
+ bounded `resolution_summary`. Free-form text rejects secret-like values.
841
+ Internal requester/actor provenance, hashes, response snapshots, and mutation
842
+ metadata are never returned by the public API.
843
+
798
844
  ## Enums
799
845
 
800
846
  | Domain | Field | Values |
@@ -876,7 +922,9 @@ be the blocked target when the caller has permission to use it.
876
922
  | `releaseColumnId` | UUID | Persistent release column in the blocking issue's project; present when the blocking issue is authorized |
877
923
  | `release_column_id` | UUID | Compatibility alias for `releaseColumnId`; present with the canonical field |
878
924
  | `releaseColumn` | object or null | `{ id, key, label, position, projectId }` release column projection |
879
- | `satisfied` | boolean or null | Whether the blocker reached the release column position, or is cancelled |
925
+ | `satisfied` | boolean or null | Whether the blocker is archived or cancelled, or reached the release column position |
926
+
927
+ Archiving a blocker preserves the dependency edge and configured release column while satisfying the dependency. Restoring it re-evaluates that same release point and can block the dependent again. Configurable release-point and cancelled-blocker behavior are unchanged.
880
928
 
881
929
  The dependency-release migration backfills existing dependencies to the
882
930
  blocking project's `done` column. During a rolling deployment, compatibility
@@ -898,6 +946,35 @@ provider response or return `422` with
898
946
  metadata-only Activity actions `external_reference.linked`,
899
947
  `external_reference.updated`, or `external_reference.unlinked`.
900
948
 
949
+ ### External operational delivery context
950
+
951
+ `GET /api/orgs/{id}/issues/{issueId}/external-operational-signals` returns
952
+ `{ deliveryContext }`. The value is `null` without a linked PR. With several
953
+ links, selection prefers an open PR, then the latest `updated_at`, then the
954
+ highest PR number. Otherwise it
955
+ contains `repository`, `pull_request`, nullable `review`, `workflows`,
956
+ `freshness`, nullable `strongest_blocker`, and `partial`. PR, review, and
957
+ workflow evidence includes nullable `observed_at`, `provider_updated_at`,
958
+ `provider_event_id`, and `source_url`. The PR includes the exact `head_sha`.
959
+ Review state is `pending`, `approved`, or `changes_requested`; workflow state is
960
+ `pending`, `passed`, `failed`, or `cancelled`. Older-head evidence is not
961
+ current. Workflow `required` is always `false` because configured workflow
962
+ paths do not prove GitHub branch protection. Raw payloads, review bodies,
963
+ actors, logs, and credentials are excluded. A `pending` review or workflow with
964
+ null provenance has no current-head observation; this absence does not by
965
+ itself set `partial`. Disabled verification stops new projections. Workflow
966
+ conclusions map `success`/`neutral` to `passed`,
967
+ `cancelled`/`stale`/`skipped` to `cancelled`, and other supported terminal
968
+ conclusions to `failed`.
969
+ The selected PR-link state is authoritative. If a same-head PR observation
970
+ disagrees, its `observed_at`, `provider_updated_at`, and `provider_event_id` are
971
+ null, `source_url` uses the link URL, the observation is excluded from
972
+ `freshness`, and `partial` is true.
973
+ Review aggregation keeps each reviewer's latest exact-head opinion, ignores
974
+ comments, and removes dismissed opinions. Change requests win; `approved`
975
+ means at least one effective approval and no effective change request. It does
976
+ not prove required-review counts or branch protection.
977
+
901
978
  ## Task Activity
902
979
 
903
980
  `GET /api/orgs/{id}/activity` returns `{ data, currentMemberId, limit, offset,