Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 2026-08-18
- Added `docs/ops-hub-v2/AUDIT-2026-08-18.md` — read-only audit of the historical
cross-project dashboard, command center, Ops Hub, Code Dashboard, project
manifest, and public portfolio surfaces, with each artifact classified
preserve / migrate / archive / unlink / remove-from-public.
- Added `docs/ops-hub-v2/OPS_HUB_V2_SPEC.md` — build spec for the consolidated
replacement: registry schema, `ops-state.json` v2 contract, nine enforced
invariants, build order, and acceptance criteria.
- README: linked both documents.

## 2026-07-06
- Added LICENSE (explicit all-rights-reserved).

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ node notionDashboardUpdate.js --dry-run # print a sample summary, no API calls

In production, the script runs unattended via the `Update Notion Dashboard` GitHub Actions workflow, which needs a `NOTION_TOKEN` secret configured on the repo.

## Related documents

This repo drives the Notion Code Dashboard page, which makes it one artifact in a
longer lineage of cross-project dashboards. That lineage was audited on
2026-08-18:

| Document | What it covers |
|---|---|
| [`docs/ops-hub-v2/AUDIT-2026-08-18.md`](docs/ops-hub-v2/AUDIT-2026-08-18.md) | Read-only audit of every historical dashboard, command center, manifest, and public portfolio surface, with each artifact classified preserve / migrate / archive / unlink / remove-from-public |
| [`docs/ops-hub-v2/OPS_HUB_V2_SPEC.md`](docs/ops-hub-v2/OPS_HUB_V2_SPEC.md) | Build spec for the consolidated replacement: registry schema, state contract, enforced invariants, and acceptance criteria |

Both are public-safe by construction and cite only already-public evidence. The
Ops Hub v2 implementation itself belongs in a private repo — see §11 of the spec.

## License

All rights reserved — see [LICENSE](LICENSE).
324 changes: 324 additions & 0 deletions docs/ops-hub-v2/AUDIT-2026-08-18.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
# Cross-Project Dashboard Surface Audit — 2026-08-18

