Skip to content

Releases: passioncode-ai/project-observatory-dashboard

v0.24.5

Choose a tag to compare

@github-actions github-actions released this 14 Oct 23:25
7e984a9

The leak scan reads every agent's transcripts (Codex, Hermes and opencode beside Claude Code) and a scan its
deadline cuts short still reports what it saw. Cleanup never removes local work it cannot restore, and the disk
guard registers worktrees cheaply before its expensive passes. Findings say when the tick, the harness scan or the
guard could not measure, and RAM and swap refresh on their own. Lessons become their own record kind; OpenRouter
spend per issued key; a DigitalOcean database door. The companion plugin observatory-log moves to 0.24.5: its
handling-secrets skill names the DigitalOcean door, the provider tools that return a credential by design, and
the header door's --server-url.

Added

  • The leak scan reads every agent's transcripts, not only Claude's (I-5, PB-032 remainder).
    Before this, a value echoed into a Codex rollout was neither a sighting nor named as unread.
    paths.transcript_homes() names each home, with the tool's own source as evidence:

    • Claude Code: the sessions source, and history.jsonl in $CLAUDE_CONFIG_DIR/~/.claude.
    • Codex: in $CODEX_HOME/~/.codex, sessions/ and archived_sessions/ (rollout-*.jsonl,
      and the compressed .jsonl.zst, read with Python 3.14's zstd) and history.jsonl; in
      $CODEX_SQLITE_HOME, the state_*, logs_*, thread_history_*, memories*, queue_*,
      goals_* and agent_message_board_* .sqlite databases.
    • Hermes: $HERMES_HOME, or ~/.hermes with $HERMES_DATA_DIR_SUFFIX, and its profiles:
      state.db and sessions/*.json|*.jsonl.
    • opencode: in $XDG_DATA_HOME/opencode, the opencode*.db databases or $OPENCODE_DB, and the
      legacy storage/**/*.json.

    An absent home is not a degradation. These are named in not_scanned with the reason: a home that
    exists but will not read, a profile that is a symbolic link, a database that will not open, and a
    .zst this Python cannot decode. coverage.transcript_homes says which homes were absent,
    scanned, partial (cut by the step's deadline) or unreadable. Sightings name the file or the
    table.column, never the value.

  • A database's mark hides fewer rows from the leak scan (I-5). Each table's signature is kept
    beside its mark: the inode, highest rowid, count, rowid sum and sum of squares (two primes),
    highest update value, and digests of the newest row and of the rows at the update maximum (one
    digest for the whole set past 64 rows). These are now read again:

    • rows in a store replaced by another file;
    • rows whose set of rowids under the mark changed (some deleted, or one inserted under the mark
      with a new rowid);
    • the deleted newest row's rowid when it is reused;
    • a row rewritten in place whose update column moved, or stayed at its maximum with a new digest
      (the column preferred finest-first: updated_at_ms before updated_at). A bulk update is
      reported once, not every tick.

    The weekly complete pass is still the only one to see:

    • a row replaced under its own rowid below the newest row, in a table with no update column;
    • an edit that moves no column (Codex UPDATE threads SET title);
    • an edit below a future-dated maximum;
    • an update column holding mixed types;
    • a change of rowids whose count and both sums collide (impractical, not impossible);
    • Codex thread_items, which is upserted in place; its rollouts are read incrementally.

    Rows are read 500 at a time, each page its own statement. The signature's aggregates each scan
    the whole table in one statement, so on a rollback-journal database a writer can wait for one
    such scan. Codex, opencode and Hermes run their stores in WAL mode by default, where a reader
    does not block a writer.

  • A damaged table is a hole, never a clean table (I-5). Any error from a table used to be
    taken for a WITHOUT ROWID table, and that table's read then stopped without a word: a damaged
    page left the store scanned with nothing degraded. Only a table declared WITHOUT ROWID is
    read whole now; SQLite itself says which tables those are (pragma_table_list), so a comment in
    the CREATE statement cannot. Any other error names the store in not_scanned as read only in
    part, with each table and the error's type, never SQLite's message, which can quote a cell.
    Its other tables are still read, and its marks do not move.

    Other inputs that used to fail are now handled:

    • Text that is not UTF-8 is read as its bytes.
    • A table name that is not UTF-8 is a hole. A column name that is not UTF-8 is a hole of its
      own, and the rest of its table is still read.
    • VIRTUAL generated columns are left out of every read, so one that fails no longer blinds its
      table. STORED generated columns are read, because their values are on disk.
    • A column named rowid no longer stands in for the real rowid. Rows are paged on whichever of
      rowid, _rowid_ and oid the table leaves free; one that takes all three is read whole.
    • A store that fails in any other way is "stopped while reading ()". Its sightings
      counted so far stand, its rows_read counts the rows it read, and the rest of the scan
      and its report go on.
    • A mark in the state file that is not a rowid is treated as absent, and its table is read
      from the start.
    • VARCHAR(n) and NCHAR(n) columns are read, by SQLite's TEXT affinity rule.

    Every SQLite store error is now reported by its type only, including "would not open" and a
    malformed schema, whose message quotes the stored CREATE statement. Before, those two copied
    SQLite's whole message into the report, the secret.leak_scan_blind finding and stdout.

    Not read: rowids of 0 or below, and text kept in a column without text affinity. A
    WITHOUT ROWID table is read whole every pass.

  • Upgrade cost, and two changed findings. The first passes after the upgrade read every Codex
    rollout in the 14-day window, both prompt histories and every transcript database whole, and
    re-read the tracked tree, the built page and the two engine stores once. The first tick or two
    may therefore raise secret.leak_scan_partial. A store that will not open is now an unreadable
    source, which raises secret.leak_scan_blind from info to warning.

  • What one issued OpenRouter key cost, by day and model (INB-019): openrouter.py spend NAME [--date YYYY-MM-DD] [--json] reads the provider's activity report through the management key that issued the key,
    filtered by that key's hash — the last 30 completed UTC days, or one day. The hash goes to the provider only and
    never to the output. A refused or unreadable report is said as such and is never printed as $0.
    read_activity() is its own function, so a project's spend can call it. An agent whose provider call ended
    without a request id can reconcile it from this record.

  • A lesson is its own record kind, for every project (observatory_lesson): a retro entry's class —
    classKey, title, trigger, check, nouns — with its origin, the project's pseudonymous key
    (p- and 12 hex digits of its root commit, never a project:<slug> id) and the evidence commit (kept as
    commit:<12 hex>). The arguments are what task-pipeline's lessons.py emit-observatory --tool observatory_lesson prints (where the engine lacks the tool, the emitter writes lesson: notes through
    observatory_record); tests/lesson_emitter_fixture.json holds that shape and the published schema is
    checked against it. One record per (classKey, project): a repeat revises it or writes nothing. A
    path, a URL, an e-mail address, a line break or a classKey the redactor would change is refused before any
    write (invalid-lesson, every reason listed); credential shapes and known values are redacted. A promoted
    lesson that cites a new commit, or whose evidence stops resolving in two known projects, is archived and
    replaced by a new proposal (outcome: replaced), which is promoted again only once its commit resolves.
    Written proposed, confidence 0.5, local stdio only. The tool publishes its outputSchema.

  • observatory_search and observatory_recall take kinds, applied inside the candidate query before the
    300-match window, and every hit names its kind; a lesson hit carries its fields as lesson. Across projects
    the search abstained with the window full of per-session turn records (measured: count 0, abstain: true);
    with kinds: ["lesson"] a lesson among 400 such rows is found.

  • Lessons are corroborated by a second project: tools/corroborate.py promotes a proposed lesson to
    observed as agent:lesson-corroborator when another, distinct project records the same classKey and both
    commits resolve — each asked of git in the registered clone whose root commit gives its project key. A key no
    registered clone carries is no witness: its lesson stays proposed, listed as "commit not resolvable here". The
    same project twice is one witness; supported stays the operator's. corroboration.json lists lessons apart
    from refusals (lessons: promoted, waiting, unresolvable, contradicted), and counts.proposed no longer
    counts them.

  • Lessons are kept 365 days while proposed, and for good once observed (retention.json →
    ledger.kinds.lesson); any kind can carry its own horizons there, read through retention.horizon_for by
    the review queue's countdown and prose, the ledger.review_expiring finding and the health page's digest,
    so none announces a proposed lesson's erasure at the general 90 days.

  • tools/migrate_lessons.py converts lessons written earlier as observatory_record notes (lesson:
    statements) into lesson records, as a new revision of the same record; --dry-run is the default.

Lessons are agents' work memory and stay in Observatory; an export of project-grade lessons to Fabric project
memory is a later seam ([agent memory → Lessons](docs/design/AG...

Read more

v0.24.4

Choose a tag to compare

@github-actions github-actions released this 12 Oct 10:02
d3e22fa

Inside the disk guard the git tier plans branches and worktrees only and fits its deadline, so a scheduled pass under load reaches its removals. The companion plugin observatory-log is unchanged.

Fixed

  • Inside the guard, the git tier plans branches and worktrees only, inside its deadline (OBS-63): on the first
    0.24.3 pass it planned 116 actions and reached none — its listing of what processes hold and a walk for build
    output (some 18,000 directories) took its whole share. The guard's own build-output class already covers that
    walk, so the guard asks for --git-only; the listing is bounded by the deadline; the result names
    plan_seconds.

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.24.3

Choose a tag to compare

@github-actions github-actions released this 12 Oct 08:51
6565d17

The disk guard's git tier removes merged and pushed branches and clean worktrees under load again: its re-check takes one listing of what processes hold per 20 seconds instead of one per worktree. The companion plugin observatory-log is unchanged.

Fixed

  • The git tier's re-check takes one listing per 20 seconds, not one per worktree (OBS-63): at load 300–700 each
    full listing of what processes hold took tens of seconds, and on 0.24.x the guard's git tier spent its whole share
    re-checking the first worktrees — no branch or worktree was removed by a scheduled pass. Run by hand on the same
    machine, the tier removed 95 merged and 3 pushed branches and 7 clean worktrees.

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.24.2

Choose a tag to compare

@github-actions github-actions released this 12 Oct 07:53
0b1dcf4

A cache command never runs past its class's share, so the disk guard's git tier — merged and pushed branches, clean worktrees and their dependencies — keeps the time 0.24.1 set aside for it. The companion plugin observatory-log is unchanged.

Fixed

  • A cache command never runs past its class's share (OBS-63): on 0.24.1 three cache commands, each inside its
    own 90–300 s timeout, ran 271 s together and took the git tier's time again. A command's timeout is now what is
    left of the share, and none starts with less than 30 s left; the rest waits for the next pass.

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.24.1

Choose a tag to compare

@github-actions github-actions released this 12 Oct 06:50
3c61154

The disk guard's git tier — merged and pushed branches, clean worktrees and their dependencies — gets its own
share of every pass under disk pressure; on 0.24.0 the path classes could spend the whole run before it. The companion
plugin observatory-log is unchanged.

Fixed

  • The disk guard's git tier keeps its own share of the run (OBS-63): the first scheduled 0.24.0 pass, at
    critical, spent 425 of its 480 s on the six path classes and skipped the git tier — merged and pushed branches
    and clean worktrees with their dependencies, where most of the space is. The tier now has git_tier_seconds
    (120 s by default, at most a third of the run) kept back before the classes split the rest.

Changed

  • The collectors take turns (OBS-52): after integrity, the tick runs its collectors least recently started
    first, not in a fixed order. Under load the collectors in front used the window and the ones at the back were
    not started tick after tick: on one machine (load average 26–30) the leak scan and the lifecycle watch had not
    run for two days. A step that was not started keeps its place at the front of the next tick; a collector that
    reads another's output this tick still runs after it (READS_FROM, held equal to the artefact graph by
    tests/test_pipeline.py). Start times are kept in store/tick-rotation.json; tick.json reports
    collector_order and not_started.

    Added to these notes on 2026-10-12: this change shipped in 0.24.1 (#231) but was missing here, and the
    CHANGELOG.md packaged in 0.24.1–0.24.4 lists it under 0.24.0. The release's files are unchanged.

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.24.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 05:43
6af55b7

The disk guard keeps up with a disk that fills fast and never removes what a session still uses: the maintenance pass runs every 15 minutes, under pressure the guard acts on every pass, and a fresh, shared listing of what processes hold — open files, maps, executables, command lines — is re-checked at every removal, under one lock for every cleanup. One project answers to many names (aliases, project.resolve). The doors grow: claim brings a key minted elsewhere under the OpenRouter door, a project's own key comes from its spend policy, and Cloudflare gains workers-routes and redirect-rules. The meter grows: the estate's month measured, grant.check declared, a usage report. The harness scan names plaintext credentials in agent tools' configs. The companion plugin observatory-log is unchanged.

Changed

  • leaks, git-hygiene and agent stop before their deadline instead of being killed (OBS-52), reading
    OBSERVATORY_STEP_DEADLINE as the folder scan, integrity and the plugins already do:

    • leaks starts a file only while two minutes are left and a SQLite store only while one is. Offsets of what it
      read are kept; what it did not read is named in degraded as unmeasured, never clean, and a store's mark does
      not move.
    • git-hygiene measures the least recently measured checkouts first. The ones it did not reach carry their last
      measurement, marked stale, so no checkout starves.
    • agent starts a model call only while a whole call (150 s) fits, so a paid answer is never lost. The run
      report names the deadline, and the deltas wait for the next run.
  • Trust tiers, written down (Fabric ADR-0144 amendment 4): on the operator's own machines (T0, and enrolled
    machines T1) the meter and the doors never demand a delegation token — slotsRequireGrant: when-token and
    keyserverRequiresDelegation: off stay the settings there; the strict ones are for a machine that serves another
    person's agents (T2). Design: docs/design/METER.md M0.

Added

  • Cloudflare door: --preset workers-routes --zone <zone> — Zone Read + Workers Routes Write on one zone,
    into a vault slot, verified by listing the zone's routes with the new value. workers-edit deploys a script
    but cannot route a hostname to it; this is that one zone right, without DNS. First use: moving a
    static site to a Worker.

  • openrouter.py claim NAME --where PATH, key on stdin — brings an OpenRouter key this door did not mint (one
    made by hand in the dashboard) under the door, so disable, limit and revoke reach it. The value is hashed,
    never stored or printed; the provider's row is read by that hash, and a label alone (prefix plus three
    characters, which collide) is accepted only when it is the single match in every listing and the ceiling the
    key reports for itself agrees. A claimed key is revoked through the door, never rotated to nowhere.
    A claimed key is identified by its hash, so a rename in the provider's dashboard never reads as a deletion; a
    bulk rotate names it and goes on; revoke refuses it while a consumer reads its label; the label path needs
    every listing whole and a ceiling to compare; a claim re-checks the ledger it writes.

  • Cloudflare door: --preset redirect-rules --zone <zone> — Zone Read + Dynamic URL Redirects Write (Single
    Redirects) on one zone, into a vault slot, verified by reading the zone's http_request_dynamic_redirect
    entrypoint with the new value. A zone with no redirect yet answers 404 there to a token that may read it and
    403 to one that may not, so for this preset alone a 404 counts as the proof.

  • A usage report (OBS-53; contract DEC-0021): the server declares surfaces.usage and answers
    GET /fabric/v1/usage with the service token — the last 31 days of the assistant's paid model calls by provider
    and model, from a receipt written at every charge (tokens and cost with its basis, never a prompt or a caller).
    The fabric-service schemas are vendored again at contract 7931c1d; the kit's conformance probe now runs
    usage.report.

  • Plaintext credentials in agent tools' configs (OBS-74): the harness scan names, per file, the credential-shaped
    values kept by Hermes, opencode, Codex, Claude Code's MCP blocks, Gemini and Cursor — names only, never a value —
    as harness.plaintext_secret, with the vault and use_secret.py as the remedy. Design: docs/design/HARNESS.md
    H15.

  • A project's own key, from its spend policy (OBS-58): openrouter.py issue-for-policy --policy FILE reads a
    contract spend-policy (DEC-0037) and issues, for each project-key source, a key for that project into the vault
    slot the source names. The key's ceiling is the project limit, and it resets monthly. A slot of another project is
    refused as a borrow (spend-policy rule 1). A policy with no project limit needs an explicit --limit, because a key
    is never unbounded. A ceiling above the project limit is refused. A second run is idempotent: it only moves a
    ceiling the policy changed.

  • grant.check is declared in fabric-agent.json (OBS-67; manifest revision 9): effect none, its
    input and output schemas published at v0.24.1, a probe that a call carrying no delegation token is denied
    principal-unproven. Fabric's registry reads the meter from the service descriptor; the token-carrying call
    goes to the same tool on the memory-http door.

  • The estate's month, measured (OBS-56): the new step scan-spend sums this month's spend of every OpenRouter
    account the door holds (GET /activity, per account, never a key) into store/raw/spend.json. The meter counts
    it, plus open reservations, against L0 (USD 1,000 by default), and the finding spend.estate_month warns at
    80 % and is critical at the limit. Measured on the operator's machine on 2026-10-11: $262.97 across four
    accounts.

  • The harness scan reads hooks and skill use (OBS-61): every hook the user's settings and each enabled
    plugin declare (event, matcher, source, program, a digest of the command and never the command), checked
    against the skill rules, with the finding harness.hook_risk; and each skill's invocations over the last 30
    days of transcripts, so the listing overflow names the unused skills first; and each project's own
    .claude/skills, counted on top of the user level (harness.project_listing_overflow when they alone push a
    session over the budget); and Codex's skills (~/.codex/skills and the shared ~/.agents/skills). Design:
    docs/design/HARNESS.md H11–H14.

  • One project under many names (OBS-65). The operator curates config/identifiers.json
    (another system's name → the canonical project:/product: id, with the name's owner); the
    emitter publishes registry/identifiers.json and one known_as relation per alias. A name
    given two targets, or contradicting a measured repository, deployment or domain, is a
    published conflict, never a silent pick. project.resolve (MCP observatory_resolve, Fabric
    capability pinned at v0.24.0) answers any name with the one canonical id, exact match only;
    an ambiguous or conflicted name is a typed refusal. Agents propose aliases through
    observatory_propose (aliases); accepting one never rewrites an existing row.

Fixed

  • The disk guard frees more, on any system (OBS-63): its pressure now lowers the git auto tier's thresholds
    (pressure in config/cleanup.json: clean worktrees and merged branches after 3 days at low, 1 day at
    critical; the manual tier never moves), it sweeps npx's package cache and macOS diagnostic reports, and it
    knows what is held on Linux without lsof (from /proc) and on Windows (a folder that cannot be renamed aside is
    held, and nothing of it is removed) — before, without lsof it removed no path at all.
  • The disk guard keeps up with a disk that fills fast (OBS-63): the maintenance pass runs every 15 minutes
    (it was hourly), and the guard acts on every pass once free space is below the low line (hourly while it is
    normal); it now also clears application and tool cache folders (~/Library/Caches, ~/.cache) untouched for
    7 / 2 / 1 days — the folders a hand cleanup freed 10 GB from — keeping downloaded browsers and SDKs a tool does
    not fetch again by itself (cache_keep); and its per-class cap is 20,000, since the run budget bounds its time.
    Measured on one machine: free space fell from 6 GB to 0.3 GB inside one hourly gap.
  • The disk guard finishes inside its pass (OBS-63): the first scheduled pass on 0.23.0 killed it at the
    job's 1200 s ceiling, under launchd's background priority, and recorded only exit 124 — no report, no
    journal. The job's ceiling is now 600 s and the guard stops itself at --budget (the job passes 480 s;
    run_budget_seconds, 900 s, by hand), shares that time fairly between its classes, acts before it refreshes
    any survey, writes each action to store/logs/disk-guard.jsonl as it happens, and always writes its report,
    naming what the budget left for the next pass.
  • The disk guard, second iteration (OBS-63; design docs/design/DISK-GUARD.md): a review of the first one
    found it could remove what a session still used, and could misreport what it did. Now:
    • Held means open files, memory maps, executables, working and root directories and paths named on a
      process's command line (holders.py: lsof with its escaped names decoded, /proc on Linux, a rename probe
      on Windows), taken fresh for every class; every removal re-checks its target, still unheld and unchanged since
      the plan, and a target that changed is skipped.
    • Every cleanup takes one lock (store/cleanup.lock): the tick, the guard and full cleanup never act at
      once, and a guard pass that finds the lock held is recorded as busy.
    • A privacy-guarded folder is never opened, an entry another account owns is never touched, and a tracked
      direc...
