From 125d8c45315c57ae9260ca87c0198db4e334c0b4 Mon Sep 17 00:00:00 2001 From: Jason Odoom Date: Tue, 18 Aug 2026 21:59:11 +0000 Subject: [PATCH 1/2] Add AER and Folio to the Start here table --- profile/README.md | 2 ++ profile/assets/icons/aer.svg | 6 ++++++ profile/assets/icons/folio.svg | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 profile/assets/icons/aer.svg create mode 100644 profile/assets/icons/folio.svg diff --git a/profile/README.md b/profile/README.md index 4969d5e..5de1e3a 100644 --- a/profile/README.md +++ b/profile/README.md @@ -32,6 +32,8 @@ different layer of the stack. | | **[ink](https://github.com/Ad-Astra-Computing/ink)** | An open protocol for signed agent-to-agent messages. The receiver verifies the envelope before it acts. | | | **[ahd](https://github.com/Ad-Astra-Computing/ahd)** | Critiques of AI-generated design that anyone can re-run. Findings stay reproducible against a named, versioned rule set. | | | **[nixpkgs](https://github.com/Ad-Astra-Computing/nixpkgs)** | The working fork for Trace, our research on AI-assisted CVE remediation. Nothing here is submitted upstream. | +| | **[AER](https://aer.adastra.computer)** | A flight recorder for AI agents. Every run leaves a signed execution record, so what an agent did stays answerable after the fact. | +| | **[Folio](https://receiptsofthought.com)** | A desktop writing app that keeps a tamper-evident record of how a piece was made. It emits open [receipts](https://receiptsofthought.com) anyone can verify. | ### Also here diff --git a/profile/assets/icons/aer.svg b/profile/assets/icons/aer.svg new file mode 100644 index 0000000..be21ff1 --- /dev/null +++ b/profile/assets/icons/aer.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/profile/assets/icons/folio.svg b/profile/assets/icons/folio.svg new file mode 100644 index 0000000..c031f67 --- /dev/null +++ b/profile/assets/icons/folio.svg @@ -0,0 +1,18 @@ + + + + Folio + + + + + + + + From 6f8eb29fa90869ffefc27956b65235671151e48c Mon Sep 17 00:00:00 2001 From: Jason Odoom Date: Tue, 18 Aug 2026 21:59:11 +0000 Subject: [PATCH 2/2] Check the copied project logos against their sources --- .github/workflows/logo-drift.yml | 58 ++++++++++++++++ profile/assets/icons/check-sources.mjs | 94 ++++++++++++++++++++++++++ profile/assets/icons/sources.json | 28 ++++++++ 3 files changed, 180 insertions(+) create mode 100644 .github/workflows/logo-drift.yml create mode 100644 profile/assets/icons/check-sources.mjs create mode 100644 profile/assets/icons/sources.json diff --git a/.github/workflows/logo-drift.yml b/.github/workflows/logo-drift.yml new file mode 100644 index 0000000..d5219ff --- /dev/null +++ b/.github/workflows/logo-drift.yml @@ -0,0 +1,58 @@ +name: logo drift + +on: + schedule: + - cron: "17 6 * * 1" + workflow_dispatch: + pull_request: + paths: + - "profile/assets/icons/**" + - "profile/README.md" + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - id: check + run: node profile/assets/icons/check-sources.mjs 2>&1 | tee report.txt + continue-on-error: true + + # On a pull request the failed step is visible in the checks, so leave it + # there. On the schedule nobody is looking, and the fix is easy to forget, + # so it needs somewhere durable to sit. + - if: steps.check.outcome == 'failure' && github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + existing=$(gh issue list --state open --label logo-drift --limit 1 --json number --jq '.[0].number // ""') + body=$(printf 'A logo on the org profile no longer matches the project it was copied from.\n\n```\n%s\n```\n\n%s\n' "$(cat report.txt)" "$RUN_URL") + if [ -n "$existing" ]; then + gh issue comment "$existing" --body "$body" + else + gh label create logo-drift --description "Copied project logos are out of date" --color d4c5f9 --force + gh issue create --title "Project logos are out of date" --label logo-drift --body "$body" + fi + + # Close the issue once the copies are back in step, so an open one always + # means there is something to do. + - if: steps.check.outcome == 'success' && github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + for n in $(gh issue list --state open --label logo-drift --json number --jq '.[].number'); do + gh issue close "$n" --comment "Logos match their sources again." + done + + - if: steps.check.outcome == 'failure' + run: exit 1 diff --git a/profile/assets/icons/check-sources.mjs b/profile/assets/icons/check-sources.mjs new file mode 100644 index 0000000..3e17026 --- /dev/null +++ b/profile/assets/icons/check-sources.mjs @@ -0,0 +1,94 @@ +// Each icon here is a copy of a mark owned by another project. This checks the +// copies against their sources so a redrawn mark does not sit stale on the org +// profile indefinitely. It never writes: on a mismatch it reports which file to +// re-copy from where, and exits non-zero. +// +// Icons with no reachable source are declared `unchecked` in sources.json and +// reported as such. A missing entry is an error, so adding an icon without +// deciding how it gets watched fails here rather than passing quietly. +import { readFile, readdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const sources = JSON.parse(await readFile(join(HERE, "sources.json"), "utf8")); + +async function fetchText(url) { + // One retry, so a single 5xx on the weekly run does not read as drift. + let last; + for (let attempt = 0; attempt < 2; attempt++) { + if (attempt > 0) await new Promise((r) => setTimeout(r, 3000)); + try { + const res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(20000) }); + if (!res.ok) { + last = new Error(`HTTP ${res.status}`); + continue; + } + return await res.text(); + } catch (err) { + last = err; + } + } + throw last; +} + +const problems = []; + +const icons = (await readdir(HERE)).filter((f) => f.endsWith(".svg")).map((f) => f.slice(0, -4)).sort(); + +for (const name of icons) { + const spec = sources[name]; + if (spec === undefined) { + problems.push(`${name}: no entry in sources.json. Add one, or mark it "unchecked" with a reason.`); + continue; + } + + if (spec.mode === "unchecked") { + console.log(`skipped ${name}: ${spec.reason}`); + continue; + } + + const local = await readFile(join(HERE, `${name}.svg`), "utf8"); + + let upstream; + try { + upstream = await fetchText(spec.url); + } catch (err) { + // An unreachable source is its own problem: the check stops being able to + // answer whether the copy is current, which is the only thing it is for. + problems.push(`${name}: source unreachable, ${spec.url} (${err.message})`); + continue; + } + + if (spec.mode === "bytes") { + if (local !== upstream) { + problems.push( + `${name}: ${name}.svg no longer matches ${spec.url}. Re-copy it, check it still reads at 20px on both GitHub themes, then commit.`, + ); + } + } else if (spec.mode === "contains") { + if (!upstream.includes(spec.marker)) { + problems.push( + `${name}: ${spec.url} no longer contains the marker this copy was taken from. The mark was probably redrawn, so re-derive ${name}.svg from it.`, + ); + } + } else { + problems.push(`${name}: unknown mode ${JSON.stringify(spec.mode)} in sources.json`); + } + + console.log(`checked ${name} against ${spec.url}`); +} + +for (const name of Object.keys(sources)) { + if (!icons.includes(name)) { + problems.push(`${name}: listed in sources.json but ${name}.svg does not exist.`); + } +} + +if (problems.length > 0) { + console.error(`\n${problems.length} problem(s):`); + for (const p of problems) console.error(` ${p}`); + process.exit(1); +} + +console.log(`\nall ${icons.length} icons accounted for`); diff --git a/profile/assets/icons/sources.json b/profile/assets/icons/sources.json new file mode 100644 index 0000000..9e1a688 --- /dev/null +++ b/profile/assets/icons/sources.json @@ -0,0 +1,28 @@ +{ + "vega-agent": { + "url": "https://raw.githubusercontent.com/Ad-Astra-Computing/vega-agent/main/.github/assets/vega-logo-light.svg", + "mode": "contains", + "marker": "M50 2 L58 42 L98 50 L58 58 L50 98 L42 58 L2 50 L42 42 Z", + "note": "vega-agent publishes only the large gradient logo, not the favicon this copy came from. The star outline is the part both files share, so this watches the path data instead of the whole file." + }, + "ink": { + "url": "https://ink.tulpa.network/favicon.svg", + "mode": "bytes" + }, + "ahd": { + "url": "https://ahd.adastra.computer/favicon.svg", + "mode": "bytes" + }, + "nixpkgs": { + "url": "https://raw.githubusercontent.com/NixOS/nixos-artwork/master/logo/nix-snowflake-colours.svg", + "mode": "bytes" + }, + "aer": { + "url": "https://aer.adastra.computer/favicon.svg", + "mode": "bytes" + }, + "folio": { + "mode": "unchecked", + "reason": "Folio has no public repo or site, so there is no source this workflow can reach. Copied by hand from assets/logo-small.svg, the variant drawn for sub-64px use. Re-copy it when Folio gets a public home, and give it a url here." + } +}