software-defence-factory 0.11.2 → 0.13.0

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.
@@ -10,7 +10,7 @@ instructions, existing source and session decisions. Preserve useful work and
10
10
  licenses. Assessment-only requests stay read-only. A method-only adoption needs
11
11
  no runtime, Docker host or model account.
12
12
 
13
- The [setup plan](../../docs/setup.md) owns supported commands and checkpoints.
13
+ The [setup plan](../../../docs/setup.md) owns supported commands and checkpoints.
14
14
  Read the relevant sections rather than inventing a host-specific setup recipe.
15
15
  Choose this skill explicitly when adopting Factory. Project preparation tools
16
16
  need no knowledge of Factory and do not invoke this transition automatically.
@@ -24,7 +24,7 @@ Use the actual host's OS, service manager, hardware and private network; do not
24
24
  assume a machine model, a VPN provider, a cloud, Git forge, issue tracker, CI
25
25
  provider or particular harness. Inspect the configured remote and actual provider
26
26
  capabilities. GitHub is one adapter; an unsupported host keeps local execution
27
- without guessed API calls. Use [integration ownership](../../docs/integrations.md).
27
+ without guessed API calls. Use [integration ownership](../../../docs/integrations.md).
28
28
 
29
29
  Use the existing task/issue record for missing obligations and evidence. Avoid a
30
30
  second project registry or a template conversion of an existing application.
@@ -38,9 +38,9 @@ Continue authorized repairs; ask only for decisions or authority actually missin
38
38
  repository instructions. Adapt the staged method to the
39
39
  project; do not overwrite its files. Reconcile the chosen provider’s issue forms/labels (where supported), CI triggers,
40
40
  required checks, protection rules and the intended PR/release path using
41
- [repository readiness](../../kit/repository.md). Before trusted GitHub PR
41
+ [repository readiness](../../../kit/repository.md). Before trusted GitHub PR
42
42
  delivery, confirm active candidate-triggered workflows fit the bounded