Read more

v0.23.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 17:35
85719cb

The disk guard keeps free space by itself, every hour, without touching anything a process holds; the meter
answers whether a delegated call may spend; a vault slot is released only under a grant that names it; and the
harness scan reports what every agent session loads. Releases are now approved by checks, not by a click. The
companion plugin observatory-log is unchanged.

Added

  • The disk guard (OBS-63): with features.auto_cleanup on, the hourly maintenance job runs
    tools/disk_guard.py first in every pass, before the update and the backup — no longer only from the tick,
    whose cleanup step a loaded machine could leave unreached for a day. Free space sets the pressure (normal,
    low under 25 GB, critical under 10 GB) and the pressure moves every threshold: the cache tools' own cleanup
    (uv, pnpm, Homebrew, Docker only while its daemon already runs; npm cache verify, uv cache clean, Go and
    Yarn from low), git-ignored build output of checkouts idle 14 / 3 / 1 days (by checkout, commit or an
    uncommitted file, never the index), temporary entries, Trash items older than 30 days (3 at critical),
    abandoned browser code-sign clones, then the cleanup's git auto tier. Nothing a process holds open or works in
    is removed: one lsof listing per run, re-checked at each removal, and without it no path goes. Freed space is
    the disk's own before/after count (freed_mb), not du, which counts an APFS clone in full.
    full disk-guard [--apply] [--level L] [--large-files] runs it by hand. The Machine page and
    observatory_machine (section: guard) show the last run, the largest files under the home directory and the
    running simulators with the command that stops each; machine.guard_idle warns when the guard has not reported
    for three hours, and machine.disk_low names the guard's run, the swap holders and the largest files.
    Configuration: config/cleanup.json → guard. Supersedes the Space-page draft (OBS-15).

  • The meter (OBS-55; Fabric ADR-0144 step 3, contract DEC-0037/0038/0039): grant.check on the memory-http
    door answers whether a call may spend. The caller presents the onward delegation token Fabric's hub minted for
    project-observatory in Authorization (never in an argument); the meter verifies it offline against the
    estate's Ed25519 keys (delegation.py, the contract's verifier table DL-13), reads the grant from the hub's
    grant directory, and reserves the amount against the grant's call, task, total, project, day, week
    and month windows and the estate's monthly total (L0, USD 1,000 by default) and provider-key ceilings, in one
    transaction. It answers a receipt, or a denial naming the code, the level and the window. full grant --status,
    --check (token on stdin) and --settle. The ledger records no person and no chain. Configuration:
    config/meter.json. Design: docs/design/METER.md.

  • A vault slot only under a grant (OBS-57): an agent hands use_secret.py its delegation token in
    FABRIC_DELEGATION_TOKEN (scope secret.use). run, serve and header then release a slot only when a grant
    the token names has credentialSource {kind: project-key, ref: PROJECT/ENV/NAME}, never read a .env, and
    remove the token before the child starts. slotsRequireGrant in config/meter.json: when-token (default
    until Fabric's issuer ships), always, off.

  • The agent harness scan (integrations.harness, sources.harness_root; off until turned on): what every
    Claude Code and Codex session loads before the first word. It counts each skill's entry in the session's skill
    listing against a budget (OBSERVATORY_HARNESS_BUDGET_CHARS, default 30,000 characters) and names the
    descriptions that no longer fit, largest first; flags an instruction file over 200 lines and the settings that
    load every MCP tool upfront; and runs deterministic rules over skill files — hidden Unicode, concealment
    directives, download-and-execute, a credential read beside a send, opaque content — weighed by where the skill
    comes from. Files are read, never run. A skill whose files change is reported until
    full harness --accept KEY; an ack of a rule hit lapses when the skill changes. Design: docs/design/HARNESS.md.

Fixed

  • full update refusing with "the workspace changed while staged" now names the paths that moved, so the writer
    to stop can be found (#200).

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.22.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 11:35
8e09e0e

Project Observatory on Windows and Linux: a desktop app with a signed Windows installer, the tick, the server
and maintenance as per-user Task Scheduler tasks or systemd units, updates that work on Windows, and the backup
passphrase in the Credential Manager. The Mac app is universal again for Intel Macs. The companion plugin
observatory-log moves to 0.22.0.

Changed

  • The vendored fabric-service kit is fabric-agent-adapter 0.8.2 (was 0.5.0): Windows and Linux support
    (contract DEC-0032, DEC-0033) — the instance lock, the token-file ACL rule, Task Scheduler and systemd in
    the descriptor — which the always-on server needs on Windows (W4b). serverd --uninstall relies on the
    kit's own wait for launchd to unload the job. The conformance probe's two new checks the server does not
    declare yet are NOT_RUN (usage.report: OBS-53).

Added

  • The Windows and Linux app (W8a, desktop/, Tauri 2): opens on this workspace's dashboard — live when its
    server answers, else the built pages under a banner with «Start server», else «Build the dashboard»; Settings
    for the program, the workspace and the language; first-run messages that name the install or full init
    step; stays in the tray when its window closes. Built as an NSIS installer and an AppImage/.deb by
    .github/workflows/desktop.yml; the release builds and Authenticode-signs it (W9). It updates itself from the
    release's latest.json, each package minisign-signed: «Restart to update» in the menu and the tray (W8c). The
    assistant window (Ctrl+Shift+A) asks the workspace's assistant, with each answer's evidence and Stop (W8b).
  • Updates on Windows (W6): full update --apply stops and restarts the tick and server tasks (systemd units
    on Linux) around the install, and replaces the project-observatory.exe it may be running from — moved aside
    first, put back if the install fails.
  • The server's always-on state off macOS (W4c): «Start server» in an app restarts the Task Scheduler task
    or systemd unit of an installed server instead of calling launchctl; server.silent watches that
    installation too.
  • Windows paths and tools (W7): the default workspace is %LOCALAPPDATA%\project-observatory-full (an
    existing ~\.local\share one stays); tools npm installs as .cmd (heroku, claude) start by name; the
    CLI and the scheduled jobs write UTF-8. README → Install has the PowerShell steps.
  • The backup passphrase in the Windows Credential Manager (W5): a generic credential per workspace, kept
    for this account on this machine (CredWriteW/CredReadW, never roaming, the value never an argument);
    a store that does not answer is not an empty one. The file fallback is owner-only on Windows too (a
    protected ACL instead of mode bits).
  • osschedule: background jobs under Windows Task Scheduler and systemd (W4a). One job description, a
    per-user task created from XML (InteractiveToken, no stored password, pythonw.exe through
    tools/scheduled.py, so no console window opens) or a systemd user unit; tools/install_launchd.py and
    tools/serverd.py --install use it off macOS, the server's fabric-service descriptor naming task-scheduler
    or systemd (W4b).

Fixed

  • The Mac app is universal. macos/scripts/build-app.sh builds arm64 and x86_64 and refuses an
    executable missing either slice; up to 0.21.0 the release carried the runner's architecture only, so
    the app did not open on an Intel Mac (PassionCode.ai platforms rule PL-01).

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.21.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 16:34
f545bd7

The engine runs on Windows, and a check of the agent on a heavily loaded machine fixed what it
said wrongly about itself and what kept its own tail from running. The companion plugin
observatory-log moves to 0.21.0.

Added

  • The engine on Windows (docs/design/WINDOWS-LINUX.md, W0–W3). One module per OS seam, each
    with a test that runs the same on every OS and refuses the POSIX-only call anywhere else:
    oslocks (file locks — LockFileEx on Windows), osproc (process groups, the stop signals an
    OS has, the assistant's deadline without SIGALRM; on Windows os.kill(pid, 0) terminates the
    process, so checking a job killed it), osprivacy (owner-only files through the file's ACL;
    descriptors opened in binary mode, without which Windows turns every LF into CRLF). The tick is
    Python (tools/tick.py; tools/tick.sh hands over to it), and a whole tick runs on the
    windows CI job. Windows needs Python 3.13 or newer.

Fixed

  • A busy machine no longer reads as a blank dashboard. Under a load average near 300 the
    page's script did not finish within 10 s and smoke reported a critical dashboard.blank about a
    page that renders in 0.13 s of processor. A timed-out script is blank only when it has used a
    5 s processor budget; a starved one is tried once more with 60 s, then recorded as not measured
    (dashboard.unverified, information).
  • The tail's cheap optional steps run before the heavy ones. The index, retention (the
    erasure), the fixture sweep, corroboration, the ledger export and the lost-projects record —
    seconds each — were not started tick after tick behind plugins and the agent's model calls.
    What the agent proposes is now indexed and exported on the next tick.
  • A plugin stopped at its limit keeps what it measured, and is told its deadline
    (OBSERVATORY_STEP_DEADLINE). disk-usage walked every tree twice and lost every row at the
    300 s limit, so it ran again on every tick and cost the tail 300 s each half hour; it now walks
    each folder once, measures the projects measured longest ago first and stops between projects
    (51 s for this machine's estate).
  • The store's integrity check is bounded. integrity_check runs once a day and after any
    verdict that was not ok, quick_check in between; the step stops itself at its deadline and
    records unmeasured (a store.integrity_unmeasured row naming the last full pass) instead of
    being killed with nothing written. Its limit is 240 s: one tick spent 785 s there and left the
    collectors no time.
  • The public-release check reads engine paths the same on Windows (as_posix()).

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc

v0.20.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 10:21
7365e75

A patch release from a read-only audit of 0.20.0 running on a working machine. The companion plugin
observatory-log moves to 0.20.1.

Fixed

  • Findings and the dashboard run again on a slow machine. No tick had finished for 13 hours:
    with the collectors held back, the tail's optional steps — plugins, rollup, the agent's model
    calls, the vector index, retention and review — still ran before the findings and the dashboard
    and used the last of the ceiling (14 ticks in a row ended inside plugins and agent). They now
    stop two minutes before the ceiling (at most a twelfth of it), leaving the core — from the merge
    to the findings, the dashboard, the notice and the registry commit — its time; a step that did
    not run leaves the previous run's output.
  • Health says why a tick did not finish. A tick the supervisor stopped at its ceiling was
    reported as "killed (a restart, a sleep, a signal)"; the supervisor's own record is read now.
  • The 3-of-10 unfinished-ticks alert reaches the service card and doctor, not only the MCP
    survey (tick.history_warning).
  • A restored workspace keeps its files' times. A rollback dated every file at the restore,
    and the tick's daily refreshes slipped by the time since the snapshot.
  • The maintenance record follows a person's update. After a manual full update it still
    named the last automatic one beside the newer engine; it now records updated-by-person.
  • The changelog's 0.19.3 section has its heading back — 0.19.4's version bump had overwritten
    it — and a test now refuses a gap or a repeat among a minor version's headings.

Documentation

  • The README says how the Mac app keeps itself current; the onboarding guide describes how the
    tick's ceiling is shared out and what tick.recent and tick.history_warning mean; AGENTS.md
    states the app's 15-minute check.

Verify

gpg --verify SHA256SUMS.asc SHA256SUMS      # key 63B30DC324BD697487AA31944FAFB8AEC803B6A7
shasum -a 256 -c SHA256SUMS --ignore-missing
gh attestation verify <file> --owner passioncode-ai --signer-repo passioncode-ai/.github

The organization's public key: https://github.com/passioncode-ai/.github/blob/main/release-signing/passioncode-release-signing.asc