> [!IMPORTANT]
> **Read-only audit.** Nothing was archived, unlinked, deleted, de-indexed, or
> republished. Every remediation below is gated behind explicit approval
> (see [Remediation bundles](#remediation-bundles)).

## Scope

Every historical surface that has, at some point, tried to answer *"what projects
exist and what state are they in"*: cross-project dashboards, command centers,
Ops Hub, Code Dashboard, the project manifest, and the public portfolio hub.

Each artifact is classified as **preserve**, **migrate**, **archive**, **unlink**,
or **remove from public**.

## Publication boundary applied to this document

`DaveHomeAssist/code-dashboard` is a **public** repository. Since the central
finding of this audit is that internal operations material leaked onto public
infrastructure, publishing an unredacted inventory here would reproduce the exact
defect being reported. This document therefore contains **only facts already
public**: anonymous HTTP crawl results, the contents of the already-public
`project-manifest.json`, and architecture description.

Deliberately excluded: workspace page titles/URLs/IDs, local filesystem paths,
private project identifiers, run IDs, and client-engagement detail. Where an
internal source is cited it is referred to by **role**, not by name — e.g.
"the dashboard standards governance page". The unredacted inventory belongs in a
private repository.

## Method

| Evidence class | How it was obtained | Trust |
|---|---|---|
| HTTP status, redirects, robots meta, page `<head>` | Anonymous `curl` against the live host, 2026-08-18 | **Confirmed** |
| Sitemap contents | `GET /sitemap.xml` | **Confirmed** |
| Public project inventory | `GET /project-manifest.json` | **Confirmed** |
| Hub rendering logic | Read of the served hub HTML | **Confirmed** |
| Repository visibility | GitHub API | **Confirmed** |
| Dashboard lineage & design intent | Internal workspace records | **Reported** — not independently reproducible here |

Not available, and therefore **not** asserted anywhere in this document: Search
Console, analytics, CrUX/Lighthouse, indexation state, or traffic outcomes. No
claim below depends on knowing whether a page is actually *indexed* — only on
whether it is *indexable*.

---

## Root cause

Six dashboard generations were built. All six were rebuilt as a **new surface**;
none established a **contract** underneath. The concrete expression of that today:

> **`visibility` is decorative metadata. Nothing in any build step converts it
> into a robots directive, a sitemap entry, or a link-rendering decision.**

Every finding below is a symptom of that single missing invariant.

### Evidence: `visibility` has no enforcement

13 projects are marked `visibility: unlisted` in the public manifest. 12 have a
live URL. Their robots directives, fetched anonymously:

| Live `<meta name="robots">` | Count | Verdict |
|---|---:|---|
| `noindex,nofollow` | 1 | Matches declared intent |
| *(no robots meta at all)* | 10 | Declared unlisted, fully indexable |
| `index, follow` | 1 | **Explicitly contradicts** its own manifest entry |

One page carries an affirmative `index, follow` while its manifest row says
`unlisted`. That is not drift — it is two systems asserting opposite things with
no arbiter.

### Evidence: the sitemap and the manifest are disjoint

| Measure | Value |
|---|---:|
| On-host project URLs in the manifest | 48 |
| Of those, returning HTTP 200 | **48 / 48** |
| Of those, present in `sitemap.xml` | **1** |
| Sitemap entries that are *not* a manifest project | **8 of 9** |

The sitemap is not stale — it is an **orphan from a different lineage**. Its 9
entries are the root hub plus a brand palette, two internal strategy reports, and
four one-off pages. It has never described the project portfolio at all.

### Evidence: hub links do not exist until JavaScript runs

The served hub HTML contains no project anchors. The project directory is built
client-side from the manifest, filtered on `visibility === 'public'`. The static
document offers one fallback: a link to the raw JSON.

This makes the manifest the **sole** discovery path — and it is the same file that
carries the unenforced `visibility` field.

---

## Findings

### F-001 · Internal operations dashboard is publicly indexable
**Severity: Critical** · **Classification: remove from public**

`/command-center-061eed/` serves HTTP 200 with a complete internal operations
console: per-project status panels, an attention queue, git sync state, daily run
history, domain status, and a triage lane.

Verified state of the document `<head>`:

| Element | Present |
|---|---|
| `<meta name="robots">` | **No** — the string `noindex` appears **zero** times in the entire 229 KB document |
| `<link rel="canonical">` | No |
| `<meta name="description">` | No |
| `<h1>` | No |

`robots.txt` is `Allow: /` with no exclusions. The manifest entry sets
`visibility: public`, so the hub renders a card linking to it.

> [!WARNING]
> Internal design records state this surface was intended to be
> **"public (noindex)"**. The published artifact has no robots directive
> whatsoever. The intent was recorded and never implemented — and no check exists
> that would have caught the gap.

**Two properties make this worse than a normal exposure:**

1. **It is republished by automation.** Internal run records show recurring
automated passes that regenerate and push this surface. A manual edit to the
published output **will be overwritten**. The fix must land in the generator.
2. **`noindex` requires a crawl.** Blocking in `robots.txt` first would prevent
crawlers from ever *seeing* a `noindex`. Correct order: ship `noindex`, let it
be crawled, *then* consider disallowing.

Treat this as a disclosure cleanup that happens to have SEO side effects — not as
an SEO defect.

### F-002 · Public manifest and private ops registry are the same document
**Severity: High** · **Classification: migrate — split the contract**

One `project-manifest.json` serves both portfolio discovery and internal
inventory. Consequences visible in the public file today:

- 79 entries, of which **18 are `visibility: private`** and 24 are `candidate`.
Private and candidate work is enumerated by name in a public JSON file.
- The schema carries `localPath` and `runCommand` fields. Both are **empty in all
79 rows** — the leak has been cleaned, but the *channel* remains. Any future
writer that populates them publishes local filesystem paths.
- The rows most likely to be sensitive are exactly the rows least likely to get a
review pass, because they render nothing.

Empty-but-present private fields are a latent leak, not a resolved one.

### F-003 · Sitemap describes an abandoned lineage
**Severity: High** · **Classification: preserve the file, regenerate the contents**

Quantified above: 1 of 48 live project URLs present; 8 of 9 sitemap entries are
not projects. Two of those 8 are internal strategy reports under
`/assets/reports/`, published and submitted for crawling.

The sitemap must become a **build artifact of the manifest**, never hand-authored.

### F-004 · Project discovery requires JavaScript
**Severity: Medium** · **Classification: preserve the hub, fix the build**

No project anchors in the served HTML. Filtering, search, and presentation can
stay client-side; the `<a href>` set must be pre-rendered.

### F-005 · Ops Hub v0.5 is the correct architecture and should not be rewritten
**Severity: High (opportunity)** · **Classification: migrate**

The Ops Hub generation lineage already established the right separation:

```
per-project state file → registry → generated hub → history + health panel
```

with candidate discovery, manual promotion, and a no-auto-registration default.
This is the only generation that treated the dashboard as a *rendered view* rather
than as the source of truth. v2 should be a **hardening** of it, not a rewrite.

Two known defects to carry forward as requirements: registry paths were fragile
across sync-backed directories, and **two parallel registry files existed in
different locations** — a split-brain the v2 schema must make structurally
impossible (one canonical registry, path resolved relative to the repo root).

### F-006 · Governance already defines the missing truth model
**Severity: Medium (opportunity)** · **Classification: preserve**

The dashboard standards governance record already specifies canonical source
hierarchy, conflict badges, freshness SLAs, and the rule that raw capture never
owns verified truth. v2 should **import** these rules rather than reinvent them.

### F-007 · One page's `noindex` may be correct, and nothing records that
**Severity: Medium** · **Classification: decision required**

`/act-two-catering/` carries `noindex,nofollow` and is marked `unlisted`. This is
the *only* unlisted page whose live directive matches its declared visibility.

It cannot be graded without knowing intent. If it is a live commercial page, this
is a serious defect. If it is a client preview, it is the one correctly configured
page on the property. **The audit's real finding is that intent is not recorded
anywhere** — so a correct configuration and a broken one are indistinguishable.

v2 requires an explicit `indexPolicy` with a reason string, so this question is
answered by the registry rather than by inference.

### F-008 · Duplicate hub URL
**Severity: Low** · **Classification: unlink or canonicalize**

`/` and `/public-hub.html` both appear in the sitemap, share an identical
`<title>` (`Dave Robertson — Projects`), serve different bodies (91 KB vs 31 KB),
and **each self-canonicalizes to its own URL**. Two documents assert they are both
the canonical version of the same titled page.

Pick one canonical; redirect the other or point its canonical at the winner.

---

## Artifact classification

Grouped by disposition. Internal-only artifacts are described by role.

### Preserve — active or canonical
| Artifact | Reason |
|---|---|
| Public portfolio hub (site root) | Correct product, defective build. Fix, don't replace. |
| `project-manifest.json` | Correct primitive. Split public/private concerns (F-002). |
| Dashboard standards governance record | Already defines v2's truth model (F-006). |
| Portfolio manifest reconciliation log | Most recent authority on manifest curation rules. |
| Standard audit procedure | Method reference for future passes. |
| Routing matrix for dev-hub surfaces | Routing rules migrate directly into v2. |

### Migrate — source material for v2
| Artifact | What survives |
|---|---|
| Ops Hub switchboard + v0.5 hardening + v0.1–v0.8 dev log | **The architecture itself** (F-005) — registry-driven generation, state reads, history, candidate discovery, health panel |
| Per-project `ops-state.json` pattern | The single strongest reusable primitive. Becomes schema v2. |
| Per-project ops-dashboard initializer | The per-project contract. Update for provenance + visibility. |
| `dashboard-registry.json` | Seeds the v2 registry. Must be de-duplicated to one canonical file. |
| Quick Start Command Center spec | **Provenance model only** — `{value, confidence, source, verifiedAt}` and freshness decay. Its published output is F-001. |
| Cross-Project Scan Dashboard | **Taxonomy only** — Security / Drift / Operability / Reuse. Becomes v2's audit lane. |
| Code Dashboard page template | **Layout only** — Top 3, Blocked, Registry, Open Threads. Not its stale content. |
| Operator Dashboard spec | **Five-zone layout and visibility gates only.** Its full integration surface is out of scope for v2 seed. |

### Archive — freeze as historical, remove from active navigation
Code Dashboard page-breakdown template and its March snapshots; the earlier
unified dashboard sync mechanism; the generated cross-project scan HTML artifact;
the June daily command-center notes; the command-center UI/live-data context
update (*after* its publishing lessons are captured in F-001); the prior
context-bucket audit prompts.

These are useful provenance and misleading authority. Label them historical.

### Unlink — remove from public navigation and sitemap, no deletion required
`/public-hub.html` · `/assets/reports/*` · `/assets/brand/brand-palette.html`
(unless deliberately public) · `/codedash-mar24/` · every `candidate` entry not
deliberately public.

Pending review before they stay linked: `/home-comic-dashboard/`,
`/daily-prophet/`, `/clever-paws/` — all three serve 200 with no robots meta.

### Remove from public
`/command-center-061eed/` and its published mirror, plus any surface exposing
project queues, audit state, run identifiers, local paths, or client references.
**Fix the generator, not the output** (F-001).

---

## Severity roll-up

| Severity | Finding |
|---|---|
| 🔴 Critical | F-001 — internal ops console publicly indexable, actively republished |
| 🟠 High | F-002 public/private contract conflation · F-003 orphaned sitemap · F-005 architecture at risk of rewrite |
| 🟡 Medium | F-004 JS-only discovery · F-006 unimported governance · F-007 unrecorded index intent |
| ⚪ Low | F-008 duplicate hub URL |

---

## Remediation bundles

None of this has been executed. Approve per bundle.

<details>
<summary><strong>Bundle A — Safety.</strong> Closes the confirmed exposure.</summary>

1. Add `noindex,nofollow` to the command center **in its generator**, so the next
automated republish preserves it rather than reverting it.
2. Flip its manifest entry off `visibility: public` so the hub stops linking it.
3. Decide whether it should remain on public hosting at all.
4. Review `/home-comic-dashboard/`, `/daily-prophet/`, `/clever-paws/` for
content sensitivity before they stay publicly linked.
5. Only after `noindex` has been crawled, consider a `robots.txt` disallow.

**Do not reorder step 5 ahead of step 1** — a disallow blocks the crawl that the
`noindex` depends on.
</details>

<details>
<summary><strong>Bundle B — Archive.</strong> Stops stale surfaces reading as current.</summary>

Mark the Code Dashboard lineage and the June command-center notes historical;
retain the cross-project scan as a dated snapshot; retain Ops Hub v0.5 as
migration source.
</details>

<details>
<summary><strong>Bundle C — Build v2.</strong> Per <a href="./OPS_HUB_V2_SPEC.md">the spec</a>.</summary>

Schemas, registry seed, validators, and a read-only private dashboard skeleton.
Depends on Bundle A only in sequencing — v2 should not be built on top of a live
exposure.
</details>

## Recommended order

**Bundle A first.** It is the only confirmed high-severity exposure, it is being
actively re-published by automation, and it is cheap. Bundle C then inherits a
clean boundary instead of encoding the current one.

Tradeoff: the v2 build waits one pass. Worth it — v2's central invariant is the
public/private boundary, and building it while that boundary is knowingly broken
would bake the defect into the schema.
Loading