43
- qualification in [recovery](../../docs/recovery.md#trusted-pr-delivery);
43
+ qualification in [recovery](../../../docs/recovery.md#trusted-pr-delivery);
44
44
  unsupported workflows keep delivery patch-only. Organization hooks and
45
45
  external CI remain operator-owned. File presence is not proof.
46
46
  - **Infrastructure:** choose a private state directory, loopback port, host and
package/README.md CHANGED
@@ -39,13 +39,16 @@ The runtime supplies policy and six focused skills to its isolated jobs. `init`
39
39
 
40
40
  ## How the factory works
41
41
 
42
- [![Factory setup, execution and delivery](https://raw.githubusercontent.com/arcitai/software-and-defence-factory/main/docs/architecture.svg)](https://github.com/arcitai/software-and-defence-factory/blob/main/docs/architecture.excalidraw)
42
+ <a href="docs/architecture.md"><img src="docs/diagrams/deployment.svg" width="720" alt="Factory setup: operator access to a private execution host with Docker, a chosen harness and inference; application deployment remains in its own CI/CD."></a>
43
43
 
44
- [Architecture and boundaries](docs/architecture.md) · [Editable Excalidraw source](https://github.com/arcitai/software-and-defence-factory/blob/main/docs/architecture.excalidraw)
44
+ [Architecture and boundaries](docs/architecture.md). Editable views:
45
+ [Setup / deployment](docs/diagrams/deployment.excalidraw) ·
46
+ [Runtime layers / ownership](docs/diagrams/architecture.excalidraw) ·
47
+ [Work lifecycle](docs/diagrams/lifecycle.excalidraw)
45
48
 
46
49
  Each result belongs to a specific candidate commit and policy. A failed check blocks delivery. Changing the candidate or check policy invalidates earlier evidence. Approval records a handoff; publishing, merging and deployment follow the application's separate authority.
47
50
 
48
- The project dashboard has an **Inbox**, measured **Analytics**, **Agents**, **Skills**, **Automations**, **Definition** and **Infrastructure**. Inbox lists repository issues with readiness and linked execution attempts. New issue offers repository templates or a blank creation form. Create an issue on the supported repository provider, then choose Start work separately; local brief execution remains available. CLI `issue` exposes the same intake. Definition lives with settings above the theme control. The CLI reads the same definition and controller state. Agent roles use a selected harness such as Codex or Pi; a worker executes their isolated jobs on a host. See [concepts](docs/concepts.md) and [supported interfaces](docs/interfaces.md). Optional automations belong to the selected harness, which calls Factory CLI/API. Factory runs no cron scheduler. See [provider boundaries](docs/integrations.md).
51
+ The project dashboard has an **Inbox**, measured **Analytics**, **Agents**, **Skills**, **Automations**, **Definition** and **Infrastructure**. Inbox opens on a shared list/board of loaded repository issues and local work, with status, search and checkbox filters, readiness and linked execution attempts. New issue offers repository templates or a blank creation form. Create an issue on the supported repository provider, then choose Start work separately; local brief execution remains available. CLI `issue` exposes the same intake. Definition lives with settings above the theme control. The CLI reads the same definition and controller state. Agent roles use a selected harness such as Codex or Pi; a worker executes their isolated jobs on a host. See [concepts](docs/concepts.md) and [supported interfaces](docs/interfaces.md). Optional automations belong to the selected harness, which calls Factory CLI/API. Factory runs no cron scheduler. See [provider boundaries](docs/integrations.md).
49
52
 
50
53
  The optional **defence** workflow accepts scoped incident evidence and produces a private, read-only draft. It does not monitor production or claim verified recovery. See [defence integration](docs/defence-integration.md).
51
54
 
@@ -58,8 +61,8 @@ Start setup with `factory foundation` and the [Factory Foundation plan](docs/set
58
61
  | `bin/` | CLI entry point |
59
62
  | `factory/` | Queue, HTTP API, isolation, evidence, updates and bundled dashboard assets |
60
63
  | `dashboard/` | Dashboard source and UI tests |
61
- | `kit/`, `.agents/skills/` | Portable method, adoption records and six job skills |
62
- | `operator-skills/` | Factory Foundation setup guidance; never mounted into jobs |
64
+ | `kit/`, `kit/skills/` | Portable method, adoption records and the canonical six job skills |
65
+ | `.agents/skills/` | Repository/operator guidance, including explicit Factory Foundation adoption; outside the job catalog |
63
66
  | `scripts/`, `tests/` | Packaging, qualification, release checks and behavioral tests |
64
67
  | `docs/` | Setup, architecture, recovery, proof and ownership |
65
68
 
package/docs/concepts.md CHANGED
@@ -18,8 +18,10 @@ The CLI `definition` command and dashboard Definition page read that same catalo
18
18
  | Automation | External schedule and agent context | Owned by the selected harness; calls Factory CLI/API, no Factory cron |
19
19
  | Definition | Effective roles, workflows, skills and settings | Installed method plus private factory.json; read-only catalog |
20
20
 
21
- Inbox contains execution history, not a copied remote issue backlog. New issue
22
- can create a repository issue without execution; Start work admits execution
21
+ Inbox combines an explicitly loaded provider page with retained execution
22
+ history in one list/board, grouped by canonical issue identity. Off-page sources
23
+ remain unknown; local requests keep separate identities. New issue
24
+ creates a repository issue without execution; Start work admits execution
23
25
  separately. The provider owns issue content/state; SQLite owns queue/attempts and
24
26
  creation receipts for recovery. An unfinished local form is not a saved backlog.
25
27
  An agent role is neither a machine nor a skill. Check is deterministic, and
@@ -11,7 +11,7 @@ shell endpoint or a second scheduler.
11
11
  | --- | --- | --- | --- | --- |
12
12
  | Project/queue/attempt state | `status`, `inbox --source factory` JSON | `GET /api/v1/status` | Project, tasks, details/history | Stable versioned agent result/error contract |
13
13
  | Start local work | `issue start --file --title` or `--draft`, explicit `--workflow`, optional `--model` | `POST /api/v1/jobs` | Local execution only → review → Create & start locally | Persistent unstarted drafts and typed incident intake remain separate |
14
- | Browse repository Inbox | `inbox [--page N] [--issue-state open/closed/all]` (also `issue list --source inbox`), `issue preview --url URL` via controller provider | Authenticated `GET /api/v1/issues`, `POST /api/v1/issues/preview` using shared readers | Inbox with provider/state/readiness, loaded-page counts, linked attempts, local/off-page history; explicit Start work for either type | Issue → execution links retained; no implicit polling |
14
+ | Browse repository Inbox | `inbox [--page N] [--issue-state open/closed/all]` (also `issue list --source inbox`), `issue preview --url URL` via controller provider | Authenticated `GET /api/v1/issues`, `POST /api/v1/issues/preview` using shared readers | Primary list/Board with shared status/search/workflow/model/label filters, compact Repository tools/loaded scope, item count with secondary issue/execution totals and progressive linked detail; explicit Start work for either type | Issue → execution links retained; no implicit polling |
15
15
  | Start repository work | `issue start --url URL --workflow software/defence [--brief-file operator.md]` | `POST /api/v1/issues/start` | Issue context → explicit Start work with operator brief | Rechecks current content and active admission |
16
16
  | Create repository issue / recovery | `issue connection`, `create --key`, `submissions`, `recover --key` | Authenticated connection, `POST /issues`, receipts and recovery | Display destination/actor, create without execution, recover uncertain result | GitHub adapter first; assignees/projects and other providers unimplemented |
17
17
  | Repository issue templates | `issue templates`, `issue draft --template --sha --file` | Authenticated template list and draft compilation | Chooser, fields/defaults/validation, review | Supports Markdown and YAML markdown/input/textarea/dropdown/checkboxes; unsupported templates link to GitHub |
@@ -106,3 +106,25 @@ Definition exposes `configuration.issueReadinessLabels`. The optional private
106
106
  config field has exactly triage/spec/ready/blocked keys with four distinct label
107
107
  names; defaults are factory:triage/spec/ready/blocked. Configuration is validated,
108
108
  not inferred from issue content. No browsing path changes labels or comments.
109
+
110
+ The additive `work_records` read model is returned by `GET /api/v1/issues`
111
+ (and CLI `inbox` / `issue list --source inbox`) alongside the existing `issues`
112
+ and `history` fields. `status` returns the same model for retained history without
113
+ fetching a provider page. Each record includes a canonical `key`, source `identity`,
114
+ `source_status` (`loaded`, `not_loaded`, `local`), optional source `issue`, all
115
+ `executions`, representative `execution_id`, actual `state`, `workflow` and `phase`.
116
+ Active/unresolved execution takes precedence over the latest terminal execution.
117
+ Without an execution, state is `not_started`, workflow/phase and execution ID are
118
+ null. Readiness lives on the source issue, separate from execution state. Dashboard
119
+ projection uses this same pure read-model function with its bounded page snapshot
120
+ and latest status; grouping/filtering does not admit execution. Individual
121
+ `#/runs/JOB_ID` links remain valid; `#/issues/KEY` opens canonical source detail.
122
+
123
+ The dashboard's Repository disclosure beside the item count holds source-state
124
+ selection, refresh, available previous/next page actions and Local execution
125
+ request. It also explains read time, remote total, the loaded search boundary
126
+ and separate issue/local request/execution counts. Loading and source errors
127
+ remain visible when it is closed. Rows and cards share compact metadata; source
128
+ readiness remains distinct from the runtime badge, and full assignments and
129
+ linked attempts remain in detail. These presentation controls do not change
130
+ headless records or admission semantics.
package/docs/npm.md CHANGED
@@ -104,8 +104,8 @@ installation or retained attempt needs them.
104
104
 
105
105
  ## Protected evidence compatibility
106
106
 
107
- Factory 0.11.2 recognizes version-1 execution profiles emitted by native
108
- **0.8.0, 0.9.0, 0.9.1, 0.10.0, 0.11.0, 0.11.1 and 0.11.2**. This is an exact allowlist in
107
+ Factory 0.13.0 recognizes version-1 execution profiles emitted by native
108
+ **0.8.0, 0.9.0, 0.9.1, 0.10.0, 0.11.0, 0.11.1, 0.11.2, 0.12.0 and 0.13.0**. This is an exact allowlist in
109
109
  `factory/execution-profile.mjs`, independent of the installed package version;
110
110
  it is not a semver range or an automatic promise for later releases. Unknown
111
111
  runtime strings, unknown profile formats and incomplete legacy acceptance
@@ -149,6 +149,24 @@ new acceptance. Older writers remain compatible only when their retained patch
149
149
  passes the current digest and tree reconstruction checks. A digest-matching
150
150
  malformed patch remains blocked and unchanged; follow the [recovery guidance](recovery.md#already-accepted-malformed-patches-91).
151
151
 
152
+ Version 0.12.0 adds the all-work Inbox read model and restores the retained
153
+ overview. Protected execution writers and acceptance/publication guards are
154
+ unchanged; the explicit compatible-writer entry preserves all checks above.
155
+
156
+ Version 0.13.0 relocates byte-identical runtime skill instructions to
157
+ `kit/skills/` and adds a read-only alias of that catalog for Codex discovery.
158
+ The audit covers `execution-profile.mjs`, `processes.mjs`, executor candidate,
159
+ check, review and acceptance writers, `execution-evidence.mjs` and both delivery
160
+ capability/validation paths. The v1 schema, candidate reconstruction, current
161
+ policy checks, isolation and model credential rules are unchanged. Foundation
162
+ is outside the job catalog. Supported old immutable evidence remains subject
163
+ to every existing guard; unknown later versions receive no automatic trust.
164
+
165
+ Old installed releases retain their own files and mount paths until an idle,
166
+ reviewed update. No installed catalog, execution profile or historical hash is
167
+ rewritten by this layout change. Qualify the exact updated package/image before
168
+ resuming work; retained releases/evidence stay available for recovery.
169
+
152
170
  New publication still requires the current remote target to equal the original
153
171
  accepted base; reviewed-candidate continuation preserves that baseline, while
154
172
  target refresh remains #72. See [the verification map](https://github.com/arcitai/software-and-defence-factory/blob/main/docs/proof.md) for delivered
package/docs/setup.md CHANGED
@@ -1,7 +1,13 @@
1
1
  # Factory Foundation: repository and execution setup
2
2
 
3
- The operator skill is available through `factory foundation`.
3
+ The operator skill is available through `factory foundation`, including from
4
+ an installed npm package without a source checkout. Its canonical source is
5
+ [.agents/skills/factory-foundation/SKILL.md](../.agents/skills/factory-foundation/SKILL.md).
6
+ Use it explicitly for adoption; it is independent of AIOS. Repository/operator
7
+ guidance stays outside the runtime catalog at `kit/skills/`.
4
8
  See [Factory concepts](concepts.md) for host, worker, harness and agent roles.
9
+ The [setup/deployment view](architecture.md#setup-and-deployment) shows operator
10
+ access, the private execution host and the separate application delivery boundary.
5
11
 
6
12
  Use this plan for a new installation or when moving an existing Factory to an
7
13
  execution host. Complete the applicable checkpoints in order and record the
@@ -36,7 +42,19 @@ this plan.
36
42
 
37
43
  For method-only adoption, run `factory kit --output NEW_DIRECTORY`
38
44
  and follow [the adoption guide](../kit/README.md). The remaining host/runtime
39
- steps apply only when using Factory's optional controller.
45
+ steps apply only when using Factory's optional controller. Export maps the six
46
+ job skills into staged `.agents/skills/`; it never changes the application's
47
+ AGENTS.md or installs global skills. Foundation stays in the installed package.
48
+
49
+ Runtime jobs receive only the reviewed catalog as read-only `/factory-skills`.
50
+ Codex agent phases also discover that same catalog at `/etc/codex/skills`; Pi
51
+ uses `--skill /factory-skills`. Verify discovery on the exact installed image
52
+ without credentials or inference (Codex native listing and Pi RPC
53
+ `get_commands`), then qualify inference separately when authorized. Custom
54
+ harness discovery remains operator-owned. A directory listing alone is not
55
+ native discovery proof. Bundled instructions install no model, browser, Docker
56
+ or provider access. Repository guidance remains readable project context and
57
+ cannot expand job permissions.
40
58
 
41
59
  ## 2. Establish host access and boot prerequisites
42
60
 
package/docs/workflows.md CHANGED
@@ -15,16 +15,22 @@ Defence currently runs **Investigate** against supplied scoped evidence and
15
15
  produces a private draft. It is not production monitoring, exploitation or
16
16
  verified recovery. See [Defence integration](defence-integration.md).
17
17
 
18
- The six bundled job skills cover triage, specification, implementation, review,
18
+ The six bundled job skills in `kit/skills/` cover triage, specification, implementation, review,
19
19
  security and evaluation. They are instructions, not six running processes.
20
20
  All are mounted read-only for agent steps; the role prompt supplies the work
21
21
  boundary. Triage/specification prepare scope before admission; evaluation is a
22
22
  separately scoped comparison. Factory Foundation is an operator setup skill,
23
- kept outside those execution mounts.
23
+ at `.agents/skills/factory-foundation/SKILL.md`, used explicitly for adoption
24
+ and kept outside those execution mounts. CLI/API/Skills show actual installed
25
+ paths, content and SHA-256. Codex additionally mounts the same runtime catalog at
26
+ `/etc/codex/skills`; Pi uses `--skill /factory-skills`. Discovery on a particular
27
+ image needs native qualification, and skills provide neither tools nor access.
24
28
 
25
29
  ## Start work
26
30
 
27
- Inbox lists repository issues directly. Choose Open, Closed or All states and use
31
+ Inbox opens on the shared list/Board of repository issues and local work, with
32
+ status rail and workflow/model/status/label multiselects. Never-started issues
33
+ have unknown workflow/model and a distinct Not started state. Choose Open, Closed or All states and use
28
34
  Previous/Next page or Refresh issues. Search covers the loaded page and visible
29
35
  history, not the whole repository. GitHub returns up to 50 records per page;
30
36
  pull requests are excluded, so even a page with zero issues can have a next page.
@@ -146,4 +152,7 @@ non-issue paths are not admitted by the provider preview. Local-only records and
146
152
  executions whose source is missing, closed or outside the loaded page remain in
147
153
  Local and other execution history. Removing local execution history preserves
148
154
  private evidence and never deletes a provider issue; unresolved delivery guards
149
- still apply. The separate Execution history tab keeps the execution list/board.
155
+ still apply. The primary list/board includes all these records; no separate
156
+ Execution history tab is needed. Its count line distinguishes issue identities
157
+ and individual executions. Open a record for progressive context and every
158
+ linked attempt; close/Escape returns to the preserved filters, view and scroll.
@@ -24,7 +24,7 @@ const skillRoles = {
24
24
  export function factoryDefinition(config) {
25
25
  const harness = harnessOf(config);
26
26
  const skills = Object.entries(skillRoles).map(([role, purpose]) => {
27
- const id = `factory-${role}`, path = `.agents/skills/${id}/SKILL.md`;
27
+ const id = `factory-${role}`, path = `kit/skills/${id}/SKILL.md`;
28
28
  const content = readFileSync(join(ROOT, path), 'utf8');
29
29
  return { id, purpose, path, content, sha256: digest(content) };
30
30
  });
@@ -58,7 +58,7 @@ export function factoryDefinition(config) {
58
58
  }
59
59
 
60
60
  export function foundationSkill() {
61
- const path = 'operator-skills/factory-foundation/SKILL.md';
61
+ const path = '.agents/skills/factory-foundation/SKILL.md';
62
62
  const content = readFileSync(join(ROOT, path), 'utf8');
63
63
  return { id: 'factory-foundation', purpose: 'Prepare a repository and host for bounded Factory work', path, content, sha256: digest(content) };
64
64
  }
@@ -9,12 +9,15 @@ import { expectedWebStories, webPolicyHash } from './web-verification.mjs';
9
9
  // Audited v1 protected evidence writers: 0.8.0 (380f749), 0.9.0 (cdaadef),
10
10
  // 0.9.1, 0.10.0, 0.11.0 and 0.11.1 (unchanged protected evidence writers),
11
11
  // 0.11.2 (exact patch bytes and pre-acceptance reconstruction; same v1 schema).
12
+ // 0.12.0 restores the Inbox read model/presentation; protected writers and guards are unchanged.
13
+ // 0.13.0 relocates identical job skills and adds a read-only Codex discovery alias.
14
+ // Candidate/check/review/acceptance schemas, isolation and credential/permission guards are unchanged.
12
15
  // Older writers still require exact patch/tree reconstruction before publication.
13
16
  // Deliberately independent of VERSION: a release bump is not
14
17
  // evidence compatibility. Re-audit this list for every trust-relevant writer,
15
18
  // isolation or validation change; remove versions whose guarantees no longer
16
19
  // satisfy current policy. See docs/npm.md. This predicate alone grants no trust.
17
- const SUPPORTED_EXECUTION_RUNTIMES_V1 = new Set(['0.8.0', '0.9.0', '0.9.1', '0.10.0', '0.11.0', '0.11.1', '0.11.2']);
20
+ const SUPPORTED_EXECUTION_RUNTIMES_V1 = new Set(['0.8.0', '0.9.0', '0.9.1', '0.10.0', '0.11.0', '0.11.1', '0.11.2', '0.12.0', '0.13.0']);
18
21
 
19
22
  export function isSupportedExecutionProfile(profile) {
20
23
  return profile?.version === 1 && SUPPORTED_EXECUTION_RUNTIMES_V1.has(profile.runtimeVersion);
@@ -91,7 +91,10 @@ async function container(mode, input, command, options = {}) {
91
91
  '--mount',`type=bind,source=${join(workspace,'.git')},target=/workspace/.git,readonly`];
92
92
  args.push('--mount',`type=bind,source=${reportDir},target=/output`,
93
93
  '--mount',`type=bind,source=${join(ROOT,'kit')},target=/factory-policy,readonly`,
94
- '--mount',`type=bind,source=${join(ROOT,'.agents/skills')},target=/factory-skills,readonly`);
94
+ '--mount',`type=bind,source=${join(ROOT,'kit/skills')},target=/factory-skills,readonly`);
95
+ // The same reviewed catalog supplies Codex native discovery; no operator skills.
96
+ if (mode !== 'verify' && harnessOf(config) === 'codex')
97
+ args.push('--mount',`type=bind,source=${join(ROOT,'kit/skills')},target=/etc/codex/skills,readonly`);
95
98
  if (scratch) args.push('--mount',`type=bind,source=${scratch},target=/scratch`);
96
99
  if (mode === 'verify') args.push('--env',`FACTORY_BASE_REVISION=${git('rev-parse',`${metadata().base}^{commit}`)}`);
97
100
  const logPath = join(folder, attempt, `${mode}.log`);
@@ -24,6 +24,13 @@ function issueLabels(labels = []) {
24
24
  return labels.map(label => ({name:label.name, color:/^[a-f0-9]{6}$/i.test(label.color || '') ? label.color.toLowerCase() : null}));
25
25
  }
26
26
 
27
+ function issueMetadata(issue) {
28
+ const date = value => typeof value === 'string' && Number.isFinite(Date.parse(value)) ? value : null;
29
+ const author = issue.user?.login || issue.author?.login;
30
+ return { author: typeof author === 'string' ? author : null,
31
+ created_at: date(issue.created_at || issue.createdAt), updated_at: date(issue.updated_at || issue.updatedAt) };
32
+ }
33
+
27
34
  export async function listIssues(repo, page = 1, read = githubRead, state = 'open') {
28
35
  if (!Number.isSafeInteger(page) || page < 1 || page > 10000) throw new Error('Issue page must be an integer between 1 and 10000.');
29
36
  if (!['open', 'closed', 'all'].includes(state)) throw new Error('Issue state must be open, closed or all.');
@@ -35,7 +42,7 @@ export async function listIssues(repo, page = 1, read = githubRead, state = 'ope
35
42
  const issues = result.filter(issue => !issue.pull_request).map(issue => {
36
43
  validateIssueURL(repository, issue.html_url);
37
44
  if (!Number.isSafeInteger(issue.number) || !issue.html_url.endsWith(`/issues/${issue.number}`) || typeof issue.title !== 'string' || !issue.title.trim()) throw new Error('GitHub returned an unexpected issue.');
38
- return { number: issue.number, title: issue.title, url: issue.html_url, state: issue.state || 'unknown', labels: issueLabels(issue.labels) };
45
+ return { ...issueMetadata(issue), number: issue.number, title: issue.title, url: issue.html_url, state: issue.state || 'unknown', labels: issueLabels(issue.labels) };
39
46
  });
40
47
  return { repository, issues, next_page: result.length === 50 && page < 10000 ? page + 1 : null };
41
48
  }
@@ -46,7 +53,7 @@ export function validateIssueURL(repoURL, value) {
46
53
  if (!repoURL || identity.repository !== repoURL.toLowerCase()) throw new Error('Issue does not belong to this project’s configured GitHub origin.');
47
54
  return identity.url;
48
55
  }
49
- export async function readIssue(repo, url, read = url => githubRead(['issue', 'view', url, '--json', 'title,body,url,labels,state'])) {
56
+ export async function readIssue(repo, url, read = url => githubRead(['issue', 'view', url, '--json', 'title,body,url,labels,state,author,createdAt,updatedAt'])) {
50
57
  const repoURL = readProjectLinks(repo)?.repository;
51
58
  url = validateIssueURL(repoURL, url);
52
59
  const issue = await read(url);
@@ -55,5 +62,5 @@ export async function readIssue(repo, url, read = url => githubRead(['issue', 'v
55
62
  const spec = `Issue: ${issue.url}\n${issue.title}\n\n${issue.body}`;
56
63
  if (Buffer.byteLength(spec) > 240000) throw new Error('Issue exceeds the 240 KB task limit. Use a bounded task file instead.');
57
64
  const labels = issueLabels(issue.labels);
58
- return { title: issue.title, url, number: canonicalIssue(url).number, state: issue.state?.toLowerCase() || 'unknown', body: issue.body, spec, labels, recommendation: recommendWork({ spec, labels: labels.map(label => label.name) }) };
65
+ return { ...issueMetadata(issue), title: issue.title, url, number: canonicalIssue(url).number, state: issue.state?.toLowerCase() || 'unknown', body: issue.body, spec, labels, recommendation: recommendWork({ spec, labels: labels.map(label => label.name) }) };
59
66
  }
@@ -57,3 +57,28 @@ export function backlogHistory(jobs, issues) {
57
57
  }
58
58
  return [...grouped.values()];
59
59
  }
60
+
61
+ // Canonical all-work read model, shared by headless readers and the dashboard.
62
+ // A source page is a bounded snapshot, never evidence that other sources closed.
63
+ export function workRecords(jobs = [], issues = []) {
64
+ const sources = new Map(issues.map(issue => [issue.identity.key, issue]));
65
+ const histories = backlogHistory(jobs, []);
66
+ const rows = new Map(histories.map(row => [row.key, row]));
67
+ for (const [key, issue] of sources) rows.set(key, {
68
+ ...issue, key, ...executionAssociation(jobs, issue.identity),
69
+ });
70
+ return [...rows.values()].map(row => {
71
+ const issue = sources.get(row.key) || null;
72
+ const execution = row.active_execution || row.latest_execution || row.executions[0] || null;
73
+ return {
74
+ key: row.key, identity: row.identity, title: issue?.title || row.title,
75
+ url: row.url, source_status: issue ? 'loaded' : row.identity ? 'not_loaded' : 'local',
76
+ issue, executions: row.executions, active_execution: row.active_execution || null,
77
+ latest_execution: row.latest_execution || execution,
78
+ execution_id: execution?.id || null,
79
+ state: execution?.state || 'not_started',
80
+ workflow: execution?.workflow || null,
81
+ phase: execution?.phase || null,
82
+ };
83
+ });
84
+ }
@@ -38,7 +38,7 @@ export function githubIssueProvider(repo, { read = githubRead, write = githubWri
38
38
  id: 'github', label: 'GitHub', repository, supported: true,
39
39
  capabilities: { issues: true, templates: true, create: true },
40
40
  list: (page, state) => listIssues(repo, page, read, state),
41
- preview: url => readIssue(repo, url, value => read(['issue', 'view', value, '--json', 'title,body,url,labels,state'])),
41
+ preview: url => readIssue(repo, url, value => read(['issue', 'view', value, '--json', 'title,body,url,labels,state,author,createdAt,updatedAt'])),
42
42
  templates: () => readTemplates(repo, read),
43
43
  draft: input => draftFromTemplate(repo, input, read),
44
44
  async context() {
@@ -1,4 +1,4 @@
1
- import { associateIssue, backlogHistory, readinessMapping } from './issue-lifecycle.mjs';
1
+ import { associateIssue, backlogHistory, readinessMapping, workRecords } from './issue-lifecycle.mjs';
2
2
  import { harnessOf } from './lib.mjs';
3
3
  import http from 'node:http';
4
4
  import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
@@ -88,7 +88,7 @@ export function createController(state, adapter = executors(state), integrations
88
88
  delivery_removal_blocked: Boolean(job.delivery && !['published', 'abandoned'].includes(job.delivery.state)),
89
89
  delivery_status: delivery.summary(job), runs: job.runs.map(attempt => attemptPresentation({ ...attempt,
90
90
  outcome: attempt.outcome || (attempt.state === 'succeeded' ? 'complete' : undefined) }, adapter.usage?.(job, attempt))) }));
91
- return send(200, { version: 1, runtime_version: VERSION, maintenance: queue.maintenance, workflows: Object.keys(definitions.workflows), commands: [], triggers: [], jobs, issue_history: backlogHistory(jobs, []), csrf_token: csrf,
91
+ return send(200, { version: 1, runtime_version: VERSION, maintenance: queue.maintenance, workflows: Object.keys(definitions.workflows), commands: [], triggers: [], jobs, issue_history: backlogHistory(jobs, []), work_records: workRecords(jobs, []), csrf_token: csrf,
92
92
  infrastructure: { host, controller: { connected: !queue.closing }, workers: [{ id: 'local-executor', name: 'Local worker', host: host.hostname, connected: !queue.closing }] },
93
93
  automations: [], automation_control: definitions.automations, issue_provider: providerInfo(provider),
94
94
  delivery_configuration: deliveryProviderInfo(configAt(state), deliveryAdapter),
@@ -106,7 +106,7 @@ export function createController(state, adapter = executors(state), integrations
106
106
  const result = provider.supported ? await provider.list(page, state) : {repository:provider.repository, issues:[],next_page:null};
107
107
  const jobs = queue.all(), issues = result.issues.map(issue => associateIssue(issue, jobs, readinessMapping(config.issueReadinessLabels)));
108
108
  return send(200, { ...result, issues, provider:providerInfo(provider), page, state, loaded_count:issues.length, total:null,
109
- history:backlogHistory(jobs, issues) });
109
+ history:backlogHistory(jobs, issues), work_records:workRecords(jobs, issues) });
110
110
  }
111
111
  catch (error) { throw new QueueError(error.message, error.status || 400); }
112
112
  }