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