Baseline: distribution workspace before observability redesign
This commit is contained in:
106
.agents/CHECKPOINT.md
Normal file
106
.agents/CHECKPOINT.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Checkpoint
|
||||
|
||||
Last updated: 2026-06-02 15:49:58 EEST
|
||||
|
||||
## Current State
|
||||
|
||||
- Workspace: `/Users/agra/projects/distribution`
|
||||
- Current phase: observability redesign implementation
|
||||
- Active run id: `2026-06-02-observability-redesign`
|
||||
- Active implementation branch: `opus/observability-redesign`
|
||||
- Git status: not a git repository; user clarified git setup is Opus
|
||||
responsibility for this implementation pass
|
||||
- Opus connector: local `opus-runner` plugin created and runtime self-test
|
||||
passed
|
||||
- Orchestration mode: sequential agents, branches only, no worktrees
|
||||
|
||||
## Completed
|
||||
|
||||
- Created initial distribution platform plan in `PLAN.md`.
|
||||
- Created a static website mock in `index.html`, `styles.css`, and `app.js`.
|
||||
- User rejected the mock layout quality.
|
||||
- Created `.agents/ORCHESTRATION.md`.
|
||||
- Created local `opus-runner` plugin at `/Users/agra/plugins/opus-runner`.
|
||||
- Verified Claude CLI is installed and reports Claude Code `2.1.158`.
|
||||
- Added detailed subplans under `.agents/subplans/`.
|
||||
- Rewrote `PLAN.md` as an overview and moved detailed execution guidance to
|
||||
`.agents/subplans/`.
|
||||
- Added agent liveness and restart policy to orchestration docs.
|
||||
- Added `.agents/scripts/status.mjs` for run/agent status and progress tails.
|
||||
- Added local observability dashboard at `.agents/observability/`.
|
||||
- Started dashboard server at `http://127.0.0.1:4317`.
|
||||
- Registered the current planning session as an active run for observability.
|
||||
- Snarky completed plan review and saved `snarky-review.md`.
|
||||
- User approved Opus read-only access to `/Users/agra/projects/distribution`
|
||||
and `/Users/agra/projects/sx`.
|
||||
- Opus completed read-only plan/sx consultation and saved `opus-review.md`.
|
||||
- Snarky and Opus completed two additional discussion rounds before
|
||||
implementation.
|
||||
- Created an observability redesign run and brief for Opus.
|
||||
- Opus completed the observability redesign proposal.
|
||||
- Saved Opus implementation instructions based on the proposal.
|
||||
- Requested Opus implementation; Opus is responsible for initializing the git
|
||||
baseline and branch.
|
||||
|
||||
## Important Decisions
|
||||
|
||||
- CLI command name is `dist`.
|
||||
- iOS mobile install must distinguish TestFlight, Enterprise/MDM, and
|
||||
artifact-only flows.
|
||||
- Standard library planning must include `HashMap`, `StringBuilder`, and
|
||||
extended `List`.
|
||||
- Unicode must be specified precisely, not described as "UTF-8-ish".
|
||||
- `pub print :: core.print` style namespace member re-export is required.
|
||||
- Opus owns layout/design decisions.
|
||||
- Snarky owns product scope and final product acceptance.
|
||||
- Opus is the only role allowed to write code during Opus implementation.
|
||||
- Opus leases and CLI/tool timeouts must never be less than 30 minutes.
|
||||
- Implementation uses git branches, not worktrees.
|
||||
|
||||
## Blockers
|
||||
|
||||
- None active. The missing git baseline is now assigned to Opus as part of the
|
||||
implementation request.
|
||||
|
||||
## Next Safe Action
|
||||
|
||||
Wait for Opus to initialize the git baseline, switch to branch
|
||||
`opus/observability-redesign`, and implement the observability redesign from the
|
||||
proposal.
|
||||
|
||||
```txt
|
||||
.agents/runs/2026-06-02-redesign-distribution-mock/
|
||||
```
|
||||
|
||||
Then run the sequential flow:
|
||||
|
||||
1. Snarky writes `brief.md` using `.agents/subplans/06-admin-ui.md`.
|
||||
2. Opus writes `opus-proposal.md`.
|
||||
3. Snarky reviews and resolves product requirements.
|
||||
4. Opus implements on branch `opus/redesign-distribution-mock`.
|
||||
5. Manager validates.
|
||||
6. Snarky approves or requests another pass.
|
||||
|
||||
## Last Validation
|
||||
|
||||
- `node --check /Users/agra/plugins/opus-runner/scripts/opus_runner.mjs`: passed
|
||||
- `node /Users/agra/plugins/opus-runner/scripts/opus_runner.mjs --self-test`:
|
||||
passed
|
||||
- MCP handshake test for `opus-runner`: passed
|
||||
- `.agents/checkpoint.json` JSON parse: passed
|
||||
- `.agents/` and new subplan files ASCII check: passed
|
||||
- `PLAN.md` overview ASCII check: passed
|
||||
- Agent recovery protocol documented in `.agents/ORCHESTRATION.md` and
|
||||
`.agents/subplans/08-orchestration-and-qa.md`.
|
||||
- `node --check .agents/scripts/status.mjs`: passed
|
||||
- `.agents/scripts/status.mjs --tail` fixture test: passed
|
||||
- `node --check .agents/scripts/observe.mjs`: passed
|
||||
- `node --check .agents/observability/app.js`: passed
|
||||
- `curl http://127.0.0.1:4317/api/status?tail=5`: passed
|
||||
- Current planning run registered in `.agents/runs/2026-06-02-orchestration-planning/`.
|
||||
- Snarky review artifact saved.
|
||||
- Opus review artifact saved.
|
||||
- Discussion round artifacts saved.
|
||||
- Opus implementation instructions saved.
|
||||
- Plugin helper validation could not run because local Python environments lack
|
||||
`yaml`.
|
||||
149
.agents/ORCHESTRATION.md
Normal file
149
.agents/ORCHESTRATION.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Distribution Platform Agent Orchestration
|
||||
|
||||
This workspace uses a sequential, branch-based agent workflow.
|
||||
|
||||
## Roles
|
||||
|
||||
Codex manager:
|
||||
|
||||
- Owns orchestration, routing, validation, and reporting.
|
||||
- Writes run metadata and decision records.
|
||||
- Does not implement application code during Opus phases.
|
||||
|
||||
Snarky:
|
||||
|
||||
- Writes product proposals and acceptance criteria.
|
||||
- Reviews whether Opus satisfies the product requirements.
|
||||
- Has final say on product scope and required behavior.
|
||||
- Challenges Opus concerns on their merits.
|
||||
|
||||
Opus:
|
||||
|
||||
- Owns layout and visual design decisions.
|
||||
- Is consulted for technical problems until the manager can drive consensus.
|
||||
- Is the only role allowed to write application code.
|
||||
- Implements on git branches, not worktrees.
|
||||
|
||||
## Branch Rules
|
||||
|
||||
- No parallel implementation agents.
|
||||
- No worktrees.
|
||||
- Every implementation run uses a named branch, for example:
|
||||
- `opus/redesign-distribution-mock`
|
||||
- `opus/install-flow-detail`
|
||||
- `opus/release-console-ui`
|
||||
- The working tree must be clean before Opus implementation starts.
|
||||
- Opus implementation receives an explicit `allowedPaths` list.
|
||||
- Manager validates the final diff before Snarky approval.
|
||||
|
||||
## Run Directory Shape
|
||||
|
||||
Each task gets a durable run directory:
|
||||
|
||||
```txt
|
||||
.agents/runs/<run-id>/
|
||||
state.json
|
||||
agents.json
|
||||
brief.md
|
||||
opus-proposal.md
|
||||
concerns.json
|
||||
snarky-review.md
|
||||
decisions.json
|
||||
implementation-instructions.md
|
||||
progress.log
|
||||
implementation-log.md
|
||||
validation.md
|
||||
final-approval.md
|
||||
```
|
||||
|
||||
Planning and orchestration work also gets a run directory when it should be
|
||||
visible in the dashboard. If the manager is only editing files without creating
|
||||
`.agents/runs/<run-id>/state.json`, the dashboard cannot show that session.
|
||||
|
||||
## Agent Recovery
|
||||
|
||||
Agents are treated as disposable workers. The run directory is the durable
|
||||
source of truth.
|
||||
|
||||
Each active run tracks:
|
||||
|
||||
- `state.json`: current phase, expected artifact, input hash, retry count, and
|
||||
next action.
|
||||
- `agents.json`: active role leases, heartbeat timestamps, process/thread ids
|
||||
when available, and last known status.
|
||||
|
||||
Manager responsibilities:
|
||||
|
||||
- Start one agent phase at a time.
|
||||
- Write the phase input before invoking the agent.
|
||||
- Record the expected output path.
|
||||
- Record a lease timeout for the active role.
|
||||
- Poll or wait for completion.
|
||||
- Mark the agent dead if the lease expires or the process/tool call fails.
|
||||
- Restart the same role with the same input artifact.
|
||||
- Refuse to advance phases until the expected output exists and validates.
|
||||
|
||||
Recovery rules:
|
||||
|
||||
- Snarky can be recreated from the run brief, decisions, and checkpoint files.
|
||||
- Opus proposal/review calls can be retried because they do not edit files.
|
||||
- Opus implementation can only be retried after checking branch and dirty state.
|
||||
- If an Opus implementation dies with a dirty branch, manager must inspect the
|
||||
diff before retrying. Do not blindly overwrite partial code.
|
||||
- Retry count is capped. After repeated failure, manager records a blocker and
|
||||
asks the user.
|
||||
|
||||
Recommended lease defaults:
|
||||
|
||||
- Snarky brief/review: 10 minutes.
|
||||
- Opus proposal/review/technical consult: 30 minutes minimum.
|
||||
- Opus implementation: 45 minutes minimum.
|
||||
|
||||
Opus CLI/tool calls must never use a timeout below 30 minutes, even for
|
||||
read-only design proposals. If a shorter timeout is accidentally used, record
|
||||
the attempt, update the lease, and retry with at least 30 minutes.
|
||||
|
||||
## Status Command
|
||||
|
||||
From the workspace root:
|
||||
|
||||
```sh
|
||||
node .agents/scripts/status.mjs --tail 40
|
||||
```
|
||||
|
||||
Browser dashboard:
|
||||
|
||||
```sh
|
||||
node .agents/scripts/observe.mjs --port 4317
|
||||
```
|
||||
|
||||
This prints every recorded run, every recorded agent, lease expiry state,
|
||||
blockers, next action, and the tail of the active run progress file. The browser
|
||||
dashboard shows the same state and refreshes automatically.
|
||||
|
||||
Managers should append progress events to `.agents/runs/<run-id>/progress.log`.
|
||||
|
||||
## Sequential Flow
|
||||
|
||||
1. Manager creates `.agents/runs/<run-id>/`.
|
||||
2. Snarky writes `brief.md`.
|
||||
3. Manager calls Opus for `opus-proposal.md`.
|
||||
4. Snarky reviews product fit and challenges concerns.
|
||||
5. Opus responds to accepted concern challenges.
|
||||
6. Manager resolves consensus for technical issues.
|
||||
7. Snarky writes `implementation-instructions.md`.
|
||||
8. Manager calls Opus implementation on a named branch.
|
||||
9. Manager validates the branch diff and checks.
|
||||
10. Snarky approves or requests another Opus pass.
|
||||
|
||||
## Opus Runner Tools
|
||||
|
||||
The local `opus-runner` plugin exposes:
|
||||
|
||||
- `opus_branch_status`
|
||||
- `opus_propose_design`
|
||||
- `opus_review_concern`
|
||||
- `opus_solve_technical_issue`
|
||||
- `opus_implement_on_branch`
|
||||
|
||||
Only `opus_implement_on_branch` may enable file editing.
|
||||
89
.agents/checkpoint.json
Normal file
89
.agents/checkpoint.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"updated_at": "2026-06-02 15:49:58 EEST",
|
||||
"workspace": "/Users/agra/projects/distribution",
|
||||
"current_phase": "observability-redesign-implementation",
|
||||
"active_run_id": "2026-06-02-observability-redesign",
|
||||
"active_branch": "opus/observability-redesign",
|
||||
"git": {
|
||||
"is_repo": false,
|
||||
"status": "not-a-git-repository; git baseline delegated to Opus for implementation"
|
||||
},
|
||||
"orchestration": {
|
||||
"mode": "sequential",
|
||||
"implementation_is_branch_based": true,
|
||||
"uses_worktrees": false,
|
||||
"code_writer": "opus",
|
||||
"product_final_say": "snarky",
|
||||
"layout_final_say": "opus"
|
||||
},
|
||||
"completed": [
|
||||
"PLAN.md created",
|
||||
"static website mock created",
|
||||
"mock layout rejected by user",
|
||||
".agents/ORCHESTRATION.md created",
|
||||
"opus-runner plugin created",
|
||||
"opus-runner runtime self-test passed",
|
||||
"detailed subplans created",
|
||||
"PLAN.md rewritten as overview",
|
||||
"agent liveness and restart policy documented",
|
||||
".agents/scripts/status.mjs added",
|
||||
"observability dashboard added",
|
||||
"observability dashboard started at http://127.0.0.1:4317",
|
||||
"current planning run registered for observability",
|
||||
"Snarky review artifact saved",
|
||||
"Opus review artifact saved after user-approved repo access",
|
||||
"Snarky and Opus completed two additional discussion rounds"
|
||||
,
|
||||
"observability redesign run created",
|
||||
"Opus timeout minimum documented as 30 minutes",
|
||||
"Opus observability redesign proposal saved",
|
||||
"Opus observability implementation requested"
|
||||
],
|
||||
"blockers": [],
|
||||
"next_action": "Wait for Opus to initialize the git baseline, switch to branch opus/observability-redesign, and implement the observability redesign from the proposal.",
|
||||
"last_validation": [
|
||||
"opus-runner node syntax passed",
|
||||
"opus-runner Claude CLI self-test passed",
|
||||
"opus-runner MCP handshake passed",
|
||||
"checkpoint JSON parse passed",
|
||||
"new agent planning files ASCII check passed",
|
||||
"PLAN.md overview ASCII check passed",
|
||||
"agent recovery protocol documented",
|
||||
"status script syntax passed",
|
||||
"status script fixture tail test passed",
|
||||
"observability server syntax passed",
|
||||
"observability app syntax passed",
|
||||
"observability API check passed",
|
||||
"current planning run registered",
|
||||
"Snarky and Opus consultation artifacts saved",
|
||||
"additional Snarky/Opus discussion artifacts saved",
|
||||
"Opus implementation instructions saved"
|
||||
],
|
||||
"artifacts": {
|
||||
"plan": "PLAN.md",
|
||||
"orchestration": ".agents/ORCHESTRATION.md",
|
||||
"checkpoint": ".agents/CHECKPOINT.md",
|
||||
"checkpoint_json": ".agents/checkpoint.json",
|
||||
"subplans": ".agents/subplans/",
|
||||
"status_script": ".agents/scripts/status.mjs",
|
||||
"observability_server": ".agents/scripts/observe.mjs",
|
||||
"observability_frontend": ".agents/observability/",
|
||||
"observability_url": "http://127.0.0.1:4317",
|
||||
"snarky_review": ".agents/runs/2026-06-02-orchestration-planning/snarky-review.md",
|
||||
"opus_review": ".agents/runs/2026-06-02-orchestration-planning/opus-review.md",
|
||||
"discussion_round_1_snarky": ".agents/runs/2026-06-02-orchestration-planning/discussion-round-1-snarky.md",
|
||||
"discussion_round_1_opus": ".agents/runs/2026-06-02-orchestration-planning/discussion-round-1-opus.md",
|
||||
"discussion_round_2_snarky": ".agents/runs/2026-06-02-orchestration-planning/discussion-round-2-snarky.md",
|
||||
"discussion_round_2_opus": ".agents/runs/2026-06-02-orchestration-planning/discussion-round-2-opus.md",
|
||||
"observability_redesign_run": ".agents/runs/2026-06-02-observability-redesign/",
|
||||
"observability_redesign_proposal": ".agents/runs/2026-06-02-observability-redesign/opus-proposal.md",
|
||||
"observability_implementation_instructions": ".agents/runs/2026-06-02-observability-redesign/implementation-instructions.md",
|
||||
"mock_files": [
|
||||
"index.html",
|
||||
"styles.css",
|
||||
"app.js"
|
||||
],
|
||||
"opus_runner_plugin": "/Users/agra/plugins/opus-runner"
|
||||
}
|
||||
}
|
||||
183
.agents/observability/app.js
Normal file
183
.agents/observability/app.js
Normal file
@@ -0,0 +1,183 @@
|
||||
const refreshButton = document.getElementById("refresh-button");
|
||||
const refreshStatus = document.getElementById("refresh-status");
|
||||
const tailSelect = document.getElementById("tail-select");
|
||||
|
||||
const fields = {
|
||||
activeRun: document.getElementById("metric-active-run"),
|
||||
phase: document.getElementById("metric-phase"),
|
||||
branch: document.getElementById("metric-branch"),
|
||||
blockers: document.getElementById("metric-blockers"),
|
||||
runCount: document.getElementById("run-count"),
|
||||
runsList: document.getElementById("runs-list"),
|
||||
generatedAt: document.getElementById("generated-at"),
|
||||
nextAction: document.getElementById("next-action"),
|
||||
workspace: document.getElementById("workspace"),
|
||||
blockersList: document.getElementById("blockers"),
|
||||
progressPath: document.getElementById("progress-path"),
|
||||
progressTail: document.getElementById("progress-tail"),
|
||||
};
|
||||
|
||||
let refreshTimer = null;
|
||||
|
||||
refreshButton.addEventListener("click", () => loadStatus());
|
||||
tailSelect.addEventListener("change", () => loadStatus());
|
||||
|
||||
loadStatus();
|
||||
refreshTimer = setInterval(loadStatus, 5000);
|
||||
window.addEventListener("beforeunload", () => clearInterval(refreshTimer));
|
||||
|
||||
async function loadStatus() {
|
||||
setRefreshState("Loading", "");
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/status?tail=${encodeURIComponent(tailSelect.value)}`);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const data = await response.json();
|
||||
render(data);
|
||||
setRefreshState("Live", "status-ok");
|
||||
} catch (error) {
|
||||
setRefreshState("Error", "status-dead");
|
||||
fields.progressTail.textContent = `Failed to load status: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
const summary = data.summary || {};
|
||||
const checkpoint = data.checkpoint || {};
|
||||
const blockers = Array.isArray(checkpoint.blockers) ? checkpoint.blockers : [];
|
||||
|
||||
fields.activeRun.textContent = summary.active_run_id || "none";
|
||||
fields.phase.textContent = summary.current_phase || "unknown";
|
||||
fields.branch.textContent = summary.active_branch || "none";
|
||||
fields.blockers.textContent = String(blockers.length);
|
||||
fields.runCount.textContent = `${data.runs.length} total`;
|
||||
fields.generatedAt.textContent = formatTime(data.generated_at);
|
||||
fields.nextAction.textContent = summary.next_action || "No next action recorded.";
|
||||
fields.workspace.textContent = data.workspace || "-";
|
||||
|
||||
renderBlockers(blockers);
|
||||
renderRuns(data.runs, summary.active_run_id);
|
||||
renderProgress(data.active_progress);
|
||||
}
|
||||
|
||||
function renderBlockers(blockers) {
|
||||
fields.blockersList.innerHTML = "";
|
||||
|
||||
if (blockers.length === 0) {
|
||||
fields.blockersList.innerHTML = '<p class="muted">No blockers recorded.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const blocker of blockers) {
|
||||
const item = document.createElement("div");
|
||||
item.className = "blocker";
|
||||
item.textContent = blocker;
|
||||
fields.blockersList.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
function renderRuns(runs, activeRunId) {
|
||||
fields.runsList.innerHTML = "";
|
||||
|
||||
if (runs.length === 0) {
|
||||
fields.runsList.innerHTML =
|
||||
'<div class="empty">No runs yet. Create .agents/runs/<run-id>/ with state.json and agents.json.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const run of runs) {
|
||||
fields.runsList.appendChild(renderRun(run, run.id === activeRunId));
|
||||
}
|
||||
}
|
||||
|
||||
function renderRun(run, active) {
|
||||
const state = run.state || {};
|
||||
const card = document.createElement("article");
|
||||
card.className = "run-card";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "run-title";
|
||||
title.innerHTML = `
|
||||
<strong>${escapeHtml(run.id)}</strong>
|
||||
<span class="chip ${active ? "status-ok" : ""}">${active ? "active" : "recorded"}</span>
|
||||
`;
|
||||
card.appendChild(title);
|
||||
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "run-meta";
|
||||
meta.innerHTML = `
|
||||
<span>Phase: ${escapeHtml(state.current_phase || state.phase || "unknown")}</span>
|
||||
<span>Branch: ${escapeHtml(state.current_branch || state.branch || "none")}</span>
|
||||
<span>Expected: ${escapeHtml(state.expected_output_artifact || state.expected_output || "none")}</span>
|
||||
<span>Retries: ${escapeHtml(String(state.retry_count ?? 0))}</span>
|
||||
`;
|
||||
card.appendChild(meta);
|
||||
|
||||
const agents = document.createElement("div");
|
||||
agents.className = "agent-list";
|
||||
if (!run.agents || run.agents.length === 0) {
|
||||
agents.innerHTML = '<small class="muted">No agents recorded.</small>';
|
||||
} else {
|
||||
for (const agent of run.agents) {
|
||||
agents.appendChild(renderAgent(agent));
|
||||
}
|
||||
}
|
||||
card.appendChild(agents);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderAgent(agent) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "agent-row";
|
||||
const statusClass = agent.lease_expired
|
||||
? "status-dead"
|
||||
: agent.status === "completed"
|
||||
? "status-ok"
|
||||
: "status-warn";
|
||||
const id = agent.thread_id || agent.process_id || agent.tool_call_id || "none";
|
||||
|
||||
row.innerHTML = `
|
||||
<div>
|
||||
<strong>${escapeHtml(agent.role || "unknown-role")}</strong>
|
||||
<small>heartbeat ${escapeHtml(agent.heartbeat_at || "none")} / id ${escapeHtml(String(id))}</small>
|
||||
${agent.last_error ? `<small>${escapeHtml(agent.last_error)}</small>` : ""}
|
||||
</div>
|
||||
<span class="chip ${statusClass}">${escapeHtml(agent.lease_expired ? "expired" : agent.status || "unknown")}</span>
|
||||
`;
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderProgress(progress) {
|
||||
if (!progress || !progress.path) {
|
||||
fields.progressPath.textContent = "no progress file";
|
||||
fields.progressTail.textContent = "No progress file found for the active run.";
|
||||
return;
|
||||
}
|
||||
|
||||
fields.progressPath.textContent = progress.path;
|
||||
fields.progressTail.textContent = progress.lines.length > 0
|
||||
? progress.lines.join("\n")
|
||||
: "Progress file is empty.";
|
||||
}
|
||||
|
||||
function setRefreshState(label, extraClass) {
|
||||
refreshStatus.className = `status-pill ${extraClass}`;
|
||||
refreshStatus.textContent = label;
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
if (!value) return "-";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
88
.agents/observability/index.html
Normal file
88
.agents/observability/index.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Agent Observability</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Distribution orchestration</p>
|
||||
<h1>Agent Observability</h1>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<label>
|
||||
Tail
|
||||
<select id="tail-select">
|
||||
<option value="40">40 lines</option>
|
||||
<option value="80" selected>80 lines</option>
|
||||
<option value="160">160 lines</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="refresh-button">Refresh</button>
|
||||
<span id="refresh-status" class="status-pill">Loading</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="summary-grid" aria-label="Summary">
|
||||
<article class="metric">
|
||||
<span>Active run</span>
|
||||
<strong id="metric-active-run">-</strong>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<span>Phase</span>
|
||||
<strong id="metric-phase">-</strong>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<span>Branch</span>
|
||||
<strong id="metric-branch">-</strong>
|
||||
</article>
|
||||
<article class="metric">
|
||||
<span>Blockers</span>
|
||||
<strong id="metric-blockers">-</strong>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="layout">
|
||||
<section class="panel runs-panel">
|
||||
<div class="panel-head">
|
||||
<h2>Runs</h2>
|
||||
<span id="run-count" class="muted">0</span>
|
||||
</div>
|
||||
<div id="runs-list" class="runs-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<div class="panel-head">
|
||||
<h2>Checkpoint</h2>
|
||||
<span id="generated-at" class="muted">-</span>
|
||||
</div>
|
||||
<div class="checkpoint-grid">
|
||||
<div>
|
||||
<span>Next action</span>
|
||||
<p id="next-action">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>Workspace</span>
|
||||
<p id="workspace">-</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="blockers" class="blockers"></div>
|
||||
|
||||
<div class="panel-head lower">
|
||||
<h2>Progress Tail</h2>
|
||||
<span id="progress-path" class="muted">-</span>
|
||||
</div>
|
||||
<pre id="progress-tail" class="progress-tail">No progress loaded.</pre>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
363
.agents/observability/styles.css
Normal file
363
.agents/observability/styles.css
Normal file
@@ -0,0 +1,363 @@
|
||||
:root {
|
||||
--bg: #f5f4ef;
|
||||
--panel: #ffffff;
|
||||
--ink: #202428;
|
||||
--muted: #68707a;
|
||||
--line: #d9d4c8;
|
||||
--soft: #faf8f2;
|
||||
--blue: #235f8f;
|
||||
--green: #237457;
|
||||
--amber: #9a6818;
|
||||
--red: #a43d34;
|
||||
--shadow: 0 14px 34px rgba(35, 32, 25, 0.08);
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 36px;
|
||||
border: 1px solid #1d4f76;
|
||||
border-radius: 8px;
|
||||
background: var(--blue);
|
||||
color: #ffffff;
|
||||
font-weight: 800;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
select {
|
||||
min-height: 36px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: var(--ink);
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(1440px, 100%);
|
||||
margin: 0 auto;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 4px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-pill,
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
border: 1px solid var(--line);
|
||||
background: #ffffff;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.status-ok {
|
||||
background: #e5f2eb;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.status-warn {
|
||||
background: #f7ead1;
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.status-dead {
|
||||
background: #f5dddd;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.metric,
|
||||
.panel {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-height: 94px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.metric span,
|
||||
.checkpoint-grid span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.metric strong {
|
||||
overflow: hidden;
|
||||
color: var(--ink);
|
||||
font-size: 23px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 430px) minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
min-width: 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.panel-head.lower {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.runs-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
border: 1px dashed var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--soft);
|
||||
color: var(--muted);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.run-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--soft);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.run-title {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.run-title strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.run-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.agent-list {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.agent-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--line);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.agent-row small {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.checkpoint-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.checkpoint-grid div,
|
||||
.blockers {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--soft);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.checkpoint-grid p {
|
||||
overflow-wrap: anywhere;
|
||||
margin-top: 5px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.blockers {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.blocker {
|
||||
color: var(--red);
|
||||
font-size: 14px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.progress-tail {
|
||||
overflow: auto;
|
||||
min-height: 360px;
|
||||
max-height: 620px;
|
||||
margin: 0;
|
||||
border: 1px solid #2d3339;
|
||||
border-radius: 8px;
|
||||
background: #202428;
|
||||
color: #f6f3ea;
|
||||
padding: 14px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.summary-grid,
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.shell {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.toolbar,
|
||||
.toolbar label,
|
||||
button,
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkpoint-grid,
|
||||
.run-meta {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
23
.agents/runs/2026-06-02-observability-redesign/agents.json
Normal file
23
.agents/runs/2026-06-02-observability-redesign/agents.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"agents": [
|
||||
{
|
||||
"role": "codex-manager",
|
||||
"status": "running",
|
||||
"started_at": "2026-06-02T12:05:10Z",
|
||||
"heartbeat_at": "2026-06-02T12:49:58Z",
|
||||
"lease_expires_at": "2026-06-02T13:04:58Z",
|
||||
"thread_id": "current-codex-thread",
|
||||
"last_error": null
|
||||
},
|
||||
{
|
||||
"role": "opus",
|
||||
"status": "running",
|
||||
"started_at": "2026-06-02T12:49:58Z",
|
||||
"heartbeat_at": "2026-06-02T12:49:58Z",
|
||||
"lease_expires_at": "2026-06-02T13:19:58Z",
|
||||
"tool_call_id": "claude-cli-observability-implementation-1",
|
||||
"last_error": null
|
||||
}
|
||||
]
|
||||
}
|
||||
75
.agents/runs/2026-06-02-observability-redesign/brief.md
Normal file
75
.agents/runs/2026-06-02-observability-redesign/brief.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Observability Redesign Brief
|
||||
|
||||
## Request
|
||||
|
||||
Ask Opus to redesign the local agent observability dashboard before any
|
||||
implementation starts.
|
||||
|
||||
## Context
|
||||
|
||||
The dashboard lives under `.agents/observability/` and is served by
|
||||
`.agents/scripts/observe.mjs` at `http://127.0.0.1:4317`.
|
||||
|
||||
Current files:
|
||||
|
||||
- `.agents/observability/index.html`
|
||||
- `.agents/observability/styles.css`
|
||||
- `.agents/observability/app.js`
|
||||
- `.agents/scripts/observe.mjs`
|
||||
|
||||
The dashboard currently shows:
|
||||
|
||||
- Summary cards for active run, phase, branch, and blockers.
|
||||
- A runs list with run metadata and agent rows.
|
||||
- Checkpoint next action, workspace, blockers, and active progress tail.
|
||||
- Tail length selector, refresh button, and live/error status.
|
||||
|
||||
Known design problem:
|
||||
|
||||
- The current layout feels generic and visually weak.
|
||||
- It does not make agent liveness, leases, progress, blockers, or next action
|
||||
feel operationally obvious enough.
|
||||
- It should feel like a focused orchestration cockpit, not a landing page.
|
||||
|
||||
## Product Requirements
|
||||
|
||||
- First viewport should immediately answer:
|
||||
- What is active?
|
||||
- Is anything dead or blocked?
|
||||
- Who is working?
|
||||
- What happened most recently?
|
||||
- What should the manager do next?
|
||||
- Preserve the data source and static app constraint.
|
||||
- Keep the UI dense, calm, and operational.
|
||||
- Avoid marketing layout, decorative cards inside cards, and hero-style copy.
|
||||
- Make liveness and lease expiry visually obvious.
|
||||
- Make progress tail readable without dominating the whole page.
|
||||
- Support desktop and mobile layouts without text overlap.
|
||||
- Prefer familiar controls and icons if implementation later adds an icon
|
||||
library or inline symbols.
|
||||
- Keep the design implementable in plain HTML, CSS, and vanilla JS.
|
||||
|
||||
## Current Constraints
|
||||
|
||||
- This is a read-only Opus design proposal request.
|
||||
- Do not edit application files yet.
|
||||
- The distribution workspace is not a git repository yet, so branch-based Opus
|
||||
implementation is still blocked.
|
||||
- If implementation is later approved, the likely write scope is:
|
||||
- `.agents/observability/index.html`
|
||||
- `.agents/observability/styles.css`
|
||||
- `.agents/observability/app.js`
|
||||
- possibly `.agents/scripts/observe.mjs` if Opus needs small API fields for
|
||||
better status grouping.
|
||||
|
||||
## Opus Output Requested
|
||||
|
||||
Write `opus-proposal.md` with:
|
||||
|
||||
1. The proposed information architecture.
|
||||
2. The exact visual layout direction.
|
||||
3. The key interactions and states.
|
||||
4. Any small API/data-shape changes requested from `observe.mjs`.
|
||||
5. Mobile behavior.
|
||||
6. Implementation notes for the eventual Opus coding pass.
|
||||
7. Concerns or blockers, if any.
|
||||
@@ -0,0 +1,135 @@
|
||||
# Observability Redesign Implementation Instructions
|
||||
|
||||
## Role
|
||||
|
||||
You are Opus in the distribution platform orchestration flow. Codex is the
|
||||
manager. Snarky owns product acceptance. You own layout and visual design
|
||||
decisions and are the only role allowed to edit code during this phase.
|
||||
|
||||
## User Direction
|
||||
|
||||
The user clarified that the git setup required for the Opus implementation
|
||||
branch is Opus responsibility.
|
||||
|
||||
If `/Users/agra/projects/distribution` is not yet a git repository, initialize a
|
||||
local git repository, create a clean baseline commit from the current workspace,
|
||||
and then create or switch to branch:
|
||||
|
||||
```txt
|
||||
opus/observability-redesign
|
||||
```
|
||||
|
||||
Use branches only. Do not create a worktree.
|
||||
|
||||
## Primary Task
|
||||
|
||||
Implement the observability dashboard redesign from:
|
||||
|
||||
```txt
|
||||
.agents/runs/2026-06-02-observability-redesign/opus-proposal.md
|
||||
```
|
||||
|
||||
The dashboard lives at:
|
||||
|
||||
```txt
|
||||
.agents/observability/
|
||||
```
|
||||
|
||||
It is served by:
|
||||
|
||||
```txt
|
||||
.agents/scripts/observe.mjs
|
||||
```
|
||||
|
||||
## Allowed Edit Scope
|
||||
|
||||
Application/dashboard edits are limited to:
|
||||
|
||||
- `.agents/observability/index.html`
|
||||
- `.agents/observability/styles.css`
|
||||
- `.agents/observability/app.js`
|
||||
- `.agents/scripts/observe.mjs`
|
||||
- `.agents/runs/2026-06-02-observability-redesign/implementation-log.md`
|
||||
|
||||
Creating `.git/` and committing/switching branches is allowed as setup for this
|
||||
phase. Do not edit root mock files such as `index.html`, `styles.css`, or
|
||||
`app.js`.
|
||||
|
||||
## Required Product Shape
|
||||
|
||||
Implement the proposal's operational cockpit direction:
|
||||
|
||||
- Replace the four metric cards with a compact sticky status bar and a "Now"
|
||||
command band.
|
||||
- Make the first viewport answer what is active, whether anything is blocked or
|
||||
stalled, who is working, what happened recently, and what the manager should do
|
||||
next.
|
||||
- Pin active-agent liveness with status, lease countdown, heartbeat age, and
|
||||
clear warning/dead states.
|
||||
- Keep progress readable but bounded; it must not dominate the whole page.
|
||||
- Keep the UI dense, calm, and operational. Avoid marketing layout, hero copy,
|
||||
decorative nesting, and generic dashboard fluff.
|
||||
- Support desktop and mobile without text overlap.
|
||||
|
||||
## Required Interactions
|
||||
|
||||
- Keep the existing `/api/status` polling model and static vanilla app.
|
||||
- Add a one-second local ticker for freshness, heartbeat age, and lease
|
||||
countdown text.
|
||||
- Add pause/resume for auto-refresh.
|
||||
- Keep the last good render on fetch error; do not replace the progress log with
|
||||
an error message.
|
||||
- Add progress expand/collapse.
|
||||
- Preserve progress bottom-stick behavior and show a jump control when new lines
|
||||
arrive while the user is scrolled up.
|
||||
- Make run rows selectable. If the API supports `?run=`, use it to refocus
|
||||
progress for the selected run; otherwise degrade gracefully.
|
||||
- Add click-to-copy for run id, branch, and progress path with a short copied
|
||||
state.
|
||||
|
||||
## Required Server/API Additions
|
||||
|
||||
Make only additive, optional changes:
|
||||
|
||||
- Per-agent liveness fields:
|
||||
- `lease_remaining_ms`
|
||||
- `heartbeat_age_ms`
|
||||
- `lease_expired`
|
||||
- `is_active`
|
||||
- Summary fields:
|
||||
- `active_role`
|
||||
- `health`
|
||||
- Active progress fields:
|
||||
- `modified_at`
|
||||
- `total_lines`
|
||||
- `truncated`
|
||||
- Support `/api/status?tail=N&run=<id>` so selected runs can show their own
|
||||
progress.
|
||||
- In progress selection, prefer the newest candidate progress artifact by mtime.
|
||||
|
||||
The frontend must remain missing-field safe.
|
||||
|
||||
## Validation Expectations
|
||||
|
||||
Before finishing, run:
|
||||
|
||||
```sh
|
||||
node --check .agents/scripts/observe.mjs
|
||||
node --check .agents/observability/app.js
|
||||
node .agents/scripts/status.mjs --tail 20
|
||||
```
|
||||
|
||||
If possible, start or reuse the local server and verify:
|
||||
|
||||
```sh
|
||||
curl http://127.0.0.1:4317/api/status?tail=5
|
||||
```
|
||||
|
||||
Write a short final implementation report to:
|
||||
|
||||
```txt
|
||||
.agents/runs/2026-06-02-observability-redesign/implementation-log.md
|
||||
```
|
||||
|
||||
Include changed files, validation commands/results, unresolved risks, and the
|
||||
current git branch/status.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Role
|
||||
|
||||
You are Opus in the distribution platform orchestration flow.
|
||||
|
||||
Codex is the manager. Snarky owns product acceptance. You own layout and visual
|
||||
design decisions, and you are consulted for technical problems until consensus.
|
||||
|
||||
This is a read-only design proposal. Do not write code. Do not assume
|
||||
implementation has started.
|
||||
|
||||
# Repositories
|
||||
|
||||
You have read-only context for:
|
||||
|
||||
- `/Users/agra/projects/distribution`
|
||||
- `/Users/agra/projects/sx`
|
||||
|
||||
For this request, focus on the distribution repo only.
|
||||
|
||||
# Task
|
||||
|
||||
Redesign the local agent observability dashboard.
|
||||
|
||||
Read these files:
|
||||
|
||||
- `/Users/agra/projects/distribution/.agents/runs/2026-06-02-observability-redesign/brief.md`
|
||||
- `/Users/agra/projects/distribution/.agents/observability/index.html`
|
||||
- `/Users/agra/projects/distribution/.agents/observability/styles.css`
|
||||
- `/Users/agra/projects/distribution/.agents/observability/app.js`
|
||||
- `/Users/agra/projects/distribution/.agents/scripts/observe.mjs`
|
||||
- `/Users/agra/projects/distribution/.agents/ORCHESTRATION.md`
|
||||
|
||||
Return a proposal in Markdown with these sections:
|
||||
|
||||
1. Verdict
|
||||
2. Information Architecture
|
||||
3. Visual Layout Direction
|
||||
4. Interactions And States
|
||||
5. API Or Data Shape Changes
|
||||
6. Mobile Behavior
|
||||
7. Implementation Notes
|
||||
8. Concerns
|
||||
|
||||
Be specific enough that an implementation pass can follow it without another
|
||||
layout brainstorm. Keep the design operational, dense, and calm. Avoid a hero
|
||||
page, marketing copy, decorative nesting, or generic SaaS fluff.
|
||||
309
.agents/runs/2026-06-02-observability-redesign/opus-proposal.md
Normal file
309
.agents/runs/2026-06-02-observability-redesign/opus-proposal.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# Opus Proposal - Local Agent Observability Dashboard Redesign
|
||||
|
||||
## 1. Verdict
|
||||
|
||||
Redesign is warranted, and the problem is structural, not cosmetic. The current
|
||||
page is a symmetric four-metric grid feeding two equal-weight panels. That
|
||||
layout treats every fact as equally important, which is wrong for a sequential,
|
||||
single-active orchestration model.
|
||||
|
||||
Keep the hard constraints: static HTML/CSS/vanilla JS, single `/api/status`
|
||||
poll, no build step, and no dependencies. The redesign needs no breaking API
|
||||
changes, only small additive, server-computed fields to make liveness and lease
|
||||
expiry honest rather than guessed client-side.
|
||||
|
||||
The reorientation:
|
||||
|
||||
- Replace the equal metric grid with a "Now" command band that answers active
|
||||
run, phase, branch, and next action in one strip.
|
||||
- Anchor the band with one system-health token.
|
||||
- Make agent liveness a live ticking signal: lease countdown plus heartbeat age.
|
||||
- Demote the progress tail from page-dominator to bounded, auto-sticking log.
|
||||
- Remove landing-page tells: heavy shadows, shouting weights, eyebrow plus large
|
||||
heading, and cards-inside-cards.
|
||||
|
||||
Implementation is blocked until the workspace is a git repo. This proposal is
|
||||
approvable now and implementable once `git init` and a baseline commit exist.
|
||||
|
||||
## 2. Information Architecture
|
||||
|
||||
Three tiers, in strict priority order. Tiers 0 through 2 should sit above the
|
||||
fold on desktop.
|
||||
|
||||
Tier 0 - Chrome/status bar:
|
||||
|
||||
- Workspace basename.
|
||||
- Connection state.
|
||||
- Freshness, ticking as "updated 3s ago".
|
||||
- Tail selector.
|
||||
- Refresh.
|
||||
- Auto-refresh pause/play.
|
||||
|
||||
Tier 1 - Now band:
|
||||
|
||||
- System token: `WORKING`, `BLOCKED`, `STALLED`, `STALE`, or `IDLE`.
|
||||
- Active run: run id, phase stepper, branch chip.
|
||||
- Next action: prominent prose label.
|
||||
|
||||
Tier 2 - Operational split:
|
||||
|
||||
- Left rail around 340px:
|
||||
- Active agent pinned at the top with status dot, role, lease countdown, and
|
||||
heartbeat age.
|
||||
- Agents for the active run.
|
||||
- Compact runs list with active run highlighted.
|
||||
- Right column:
|
||||
- Conditional blockers strip at the top when blockers exist.
|
||||
- Progress tail with path, "last N of M", and last-modified age.
|
||||
|
||||
Remove the standalone workspace metric and the four-up summary grid. Workspace
|
||||
moves to the status bar; the grid is absorbed into the Now band and health
|
||||
token.
|
||||
|
||||
## 3. Visual Layout Direction
|
||||
|
||||
Desktop layout:
|
||||
|
||||
```txt
|
||||
+--------------------------------------------------------------------+
|
||||
| distribution updated 3s ago Tail [80] refresh pause |
|
||||
+--------------------------------------------------------------------+
|
||||
| WORKING | run 2026-06-02-obs... 4/10 Opus proposal | NEXT ACTION |
|
||||
| | branch none Wait on ... |
|
||||
+----------------------------+---------------------------------------+
|
||||
| ACTIVE AGENT | BLOCKERS, if any |
|
||||
| opus running +---------------------------------------+
|
||||
| lease 28:14 heartbeat 4s | PROGRESS runs/.../progress.log |
|
||||
| | +-----------------------------------+ |
|
||||
| AGENTS | | 14:02:11 proposal requested | |
|
||||
| codex running | | 14:02:48 reading brief.md | |
|
||||
| snarky completed | | ... | |
|
||||
| | +-----------------------------------+ |
|
||||
| RUNS | expand new |
|
||||
| active run | |
|
||||
+----------------------------+---------------------------------------+
|
||||
```
|
||||
|
||||
Grid and sizing:
|
||||
|
||||
- Shell: `width: min(1440px, 100%)`, padding `20px`.
|
||||
- Status bar: sticky, top `0`, about `44px`, hairline bottom border.
|
||||
- Now band: single panel with
|
||||
`grid-template-columns: 150px minmax(0, 1fr) minmax(300px, 1fr)`.
|
||||
- Main split: `grid-template-columns: minmax(300px, 340px) minmax(0, 1fr)`.
|
||||
- Progress body: `max-height: clamp(320px, 52vh, 720px); overflow: auto`.
|
||||
- Expanded progress body: about `80vh`.
|
||||
|
||||
Visual language:
|
||||
|
||||
- Use one elevation level. Prefer hairline borders over heavy shadows.
|
||||
- Delete nested bordered boxes; internal sections use dividers and spacing.
|
||||
- Reduce blanket `font-weight: 800`. Labels around 600, values 400, system
|
||||
token 700.
|
||||
- Use monospace for machine facts: run ids, branches, paths, timestamps, counts,
|
||||
lease values, heartbeat values.
|
||||
- Keep prose in the sans stack.
|
||||
- Keep the warm-paper base and the current semantic colors, but assign strict
|
||||
meaning:
|
||||
- green: healthy, live, completed
|
||||
- blue: working or active focus
|
||||
- amber: warning, expiring lease, aging heartbeat, stale data
|
||||
- red: blocked, expired, dead, fetch error
|
||||
- muted grey: idle, recorded, none, absent
|
||||
- Use 8px status dots for rows. Reserve pills for the system token and active
|
||||
or recorded run status.
|
||||
- Keep the progress log dark as the one large terminal-like surface.
|
||||
|
||||
Phase stepper:
|
||||
|
||||
- Render the ten canonical sequential-flow steps as a segmented bar.
|
||||
- Filled segments are done, current segment is ringed, future segments are
|
||||
hollow.
|
||||
- Show `N / 10 - <label>`.
|
||||
- If phase text is unknown, degrade to label-only text.
|
||||
|
||||
## 4. Interactions And States
|
||||
|
||||
System health token priority:
|
||||
|
||||
```txt
|
||||
BLOCKED red blocker_count > 0
|
||||
STALLED red active agent lease_expired and not blocked
|
||||
STALE amber data age is stale or active heartbeat is stale
|
||||
WORKING blue active-run agent is running with healthy unexpired lease
|
||||
IDLE grey no active run or no leased agent
|
||||
```
|
||||
|
||||
Use `aria-live="polite"` for health transitions.
|
||||
|
||||
Liveness:
|
||||
|
||||
- Add a 1-second local ticker independent from the 5-second poll.
|
||||
- Lease countdown displays `mm:ss` remaining.
|
||||
- Lease at or below two minutes becomes amber.
|
||||
- Expired lease becomes red and displays elapsed expiry age.
|
||||
- Heartbeat age displays as `heartbeat 4s`.
|
||||
- Heartbeat over 60 seconds becomes amber.
|
||||
- Heartbeat over 180 seconds becomes red.
|
||||
- Data freshness in the status bar ticks locally.
|
||||
- The ticker only updates timestamp text nodes and never re-renders lists.
|
||||
|
||||
Polling and errors:
|
||||
|
||||
- Keep the last good render on fetch error.
|
||||
- Flip the status bar to red error/stale state.
|
||||
- Do not replace the progress tail with an error string.
|
||||
- Pause timers while the tab is hidden and resume with an immediate refetch on
|
||||
focus.
|
||||
|
||||
Selection and affordances:
|
||||
|
||||
- Run rows should be selectable.
|
||||
- Selecting a run refocuses progress and agent details if the API supports
|
||||
`?run=`.
|
||||
- Progress auto-sticks to bottom only if the user was already at bottom.
|
||||
- If new content arrives while scrolled up, show a "new" jump button.
|
||||
- Add expand/collapse for the progress log.
|
||||
- Add click-to-copy for run id, branch, and progress path with a brief copied
|
||||
state.
|
||||
|
||||
Empty and degraded states:
|
||||
|
||||
- No runs.
|
||||
- No active run.
|
||||
- No progress file.
|
||||
- No lease recorded: show `lease -`, muted.
|
||||
- No heartbeat recorded: show `heartbeat -`, muted.
|
||||
- Unknown phase: label-only stepper.
|
||||
- Checkpoint parse error: surface it in the blockers strip.
|
||||
|
||||
## 5. API Or Data Shape Changes
|
||||
|
||||
All requests are additive and optional. The dashboard must render correctly if
|
||||
any are absent.
|
||||
|
||||
Per agent:
|
||||
|
||||
```json
|
||||
{
|
||||
"lease_remaining_ms": 1694000,
|
||||
"heartbeat_age_ms": 4000,
|
||||
"lease_expired": true,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
Summary:
|
||||
|
||||
```json
|
||||
{
|
||||
"active_role": "opus",
|
||||
"health": "working"
|
||||
}
|
||||
```
|
||||
|
||||
Active progress:
|
||||
|
||||
```json
|
||||
{
|
||||
"modified_at": "2026-06-02T14:05:03Z",
|
||||
"total_lines": 342,
|
||||
"truncated": true
|
||||
}
|
||||
```
|
||||
|
||||
New optional query parameter:
|
||||
|
||||
```txt
|
||||
/api/status?tail=N&run=<id>
|
||||
```
|
||||
|
||||
When `run` is supplied, `active_progress` reflects that run instead of the
|
||||
active one. Default behavior remains unchanged.
|
||||
|
||||
Optional manager-written fields in `state.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"phase_index": 4,
|
||||
"phase_total": 10,
|
||||
"updated_at": "2026-06-02T14:05:03Z"
|
||||
}
|
||||
```
|
||||
|
||||
Suggested server tweak: in `readProgress`, pick the newest candidate file by
|
||||
mtime rather than the first file in a fixed list.
|
||||
|
||||
## 6. Mobile Behavior
|
||||
|
||||
At widths up to 980px, switch to one column.
|
||||
|
||||
Stacking order:
|
||||
|
||||
1. Sticky status bar, compressed to workspace basename.
|
||||
2. Now band, collapsed to rows: token, run plus stepper, next action.
|
||||
3. Blockers, if any.
|
||||
4. Active agent.
|
||||
5. Progress tail, collapsed by default to about `42vh`.
|
||||
6. Runs accordion, collapsed.
|
||||
|
||||
At widths up to 560px:
|
||||
|
||||
- Toolbar becomes a compact wrapping row: tail selector, refresh, pause.
|
||||
- Tap targets stay at least 44px.
|
||||
- The segmented phase bar degrades to text.
|
||||
- Mono ids and paths ellipsize.
|
||||
- Prose wraps.
|
||||
- No text overlap.
|
||||
|
||||
## 7. Implementation Notes
|
||||
|
||||
Likely write scope:
|
||||
|
||||
- `.agents/observability/index.html`
|
||||
- `.agents/observability/styles.css`
|
||||
- `.agents/observability/app.js`
|
||||
- `.agents/scripts/observe.mjs` for additive API fields
|
||||
|
||||
Implementation guidance:
|
||||
|
||||
- No frameworks and no build step.
|
||||
- Use a small inline SVG sprite or simple text/icon fallbacks.
|
||||
- Keep two timers:
|
||||
- `setInterval(poll, 5000)` rebuilds data.
|
||||
- `setInterval(tick, 1000)` updates timestamp/countdown text only.
|
||||
- Centralize constants:
|
||||
- `POLL_MS`
|
||||
- `DATA_STALE_MS`
|
||||
- `LEASE_WARN_MS`
|
||||
- `HEARTBEAT_WARN_MS`
|
||||
- `HEARTBEAT_DEAD_MS`
|
||||
- Add one pure `computeHealth(data)` helper.
|
||||
- Add `formatAge(ms)` and `formatCountdown(ms)` helpers.
|
||||
- Prefer `document.createElement` and `textContent` for rows instead of
|
||||
template-literal `innerHTML`.
|
||||
- Preserve progress scroll position and bottom-stick behavior across renders.
|
||||
- Remember selected run id.
|
||||
- Treat every optional API field as missing-safe.
|
||||
- Add `role="status"` to freshness and `aria-live="polite"` to health and
|
||||
blocker changes.
|
||||
- Gate any pulse animation behind `prefers-reduced-motion`.
|
||||
- Keep the CSS token approach and add a mono font token.
|
||||
|
||||
## 8. Concerns
|
||||
|
||||
1. Implementation is currently blocked by process. The workspace is not a git
|
||||
repository, and Opus implementation must happen on a named branch from a
|
||||
clean baseline.
|
||||
2. Liveness is only honest if the manager writes `lease_expires_at` and
|
||||
`heartbeat_at`. Missing values must render as unknown, not healthy.
|
||||
3. Heartbeat cadence is unspecified. Proposed defaults are warning at 60 seconds
|
||||
and dead at 180 seconds, but the manager should pick a real cadence.
|
||||
4. Phase stepper depends on canonical phase strings. Unknown phases must degrade
|
||||
gracefully.
|
||||
5. Server-computed millisecond deltas are preferred to avoid client clock drift.
|
||||
6. Polling cannot show sub-5-second events, which is acceptable for
|
||||
human-paced orchestration.
|
||||
7. Avoid adding timelines, charts, or history graphs in the first pass.
|
||||
8. Non-active run progress depends on the optional `?run=` parameter. Without
|
||||
it, run selection degrades to metadata-only.
|
||||
10
.agents/runs/2026-06-02-observability-redesign/progress.log
Normal file
10
.agents/runs/2026-06-02-observability-redesign/progress.log
Normal file
@@ -0,0 +1,10 @@
|
||||
2026-06-02 15:05:10 EEST - User asked Codex manager to ask Opus to redesign the observability dashboard.
|
||||
2026-06-02 15:05:10 EEST - Created observability redesign run directory and brief.
|
||||
2026-06-02 15:06:20 EEST - Opus design proposal requested through Claude CLI.
|
||||
2026-06-02 15:08:55 EEST - First Opus CLI invocation produced no output and was stopped; retrying with explicit read-only tools.
|
||||
2026-06-02 15:10:50 EEST - User clarified Opus timeout must never be less than 30 minutes.
|
||||
2026-06-02 15:10:50 EEST - Retry 1 exited with empty output; retrying with a 30-minute Opus timeout and lease.
|
||||
2026-06-02 15:17:03 EEST - Opus returned observability redesign proposal; saved to opus-proposal.md.
|
||||
2026-06-02 15:48:02 EEST - Manager attempted to initialize git baseline; sandbox blocked .git creation and escalation was rejected.
|
||||
2026-06-02 15:49:58 EEST - User clarified git setup is Opus responsibility.
|
||||
2026-06-02 15:49:58 EEST - Saved implementation instructions and requested Opus implementation on branch opus/observability-redesign.
|
||||
12
.agents/runs/2026-06-02-observability-redesign/state.json
Normal file
12
.agents/runs/2026-06-02-observability-redesign/state.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"run_id": "2026-06-02-observability-redesign",
|
||||
"current_phase": "opus-implementation-requested",
|
||||
"current_branch": "opus/observability-redesign",
|
||||
"input_artifact": ".agents/runs/2026-06-02-observability-redesign/implementation-instructions.md",
|
||||
"input_hash": "manual-observability-implementation-2026-06-02",
|
||||
"expected_output_artifact": ".agents/runs/2026-06-02-observability-redesign/implementation-log.md",
|
||||
"retry_count": 0,
|
||||
"next_action": "Wait for Opus to initialize the git baseline and implement the observability redesign on branch opus/observability-redesign.",
|
||||
"blocker": null
|
||||
}
|
||||
32
.agents/runs/2026-06-02-orchestration-planning/agents.json
Normal file
32
.agents/runs/2026-06-02-orchestration-planning/agents.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"agents": [
|
||||
{
|
||||
"role": "codex-manager",
|
||||
"status": "running",
|
||||
"started_at": "2026-06-02T11:28:30Z",
|
||||
"heartbeat_at": "2026-06-02T11:50:10Z",
|
||||
"lease_expires_at": "2026-06-02T12:05:10Z",
|
||||
"thread_id": "current-codex-thread",
|
||||
"last_error": null
|
||||
},
|
||||
{
|
||||
"role": "snarky",
|
||||
"status": "completed",
|
||||
"started_at": "2026-06-02T11:33:00Z",
|
||||
"heartbeat_at": "2026-06-02T11:57:20Z",
|
||||
"lease_expires_at": "2026-06-02T12:06:58Z",
|
||||
"thread_id": "019e8831-eab0-7f20-9536-fb10f0f906f0",
|
||||
"last_error": null
|
||||
},
|
||||
{
|
||||
"role": "opus",
|
||||
"status": "completed",
|
||||
"started_at": "2026-06-02T11:33:00Z",
|
||||
"heartbeat_at": "2026-06-02T12:02:05Z",
|
||||
"lease_expires_at": "2026-06-02T12:21:27Z",
|
||||
"tool_call_id": "claude-cli-session-54314",
|
||||
"last_error": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
# Discussion Round 1 - Opus
|
||||
|
||||
## Agreements
|
||||
|
||||
Opus accepts Snarky's eight decisions:
|
||||
|
||||
- Ship the NAS release console first.
|
||||
- Drop `pub`/alias/namespace re-export from the critical path.
|
||||
- Replace Milestone 1 with a walking skeleton.
|
||||
- Make Linux/NAS the first runtime target.
|
||||
- Allow FFI as a thin, named, replaceable boundary.
|
||||
- Keep orchestration sequential and artifact-driven.
|
||||
- Remove `Release.channel`, add release state, and use promotion history for
|
||||
rollback.
|
||||
- Use the v1 access model with app visibility, roles, scoped tokens, and signed
|
||||
install links.
|
||||
|
||||
Opus also accepts Snarky's challenges: temporary boundaries must be named, M1
|
||||
can be small but the product model cannot be vague, FFI must be Linux-first, and
|
||||
the mock redesign must preserve iOS install truth.
|
||||
|
||||
## Remaining Technical Objections
|
||||
|
||||
1. **The cross-repo sx governance stop is still unresolved.**
|
||||
|
||||
FFI reduces compiler-bug surface but does not remove it. `#foreign` and
|
||||
comptime are themselves active sx risk areas. If an M1 slice trips an sx bug,
|
||||
`sx/CLAUDE.md` requires filing an issue, marking blocked, ending the session,
|
||||
and not working around it. The distribution orchestration needs an explicit
|
||||
`sx-blocked` state and handoff.
|
||||
|
||||
2. **M1 std signatures must pin to landed sx error handling.**
|
||||
|
||||
Do not implement against planned error semantics. Pin to the current landed
|
||||
shape and record the checkpoint/commit.
|
||||
|
||||
3. **M1 APK inspection must not become an AXML decoder.**
|
||||
|
||||
For M1, artifact inspection means opening the zip central directory and
|
||||
confirming `AndroidManifest.xml` exists with size/offset. Decoding binary
|
||||
Android XML is M2.
|
||||
|
||||
4. **Enterprise install readiness depends on proxy HTTPS.**
|
||||
|
||||
`distd` cannot self-satisfy the Enterprise HTTPS requirement if TLS is behind
|
||||
a reverse proxy. `dist doctor` can validate config/reachability, not TLS
|
||||
termination by `distd`.
|
||||
|
||||
5. **Public visibility needs an iOS artifact-only carve-out.**
|
||||
|
||||
An iOS artifact-only IPA should never be anonymously downloadable, even on a
|
||||
public app.
|
||||
|
||||
## Refinements To Snarky
|
||||
|
||||
- M1 JSON output should be the same shape later used by HTTP and remote
|
||||
`dist ci publish`.
|
||||
- M1 artifact inspection is zip-entry presence/size only.
|
||||
- FFI boundaries should be named:
|
||||
- miniz/libzip or zlib for zip reads
|
||||
- libsqlite3 deferred to M2
|
||||
- HMAC for signed links in M2
|
||||
- SHA-256 remains a compiler-risk choice, not a portability mandate
|
||||
- `rejected` is terminal.
|
||||
- M1 releases can go `draft -> published` with `validation=not_run`.
|
||||
- Signed install links are HMAC over `(artifact_id, expiry)` with a server key.
|
||||
- Download auth should be a table across actor/auth mode, visibility, and
|
||||
platform.
|
||||
|
||||
## Minimal Plan Patch Order
|
||||
|
||||
1. `git init` and baseline commit.
|
||||
2. Patch Subplan 01:
|
||||
- remove dangling references
|
||||
- park `pub` as language-track work
|
||||
- pin error model to landed sx state
|
||||
- mark FFI-first/Linux boundaries
|
||||
3. Patch `PLAN.md`:
|
||||
- replace the first milestone with the walking skeleton
|
||||
- demote HTTP/SQLite/UI/Docker to later milestones
|
||||
4. Patch Subplan 02:
|
||||
- remove `Release.channel`
|
||||
- add `Release.state`
|
||||
- add validation outcome
|
||||
- add `ChannelPromotion`
|
||||
5. Patch Subplan 03:
|
||||
- local publish first
|
||||
- add `dist.json` schema and example
|
||||
- add idempotency key and rerun behavior
|
||||
6. Patch Subplan 05:
|
||||
- label validation checks as `required`, `warning`, `informational`, or
|
||||
`not_supported`
|
||||
- scope M1 APK to zip-entry presence
|
||||
7. Patch Subplan 04:
|
||||
- split Enterprise and MDM
|
||||
- add TLS-by-proxy caveat
|
||||
- define concrete routes and JSON shapes
|
||||
8. Add access-policy section:
|
||||
- roles
|
||||
- visibility
|
||||
- token scopes
|
||||
- signed-link HMAC
|
||||
- download authorization table
|
||||
- iOS artifact-only never public
|
||||
9. Patch orchestration:
|
||||
- add `sx-blocked` state
|
||||
- split Opus read-only consultation approval from write implementation
|
||||
approval
|
||||
|
||||
## What Opus Needs Before Any Code
|
||||
|
||||
1. Answer the sx governance question: on an M1 compiler bug, does the
|
||||
distribution run hard-stop and file in `sx/issues`, or is there a sanctioned
|
||||
narrow-workaround budget?
|
||||
2. Pin the current landed sx error-handling shape.
|
||||
3. Approve the `dist.json` schema and idempotency key.
|
||||
4. Approve the download authorization table.
|
||||
5. Confirm libsqlite3 is M2 and M1 is DB-free.
|
||||
6. For the mock branch, provide a Snarky `brief.md` with seed data and required
|
||||
UI states.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Discussion Round 1 - Snarky
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Accept: ship the NAS release console first.**
|
||||
|
||||
The goal is to ship a working distribution platform in `sx`, not to mature
|
||||
`sx` for its own sake. `sx` work is justified only when it directly unblocks
|
||||
`dist`.
|
||||
|
||||
2. **Accept with narrowing: drop `pub` from the critical path.**
|
||||
|
||||
`pub`, alias imports, and `pub print :: core.print` remain desirable
|
||||
language work, but they are no longer Phase 0 blockers for the product. They
|
||||
move to a parallel/future language track unless a concrete implementation
|
||||
blocker appears.
|
||||
|
||||
3. **Accept: replace Milestone 1 with a walking skeleton.**
|
||||
|
||||
M1 should be:
|
||||
|
||||
- `dist ci publish --local-store .dist`
|
||||
- file-backed or JSON-backed domain model
|
||||
- content-addressed artifact storage
|
||||
- manifest parsing
|
||||
- stable JSON output
|
||||
- at least one simple artifact inspection path
|
||||
|
||||
No HTTP, SQLite, admin UI, Docker, or full IPA/APK validation in M1.
|
||||
|
||||
4. **Accept: Linux/NAS-first runtime.**
|
||||
|
||||
`distd` targets Linux Docker on UGREEN NAS first. macOS support is useful for
|
||||
dev, but Linux correctness wins for server primitives.
|
||||
|
||||
5. **Accept: FFI is allowed as a thin boundary.**
|
||||
|
||||
For v1, FFI/system libs are acceptable for SQLite, hashing, archive
|
||||
inspection, TLS boundary, and platform APIs. The product still lives in `sx`;
|
||||
the wrappers must be explicit and replaceable.
|
||||
|
||||
6. **Accept: sequential orchestration stays.**
|
||||
|
||||
We are not doing parallel agents. Codex manages one visible workflow, Snarky
|
||||
and Opus communicate through run artifacts, and Opus implements only on
|
||||
branches.
|
||||
|
||||
7. **Decide release/channel model.**
|
||||
|
||||
`Release.channel` is removed. Releases have state. Channels point to
|
||||
releases. Rollback uses promotion history, not magic memory.
|
||||
|
||||
Minimum states:
|
||||
|
||||
- `draft`
|
||||
- `validating`
|
||||
- `published`
|
||||
- `rejected`
|
||||
- `withdrawn`
|
||||
|
||||
`superseded` is derived from channel history, not a release state.
|
||||
|
||||
8. **Decide v1 access model.**
|
||||
|
||||
V1 supports:
|
||||
|
||||
- app visibility: `private`, `link`, `public`
|
||||
- human roles: `admin`, `release_manager`, `viewer`
|
||||
- CI tokens scoped by app and allowed actions
|
||||
- downloads allowed by public visibility, signed install link,
|
||||
authenticated user, or scoped token
|
||||
|
||||
## Challenges To Opus
|
||||
|
||||
1. "Build against sx as-is" is acceptable only if it does not create a junk
|
||||
drawer of one-off shims. Opus needs to name every temporary boundary clearly.
|
||||
|
||||
2. "APK zip entry only" is fine for the walking skeleton, but the product plan
|
||||
must still model iOS modes, access policy, release states, and validation
|
||||
outcomes. M1 can be small; the product model cannot be vague.
|
||||
|
||||
3. FFI-first must be Linux-first. CommonCrypto-only, macOS-only sockets, or
|
||||
host-specific assumptions are not acceptable for `distd`.
|
||||
|
||||
4. The UI mock redesign can proceed independently from the sx backend, but Opus
|
||||
must preserve product truth: especially iOS TestFlight vs Enterprise vs MDM
|
||||
vs artifact-only.
|
||||
|
||||
## Required Plan Patches
|
||||
|
||||
1. Fix Subplan 01 dangling references to removed `PLAN.md` sections.
|
||||
2. Re-sequence language work:
|
||||
- move `pub` and namespace re-export out of the blocker path
|
||||
- keep actual required std primitives tied to product slices
|
||||
3. Rewrite Milestone 1 as the walking skeleton.
|
||||
4. Patch domain model:
|
||||
- remove `Release.channel`
|
||||
- add `Release.state`
|
||||
- add `ChannelPromotion` / channel history
|
||||
- define rollback semantics
|
||||
5. Patch access policy:
|
||||
- roles
|
||||
- app visibility
|
||||
- token scopes
|
||||
- signed install links
|
||||
- download authorization rules
|
||||
6. Patch CI contract:
|
||||
- concrete `dist.json` schema
|
||||
- example manifest
|
||||
- idempotency key
|
||||
- rerun behavior
|
||||
- duplicate version/build behavior
|
||||
7. Patch HTTP API with concrete routes and JSON response shapes before HTTP
|
||||
implementation.
|
||||
8. Patch validation policy so every check is marked:
|
||||
- `required`
|
||||
- `warning`
|
||||
- `informational`
|
||||
- `not_supported`
|
||||
9. Split iOS Enterprise and MDM. They are related, not identical.
|
||||
10. Patch orchestration docs to make read-only Opus consultation and
|
||||
write-capable Opus implementation separate approval modes.
|
||||
|
||||
## Implementation Gate
|
||||
|
||||
No implementation until:
|
||||
|
||||
- distribution repo has a clean git baseline
|
||||
- plan patches above are applied
|
||||
- active run has `brief.md`, `state.json`, and `agents.json`
|
||||
- Opus has produced a design/technical proposal for the relevant slice
|
||||
- Snarky has accepted product coverage
|
||||
- allowed write paths are explicit
|
||||
- branch is clean before Opus writes
|
||||
|
||||
For the website mock specifically: once those gates are satisfied, Opus may
|
||||
redesign only the allowed UI files, and Snarky reviews product coverage
|
||||
afterward.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Discussion Round 2 - Opus
|
||||
|
||||
## Final Opus Position
|
||||
|
||||
Opus accepts all six of Snarky's final decisions and accepted refinements:
|
||||
|
||||
- `sx-blocked` is a hard governance state.
|
||||
- Error model is pinned to landed sx with real examples.
|
||||
- M1 APK inspection is ZIP container plus `AndroidManifest.xml` presence only.
|
||||
- Enterprise, MDM, proxy HTTPS readiness, and artifact-only are distinct states.
|
||||
- Public visibility does not imply public IPA download.
|
||||
- M1 is DB-free and SQLite moves to M2.
|
||||
|
||||
No open contest remains on product scope or sequencing.
|
||||
|
||||
## Any Remaining Blockers
|
||||
|
||||
1. **Cross-repo sx-authoring ownership is undefined.**
|
||||
|
||||
`sx-blocked` handles bugs, but not normal sx work. The DB-free skeleton still
|
||||
requires new sx modules such as JSON, CLI, hash, and archive wrappers. Decide
|
||||
whether distribution M1 consumes sx strictly as-is, or whether there is a
|
||||
sanctioned sx-repo authoring lane under `sx/CLAUDE.md`.
|
||||
|
||||
2. **Repo/module-resolution boundary is undefined.**
|
||||
|
||||
Decide where `dist`/`distd` product code lives and how it imports sx stdlib
|
||||
across the two repos.
|
||||
|
||||
3. **Subagent machinery versus single-thread rule is still unresolved.**
|
||||
|
||||
Either explicitly waive the no-fan-out rule for this project or drop the
|
||||
machinery and keep only run-dir artifacts.
|
||||
|
||||
## Exact First Patch Set To Apply
|
||||
|
||||
1. `git init` and baseline commit in `/Users/agra/projects/distribution`.
|
||||
2. Patch `PLAN.md` to:
|
||||
- replace First Milestone with DB-free walking skeleton
|
||||
- demote HTTP, SQLite, admin UI, Docker to M2+
|
||||
- move `pub`/alias/re-export off the critical path
|
||||
3. Patch orchestration docs:
|
||||
- add `sx-blocked`
|
||||
- add cross-repo handoff
|
||||
- split Opus read-only approval from write approval
|
||||
- record subagent/single-thread decision
|
||||
4. Patch Subplan 01:
|
||||
- remove dead `PLAN.md` references
|
||||
- park `pub`
|
||||
- pin landed sx commit and error examples
|
||||
- mark FFI-first/Linux-first boundaries
|
||||
5. Patch Subplan 02:
|
||||
- remove `Release.channel`
|
||||
- add `Release.state`
|
||||
- add validation outcome
|
||||
- add `ChannelPromotion`
|
||||
- mark SQLite as M2
|
||||
6. Patch Subplan 03:
|
||||
- local publish becomes M1
|
||||
- add concrete `dist.json` schema and example
|
||||
- add idempotency and rerun semantics
|
||||
- clarify `dist doctor` TLS/proxy checks
|
||||
7. Patch Subplan 05:
|
||||
- classify every validation check
|
||||
- scope M1 APK to zip-entry metadata
|
||||
- move full IPA/APK checks post-M1
|
||||
8. Patch Subplan 04:
|
||||
- split Enterprise, MDM, artifact-only
|
||||
- add TLS caveat
|
||||
- add concrete routes and JSON shapes
|
||||
- mark HTTP as M2+
|
||||
9. Add access policy section:
|
||||
- roles
|
||||
- visibility
|
||||
- token scopes
|
||||
- signed-link HMAC
|
||||
- download authorization table
|
||||
- iOS artifact-only never public
|
||||
10. Author the real implementation run directory for the chosen first slice.
|
||||
|
||||
## Whether Implementation Can Start After Those Patches
|
||||
|
||||
Product/sx track:
|
||||
|
||||
- No, not after plan patches alone.
|
||||
- Still needs sx authoring lane, module resolution boundary, pinned sx commit
|
||||
and error examples, accepted Opus proposal, clean branch, and write-paths.
|
||||
|
||||
Mock-redesign track:
|
||||
|
||||
- Yes, after git baseline plus a Snarky `brief.md`.
|
||||
- It has no sx/backend dependency and can run independently under the UI
|
||||
allowed paths.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Discussion Round 2 - Snarky
|
||||
|
||||
## Final Decisions
|
||||
|
||||
1. **`sx-blocked` is a hard governance state.**
|
||||
|
||||
If an M1 slice hits an `sx` compiler/runtime/FFI bug, that slice stops. File
|
||||
or link the `sx` issue, record it in the distribution run, mark
|
||||
`sx-blocked`, and do not invent local workarounds unless Snarky explicitly
|
||||
approves a narrow boundary change.
|
||||
|
||||
2. **Error model is pinned to landed `sx`, not imagined `sx`.**
|
||||
|
||||
No `!T` folklore, no planned syntax, no aspirational std signatures. Before
|
||||
std work starts, pin the exact `sx` commit and include tiny real examples of
|
||||
current error handling.
|
||||
|
||||
3. **M1 APK inspection is deliberately tiny.**
|
||||
|
||||
M1 only proves ZIP container access and confirms `AndroidManifest.xml` exists
|
||||
with basic entry metadata. No AXML decoder, no package/version parsing, no
|
||||
signature validation.
|
||||
|
||||
4. **Enterprise, MDM, and proxy readiness must be separate.**
|
||||
|
||||
Enterprise install, MDM install, and artifact-only IPA access are distinct
|
||||
product states. `distd` behind a reverse proxy may validate configured HTTPS
|
||||
reachability, but it must not claim to terminate or own TLS.
|
||||
|
||||
5. **Public visibility has iOS carve-outs.**
|
||||
|
||||
Public app visibility does not mean public IPA download. iOS artifact-only
|
||||
IPAs require authenticated access, scoped token access, or signed install
|
||||
link access.
|
||||
|
||||
6. **M1 is DB-free.**
|
||||
|
||||
M1 uses file-backed or JSON-backed state only. SQLite is M2.
|
||||
|
||||
## Accepted Opus Refinements
|
||||
|
||||
- M1 JSON output should become the shape later reused by HTTP and remote CI.
|
||||
- `rejected` is terminal.
|
||||
- M1 release path may be `draft -> published` with `validation=not_run`.
|
||||
- Signed install links are HMAC over artifact/link identity plus expiry, with a
|
||||
server secret.
|
||||
- Download authorization must be a table, not prose soup.
|
||||
- FFI is acceptable when named, Linux-first, thin, and replaceable.
|
||||
|
||||
## Rejected Or Deferred Items
|
||||
|
||||
- `pub`, alias imports, and namespace re-export are deferred from the product
|
||||
critical path.
|
||||
- Pure-`sx` crypto/archive/SQLite is deferred.
|
||||
- HTTP, SQLite, admin UI, Docker/NAS deployment, full APK validation, and IPA
|
||||
validation are not M1.
|
||||
- Any layout redesign before product coverage is pinned is rejected.
|
||||
|
||||
## Plan Patch Backlog
|
||||
|
||||
1. Add `sx-blocked` state and cross-repo handoff rules.
|
||||
2. Patch Subplan 01 to remove dangling `PLAN.md` references and pin landed
|
||||
error handling.
|
||||
3. Rewrite M1 as the DB-free walking skeleton.
|
||||
4. Patch domain model: release states, validation outcome, channel promotion
|
||||
history.
|
||||
5. Patch CI manifest schema, example, idempotency key, rerun behavior.
|
||||
6. Patch access policy with roles, visibility, token scopes, signed links, and
|
||||
download table.
|
||||
7. Patch validation policy with `required`, `warning`, `informational`,
|
||||
`not_supported`.
|
||||
8. Split Enterprise, MDM, proxy HTTPS readiness, and iOS artifact-only behavior.
|
||||
|
||||
## Implementation Still Blocked Until
|
||||
|
||||
- Distribution repo has a clean git baseline.
|
||||
- Plan patches above are applied.
|
||||
- Active run has `brief.md`, `state.json`, and `agents.json`.
|
||||
- Current `sx` commit and error model are pinned.
|
||||
- Opus produces a proposal for the exact slice.
|
||||
- Snarky accepts product coverage.
|
||||
- Allowed write paths and branch are explicit and clean.
|
||||
267
.agents/runs/2026-06-02-orchestration-planning/opus-review.md
Normal file
267
.agents/runs/2026-06-02-orchestration-planning/opus-review.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# Opus Consultation - Distribution Platform Plan Review
|
||||
|
||||
## Context
|
||||
|
||||
Read-only review of the distribution platform plan package (`PLAN.md`,
|
||||
`.agents/ORCHESTRATION.md`, `.agents/subplans/*.md`, checkpoints) against the
|
||||
real state of the `sx` repository, from four lenses: design, technical
|
||||
sequencing, implementation feasibility, and platform risk.
|
||||
|
||||
Verdict in one line: **the plan is well-written and the product model is mostly
|
||||
right, but it is sequenced as if `sx` were a mature systems language. It is
|
||||
not.** `sx` is self-described "highly experimental" with ~37 open compiler
|
||||
issues and five active fix-streams, and its own governance forbids working
|
||||
around compiler bugs. The plan's Phase 0-1 (build a full HTTP/TLS/SQLite/
|
||||
crypto/JSON/archive stdlib in `sx` first) is the single largest risk and is
|
||||
underestimated by roughly an order of magnitude.
|
||||
|
||||
This file is a consultation report, not an implementation plan. Findings are
|
||||
severity-ordered with file references. The last sections are where Opus would
|
||||
overrule/refine/ask, and the layout read on the rejected mock (Opus's authority
|
||||
domain).
|
||||
|
||||
---
|
||||
|
||||
## Findings (severity-ordered)
|
||||
|
||||
### P0 - The two repos have incompatible governance; nobody owns the sx changes
|
||||
|
||||
- Distribution `ORCHESTRATION.md:24` makes Opus the *only* code writer, on
|
||||
named git branches, under lease timeouts. But the work in subplan 01 is
|
||||
**`sx` compiler + stdlib work**, and `sx/CLAUDE.md:5-65` ("IMPASSIBLE RULES")
|
||||
mandates: the moment you hit a compiler bug, **STOP, file an issue, end the
|
||||
session, wait for a fix in another session - do not work around it.**
|
||||
- Building HTTP/TLS/SQLite/zip/crypto on an experimental compiler with 37 open
|
||||
issues *will* hit unimplemented/buggy paths repeatedly (FFI itself is an
|
||||
active, buggy stream: see `sx/issues/0043,0052,0057`). Under sx's own rules,
|
||||
the distribution "Opus implementation" phase will keep hard-stopping.
|
||||
- The two checkpoint systems (distribution `.agents/runs/*` vs sx
|
||||
`current/CHECKPOINT-*.md`) have **no defined handoff**. There is no answer to
|
||||
"who advances sx, in which repo's process, under whose lease."
|
||||
- **This is the gating decision for the whole program** and it is not addressed
|
||||
anywhere in the plan.
|
||||
|
||||
### P0 - Distribution workspace is not a git repo (already flagged, still true)
|
||||
|
||||
- `subplan 08:194-198`, `CHECKPOINT.md:48-53`, `checkpoint.json:8-11`. The
|
||||
entire orchestration is branch-based; it cannot start. Snarky P0 is correct.
|
||||
Trivial to fix (`git init` + baseline commit) but it blocks everything, so
|
||||
it should be the literal first action.
|
||||
|
||||
### P1 - Phase 0 "language prerequisites" are largely gold-plating, not prerequisites
|
||||
|
||||
- `PLAN.md:159-190`, `subplan 01:23-52` require `pub` exports, alias imports,
|
||||
and `pub print :: core.print` namespace re-export **before product code**.
|
||||
- `pub`/module-member visibility **does not exist** in `sx` (specs.md:550 only
|
||||
has import-scoped `impl` visibility) and is **on no active sx workstream**.
|
||||
So the plan's first "blocking" slice is net-new parser/AST/resolver/
|
||||
module-graph compiler work that nobody is scheduled to do.
|
||||
- But `sx` already has namespaced imports (`math :: #import "..."`) and
|
||||
import-scoped impl visibility. **The product can be built without `pub`.**
|
||||
Treating it as a prerequisite front-loads hard compiler work and delays any
|
||||
product signal. (See "Opus would overrule," below.)
|
||||
|
||||
### P1 - The server substrate that Phase 1 assumes does not exist, and the one primitive that does is wrong-platform
|
||||
|
||||
- Confirmed absent in `library/modules/`: HTTP, TLS, SQLite, JSON, SHA-256/
|
||||
crypto, base64/hex, RFC3339/time, CLI parser, config, zip/tar/gzip. Subplan
|
||||
01 Slices 6-8 (`subplan 01:104-145`) describe all of these as if they are
|
||||
small deliverables. Each is a real subsystem.
|
||||
- `library/modules/socket.sx:1-2,23-29` is the only networking code: raw POSIX,
|
||||
**macOS-only** (`sin_len`, macOS `SO_REUSEADDR=0x4`), blocking `read`/`write`,
|
||||
no event loop. The deployment target is **Linux Docker on UGREEN NAS**
|
||||
(`PLAN.md:147-158`), where `sockaddr_in` has no `sin_len` and constants
|
||||
differ. The HTTP foundation must be (re)written Linux-correct before `distd`
|
||||
can run where it ships.
|
||||
- Implication: every Phase-1 "slice" is actually a from-scratch systems library
|
||||
on an unstable compiler. This is the bulk of the project, mislabeled as
|
||||
foundation.
|
||||
|
||||
### P1 - `IPA`/`APK` validation needs a ZIP reader that does not exist
|
||||
|
||||
- `subplan 05:19-48` requires reading the zip structure of IPA (Info.plist) and
|
||||
APK (AndroidManifest) to validate bundle id / version / signature. IPA and
|
||||
APK are ZIP containers. There is no `std.archive`/zip reader (`subplan
|
||||
01:137`), and `AndroidManifest.xml` inside an APK is *binary* XML (AXML), not
|
||||
text - a non-trivial parser on its own. This is materially harder than the
|
||||
one-line "manifest package id" bullet implies.
|
||||
|
||||
### P1 - Release/Channel model is internally contradictory and cannot support rollback as specified
|
||||
|
||||
- Snarky already flagged the redundancy (`Release.channel` at `subplan
|
||||
02:43-54` vs `Channel.current_release_id` at `subplan 02:56-61`). Concur.
|
||||
- Beyond that: `Channel` stores only `current_release_id` with **no promotion
|
||||
history**. `dist release rollback` "moves a channel pointer to the previous
|
||||
valid release" (`subplan 03:45-47`) - but "previous" is unknowable from a
|
||||
single pointer. Rollback requires a promotion/channel-history table (or
|
||||
deriving it from audit events). The data model as drawn cannot do what the
|
||||
CLI promises.
|
||||
- No release state machine is defined (`draft/validating/published/rejected/
|
||||
superseded`). Snarky P1 #3 is correct and should block domain implementation.
|
||||
|
||||
### P1 - The multi-agent orchestration conflicts with the operator's own standing rules
|
||||
|
||||
- `ORCHESTRATION.md` + `subplan 08` are built on multiple named subagents
|
||||
(Codex/Snarky/Opus) invoked through an `opus-runner` MCP plugin
|
||||
(`ORCHESTRATION.md:136-146`), which is **unbootstrapped** ("may need Codex
|
||||
reload before MCP tools are available," `CHECKPOINT.md:52-53`).
|
||||
- The operator's global rules forbid subagent fan-out and require work in a
|
||||
single visible thread. The orchestration design is therefore in direct
|
||||
tension with how the operator actually wants work done. Either the global
|
||||
rule is waived for this project, or the multi-agent machinery should be
|
||||
dropped in favor of sequential single-thread phases with the same run-dir
|
||||
artifacts. This needs an explicit decision before more tooling is built.
|
||||
|
||||
### P2 - Milestone 1 is the whole project, not a milestone
|
||||
|
||||
- `PLAN.md:259-272` bundles: sx language work + full stdlib + server + SQLite +
|
||||
APK *and* IPA validators + channels + install pages + admin UI + Docker/NAS.
|
||||
On an experimental language. Snarky P2 #8 is correct; I'd escalate it to P1
|
||||
for scheduling purposes. Replace with a "walking skeleton" (below).
|
||||
|
||||
### P2 - Validation policy mixes requirements with wishful tooling
|
||||
|
||||
- `subplan 05` is full of "if tool support exists," "when provided," "malware
|
||||
scan placeholder" (`05:55-89`). Concur with Snarky P2 #7: for v1 mark each
|
||||
check `required | warning | informational | not-supported` and delete the
|
||||
rest. Notarization/authenticode/malware-scan are out of reach for v1 and
|
||||
should be `not-supported`, not aspirational statuses.
|
||||
|
||||
### P2 - iOS Enterprise vs MDM are conflated
|
||||
|
||||
- `subplan 04:50-52` / `05:24-28` treat "Enterprise/MDM" as one mode serving "a
|
||||
signed HTTPS manifest plist for enrolled devices." Those are two different
|
||||
mechanisms: `itms-services://` in-house (Apple Enterprise Program, cert-trust
|
||||
on device) vs MDM-managed `InstallApplication`. The plist/host requirements
|
||||
differ. The *product intent* (don't imply a normal iPhone can sideload) is
|
||||
correct and is the strongest part of the plan - just split the two modes.
|
||||
- Also: `distd` cannot self-satisfy the HTTPS requirement (no TLS); it relies on
|
||||
the reverse proxy. `dist doctor`'s "HTTPS base URL" check (`subplan 03:14-16`)
|
||||
can therefore only validate config/reachability, not terminate TLS itself.
|
||||
State that explicitly so Enterprise mode isn't marked "ready" when it isn't.
|
||||
|
||||
### P2 - Subplan 01 still points implementers at deleted PLAN sections
|
||||
|
||||
- `subplan 01:18-21` tells implementers to read `Standard Library API Surface`
|
||||
and `Detailed Std Struct And Method Sketches`, removed from `PLAN.md`. Snarky
|
||||
P1 #4 confirmed; fix or the first sx session starts by chasing ghosts.
|
||||
|
||||
### P3 - CI manifest schema / idempotency undefined
|
||||
|
||||
- `subplan 03:48-63` lists fields but no JSON schema, example, idempotency key,
|
||||
or rerun semantics. "CI is the primary writer" and "releases are immutable"
|
||||
(`PLAN.md:38-42`) make rerun-of-same-build behavior a v1 correctness concern,
|
||||
not a later detail. Define an idempotency key (app+version+build digest) now.
|
||||
|
||||
---
|
||||
|
||||
## Where Opus would overrule, refine, or ask Snarky
|
||||
|
||||
**Overrule - drop Phase 0 language work from the critical path.**
|
||||
`pub`/alias-imports/namespace-re-export (`PLAN.md:194-196`, `subplan 01:23-52`)
|
||||
are ergonomics, not blockers. sx already has namespaced imports and impl
|
||||
visibility. Building net-new compiler features before the product compiles once
|
||||
is exactly backwards on an unstable compiler. Opus would build the product
|
||||
against the language as-is and only add `pub` if a concrete name-leak actually
|
||||
bites. This reorders the whole plan and Opus would hold this line against a
|
||||
"do it properly first" push.
|
||||
|
||||
**Overrule - Milestone 1 is replaced by a walking skeleton.** Opus's M1:
|
||||
`dist ci publish --local-store` writes a content-addressed artifact to disk +
|
||||
an in-memory/JSON-file domain model + one platform's metadata read (APK zip
|
||||
entry only) + a JSON output contract. No HTTP, no SQLite, no TLS, no UI. This
|
||||
exercises the real sx pain (fs, zip, hashing, JSON, CLI) on the smallest
|
||||
surface and produces a runnable artifact in days, not a quarter. SQLite/HTTP/
|
||||
admin-UI become M2+, each gated on the prior actually running on Linux.
|
||||
|
||||
**Refine - make the SHA-256 and zip decisions explicitly FFI-first.** Pure-sx
|
||||
streaming SHA-256 and inflate are real projects and will trip compiler bugs.
|
||||
Bind libsqlite3, a crypto lib (CommonCrypto on mac / OpenSSL on Linux), and
|
||||
miniz/libzip via `#foreign` first; only reimplement in sx if FFI proves worse.
|
||||
This trades "pure sx" purity for actually shipping, and keeps bug-surface on the
|
||||
linker, not the comptime evaluator.
|
||||
|
||||
**Refine - model promotion history, not just a channel pointer.** Add a
|
||||
`channel_release` history (or derive rollback from audit). Without it, rollback
|
||||
is undefined.
|
||||
|
||||
**Ask Snarky to decide before any code:**
|
||||
1. Is the goal to *ship a NAS release console* or to *mature sx*? They imply
|
||||
opposite sequencing. If shipping is the goal, the sx-first framing is wrong.
|
||||
2. Is the multi-agent/opus-runner machinery actually required, given the
|
||||
operator's single-thread rule? If not, delete it and keep only run-dir
|
||||
artifacts.
|
||||
3. Linux-first or macOS-first for `distd` runtime? The only socket code is
|
||||
macOS-only; the deploy target is Linux. Pick one and make the socket layer
|
||||
match it before "server skeleton."
|
||||
4. For v1, is FFI-to-system-libs acceptable, or is "pure sx stdlib" a hard
|
||||
product constraint? This single answer changes the size of the project by
|
||||
months.
|
||||
|
||||
---
|
||||
|
||||
## Layout read on the rejected mock (Opus authority domain)
|
||||
|
||||
The mock (`index.html`, `styles.css`) is **not** weak at the system level: warm
|
||||
off-white token palette, semantic green/amber/red with soft fills, Inter, a real
|
||||
shadow token, a clean CSS-grid shell (224px sidebar + 64px topbar,
|
||||
`styles.css:49-69`). The information architecture (apps list + detail, release,
|
||||
install, tokens, audit, settings) matches the brief. So the rejection is almost
|
||||
certainly about *finish and tone*, not structure:
|
||||
|
||||
1. **Placeholder iconography reads as a wireframe.** Single letters as icons
|
||||
everywhere: brand "d," nav glyphs `A/R/I/T/L/S` (`index.html:41-64`), top
|
||||
actions literally rendering "R" for sync and "!" for notifications
|
||||
(`index.html:26-31`), 2-letter app monograms, and a QR faked as empty
|
||||
`<span>`s (`index.html:439-446`). Nothing signals "unfinished" harder than
|
||||
letter-in-a-box icons. A minimal real stroke-icon set would lift perceived
|
||||
quality more than any layout change.
|
||||
2. **Tone fights the brief.** The brief wants "operational SaaS density, quiet,
|
||||
no oversized marketing hero" (`subplan 06:64-72`). The mock leans airy/
|
||||
card-y with big metric numbers and a phone-mockup hero on the Install tab
|
||||
(`index.html:362-382`) - that's the marketing-flavored hero the brief warns
|
||||
against. A release console likely wants a denser, cooler, more devtools/
|
||||
terminal register than warm paper.
|
||||
3. **Six fake views via class-toggle** inflate surface area that all has to look
|
||||
finished at once; it spreads the polish too thin.
|
||||
|
||||
Opus's design direction for `opus/redesign-distribution-mock`: keep the 3-zone
|
||||
shell and IA; (a) replace every letter-glyph with a real minimal icon set; (b)
|
||||
shift to a cooler, denser operator palette and a tighter type/spacing scale; (c)
|
||||
demote the Install phone-hero to an inline preview, not a centerpiece; (d) make
|
||||
the first viewport land on real working content (apps + latest release), per
|
||||
`subplan 06:94-99`; (e) keep the iOS TestFlight/Enterprise/Artifact distinction
|
||||
loud - it's the one thing the current mock gets exactly right
|
||||
(`index.html:384-437`).
|
||||
|
||||
---
|
||||
|
||||
## Recommended re-sequencing (concise)
|
||||
|
||||
1. `git init` the distribution workspace; baseline commit. (Unblocks
|
||||
everything.)
|
||||
2. Snarky decides the four questions above (ship-vs-mature, agents-yes/no,
|
||||
Linux-vs-mac, FFI-vs-pure-sx). These are gating.
|
||||
3. Drop Phase 0 language work from the critical path.
|
||||
4. Build the walking-skeleton M1 (`dist ci publish --local-store`, content-
|
||||
addressed fs store, JSON contract, APK zip-entry read) against sx as-is,
|
||||
FFI-first for hash/zip.
|
||||
5. Only then add SQLite (FFI), then the HTTP server (Linux-correct sockets),
|
||||
then install pages, then admin UI - each gated on the prior running on the
|
||||
actual Linux/NAS target.
|
||||
6. In parallel and independently: the mock redesign (no backend dependency),
|
||||
under Opus design direction above.
|
||||
|
||||
## Verification (how to sanity-check the re-scope, read-only)
|
||||
|
||||
- Confirm `pub` truly absent: `sx ir` / compile a probe with `pub x :: 1` and
|
||||
observe parser rejection (don't land it - just confirm scope of work).
|
||||
- Confirm Linux socket gap: attempt `sx build --target linux` of a trivial
|
||||
`socket.sx` consumer and observe `sin_len` mismatch.
|
||||
- Confirm error model present (it is): `sx/CHECKPOINT-ERR.md:15`, plus the
|
||||
`examples/10xx-errors-*` suite passing (341 tests, `CHECKPOINT-ERR.md:42-43`).
|
||||
- For the redesign: build the static mock and screenshot the first viewport on
|
||||
desktop + mobile widths to validate the "working content first, no hero"
|
||||
acceptance (`subplan 06:94-99`).
|
||||
</content>
|
||||
</invoke>
|
||||
20
.agents/runs/2026-06-02-orchestration-planning/progress.log
Normal file
20
.agents/runs/2026-06-02-orchestration-planning/progress.log
Normal file
@@ -0,0 +1,20 @@
|
||||
2026-06-02 14:28:30 EEST - Created active planning run after noticing the observability dashboard only showed durable run records.
|
||||
2026-06-02 14:28:30 EEST - Registered Codex manager as the active planning agent.
|
||||
2026-06-02 14:28:30 EEST - Next action: explain that planning sessions must create .agents/runs records to be visible.
|
||||
2026-06-02 14:30:39 EEST - Reviewing PLAN.md and .agents/subplans for gaps.
|
||||
2026-06-02 14:31:38 EEST - User asked to introduce Snarky and Opus to review the plan gaps.
|
||||
2026-06-02 14:33:00 EEST - Snarky and Opus marked running for plan-gap consultation.
|
||||
2026-06-02 14:33:00 EEST - Snarky spawned as sub-agent Locke / 019e881c-6aa7-7901-8069-22411d955e06.
|
||||
2026-06-02 14:34:22 EEST - Opus CLI consultation blocked pending explicit approval for external plan-file disclosure.
|
||||
2026-06-02 14:34:22 EEST - Snarky completed plan review; saved to snarky-review.md.
|
||||
2026-06-02 14:36:22 EEST - User asked how to give Opus access; waiting for explicit approval scope.
|
||||
2026-06-02 14:37:39 EEST - User approved Opus access to the entire distribution and sx repos for consultation.
|
||||
2026-06-02 14:39:33 EEST - Opus Claude CLI review running with read-only tools and access to distribution plus sx repos.
|
||||
2026-06-02 14:45:10 EEST - Opus completed review; saved to opus-review.md.
|
||||
2026-06-02 14:50:05 EEST - User requested a few more Snarky/Opus discussion rounds before implementation.
|
||||
2026-06-02 14:50:05 EEST - Snarky round 1 spawned as sub-agent Linnaeus / 019e882b-f0a8-7ed1-ab9d-fa32caf8d5ad.
|
||||
2026-06-02 14:50:05 EEST - Snarky round 1 completed and saved to discussion-round-1-snarky.md.
|
||||
2026-06-02 14:56:10 EEST - Opus round 1 completed and saved to discussion-round-1-opus.md.
|
||||
2026-06-02 14:56:58 EEST - Snarky round 2 spawned as sub-agent Fermat / 019e8831-eab0-7f20-9536-fb10f0f906f0.
|
||||
2026-06-02 14:57:20 EEST - Snarky round 2 completed and saved to discussion-round-2-snarky.md.
|
||||
2026-06-02 15:02:05 EEST - Opus round 2 completed and saved to discussion-round-2-opus.md.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Snarky Review
|
||||
|
||||
Findings first, because vibes are not a roadmap.
|
||||
|
||||
1. **P0: Opus cannot implement yet because the workspace has no git baseline.**
|
||||
|
||||
Branch-based implementation is mandatory, but the plan admits this workspace
|
||||
is not a git repo. Snarky will not approve an Opus implementation phase until
|
||||
git is initialized or attached and the current baseline is committed.
|
||||
|
||||
2. **P1: Access policy is hand-waved, and this product lives or dies there.**
|
||||
|
||||
The plan names actors and says access policy decides install/download, but
|
||||
does not define auth modes, human sessions, public links, private apps, app
|
||||
ownership, RBAC, or scoped install access. "Authenticated and public modes"
|
||||
is not a model.
|
||||
|
||||
3. **P1: Release lifecycle is internally muddy.**
|
||||
|
||||
Releases are supposed to be immutable and channels mutable. But `Release`
|
||||
still has `channel`, while `Channel` separately points to
|
||||
`current_release_id`. Also the CLI creates a draft release, validates,
|
||||
publishes, and maybe promotes, but no state machine is defined. Define
|
||||
`draft`, `validating`, `published`, `rejected`, `superseded`, etc.
|
||||
|
||||
4. **P1: The std subplan references sections that no longer exist.**
|
||||
|
||||
Subplan 01 tells implementers to read `Standard Library API Surface` and
|
||||
`Detailed Std Struct And Method Sketches`. Those were removed from
|
||||
`PLAN.md`.
|
||||
|
||||
5. **P1: CI manifest and idempotency are underspecified.**
|
||||
|
||||
The CLI plan gives required fields, but not a schema, examples, retry
|
||||
semantics, duplicate release behavior, idempotency keys, or what happens
|
||||
when CI reruns the same build.
|
||||
|
||||
6. **P1: HTTP API is too abstract to build against.**
|
||||
|
||||
The API plan lists slices but no concrete routes, request bodies, response
|
||||
schemas, pagination/filtering, auth errors, upload protocol, or download
|
||||
authorization rules.
|
||||
|
||||
7. **P2: Validation policy mixes product requirements with wishful tooling.**
|
||||
|
||||
"If tool support exists," "provided or checked," and "malware scan
|
||||
placeholder" are not acceptance criteria. For v1, mark each check as
|
||||
`required`, `warning`, `informational`, or `not supported`.
|
||||
|
||||
8. **P2: Milestone 1 is too large to be a milestone.**
|
||||
|
||||
It includes sx primitives, local server, APK and IPA publishing, SQLite,
|
||||
channels, install pages, admin UI, and NAS Docker deployment.
|
||||
|
||||
## Required Before Opus Implements
|
||||
|
||||
- Create a clean git baseline.
|
||||
- Write a Snarky `brief.md` for the redesign run with hard acceptance criteria.
|
||||
- Resolve the minimum product model for the mock: release states, channel
|
||||
behavior, access modes, token scopes, and iOS install modes.
|
||||
- Provide seed data and required UI states Opus must represent.
|
||||
- Let Opus produce a design proposal first. No code until Snarky accepts the
|
||||
product coverage.
|
||||
12
.agents/runs/2026-06-02-orchestration-planning/state.json
Normal file
12
.agents/runs/2026-06-02-orchestration-planning/state.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"run_id": "2026-06-02-orchestration-planning",
|
||||
"current_phase": "snarky-opus-discussion",
|
||||
"current_branch": null,
|
||||
"input_artifact": "user request: why is the current planning session not visible in observability",
|
||||
"input_hash": "manual-current-turn",
|
||||
"expected_output_artifact": ".agents/runs/2026-06-02-orchestration-planning/progress.log",
|
||||
"retry_count": 0,
|
||||
"next_action": "Apply consensus plan patches, then create a git baseline before implementation.",
|
||||
"blocker": null
|
||||
}
|
||||
219
.agents/scripts/observe.mjs
Normal file
219
.agents/scripts/observe.mjs
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env node
|
||||
import { createServer } from "node:http";
|
||||
import {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const workspace = path.resolve(scriptDir, "../..");
|
||||
const agentsDir = path.join(workspace, ".agents");
|
||||
const runsDir = path.join(agentsDir, "runs");
|
||||
const checkpointPath = path.join(agentsDir, "checkpoint.json");
|
||||
const staticDir = path.join(agentsDir, "observability");
|
||||
const port = parsePort(process.argv) || 4317;
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
||||
|
||||
if (url.pathname === "/api/status") {
|
||||
const tail = parseTail(url.searchParams.get("tail"));
|
||||
sendJson(res, buildStatus(tail));
|
||||
return;
|
||||
}
|
||||
|
||||
serveStatic(res, url.pathname);
|
||||
});
|
||||
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
console.log(`Agent observability dashboard: http://127.0.0.1:${port}`);
|
||||
});
|
||||
|
||||
server.on("error", (error) => {
|
||||
console.error(`Could not start observability dashboard on 127.0.0.1:${port}`);
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function parsePort(argv) {
|
||||
const index = argv.findIndex((arg) => arg === "--port" || arg === "-p");
|
||||
if (index === -1) return null;
|
||||
const value = Number(argv[index + 1]);
|
||||
if (!Number.isInteger(value) || value < 1 || value > 65535) return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseTail(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed < 0) return 80;
|
||||
return Math.min(parsed, 500);
|
||||
}
|
||||
|
||||
function buildStatus(tailLines) {
|
||||
const checkpoint = readJsonIfExists(checkpointPath);
|
||||
const runs = listRuns();
|
||||
const activeRunId = checkpoint?.active_run_id || findLatestRunId(runs);
|
||||
const activeRun = runs.find((run) => run.id === activeRunId) || null;
|
||||
|
||||
return {
|
||||
generated_at: new Date().toISOString(),
|
||||
workspace,
|
||||
checkpoint,
|
||||
summary: {
|
||||
run_count: runs.length,
|
||||
active_run_id: activeRunId,
|
||||
current_phase: checkpoint?.current_phase || null,
|
||||
active_branch: checkpoint?.active_branch || null,
|
||||
blocker_count: Array.isArray(checkpoint?.blockers) ? checkpoint.blockers.length : 0,
|
||||
next_action: checkpoint?.next_action || null,
|
||||
},
|
||||
runs,
|
||||
active_progress: activeRun ? readProgress(activeRun, tailLines) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function listRuns() {
|
||||
if (!existsSync(runsDir)) return [];
|
||||
|
||||
return readdirSync(runsDir)
|
||||
.map((name) => path.join(runsDir, name))
|
||||
.filter((runPath) => statSync(runPath).isDirectory())
|
||||
.map((runPath) => {
|
||||
const id = path.basename(runPath);
|
||||
const state = readJsonIfExists(path.join(runPath, "state.json"));
|
||||
const agents = normalizeAgents(readJsonIfExists(path.join(runPath, "agents.json")));
|
||||
return {
|
||||
id,
|
||||
path: path.relative(workspace, runPath),
|
||||
state,
|
||||
agents: agents.map((agent) => ({
|
||||
...agent,
|
||||
lease_expired: isExpired(agent.lease_expires_at),
|
||||
})),
|
||||
mtime_ms: statSync(runPath).mtimeMs,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.mtime_ms - a.mtime_ms);
|
||||
}
|
||||
|
||||
function normalizeAgents(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
if (Array.isArray(value.agents)) return value.agents;
|
||||
return Object.entries(value).map(([role, agent]) => ({
|
||||
role,
|
||||
...(typeof agent === "object" && agent ? agent : { status: String(agent) }),
|
||||
}));
|
||||
}
|
||||
|
||||
function findLatestRunId(runs) {
|
||||
if (runs.length === 0) return null;
|
||||
return runs.reduce((latest, run) => (run.mtime_ms > latest.mtime_ms ? run : latest)).id;
|
||||
}
|
||||
|
||||
function readProgress(run, tailLines) {
|
||||
const runPath = path.join(workspace, run.path);
|
||||
const candidateNames = [
|
||||
"progress.log",
|
||||
"implementation-log.md",
|
||||
"validation.md",
|
||||
"opus-proposal.md",
|
||||
"snarky-review.md",
|
||||
];
|
||||
const filePath = candidateNames
|
||||
.map((name) => path.join(runPath, name))
|
||||
.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
|
||||
|
||||
if (!filePath) {
|
||||
return {
|
||||
run_id: run.id,
|
||||
path: null,
|
||||
lines: [],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
return {
|
||||
run_id: run.id,
|
||||
path: path.relative(workspace, filePath),
|
||||
lines: lines.slice(Math.max(0, lines.length - tailLines)),
|
||||
};
|
||||
}
|
||||
|
||||
function isExpired(value) {
|
||||
if (!value) return false;
|
||||
const time = Date.parse(value);
|
||||
if (!Number.isFinite(time)) return false;
|
||||
return time < Date.now();
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath) {
|
||||
if (!existsSync(filePath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
} catch (error) {
|
||||
return {
|
||||
parse_error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function sendJson(res, value) {
|
||||
const body = JSON.stringify(value);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(body),
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function serveStatic(res, pathname) {
|
||||
const relative = pathname === "/" ? "index.html" : pathname.slice(1);
|
||||
const filePath = path.resolve(staticDir, relative);
|
||||
|
||||
if (filePath !== staticDir && !filePath.startsWith(`${staticDir}${path.sep}`)) {
|
||||
sendNotFound(res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
||||
sendNotFound(res);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = readFileSync(filePath);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": contentType(filePath),
|
||||
"Content-Length": body.length,
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function sendNotFound(res) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("Not found");
|
||||
}
|
||||
|
||||
function contentType(filePath) {
|
||||
const extension = path.extname(filePath);
|
||||
switch (extension) {
|
||||
case ".html":
|
||||
return "text/html; charset=utf-8";
|
||||
case ".css":
|
||||
return "text/css; charset=utf-8";
|
||||
case ".js":
|
||||
return "text/javascript; charset=utf-8";
|
||||
case ".json":
|
||||
return "application/json";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
192
.agents/scripts/status.mjs
Normal file
192
.agents/scripts/status.mjs
Normal file
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const workspace = process.cwd();
|
||||
const agentsDir = path.join(workspace, ".agents");
|
||||
const runsDir = path.join(agentsDir, "runs");
|
||||
const checkpointPath = path.join(agentsDir, "checkpoint.json");
|
||||
const tailLines = parseTailLines(process.argv);
|
||||
|
||||
main();
|
||||
|
||||
function main() {
|
||||
const checkpoint = readJsonIfExists(checkpointPath);
|
||||
const runs = listRuns();
|
||||
const activeRunId = checkpoint?.active_run_id || findLatestRunId(runs);
|
||||
|
||||
printHeader(checkpoint, runs);
|
||||
|
||||
if (runs.length === 0) {
|
||||
console.log("No runs found under .agents/runs.");
|
||||
console.log("Next: create .agents/runs/<run-id>/ with state.json and agents.json.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const run of runs) {
|
||||
printRun(run, run.id === activeRunId);
|
||||
}
|
||||
|
||||
const activeRun = runs.find((run) => run.id === activeRunId);
|
||||
if (activeRun) {
|
||||
printProgressTail(activeRun);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTailLines(argv) {
|
||||
const tailIndex = argv.findIndex((arg) => arg === "--tail" || arg === "-n");
|
||||
if (tailIndex === -1) return 30;
|
||||
const value = Number(argv[tailIndex + 1]);
|
||||
if (!Number.isFinite(value) || value < 0) return 30;
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function printHeader(checkpoint, runs) {
|
||||
console.log("Agent Orchestration Status");
|
||||
console.log("==========================");
|
||||
console.log(`Workspace: ${workspace}`);
|
||||
console.log(`Runs: ${runs.length}`);
|
||||
console.log(`Active run: ${checkpoint?.active_run_id || "(none)"}`);
|
||||
console.log(`Current phase: ${checkpoint?.current_phase || "(unknown)"}`);
|
||||
console.log(`Active branch: ${checkpoint?.active_branch || "(none)"}`);
|
||||
console.log(`Next action: ${checkpoint?.next_action || "(none recorded)"}`);
|
||||
|
||||
const blockers = Array.isArray(checkpoint?.blockers) ? checkpoint.blockers : [];
|
||||
if (blockers.length > 0) {
|
||||
console.log("Blockers:");
|
||||
for (const blocker of blockers) {
|
||||
console.log(`- ${blocker}`);
|
||||
}
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function listRuns() {
|
||||
if (!existsSync(runsDir)) return [];
|
||||
|
||||
return readdirSync(runsDir)
|
||||
.map((name) => path.join(runsDir, name))
|
||||
.filter((runPath) => statSync(runPath).isDirectory())
|
||||
.map((runPath) => {
|
||||
const id = path.basename(runPath);
|
||||
return {
|
||||
id,
|
||||
path: runPath,
|
||||
state: readJsonIfExists(path.join(runPath, "state.json")),
|
||||
agents: normalizeAgents(readJsonIfExists(path.join(runPath, "agents.json"))),
|
||||
mtimeMs: statSync(runPath).mtimeMs,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
function normalizeAgents(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
if (Array.isArray(value.agents)) return value.agents;
|
||||
return Object.entries(value).map(([role, agent]) => ({
|
||||
role,
|
||||
...(typeof agent === "object" && agent ? agent : { status: String(agent) }),
|
||||
}));
|
||||
}
|
||||
|
||||
function findLatestRunId(runs) {
|
||||
if (runs.length === 0) return null;
|
||||
return runs.reduce((latest, run) => (run.mtimeMs > latest.mtimeMs ? run : latest)).id;
|
||||
}
|
||||
|
||||
function printRun(run, isActive) {
|
||||
const state = run.state || {};
|
||||
const marker = isActive ? "*" : "-";
|
||||
console.log(`${marker} Run: ${run.id}`);
|
||||
console.log(` Phase: ${state.current_phase || state.phase || "(unknown)"}`);
|
||||
console.log(` Branch: ${state.current_branch || state.branch || "(none)"}`);
|
||||
console.log(` Expected output: ${state.expected_output_artifact || state.expected_output || "(none)"}`);
|
||||
console.log(` Retry count: ${state.retry_count ?? 0}`);
|
||||
console.log(` Next action: ${state.next_action || "(none recorded)"}`);
|
||||
if (state.blocker) console.log(` Blocker: ${state.blocker}`);
|
||||
|
||||
if (run.agents.length === 0) {
|
||||
console.log(" Agents: none recorded");
|
||||
} else {
|
||||
console.log(" Agents:");
|
||||
for (const agent of run.agents) {
|
||||
printAgent(agent);
|
||||
}
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function printAgent(agent) {
|
||||
const role = agent.role || "(unknown-role)";
|
||||
const status = agent.status || "(unknown)";
|
||||
const heartbeat = agent.heartbeat_at || "(none)";
|
||||
const lease = agent.lease_expires_at || "(none)";
|
||||
const expired = isExpired(agent.lease_expires_at);
|
||||
const thread = agent.thread_id || agent.process_id || agent.tool_call_id || "(none)";
|
||||
|
||||
console.log(` - ${role}: ${status}${expired ? " [lease expired]" : ""}`);
|
||||
console.log(` heartbeat: ${heartbeat}`);
|
||||
console.log(` lease: ${lease}`);
|
||||
console.log(` id: ${thread}`);
|
||||
if (agent.last_error) {
|
||||
console.log(` last_error: ${agent.last_error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printProgressTail(run) {
|
||||
if (tailLines === 0) return;
|
||||
|
||||
const candidateNames = [
|
||||
"progress.log",
|
||||
"implementation-log.md",
|
||||
"validation.md",
|
||||
"opus-proposal.md",
|
||||
"snarky-review.md",
|
||||
];
|
||||
const progressPath = candidateNames
|
||||
.map((name) => path.join(run.path, name))
|
||||
.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
|
||||
|
||||
console.log("Progress Tail");
|
||||
console.log("=============");
|
||||
|
||||
if (!progressPath) {
|
||||
console.log(`No progress file found for run ${run.id}.`);
|
||||
console.log("Expected one of: progress.log, implementation-log.md, validation.md.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${path.relative(workspace, progressPath)} last ${tailLines} lines:`);
|
||||
const lines = readFileSync(progressPath, "utf8").split(/\r?\n/);
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop();
|
||||
}
|
||||
const tail = lines.slice(Math.max(0, lines.length - tailLines));
|
||||
for (const line of tail) {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
function isExpired(value) {
|
||||
if (!value) return false;
|
||||
const time = Date.parse(value);
|
||||
if (!Number.isFinite(time)) return false;
|
||||
return time < Date.now();
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath) {
|
||||
if (!existsSync(filePath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
} catch (error) {
|
||||
return {
|
||||
parse_error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
153
.agents/subplans/01-language-and-stdlib.md
Normal file
153
.agents/subplans/01-language-and-stdlib.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Subplan 01 - sx Language And Standard Library
|
||||
|
||||
## Goal
|
||||
|
||||
Build the language and std primitives required before the distribution platform
|
||||
can be implemented cleanly in sx.
|
||||
|
||||
## Work Location
|
||||
|
||||
- Primary repo: `/Users/agra/projects/sx`
|
||||
- Planning repo: `/Users/agra/projects/distribution`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Read `/Users/agra/projects/sx/readme.md`
|
||||
- Read `/Users/agra/projects/sx/specs.md`
|
||||
- Read `/Users/agra/projects/sx/docs/error-handling.md`
|
||||
- Re-read `PLAN.md` sections:
|
||||
- `Phase 0 - sx Language and Module Prerequisites`
|
||||
- `Standard Library API Surface`
|
||||
- `Detailed Std Struct And Method Sketches`
|
||||
|
||||
## Slice 1 - Public Exports And Namespace Aliases
|
||||
|
||||
Deliver:
|
||||
|
||||
- Add `pub` declarations for public module members.
|
||||
- Add public namespace member re-export aliases:
|
||||
- `pub print :: core.print`
|
||||
- `pub format :: core.format`
|
||||
- Support alias imports without forcing every caller to know the source module.
|
||||
- Add parser, AST, resolver, and compiler tests.
|
||||
- Add examples for module barrels such as `std.sx`.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Private module members are not importable outside their namespace.
|
||||
- Re-exported members keep correct identity for diagnostics and docs.
|
||||
- Cycles and duplicate public names produce clear errors.
|
||||
|
||||
## Slice 2 - Error Handling Alignment
|
||||
|
||||
Deliver:
|
||||
|
||||
- Audit all proposed std signatures against sx error handling.
|
||||
- Replace any incorrect `!T` assumptions with the actual sx shape.
|
||||
- Add tests that show success and error return forms.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- New std APIs follow the actual sx error model.
|
||||
- Distribution platform examples compile with the same error style.
|
||||
|
||||
## Slice 3 - Unicode And String Model
|
||||
|
||||
Deliver:
|
||||
|
||||
- Define `String` as validated UTF-8 bytes.
|
||||
- Define explicit terms:
|
||||
- byte length
|
||||
- Unicode scalar value
|
||||
- grapheme cluster
|
||||
- code point index is not a random-access string index
|
||||
- Add `std.unicode` primitives for validation, scalar iteration,
|
||||
normalization policy, case mapping policy, and ASCII fast paths.
|
||||
- Add `StringBuilder` for efficient construction.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Invalid UTF-8 cannot silently become `String`.
|
||||
- APIs that operate on bytes, scalars, and display width are named distinctly.
|
||||
- Platform identifiers and paths do not accidentally use display width logic.
|
||||
|
||||
## Slice 4 - Collections
|
||||
|
||||
Deliver:
|
||||
|
||||
- Extend `List` with reserve, resize, insert, remove, pop, clear, sort, find,
|
||||
contains, map/filter style helpers where idiomatic.
|
||||
- Add `HashMap` with deterministic iteration option or clear documentation that
|
||||
order is unspecified.
|
||||
- Add hash/equality hooks for custom key types.
|
||||
- Add tests for resize, collisions, deletion, and iteration.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Manifest parsing, route tables, and in-memory product stores can be written
|
||||
without ad hoc arrays.
|
||||
|
||||
## Slice 5 - Bytes, Paths, FS, And Process
|
||||
|
||||
Deliver:
|
||||
|
||||
- `std.bytes` for slices, builders, compare, copy, starts_with, ends_with.
|
||||
- `std.path` with platform-aware join, normalize, basename, dirname, extension.
|
||||
- `std.fs` with open/read/write/stat/mkdir/rename/remove/temp_dir/walk.
|
||||
- `std.process` with args, env, cwd, exit, spawn, wait, stdout/stderr capture.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- A CLI can read a manifest, inspect files, spawn validation helpers, and write
|
||||
artifacts safely.
|
||||
|
||||
## Slice 6 - Time, Random, Hashing, Encoding
|
||||
|
||||
Deliver:
|
||||
|
||||
- `std.time` timestamps, durations, monotonic clock, RFC3339 parse/format.
|
||||
- `std.random` secure bytes and deterministic PRNG for tests.
|
||||
- `std.hash` SHA-256 streaming.
|
||||
- `std.encoding` hex, base64url, percent encoding.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Release ids, token hashes, artifact digests, and audit timestamps can be
|
||||
implemented without platform-specific shims.
|
||||
|
||||
## Slice 7 - JSON, Config, CLI, And Logging
|
||||
|
||||
Deliver:
|
||||
|
||||
- `std.json` parser/writer with useful typed access.
|
||||
- `std.config` for env/file/CLI config layering.
|
||||
- `std.cli` parser with subcommands, flags, help, exit codes.
|
||||
- `std.log` structured events and levels.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- `dist ci publish --manifest dist.json --json` can parse args and emit stable
|
||||
JSON output.
|
||||
|
||||
## Slice 8 - HTTP, TLS, SQLite, Archive, Testing
|
||||
|
||||
Deliver:
|
||||
|
||||
- `std.http` server/client basics.
|
||||
- `std.tls` client/server wiring or explicit temporary boundary.
|
||||
- `std.db.sqlite` minimal prepared statements and transactions.
|
||||
- `std.archive` zip/tar/gzip reading needed for artifacts.
|
||||
- `std.testing` assertions, fixtures, temp directories, golden files.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- `distd` can expose endpoints, persist releases, inspect artifacts, and run
|
||||
repeatable tests.
|
||||
|
||||
## Checkpoint Notes
|
||||
|
||||
After each slice, update:
|
||||
|
||||
- `.agents/CHECKPOINT.md`
|
||||
- `.agents/checkpoint.json`
|
||||
- Any changed API signatures in `PLAN.md`
|
||||
151
.agents/subplans/02-domain-and-storage.md
Normal file
151
.agents/subplans/02-domain-and-storage.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Subplan 02 - Product Domain And Storage
|
||||
|
||||
## Goal
|
||||
|
||||
Define the distribution platform's core data model and persistence layer once
|
||||
the required std primitives exist.
|
||||
|
||||
## Core Structs
|
||||
|
||||
App:
|
||||
|
||||
- id
|
||||
- slug
|
||||
- display_name
|
||||
- bundle identifiers by platform
|
||||
- owner
|
||||
- visibility
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
Platform:
|
||||
|
||||
- ios
|
||||
- android_apk
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
|
||||
Artifact:
|
||||
|
||||
- id
|
||||
- app_id
|
||||
- release_id
|
||||
- platform
|
||||
- filename
|
||||
- content_type
|
||||
- size_bytes
|
||||
- sha256
|
||||
- storage_key
|
||||
- metadata
|
||||
- validation_status
|
||||
|
||||
Release:
|
||||
|
||||
- id
|
||||
- app_id
|
||||
- version
|
||||
- build
|
||||
- channel
|
||||
- notes
|
||||
- created_by
|
||||
- created_at
|
||||
- published_at
|
||||
|
||||
Channel:
|
||||
|
||||
- app_id
|
||||
- name
|
||||
- current_release_id
|
||||
- policy
|
||||
- rollout_percent
|
||||
|
||||
Token:
|
||||
|
||||
- id
|
||||
- name
|
||||
- token_hash
|
||||
- scopes
|
||||
- created_at
|
||||
- expires_at
|
||||
- last_used_at
|
||||
- revoked_at
|
||||
|
||||
AuditEvent:
|
||||
|
||||
- id
|
||||
- actor
|
||||
- action
|
||||
- target_type
|
||||
- target_id
|
||||
- metadata
|
||||
- created_at
|
||||
|
||||
## Slice 1 - In-Memory Model
|
||||
|
||||
Deliver:
|
||||
|
||||
- In-memory repository interfaces.
|
||||
- Basic create/list/get/update operations.
|
||||
- Domain validation for slugs, versions, channels, and platform ids.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- CLI and HTTP tests can run without SQLite.
|
||||
- Invalid app/release/artifact state is rejected at the domain boundary.
|
||||
|
||||
## Slice 2 - SQLite Schema
|
||||
|
||||
Deliver:
|
||||
|
||||
- Schema migrations.
|
||||
- Tables for apps, releases, artifacts, channels, tokens, audit events.
|
||||
- Indexes for slug lookup, app releases, artifact digest lookup, token hash.
|
||||
- Transaction wrapper for publish flows.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- A release and all artifacts can be created atomically.
|
||||
- Channel promotion can be rolled back if a validation gate fails.
|
||||
|
||||
## Slice 3 - Artifact Storage
|
||||
|
||||
Deliver:
|
||||
|
||||
- Content-addressed storage by SHA-256.
|
||||
- Staging directory for uploads.
|
||||
- Atomic move from staging to final storage.
|
||||
- Metadata sidecar or DB rows for content type, size, and platform metadata.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Duplicate artifact bytes are not stored twice unless policy requires it.
|
||||
- Interrupted uploads do not create published artifacts.
|
||||
|
||||
## Slice 4 - Retention And Cleanup
|
||||
|
||||
Deliver:
|
||||
|
||||
- Retention policy per app/channel.
|
||||
- Cleanup of unreferenced staged files.
|
||||
- Audit events for deletion.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Stable releases can be retained longer than beta/internal builds.
|
||||
- Cleanup never deletes the release currently pointed at by a channel.
|
||||
|
||||
## Slice 5 - Token Security
|
||||
|
||||
Deliver:
|
||||
|
||||
- Token generation.
|
||||
- Token hashing at rest.
|
||||
- Scope checks.
|
||||
- Expiration and revocation.
|
||||
- Last-used audit updates.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Raw tokens are shown only once.
|
||||
- CI publish tokens can be scoped to one app and one channel.
|
||||
125
.agents/subplans/03-cli-and-ci.md
Normal file
125
.agents/subplans/03-cli-and-ci.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Subplan 03 - CLI And CI Integration
|
||||
|
||||
## Goal
|
||||
|
||||
Make `dist` useful from CI before the admin UI exists.
|
||||
|
||||
## Commands
|
||||
|
||||
`dist server`:
|
||||
|
||||
- starts `distd`
|
||||
- accepts config file and env overrides
|
||||
|
||||
`dist doctor`:
|
||||
|
||||
- checks config, database, storage directory, HTTPS base URL, and platform tools
|
||||
|
||||
`dist app create`:
|
||||
|
||||
- creates an app
|
||||
- accepts slug, display name, owner, platform ids
|
||||
|
||||
`dist token create`:
|
||||
|
||||
- creates scoped CI tokens
|
||||
- prints raw token once
|
||||
|
||||
`dist ci publish`:
|
||||
|
||||
- validates manifest
|
||||
- creates/fetches app
|
||||
- creates draft release
|
||||
- uploads artifacts
|
||||
- validates artifacts
|
||||
- publishes release
|
||||
- optionally promotes a channel
|
||||
- prints JSON output
|
||||
|
||||
`dist release promote`:
|
||||
|
||||
- promotes a release to a channel
|
||||
- checks policy gates
|
||||
|
||||
`dist release rollback`:
|
||||
|
||||
- moves a channel pointer to the previous valid release
|
||||
|
||||
## Manifest Shape
|
||||
|
||||
Required fields:
|
||||
|
||||
- app slug
|
||||
- version
|
||||
- channel
|
||||
- artifacts list
|
||||
|
||||
Artifact fields:
|
||||
|
||||
- platform
|
||||
- path
|
||||
- filename override
|
||||
- content type override
|
||||
- metadata
|
||||
|
||||
## Slice 1 - Parser And Help
|
||||
|
||||
Deliver:
|
||||
|
||||
- Top-level parser.
|
||||
- Subcommand parser.
|
||||
- Help text.
|
||||
- Exit code contract.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Unknown commands produce readable errors.
|
||||
- `--json` never emits human-only text on stdout.
|
||||
|
||||
## Slice 2 - Local Publish
|
||||
|
||||
Deliver:
|
||||
|
||||
- `dist ci publish --manifest dist.json --local-store .dist`
|
||||
- In-memory or file-backed local mode for early testing.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- CI flow can be dogfooded without a running HTTP server.
|
||||
|
||||
## Slice 3 - Remote Publish
|
||||
|
||||
Deliver:
|
||||
|
||||
- HTTP client integration.
|
||||
- Token auth.
|
||||
- Streaming uploads with SHA-256.
|
||||
- Retry policy for safe requests.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Upload output includes release id, artifact ids, digests, and URLs.
|
||||
|
||||
## Slice 4 - CI Templates
|
||||
|
||||
Deliver:
|
||||
|
||||
- GitHub Actions example.
|
||||
- GitLab CI example.
|
||||
- Generic shell example.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Users can copy the command and only set server, token, and manifest path.
|
||||
|
||||
## Slice 5 - Observability
|
||||
|
||||
Deliver:
|
||||
|
||||
- Structured logs.
|
||||
- `--verbose`.
|
||||
- Machine-readable errors under `--json`.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- CI logs explain what failed without leaking tokens.
|
||||
104
.agents/subplans/04-http-api-and-install.md
Normal file
104
.agents/subplans/04-http-api-and-install.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Subplan 04 - HTTP API And Install Experience
|
||||
|
||||
## Goal
|
||||
|
||||
Expose release management and install/download flows through `distd`.
|
||||
|
||||
## API Slices
|
||||
|
||||
Slice 1 - Server Skeleton:
|
||||
|
||||
- config loading
|
||||
- routing
|
||||
- JSON request/response helpers
|
||||
- error response shape
|
||||
- health endpoint
|
||||
|
||||
Slice 2 - Auth:
|
||||
|
||||
- bearer token parsing
|
||||
- scope checks
|
||||
- audit actor resolution
|
||||
|
||||
Slice 3 - Apps And Releases:
|
||||
|
||||
- create/list/get apps
|
||||
- create/list/get releases
|
||||
- publish release
|
||||
- promote/rollback channel
|
||||
|
||||
Slice 4 - Uploads And Downloads:
|
||||
|
||||
- streaming upload endpoint
|
||||
- digest validation
|
||||
- resumable upload optional for later
|
||||
- download endpoint with content length and SHA-256 headers
|
||||
|
||||
Slice 5 - Public Install Pages:
|
||||
|
||||
- app page by slug/channel
|
||||
- platform detection
|
||||
- QR/deep link support
|
||||
- authenticated and public modes
|
||||
|
||||
## iOS Install Rules
|
||||
|
||||
Normal iOS devices cannot install arbitrary IPA files from a web page.
|
||||
|
||||
Supported modes:
|
||||
|
||||
- TestFlight link: open Apple's TestFlight flow.
|
||||
- Enterprise/MDM: serve signed HTTPS manifest plist for enrolled devices.
|
||||
- Artifact only: allow authenticated IPA download without claiming mobile
|
||||
install support.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- The UI labels iOS install mode accurately.
|
||||
- Enterprise install requires HTTPS and a valid manifest.
|
||||
- Artifact-only mode does not display a misleading "Install on iPhone" action.
|
||||
|
||||
## Android APK Install Rules
|
||||
|
||||
Supported modes:
|
||||
|
||||
- Direct APK download.
|
||||
- Optional install instructions shown only when relevant.
|
||||
- SHA-256 visible in download metadata.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- APK download is access-controlled according to app/channel policy.
|
||||
|
||||
## Desktop Install Rules
|
||||
|
||||
macOS:
|
||||
|
||||
- notarization status displayed when available
|
||||
- download zip/dmg/pkg
|
||||
|
||||
Linux:
|
||||
|
||||
- tar/appimage/deb/rpm metadata
|
||||
|
||||
Windows:
|
||||
|
||||
- installer zip/exe/msi metadata
|
||||
- signature status displayed when available
|
||||
|
||||
## API Contract
|
||||
|
||||
All JSON endpoints should return:
|
||||
|
||||
- stable status code
|
||||
- error code
|
||||
- message
|
||||
- request id
|
||||
- optional details
|
||||
|
||||
Downloads should include:
|
||||
|
||||
- `Content-Length`
|
||||
- `Content-Type`
|
||||
- `X-Artifact-SHA256`
|
||||
- cache policy by channel
|
||||
105
.agents/subplans/05-artifact-validation.md
Normal file
105
.agents/subplans/05-artifact-validation.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Subplan 05 - Artifact Validation
|
||||
|
||||
## Goal
|
||||
|
||||
Validate uploaded platform artifacts before publication and channel promotion.
|
||||
|
||||
## Common Validation
|
||||
|
||||
All artifacts:
|
||||
|
||||
- file exists
|
||||
- size matches upload metadata
|
||||
- SHA-256 matches
|
||||
- content type is allowed
|
||||
- extension matches platform policy
|
||||
- archive is readable when applicable
|
||||
|
||||
## iOS IPA
|
||||
|
||||
Validate:
|
||||
|
||||
- zip structure
|
||||
- `.app` bundle exists
|
||||
- Info.plist can be read
|
||||
- bundle id matches app policy
|
||||
- version/build metadata matches release
|
||||
- provisioning profile mode is classified
|
||||
- install mode is classified as TestFlight, Enterprise/MDM, or Artifact only
|
||||
|
||||
Acceptance:
|
||||
|
||||
- The system never implies browser IPA install unless enterprise/MDM manifest
|
||||
requirements are met.
|
||||
|
||||
## Android APK
|
||||
|
||||
Validate:
|
||||
|
||||
- APK structure
|
||||
- manifest package id
|
||||
- version code/name
|
||||
- min sdk
|
||||
- signature presence
|
||||
- basic metadata extraction
|
||||
|
||||
Acceptance:
|
||||
|
||||
- APK validator can reject package id mismatch before publication.
|
||||
|
||||
## macOS
|
||||
|
||||
Validate:
|
||||
|
||||
- archive opens
|
||||
- app bundle or installer package exists
|
||||
- Info.plist metadata when relevant
|
||||
- code signing status if tool support exists
|
||||
- notarization state when provided or checked
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Notarization can be pending for beta but must satisfy stable policy if
|
||||
configured.
|
||||
|
||||
## Linux
|
||||
|
||||
Validate:
|
||||
|
||||
- archive/package opens
|
||||
- architecture metadata when available
|
||||
- executable presence
|
||||
- optional package metadata for deb/rpm/appimage
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Platform and architecture metadata can drive download labels.
|
||||
|
||||
## Windows
|
||||
|
||||
Validate:
|
||||
|
||||
- exe/msi/zip exists
|
||||
- authenticode status if supported
|
||||
- malware scan placeholder status
|
||||
- architecture metadata when available
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Stable promotion can require completed scan policy.
|
||||
|
||||
## Validation Pipeline
|
||||
|
||||
Statuses:
|
||||
|
||||
- pending
|
||||
- passed
|
||||
- failed
|
||||
- warning
|
||||
- skipped
|
||||
|
||||
Policy:
|
||||
|
||||
- beta/internal may allow warnings.
|
||||
- stable requires all configured required checks.
|
||||
- every validation result is auditable.
|
||||
100
.agents/subplans/06-admin-ui.md
Normal file
100
.agents/subplans/06-admin-ui.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Subplan 06 - Admin UI
|
||||
|
||||
## Goal
|
||||
|
||||
Build a dense, operational admin UI for managing apps, releases, artifacts,
|
||||
channels, tokens, install pages, and audit logs.
|
||||
|
||||
## Authority
|
||||
|
||||
- Snarky defines product requirements and acceptance criteria.
|
||||
- Opus owns layout, visual hierarchy, and interaction design.
|
||||
- Opus is the only role that writes UI code during implementation.
|
||||
|
||||
## Screens
|
||||
|
||||
Apps:
|
||||
|
||||
- app list
|
||||
- platform coverage
|
||||
- channel status
|
||||
- latest release summary
|
||||
|
||||
App detail:
|
||||
|
||||
- metadata
|
||||
- releases table
|
||||
- channels
|
||||
- install page visibility
|
||||
|
||||
Release detail:
|
||||
|
||||
- artifact list
|
||||
- validation status
|
||||
- timeline
|
||||
- promote/rollback actions
|
||||
|
||||
Install preview:
|
||||
|
||||
- public install page preview
|
||||
- platform detection states
|
||||
- iOS mode selector
|
||||
- QR/deep link preview
|
||||
|
||||
Tokens:
|
||||
|
||||
- token list
|
||||
- create token
|
||||
- scopes
|
||||
- revoke
|
||||
|
||||
Audit:
|
||||
|
||||
- filter by actor, action, app, release
|
||||
- event details
|
||||
|
||||
Settings:
|
||||
|
||||
- server URL
|
||||
- storage
|
||||
- policy gates
|
||||
- NAS deployment values
|
||||
|
||||
## UX Requirements
|
||||
|
||||
- First screen is the working product, not a landing page.
|
||||
- Operational SaaS density: quiet, scan-friendly, no oversized marketing hero.
|
||||
- Layout works on desktop and mobile without text overlap.
|
||||
- Tables and panels should have stable dimensions.
|
||||
- Repeated items can be cards; avoid nested cards.
|
||||
- The UI must distinguish iOS TestFlight, Enterprise/MDM, and Artifact-only
|
||||
modes clearly.
|
||||
|
||||
## Mock Redesign Slice
|
||||
|
||||
Current static mock files:
|
||||
|
||||
- `index.html`
|
||||
- `styles.css`
|
||||
- `app.js`
|
||||
|
||||
The current layout was rejected by the user.
|
||||
|
||||
Next redesign branch:
|
||||
|
||||
- `opus/redesign-distribution-mock`
|
||||
|
||||
Allowed paths for Opus:
|
||||
|
||||
- `index.html`
|
||||
- `styles.css`
|
||||
- `app.js`
|
||||
- `assets/**`
|
||||
|
||||
Acceptance:
|
||||
|
||||
- App console is usable in the first viewport.
|
||||
- Navigation is not visually heavy.
|
||||
- Release workflow and install workflow are immediately legible.
|
||||
- Mobile layout prioritizes content before secondary chrome.
|
||||
- No misleading iOS install claim.
|
||||
84
.agents/subplans/07-packaging-nas.md
Normal file
84
.agents/subplans/07-packaging-nas.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Subplan 07 - Packaging, Docker, And UGREEN NAS
|
||||
|
||||
## Goal
|
||||
|
||||
Package the distribution platform so it can run on a UGREEN NAS through Docker.
|
||||
|
||||
## Runtime Shape
|
||||
|
||||
Container:
|
||||
|
||||
- `distd` server binary
|
||||
- static admin UI assets
|
||||
- config file support
|
||||
- healthcheck
|
||||
|
||||
Volumes:
|
||||
|
||||
- database
|
||||
- artifact storage
|
||||
- logs
|
||||
- optional TLS certs
|
||||
|
||||
Environment:
|
||||
|
||||
- `DIST_BASE_URL`
|
||||
- `DIST_DATA_DIR`
|
||||
- `DIST_DB_PATH`
|
||||
- `DIST_STORAGE_DIR`
|
||||
- `DIST_ADMIN_BIND`
|
||||
- `DIST_PUBLIC_BIND`
|
||||
- `DIST_LOG_LEVEL`
|
||||
|
||||
## Slice 1 - Local Container
|
||||
|
||||
Deliver:
|
||||
|
||||
- Dockerfile
|
||||
- docker compose file
|
||||
- healthcheck endpoint
|
||||
- local volume layout
|
||||
|
||||
Acceptance:
|
||||
|
||||
- `docker compose up` starts `distd`.
|
||||
- The admin UI loads.
|
||||
- Data survives container restart.
|
||||
|
||||
## Slice 2 - NAS Deployment
|
||||
|
||||
Deliver:
|
||||
|
||||
- UGREEN NAS deployment notes.
|
||||
- Recommended volume paths.
|
||||
- Reverse proxy/TLS guidance.
|
||||
- Backup/restore guidance.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- A user can deploy with Docker UI or compose.
|
||||
- Required external ports and volumes are clear.
|
||||
|
||||
## Slice 3 - Updates And Rollback
|
||||
|
||||
Deliver:
|
||||
|
||||
- image tag policy
|
||||
- migration policy
|
||||
- backup before migration
|
||||
- rollback instructions
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Updating the platform does not risk artifact loss without warning.
|
||||
|
||||
## Slice 4 - Dogfood
|
||||
|
||||
Deliver:
|
||||
|
||||
- Publish the distribution platform's own release through `dist`.
|
||||
- Document the self-hosted release flow.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- The platform can distribute its own updates.
|
||||
200
.agents/subplans/08-orchestration-and-qa.md
Normal file
200
.agents/subplans/08-orchestration-and-qa.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Subplan 08 - Orchestration, Checkpoints, And QA
|
||||
|
||||
## Goal
|
||||
|
||||
Keep agent work resumable, auditable, and constrained.
|
||||
|
||||
## Required Files
|
||||
|
||||
- `.agents/ORCHESTRATION.md`
|
||||
- `.agents/CHECKPOINT.md`
|
||||
- `.agents/checkpoint.json`
|
||||
- `.agents/subplans/README.md`
|
||||
- `.agents/runs/<run-id>/...`
|
||||
|
||||
## Run Creation
|
||||
|
||||
For each substantial task:
|
||||
|
||||
1. Create `.agents/runs/<run-id>/`.
|
||||
2. Copy relevant acceptance criteria into `brief.md`.
|
||||
3. Record the active branch.
|
||||
4. Record allowed write paths.
|
||||
5. Update checkpoint before invoking Opus.
|
||||
|
||||
Manager planning sessions count as substantial tasks when the user expects
|
||||
observability. Create a run record for planning work too, with Codex manager as
|
||||
the active agent.
|
||||
|
||||
## Agent Liveness
|
||||
|
||||
Each active run should include:
|
||||
|
||||
```txt
|
||||
.agents/runs/<run-id>/
|
||||
state.json
|
||||
agents.json
|
||||
```
|
||||
|
||||
`state.json` records:
|
||||
|
||||
- run id
|
||||
- current phase
|
||||
- current branch
|
||||
- input artifact
|
||||
- input hash
|
||||
- expected output artifact
|
||||
- retry count
|
||||
- next action
|
||||
- blocker, if any
|
||||
|
||||
`agents.json` records:
|
||||
|
||||
- role
|
||||
- status: queued, running, completed, failed, dead, restarted
|
||||
- started_at
|
||||
- heartbeat_at
|
||||
- lease_expires_at
|
||||
- thread id, process id, or tool call id when available
|
||||
- last_error
|
||||
|
||||
## Status And Progress Tail
|
||||
|
||||
Use the local status command from the workspace root:
|
||||
|
||||
```sh
|
||||
node .agents/scripts/status.mjs --tail 40
|
||||
```
|
||||
|
||||
For a browser dashboard:
|
||||
|
||||
```sh
|
||||
node .agents/scripts/observe.mjs --port 4317
|
||||
```
|
||||
|
||||
Then open `http://127.0.0.1:4317`.
|
||||
|
||||
The command reads:
|
||||
|
||||
- `.agents/checkpoint.json`
|
||||
- every `.agents/runs/<run-id>/state.json`
|
||||
- every `.agents/runs/<run-id>/agents.json`
|
||||
|
||||
It prints:
|
||||
|
||||
- all known runs
|
||||
- current phase and branch
|
||||
- all recorded agents and their lease status
|
||||
- expired leases
|
||||
- blockers
|
||||
- the next action
|
||||
- the tail of the active run's progress file
|
||||
|
||||
Progress files are checked in this order:
|
||||
|
||||
- `progress.log`
|
||||
- `implementation-log.md`
|
||||
- `validation.md`
|
||||
- `opus-proposal.md`
|
||||
- `snarky-review.md`
|
||||
|
||||
Managers should append progress events to `progress.log` whenever possible.
|
||||
Human-readable phase artifacts still stay in their named markdown files.
|
||||
|
||||
## Agent Restart Policy
|
||||
|
||||
If an agent dies, the manager restarts the role from durable files, not memory.
|
||||
|
||||
Snarky restart:
|
||||
|
||||
- Read `PLAN.md`, `.agents/ORCHESTRATION.md`, checkpoint files, and active run
|
||||
artifacts.
|
||||
- Re-run the current Snarky phase using the same input artifact.
|
||||
- Replace only the expected Snarky output for that phase.
|
||||
|
||||
Opus proposal/review restart:
|
||||
|
||||
- Re-run the same `opus-runner` planning tool with the same input artifact.
|
||||
- Keep previous failed output, if any, as diagnostic context.
|
||||
- Do not advance until the expected output validates.
|
||||
- Use a lease and CLI/tool timeout of at least 30 minutes.
|
||||
|
||||
Opus implementation restart:
|
||||
|
||||
- Check current branch.
|
||||
- Check dirty state.
|
||||
- If the branch is clean, retry the same implementation instruction.
|
||||
- If the branch is dirty, manager must inspect the diff and decide whether to
|
||||
continue, ask Opus to repair, or ask the user.
|
||||
- Never auto-reset or discard partial Opus edits.
|
||||
- Use a lease and CLI/tool timeout of at least 30 minutes.
|
||||
|
||||
Retry limits:
|
||||
|
||||
- Retry a dead planning phase up to 2 times.
|
||||
- Retry an implementation phase up to 1 time without user input.
|
||||
- After the retry cap, record a blocker in checkpoint files.
|
||||
|
||||
## Checkpoint Policy
|
||||
|
||||
Update checkpoints:
|
||||
|
||||
- at the start of a run
|
||||
- after Snarky brief
|
||||
- after Opus proposal
|
||||
- after concern resolution
|
||||
- before Opus implementation
|
||||
- after implementation
|
||||
- after validation
|
||||
- before ending the turn
|
||||
|
||||
Checkpoint must include:
|
||||
|
||||
- timestamp
|
||||
- current phase
|
||||
- current branch
|
||||
- active run id
|
||||
- completed artifacts
|
||||
- next action
|
||||
- blockers
|
||||
- commands/checks already run
|
||||
|
||||
## Validation Layers
|
||||
|
||||
Manager validation:
|
||||
|
||||
- git branch and diff check
|
||||
- out-of-scope file check
|
||||
- syntax checks
|
||||
- unit/integration tests when available
|
||||
- browser/screenshot checks for UI work when available
|
||||
|
||||
Snarky validation:
|
||||
|
||||
- product requirements
|
||||
- acceptance criteria
|
||||
- install flow accuracy
|
||||
- scope discipline
|
||||
|
||||
Opus validation:
|
||||
|
||||
- layout/design quality
|
||||
- interaction clarity
|
||||
- technical design concerns
|
||||
|
||||
## Resume Procedure
|
||||
|
||||
After power loss or interruption:
|
||||
|
||||
1. Read `.agents/CHECKPOINT.md`.
|
||||
2. Read `.agents/checkpoint.json`.
|
||||
3. Check git branch and dirty state.
|
||||
4. Read the active run directory if present.
|
||||
5. Continue from `next_action`.
|
||||
6. Do not assume an Opus implementation completed unless validation is recorded.
|
||||
|
||||
## Current Known Setup Issue
|
||||
|
||||
The distribution workspace is not currently a git repository. Branch-based Opus
|
||||
implementation requires initializing git or moving these files into a repo with
|
||||
a clean baseline commit.
|
||||
44
.agents/subplans/README.md
Normal file
44
.agents/subplans/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Detailed Subplan Index
|
||||
|
||||
These subplans break `PLAN.md` into implementation slices that can be handed to
|
||||
Snarky and Opus without reloading the whole plan every time.
|
||||
|
||||
## Reading Order
|
||||
|
||||
1. `01-language-and-stdlib.md`
|
||||
2. `02-domain-and-storage.md`
|
||||
3. `03-cli-and-ci.md`
|
||||
4. `04-http-api-and-install.md`
|
||||
5. `05-artifact-validation.md`
|
||||
6. `06-admin-ui.md`
|
||||
7. `07-packaging-nas.md`
|
||||
8. `08-orchestration-and-qa.md`
|
||||
|
||||
## Session Contract
|
||||
|
||||
- Read `PLAN.md`, `.agents/ORCHESTRATION.md`, `.agents/CHECKPOINT.md`, and
|
||||
the active subplan before doing work.
|
||||
- Keep work sequential. Do not use parallel implementation agents.
|
||||
- Use git branches for implementation. Do not use worktrees.
|
||||
- For product or UX scope, Snarky writes acceptance criteria and has final say.
|
||||
- For layout and visual design, Opus has final say.
|
||||
- For technical problems, consult Opus and resolve by consensus.
|
||||
- Opus is the only role that writes application code during Opus phases.
|
||||
- Update `.agents/CHECKPOINT.md` and `.agents/checkpoint.json` after every
|
||||
completed slice, before any risky handoff, and after validation.
|
||||
|
||||
## Slice Exit Criteria
|
||||
|
||||
Every slice should finish with:
|
||||
|
||||
- Changed files listed.
|
||||
- Tests/checks run listed.
|
||||
- Known risks listed.
|
||||
- Next slice named.
|
||||
- Checkpoint updated.
|
||||
|
||||
## Current Priority
|
||||
|
||||
The first blocking priority is `01-language-and-stdlib.md`. Product code should
|
||||
wait until the required `sx` primitives exist or an explicit temporary shim is
|
||||
approved.
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
304
PLAN.md
Normal file
304
PLAN.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# Distribution Platform in sx
|
||||
|
||||
## Goal
|
||||
|
||||
Build an App Store-like distribution platform in `sx`, but not limited to iOS
|
||||
apps. It should distribute mobile, desktop, and server/client artifacts through
|
||||
a CI-friendly release workflow.
|
||||
|
||||
Supported targets for the first product direction:
|
||||
|
||||
- iOS: IPA metadata, TestFlight links, Enterprise/MDM manifests, artifact-only
|
||||
downloads
|
||||
- Android: APK first, AAB later
|
||||
- macOS: app archives, dmg/pkg metadata, signing and notarization status
|
||||
- Linux: tarballs first, then deb/rpm/AppImage metadata
|
||||
- Windows: zip/installer first, then MSIX/signing metadata
|
||||
|
||||
The main commands are:
|
||||
|
||||
- `distd`: the HTTP API, install page, and artifact server
|
||||
- `dist`: the CI/admin CLI
|
||||
|
||||
The release workflow must be easy to integrate into CI. A CI job should be able
|
||||
to upload artifacts, create a release, attach platform variants, promote a
|
||||
channel, and receive machine-readable JSON output.
|
||||
|
||||
Product code should live in `sx`. C or system APIs are acceptable only as thin
|
||||
platform backends for capabilities that `sx` cannot express yet.
|
||||
|
||||
## Product Shape
|
||||
|
||||
This is an operational distribution tool first, not a public marketplace. The
|
||||
initial product should feel like a release console: quiet, dense, fast to scan,
|
||||
and optimized for repeated internal/private release work.
|
||||
|
||||
Core principles:
|
||||
|
||||
- CI is the primary writer.
|
||||
- Humans inspect, promote, revoke, download, and audit.
|
||||
- Releases are immutable.
|
||||
- Channels are mutable pointers to releases.
|
||||
- Artifacts are content-addressed and never silently replaced.
|
||||
- Every upload, publish, promotion, token change, and deletion writes an audit
|
||||
event.
|
||||
- The first version should be understandable from the filesystem and SQLite
|
||||
database during development.
|
||||
|
||||
## Users And Actors
|
||||
|
||||
- CI runner: publishes releases with `dist ci publish`.
|
||||
- Release manager: promotes channels, checks validation, and reads audit logs.
|
||||
- Developer/tester: downloads the current artifact for a platform/channel.
|
||||
- Admin: creates apps, manages tokens, and configures identity constraints.
|
||||
- `distd`: server/API/artifact store.
|
||||
- `dist`: CLI for CI and local admin work.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
App:
|
||||
|
||||
- Product being distributed.
|
||||
- Owns identity rules, allowed platforms, releases, channels, and tokens.
|
||||
|
||||
Release:
|
||||
|
||||
- Immutable version/build record for one app.
|
||||
- Contains one or more platform artifacts.
|
||||
|
||||
Artifact:
|
||||
|
||||
- Uploaded file for a platform.
|
||||
- Stored by digest and linked to release metadata.
|
||||
|
||||
Channel:
|
||||
|
||||
- Mutable pointer such as stable, beta, internal, nightly.
|
||||
- Promotion changes the pointer, not the release.
|
||||
|
||||
Token:
|
||||
|
||||
- Scoped credential for CI and automation.
|
||||
- Stored hashed, expires or can be revoked.
|
||||
|
||||
Audit event:
|
||||
|
||||
- Immutable record of important actions.
|
||||
|
||||
## Main Workflows
|
||||
|
||||
CI publish:
|
||||
|
||||
1. CI builds artifacts.
|
||||
2. CI writes or updates `dist.json`.
|
||||
3. CI runs `dist ci publish --server "$DIST_SERVER" --token "$DIST_TOKEN"
|
||||
--manifest dist.json --json`.
|
||||
4. `dist` validates the manifest and streams artifact uploads.
|
||||
5. `distd` validates metadata and stores artifacts by digest.
|
||||
6. The release is published.
|
||||
7. A requested channel is promoted if policy allows it.
|
||||
8. CI receives JSON with release ids, artifact ids, digests, and URLs.
|
||||
|
||||
Human release management:
|
||||
|
||||
1. Release manager opens the admin UI.
|
||||
2. They inspect release status, artifact validation, and audit history.
|
||||
3. They promote, rollback, revoke tokens, or download artifacts.
|
||||
|
||||
Install/download:
|
||||
|
||||
1. User opens an app/channel install page.
|
||||
2. Platform detection chooses the most relevant artifact or install path.
|
||||
3. The page shows accurate platform-specific actions and metadata.
|
||||
4. Access policy determines whether the user can download or install.
|
||||
|
||||
## iOS Install Policy
|
||||
|
||||
iOS needs special handling. The platform must not imply that a normal iPhone can
|
||||
install an arbitrary IPA from a web page.
|
||||
|
||||
Supported iOS modes:
|
||||
|
||||
- TestFlight: store a TestFlight link and open Apple's install flow.
|
||||
- Enterprise/MDM: serve a valid HTTPS manifest plist for enrolled devices.
|
||||
- Artifact only: allow authenticated IPA download, without presenting it as a
|
||||
normal mobile install action.
|
||||
|
||||
This distinction must be visible in both the API model and admin/install UI.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The intended stack is:
|
||||
|
||||
```text
|
||||
sx language and std primitives
|
||||
-> infra libraries: http, json, hashing, storage, db, auth, archive
|
||||
-> distribution domain: app, artifact, release, channel, token, audit
|
||||
-> interfaces: CLI, HTTP API, install pages, admin UI
|
||||
-> deployment: Docker image, NAS-friendly config, persistent data volume
|
||||
```
|
||||
|
||||
Storage direction:
|
||||
|
||||
- SQLite first.
|
||||
- Local filesystem artifact storage first.
|
||||
- Object storage adapter later.
|
||||
|
||||
Deployment direction:
|
||||
|
||||
- Docker/OCI image.
|
||||
- One persistent `/data` volume for database, artifacts, uploads, config, and
|
||||
logs.
|
||||
- One HTTP port.
|
||||
- `/healthz` endpoint.
|
||||
- Runs behind a reverse proxy for TLS.
|
||||
- Runs as a non-root user.
|
||||
- UGREEN NAS deployment through Docker/Container Manager is a first-version
|
||||
requirement.
|
||||
|
||||
## sx Foundation Work
|
||||
|
||||
Before the product can be implemented well, `sx` needs a stronger language and
|
||||
standard library foundation.
|
||||
|
||||
Language/module needs:
|
||||
|
||||
- `pub` exports so std modules do not leak private helpers.
|
||||
- Alias imports and curated namespace barrels.
|
||||
- Namespace member re-export syntax such as `pub print :: core.print`.
|
||||
- Error handling that follows the real sx model. `!` is an error channel, not a
|
||||
generic result wrapper.
|
||||
|
||||
Standard library needs, at overview level:
|
||||
|
||||
- Collections: extended `List`, `HashMap`
|
||||
- Strings: validated UTF-8 `String`, `StringBuilder`, explicit Unicode model
|
||||
- Bytes and paths
|
||||
- Filesystem and process APIs
|
||||
- Time, random, hashing, and encoding
|
||||
- JSON, URL, MIME, config, CLI, and logging
|
||||
- HTTP server/client and TLS boundary
|
||||
- SQLite
|
||||
- Archive inspection
|
||||
- Testing helpers
|
||||
|
||||
Unicode must be specified precisely:
|
||||
|
||||
- `String` is validated UTF-8 bytes.
|
||||
- Byte length, scalar values, grapheme clusters, and display width are distinct.
|
||||
- APIs must clearly say which unit they operate on.
|
||||
- Invalid UTF-8 must not silently become a `String`.
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
Phase 0 - sx language/module prerequisites:
|
||||
|
||||
- Add `pub` support, alias imports, and namespace member re-exports.
|
||||
|
||||
Phase 1 - standard library foundation:
|
||||
|
||||
- Build the std primitives needed for CLI, HTTP, storage, validation, and tests.
|
||||
|
||||
Phase 2 - product domain:
|
||||
|
||||
- Define apps, releases, artifacts, channels, tokens, policies, and audit
|
||||
events.
|
||||
|
||||
Phase 3 - storage:
|
||||
|
||||
- Add SQLite persistence and filesystem artifact storage.
|
||||
|
||||
Phase 4 - CLI first:
|
||||
|
||||
- Make `dist ci publish --manifest dist.json --json` work before the admin UI.
|
||||
|
||||
Phase 5 - HTTP API:
|
||||
|
||||
- Expose app, release, upload, download, channel, token, and audit endpoints.
|
||||
|
||||
Phase 6 - artifact validation:
|
||||
|
||||
- Validate APK and IPA first, then desktop artifact metadata.
|
||||
|
||||
Phase 7 - channels and install pages:
|
||||
|
||||
- Add promotion, rollback, download URLs, and platform-specific install pages.
|
||||
|
||||
Phase 8 - admin UI:
|
||||
|
||||
- Build a dense operational UI for apps, releases, artifacts, tokens, settings,
|
||||
install pages, and audit logs.
|
||||
|
||||
Phase 9 - packaging:
|
||||
|
||||
- Package `distd` and the admin UI into a Docker image suitable for UGREEN NAS.
|
||||
|
||||
## CI Contract
|
||||
|
||||
The primary CI command should be:
|
||||
|
||||
```sh
|
||||
dist ci publish \
|
||||
--server "$DIST_SERVER" \
|
||||
--token "$DIST_TOKEN" \
|
||||
--manifest dist.json \
|
||||
--json
|
||||
```
|
||||
|
||||
The command should:
|
||||
|
||||
1. Validate the manifest.
|
||||
2. Create or find the app.
|
||||
3. Create a draft release.
|
||||
4. Upload all artifacts with streaming SHA-256.
|
||||
5. Validate platform metadata.
|
||||
6. Publish the release.
|
||||
7. Promote the requested channel if specified.
|
||||
8. Print JSON containing release id, artifact ids, digests, and download URLs.
|
||||
|
||||
## First Milestone
|
||||
|
||||
Milestone 1 is complete when:
|
||||
|
||||
- Required `sx` language/std primitives exist or have explicit temporary
|
||||
boundaries.
|
||||
- `distd` can run locally.
|
||||
- `dist ci publish` can publish a release with at least APK and IPA artifacts.
|
||||
- Artifacts are stored by digest.
|
||||
- SQLite stores apps, releases, artifacts, channels, tokens, and audit events.
|
||||
- A channel can be promoted and rolled back.
|
||||
- Install/download pages accurately handle iOS, Android, and desktop artifacts.
|
||||
- The admin UI can inspect apps, releases, validations, tokens, and audit logs.
|
||||
- A Docker image can run on a UGREEN NAS with a persistent data volume.
|
||||
|
||||
## Non-goals For Version 1
|
||||
|
||||
- Public marketplace payments.
|
||||
- Reviews and ratings.
|
||||
- Organization billing.
|
||||
- Sophisticated staged rollout targeting.
|
||||
- CDN integration.
|
||||
- Full signing/notarization automation.
|
||||
- Object storage.
|
||||
- Postgres.
|
||||
|
||||
## Detailed Execution
|
||||
|
||||
`PLAN.md` is the overview. Detailed implementation breakdowns live in
|
||||
`.agents/subplans/`.
|
||||
|
||||
Before starting or resuming work, read:
|
||||
|
||||
- `.agents/ORCHESTRATION.md`
|
||||
- `.agents/CHECKPOINT.md`
|
||||
- `.agents/checkpoint.json`
|
||||
- the active `.agents/subplans/*.md` file
|
||||
|
||||
The workflow is sequential and branch-based:
|
||||
|
||||
- Codex manages orchestration and validation.
|
||||
- Snarky owns product briefs and final product acceptance.
|
||||
- Opus owns layout/design decisions.
|
||||
- Opus is the only role that writes code during Opus implementation phases.
|
||||
- Implementation uses git branches, not worktrees.
|
||||
- Checkpoints are updated after every completed slice and before stopping work.
|
||||
104
app.js
Normal file
104
app.js
Normal file
@@ -0,0 +1,104 @@
|
||||
const views = Array.from(document.querySelectorAll(".view"));
|
||||
const navItems = Array.from(document.querySelectorAll(".nav-item"));
|
||||
const viewLinks = Array.from(document.querySelectorAll("[data-view-link]"));
|
||||
|
||||
function showView(name) {
|
||||
views.forEach((view) => {
|
||||
view.classList.toggle("is-active", view.id === `view-${name}`);
|
||||
});
|
||||
|
||||
navItems.forEach((item) => {
|
||||
item.classList.toggle("is-active", item.dataset.view === name);
|
||||
});
|
||||
}
|
||||
|
||||
navItems.forEach((item) => {
|
||||
item.addEventListener("click", () => showView(item.dataset.view));
|
||||
});
|
||||
|
||||
viewLinks.forEach((link) => {
|
||||
link.addEventListener("click", () => showView(link.dataset.viewLink));
|
||||
});
|
||||
|
||||
const appData = {
|
||||
atlas: {
|
||||
name: "Atlas Notes",
|
||||
icon: "AN",
|
||||
iconClass: "app-icon-atlas",
|
||||
subtitle: "com.example.atlas - owner mobile-platform",
|
||||
stable: "2.8.4",
|
||||
downloads: "14,283",
|
||||
platforms: "4",
|
||||
},
|
||||
forge: {
|
||||
name: "Forge Build",
|
||||
icon: "FB",
|
||||
iconClass: "app-icon-forge",
|
||||
subtitle: "io.example.forge - owner release-engineering",
|
||||
stable: "1.12.0",
|
||||
downloads: "7,421",
|
||||
platforms: "3",
|
||||
},
|
||||
field: {
|
||||
name: "FieldKit",
|
||||
icon: "FK",
|
||||
iconClass: "app-icon-field",
|
||||
subtitle: "com.example.fieldkit - owner field-ops",
|
||||
stable: "0.9.8",
|
||||
downloads: "2,906",
|
||||
platforms: "2",
|
||||
},
|
||||
ledger: {
|
||||
name: "LedgerWorks",
|
||||
icon: "LW",
|
||||
iconClass: "app-icon-ledger",
|
||||
subtitle: "net.example.ledger - owner finance-tools",
|
||||
stable: "5.4.1",
|
||||
downloads: "4,108",
|
||||
platforms: "2",
|
||||
},
|
||||
};
|
||||
|
||||
const appRows = Array.from(document.querySelectorAll(".app-row"));
|
||||
const detailIcon = document.getElementById("detail-icon");
|
||||
const detailName = document.getElementById("detail-name");
|
||||
const detailSubtitle = document.getElementById("detail-subtitle");
|
||||
const metricStable = document.getElementById("metric-stable");
|
||||
const metricDownloads = document.getElementById("metric-downloads");
|
||||
const metricPlatforms = document.getElementById("metric-platforms");
|
||||
|
||||
function selectApp(key) {
|
||||
const data = appData[key];
|
||||
if (!data) return;
|
||||
|
||||
appRows.forEach((row) => {
|
||||
row.classList.toggle("is-selected", row.dataset.app === key);
|
||||
});
|
||||
|
||||
detailIcon.className = `app-icon app-icon-large ${data.iconClass}`;
|
||||
detailIcon.textContent = data.icon;
|
||||
detailName.textContent = data.name;
|
||||
detailSubtitle.textContent = data.subtitle;
|
||||
metricStable.textContent = data.stable;
|
||||
metricDownloads.textContent = data.downloads;
|
||||
metricPlatforms.textContent = data.platforms;
|
||||
}
|
||||
|
||||
appRows.forEach((row) => {
|
||||
row.addEventListener("click", () => selectApp(row.dataset.app));
|
||||
});
|
||||
|
||||
const methodButtons = Array.from(document.querySelectorAll("#ios-methods button"));
|
||||
const methodDetails = Array.from(document.querySelectorAll(".method-detail"));
|
||||
|
||||
methodButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
methodButtons.forEach((item) => {
|
||||
item.classList.toggle("is-selected", item === button);
|
||||
});
|
||||
|
||||
methodDetails.forEach((detail) => {
|
||||
detail.classList.toggle("is-hidden", detail.id !== `method-${button.dataset.method}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
596
index.html
Normal file
596
index.html
Normal file
@@ -0,0 +1,596 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>dist release console mock</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<header class="topbar">
|
||||
<div class="brand-block">
|
||||
<div class="brand-mark">d</div>
|
||||
<div>
|
||||
<div class="brand-name">dist</div>
|
||||
<div class="brand-meta">UGREEN NAS - local.distro.test</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="search">
|
||||
<span>/</span>
|
||||
<input type="search" placeholder="Search apps, releases, artifacts" />
|
||||
</label>
|
||||
|
||||
<div class="top-actions">
|
||||
<button class="icon-button" aria-label="Sync">
|
||||
<span aria-hidden="true">R</span>
|
||||
</button>
|
||||
<button class="icon-button" aria-label="Notifications">
|
||||
<span aria-hidden="true">!</span>
|
||||
</button>
|
||||
<div class="server-pill">
|
||||
<span class="dot dot-ok"></span>
|
||||
distd healthy
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside class="sidebar">
|
||||
<nav class="nav-list" aria-label="Primary">
|
||||
<button class="nav-item is-active" data-view="apps">
|
||||
<span class="nav-glyph">A</span>
|
||||
Apps
|
||||
</button>
|
||||
<button class="nav-item" data-view="release">
|
||||
<span class="nav-glyph">R</span>
|
||||
Release
|
||||
</button>
|
||||
<button class="nav-item" data-view="install">
|
||||
<span class="nav-glyph">I</span>
|
||||
Install
|
||||
</button>
|
||||
<button class="nav-item" data-view="tokens">
|
||||
<span class="nav-glyph">T</span>
|
||||
Tokens
|
||||
</button>
|
||||
<button class="nav-item" data-view="audit">
|
||||
<span class="nav-glyph">L</span>
|
||||
Audit
|
||||
</button>
|
||||
<button class="nav-item" data-view="settings">
|
||||
<span class="nav-glyph">S</span>
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-status">
|
||||
<div class="side-label">Storage</div>
|
||||
<div class="meter" aria-label="Storage used">
|
||||
<span style="width: 58%"></span>
|
||||
</div>
|
||||
<div class="side-value">824 GB of 1.4 TB</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="workspace">
|
||||
<section class="view is-active" id="view-apps">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Applications</h1>
|
||||
<p>5 apps - 31 active release artifacts - 3 pending checks</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<button class="secondary-button">Import manifest</button>
|
||||
<button class="primary-button">New app</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="apps-layout">
|
||||
<section class="panel app-list-panel" aria-label="Application list">
|
||||
<div class="panel-toolbar">
|
||||
<div class="segmented">
|
||||
<button class="is-selected">All</button>
|
||||
<button>Mobile</button>
|
||||
<button>Desktop</button>
|
||||
</div>
|
||||
<button class="small-button">Filter</button>
|
||||
</div>
|
||||
|
||||
<div class="app-list">
|
||||
<button class="app-row is-selected" data-app="atlas">
|
||||
<span class="app-icon app-icon-atlas">AN</span>
|
||||
<span class="app-main">
|
||||
<strong>Atlas Notes</strong>
|
||||
<small>ios - apk - macos - windows</small>
|
||||
</span>
|
||||
<span class="status-badge status-live">Stable</span>
|
||||
</button>
|
||||
<button class="app-row" data-app="forge">
|
||||
<span class="app-icon app-icon-forge">FB</span>
|
||||
<span class="app-main">
|
||||
<strong>Forge Build</strong>
|
||||
<small>linux - windows - macos</small>
|
||||
</span>
|
||||
<span class="status-badge status-beta">Beta</span>
|
||||
</button>
|
||||
<button class="app-row" data-app="field">
|
||||
<span class="app-icon app-icon-field">FK</span>
|
||||
<span class="app-main">
|
||||
<strong>FieldKit</strong>
|
||||
<small>ios - apk</small>
|
||||
</span>
|
||||
<span class="status-badge status-hold">Hold</span>
|
||||
</button>
|
||||
<button class="app-row" data-app="ledger">
|
||||
<span class="app-icon app-icon-ledger">LW</span>
|
||||
<span class="app-main">
|
||||
<strong>LedgerWorks</strong>
|
||||
<small>windows - linux</small>
|
||||
</span>
|
||||
<span class="status-badge status-live">Stable</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel app-detail-panel" aria-label="Application detail">
|
||||
<div class="detail-top">
|
||||
<div class="app-title">
|
||||
<span class="app-icon app-icon-atlas app-icon-large" id="detail-icon">AN</span>
|
||||
<div>
|
||||
<h2 id="detail-name">Atlas Notes</h2>
|
||||
<p id="detail-subtitle">com.example.atlas - owner mobile-platform</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-actions">
|
||||
<button class="secondary-button">Edit</button>
|
||||
<button class="primary-button">Publish</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-grid">
|
||||
<div class="metric">
|
||||
<span>Latest stable</span>
|
||||
<strong id="metric-stable">2.8.4</strong>
|
||||
<small>promoted 18 min ago</small>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span>Downloads</span>
|
||||
<strong id="metric-downloads">14,283</strong>
|
||||
<small>last 30 days</small>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span>Platforms</span>
|
||||
<strong id="metric-platforms">4</strong>
|
||||
<small>ios, apk, macos, windows</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-row">
|
||||
<h3>Channels</h3>
|
||||
<button class="small-button">Manage</button>
|
||||
</div>
|
||||
<div class="channel-grid">
|
||||
<div class="channel-box">
|
||||
<span class="dot dot-ok"></span>
|
||||
<strong>stable</strong>
|
||||
<small>2.8.4 - signed - public</small>
|
||||
</div>
|
||||
<div class="channel-box">
|
||||
<span class="dot dot-warn"></span>
|
||||
<strong>beta</strong>
|
||||
<small>2.9.0-rc.3 - notarizing</small>
|
||||
</div>
|
||||
<div class="channel-box">
|
||||
<span class="dot dot-cold"></span>
|
||||
<strong>internal</strong>
|
||||
<small>main.6814 - CI only</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-row">
|
||||
<h3>Recent Releases</h3>
|
||||
<button class="small-button" data-view-link="release">Open latest</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Channel</th>
|
||||
<th>Artifacts</th>
|
||||
<th>Checks</th>
|
||||
<th>Published</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>2.9.0-rc.3</td>
|
||||
<td><span class="status-badge status-beta">beta</span></td>
|
||||
<td>5</td>
|
||||
<td><span class="check-text check-warn">2 pending</span></td>
|
||||
<td>9 min ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2.8.4</td>
|
||||
<td><span class="status-badge status-live">stable</span></td>
|
||||
<td>4</td>
|
||||
<td><span class="check-text check-ok">passed</span></td>
|
||||
<td>18 min ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2.8.3</td>
|
||||
<td><span class="status-badge status-live">stable</span></td>
|
||||
<td>4</td>
|
||||
<td><span class="check-text check-ok">passed</span></td>
|
||||
<td>May 31</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-release">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Atlas Notes 2.9.0-rc.3</h1>
|
||||
<p>Release candidate - build 6814 - uploaded by ci-github</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<button class="secondary-button">Rollback</button>
|
||||
<button class="primary-button">Promote to stable</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="release-layout">
|
||||
<section class="panel">
|
||||
<div class="section-row">
|
||||
<h2>Artifacts</h2>
|
||||
<button class="small-button">Upload</button>
|
||||
</div>
|
||||
<div class="artifact-list">
|
||||
<div class="artifact-row">
|
||||
<span class="platform-badge ios">iOS</span>
|
||||
<div>
|
||||
<strong>AtlasNotes.ipa</strong>
|
||||
<small>sha256 verified - 92.4 MB - enterprise profile</small>
|
||||
</div>
|
||||
<span class="check-text check-ok">signed</span>
|
||||
</div>
|
||||
<div class="artifact-row">
|
||||
<span class="platform-badge apk">APK</span>
|
||||
<div>
|
||||
<strong>atlas-notes.apk</strong>
|
||||
<small>v2 signature - 78.1 MB - min sdk 26</small>
|
||||
</div>
|
||||
<span class="check-text check-ok">signed</span>
|
||||
</div>
|
||||
<div class="artifact-row">
|
||||
<span class="platform-badge mac">mac</span>
|
||||
<div>
|
||||
<strong>Atlas Notes.app.zip</strong>
|
||||
<small>notarization submitted - 134 MB</small>
|
||||
</div>
|
||||
<span class="check-text check-warn">pending</span>
|
||||
</div>
|
||||
<div class="artifact-row">
|
||||
<span class="platform-badge linux">linux</span>
|
||||
<div>
|
||||
<strong>atlas-notes-x86_64.tar.gz</strong>
|
||||
<small>ELF scan complete - 119 MB</small>
|
||||
</div>
|
||||
<span class="check-text check-ok">clean</span>
|
||||
</div>
|
||||
<div class="artifact-row">
|
||||
<span class="platform-badge win">win</span>
|
||||
<div>
|
||||
<strong>AtlasNotesSetup.exe</strong>
|
||||
<small>authenticode verified - 146 MB</small>
|
||||
</div>
|
||||
<span class="check-text check-warn">scan</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="panel release-side">
|
||||
<h2>CI Publish</h2>
|
||||
<div class="code-card">
|
||||
<code>dist ci publish --manifest dist.json --channel beta --json</code>
|
||||
</div>
|
||||
<dl class="definition-list">
|
||||
<div>
|
||||
<dt>Token</dt>
|
||||
<dd>ci-github-atlas</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Policy</dt>
|
||||
<dd>publish:atlas, promote:beta</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Retention</dt>
|
||||
<dd>keep 20 stable, 10 beta</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section class="panel timeline-panel">
|
||||
<h2>Release Timeline</h2>
|
||||
<ol class="timeline">
|
||||
<li>
|
||||
<span class="dot dot-ok"></span>
|
||||
<div>
|
||||
<strong>Manifest accepted</strong>
|
||||
<small>All artifact targets matched app policy</small>
|
||||
</div>
|
||||
<time>09:21</time>
|
||||
</li>
|
||||
<li>
|
||||
<span class="dot dot-ok"></span>
|
||||
<div>
|
||||
<strong>Mobile signatures verified</strong>
|
||||
<small>iOS enterprise profile and APK v2 signature passed</small>
|
||||
</div>
|
||||
<time>09:23</time>
|
||||
</li>
|
||||
<li>
|
||||
<span class="dot dot-warn"></span>
|
||||
<div>
|
||||
<strong>Desktop checks running</strong>
|
||||
<small>macOS notarization and Windows malware scan pending</small>
|
||||
</div>
|
||||
<time>09:29</time>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-install">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Install Experience</h1>
|
||||
<p>Public web install page, mobile-safe flows, and platform fallbacks</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<button class="secondary-button">Preview</button>
|
||||
<button class="primary-button">Publish page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="install-layout">
|
||||
<section class="panel install-phone-panel">
|
||||
<div class="phone-frame" aria-label="Mobile install preview">
|
||||
<div class="phone-speaker"></div>
|
||||
<div class="mobile-page">
|
||||
<span class="app-icon app-icon-atlas app-icon-large">AN</span>
|
||||
<h2>Atlas Notes</h2>
|
||||
<p>Version 2.8.4 - stable</p>
|
||||
<button class="mobile-primary">Install</button>
|
||||
<div class="mobile-platforms">
|
||||
<span>iOS</span>
|
||||
<span>Android</span>
|
||||
<span>macOS</span>
|
||||
<span>Windows</span>
|
||||
</div>
|
||||
<div class="mobile-note">
|
||||
iOS opens the configured TestFlight, MDM, or enterprise install path.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel install-config">
|
||||
<div class="section-row">
|
||||
<h2>iOS Path</h2>
|
||||
<div class="segmented" id="ios-methods">
|
||||
<button class="is-selected" data-method="testflight">TestFlight</button>
|
||||
<button data-method="enterprise">Enterprise</button>
|
||||
<button data-method="artifact">Artifact</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-detail" id="method-testflight">
|
||||
<h3>TestFlight</h3>
|
||||
<p>Install action opens Apple's TestFlight URL. The page still tracks release, channel, and access policy in dist.</p>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
Public link
|
||||
<input value="https://testflight.apple.com/join/atlasnotes" />
|
||||
</label>
|
||||
<label>
|
||||
Fallback
|
||||
<input value="/download/atlas/ios/2.8.4" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-detail is-hidden" id="method-enterprise">
|
||||
<h3>Enterprise / MDM</h3>
|
||||
<p>Install action serves a signed manifest plist over HTTPS for enrolled devices and keeps the IPA behind policy.</p>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
Manifest path
|
||||
<input value="/ios/atlas/2.8.4/install.plist" />
|
||||
</label>
|
||||
<label>
|
||||
Allowed teams
|
||||
<input value="mobile-platform, field-ops" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-detail is-hidden" id="method-artifact">
|
||||
<h3>Artifact Only</h3>
|
||||
<p>Install action becomes download-only. The page warns that sideload installation is not available on normal iOS devices.</p>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
Artifact
|
||||
<input value="AtlasNotes.ipa" />
|
||||
</label>
|
||||
<label>
|
||||
Visibility
|
||||
<input value="authenticated users" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-row">
|
||||
<div class="qr-code" aria-label="QR code preview">
|
||||
<span></span><span></span><span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span><span></span><span></span>
|
||||
<span></span><span></span><span></span><span></span><span></span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>install.local/atlas/stable</strong>
|
||||
<small>QR and deep links resolve to the same access-controlled install page.</small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-tokens">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Access Tokens</h1>
|
||||
<p>CI-friendly scoped credentials with short lived publish permissions</p>
|
||||
</div>
|
||||
<button class="primary-button">Create token</button>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Scope</th>
|
||||
<th>Last used</th>
|
||||
<th>Expires</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>ci-github-atlas</td>
|
||||
<td>publish:atlas, promote:beta</td>
|
||||
<td>9 min ago</td>
|
||||
<td>30 days</td>
|
||||
<td><span class="status-badge status-live">active</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ci-gitlab-forge</td>
|
||||
<td>publish:forge</td>
|
||||
<td>3 hours ago</td>
|
||||
<td>12 days</td>
|
||||
<td><span class="status-badge status-live">active</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>old-jenkins-mobile</td>
|
||||
<td>publish:fieldkit</td>
|
||||
<td>May 12</td>
|
||||
<td>expired</td>
|
||||
<td><span class="status-badge status-hold">revoked</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-audit">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Audit Log</h1>
|
||||
<p>Release, token, and policy events across the distribution server</p>
|
||||
</div>
|
||||
<button class="secondary-button">Export</button>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="audit-list">
|
||||
<div class="audit-row">
|
||||
<span class="audit-kind">publish</span>
|
||||
<div>
|
||||
<strong>ci-github-atlas uploaded Atlas Notes 2.9.0-rc.3</strong>
|
||||
<small>5 artifacts - manifest dist.json - request 0c8f</small>
|
||||
</div>
|
||||
<time>09:21</time>
|
||||
</div>
|
||||
<div class="audit-row">
|
||||
<span class="audit-kind">promote</span>
|
||||
<div>
|
||||
<strong>mira promoted Atlas Notes 2.8.4 to stable</strong>
|
||||
<small>approval policy satisfied - public install page updated</small>
|
||||
</div>
|
||||
<time>08:52</time>
|
||||
</div>
|
||||
<div class="audit-row">
|
||||
<span class="audit-kind">token</span>
|
||||
<div>
|
||||
<strong>kai rotated ci-gitlab-forge</strong>
|
||||
<small>new token expires in 30 days - old token revoked</small>
|
||||
</div>
|
||||
<time>Yesterday</time>
|
||||
</div>
|
||||
<div class="audit-row">
|
||||
<span class="audit-kind">policy</span>
|
||||
<div>
|
||||
<strong>Windows malware scan policy changed</strong>
|
||||
<small>stable channel now requires completed scan before promotion</small>
|
||||
</div>
|
||||
<time>May 30</time>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-settings">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Server Settings</h1>
|
||||
<p>NAS deployment, storage, HTTPS, and namespace policy</p>
|
||||
</div>
|
||||
<button class="primary-button">Save changes</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid">
|
||||
<section class="panel">
|
||||
<h2>Deployment</h2>
|
||||
<div class="settings-list">
|
||||
<label>
|
||||
Docker image
|
||||
<input value="ghcr.io/example/distd:0.1.0" />
|
||||
</label>
|
||||
<label>
|
||||
Data directory
|
||||
<input value="/volume1/docker/dist/data" />
|
||||
</label>
|
||||
<label>
|
||||
Public base URL
|
||||
<input value="https://install.example.test" />
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Policy</h2>
|
||||
<div class="check-list">
|
||||
<label><input type="checkbox" checked /> Require checks before stable promotion</label>
|
||||
<label><input type="checkbox" checked /> Require scoped tokens for CI publishing</label>
|
||||
<label><input type="checkbox" /> Allow anonymous public downloads</label>
|
||||
<label><input type="checkbox" checked /> Keep audit log immutable</label>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1104
styles.css
Normal file
1104
styles.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user