Skip to content

Import YAAS v2 snapshot - #1

Merged
guangmian-circle merged 14 commits into
masterfrom
publish/yaas-v2-20260810-074616
Aug 16, 2026
Merged

guangmian-circle merged 14 commits into
masterfrom
publish/yaas-v2-20260810-074616

Conversation

@guangmian-circle

@guangmian-circle guangmian-circle commented Aug 10, 2026 •

Copy link
Copy Markdown
Collaborator

Updates the committed YAAS v2 snapshot from source commit 378baa783b342783ce282b3eda15bf932e804d71.

The replacement upper commit remains parented to c16bd1d, preserving the existing PR history. Local history remains on .git-yaas-v2/main. Modified and untracked workspace files were excluded by importing the captured Git tree directly.

Delivery-only adjustment:

  • Updated .github/workflows/scan.yml branch filters from main to master.

Verification:

  • GitHub signature: verified
  • Full suite: 29 suites passed, 0 failed
  • Differential: 29 passed, 0 failed
  • StepSecurity Required Checks: passed
  • Scan workflow: passed

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 2 package(s) with unknown licenses.
See the Details below.

License Issues

.github/workflows/scan.yml

PackageVersionLicenseIssue Type
circlefin/circle-public-github-workflows/.github/workflows/attach-release-assets.yaml1.*.*NullUnknown License
circlefin/circle-public-github-workflows/.github/workflows/pr-scan.yaml1.*.*NullUnknown License
Allowed Licenses: BSD-1-Clause, BSD-2-Clause, BSD-3-Clause, MIT, MIT-0, Apache-1.1, Apache-2.0, Artistic-1.0, Artistic-2.0, PHP-3.0, PHP-3.01, PSF-2.0, Zlib, zlib-acknowledgement, BSL-1.0, OpenSSL, WTFPL, CC0-1.0, CC-PDDC, CC-BY-1.0, CC-BY-2.0, CC-BY-2.5, CC-BY-3.0, CC-BY-4.0, Unlicense, ISC, BlueOak-1.0.0, BSD-2-Clause-Patent, ADSL, Apache-2.0, APAFML, BSD-1-Clause, BSD-2-Clause, BSD-2-Clause-FreeBSD, BSD-2-Clause-NetBSD, BSD-2-Clause-Views, BSL-1.0, DSDP, ECL-1.0, ECL-2.0, ImageMagick, ISC, Linux-OpenIB, MIT, MIT-Modern-Variant, MS-PL, MulanPSL-1.0, Mup, PostgreSQL, Spencer-99, UPL-1.0, Xerox, 0BSD, AFL-1.1, AFL-1.2, AFL-2.0, AFL-2.1, AFL-3.0, AMDPLPA, AML, AMPAS, ANTLR-PD, ANTLR-PD-fallback, Apache-1.0, Apache-1.1, Artistic-2.0, Bahyph, Barr, BSD-3-Clause, BSD-3-Clause-Attribution, BSD-3-Clause-Clear, BSD-3-Clause-LBNL, BSD-3-Clause-Modification, BSD-3-Clause-No-Nuclear-License-2014, BSD-3-Clause-No-Nuclear-Warranty, BSD-3-Clause-Open-MPI, BSD-4-Clause, BSD-4-Clause-Shortened, BSD-4-Clause-UC, BSD-Source-Code, bzip2-1.0.5, bzip2-1.0.6, CC0-1.0, CNRI-Jython, CNRI-Python, CNRI-Python-GPL-Compatible, Cube, curl, eGenix, Entessa, FTL, HTMLTIDY, IBM-pibs, ICU, Info-ZIP, Intel, JasPer-2.0, Libpng, libpng-2.0, libtiff, LPPL-1.3c, MIT-0, MIT-advertising, MIT-open-group, MIT-CMU, MIT-enna, MIT-feh, MITNFA, MTLL, MulanPSL-2.0, Multics, Naumen, NCSA, Net-SNMP, NetCDF, NTP, OLDAP-2.0, OLDAP-2.0.1, OLDAP-2.1, OLDAP-2.2, OLDAP-2.2.1, OLDAP-2.2.2, OLDAP-2.3, OLDAP-2.4, OLDAP-2.5, OLDAP-2.6, OLDAP-2.7, OLDAP-2.8, OML, OpenSSL, PHP-3.0, PHP-3.01, Plexus, PSF-2.0, Python-2.0, Ruby, Saxpath, SGI-B-2.0, SMLNJ, SWL, TCL, TCP-wrappers, Unicode-DFS-2015, Unicode-DFS-2016, Unlicense, VSL-1.0, W3C, X11, XFree86-1.1, Xnet, xpp, Zlib, zlib-acknowledgement, ZPL-2.0, ZPL-2.1, AAL, Adobe-2006, Afmparse, Artistic-1.0, Artistic-1.0-cl8, Artistic-1.0-Perl, Beerware, blessing, Borceux, CECILL-B, ClArtistic, Condor-1.1, Crossword, CrystalStacker, diffmark, DOC, EFL-1.0, EFL-2.0, Fair, FSFUL, FSFULLR, Giftware, HPND, IJG, Leptonica, LPL-1.0, LPL-1.02, MirOS, mpich2, NASA-1.3, NBPL-1.0, Newsletr, NLPL, NRL, OGTSL, OLDAP-1.1, OLDAP-1.2, OLDAP-1.3, OLDAP-1.4, psutils, Qhull, Rdisc, RSA-MD, Spencer-86, Spencer-94, TU-Berlin-1.0, TU-Berlin-2.0, Vim, W3C-19980720, W3C-20150513, Wsuipa, WTFPL, xinetd, Zed, Zend-2.0, ZPL-1.1, GPL-2.0, GPL-2.0+, GPL-2.0-or-later, GPL-3.0, GPL-3.0+, GPL-3.0-or-later, LGPL-2.1, LGPL-2.1+, LGPL-2.1-or-later, LGPL-3.0, LGPL-3.0+, LGPL-3.0-or-later, MPL-1.1, MPL-2.0, MPL-2.0-no-copyleft-exception, CDDL-1.0, CDDL-1.1, CPL-1.0, IPL-1.0, EPL-1.0, EPL-2.0, Apache-1.0, CC-BY-SA-1.0, CC-BY-SA-2.0
Excluded from license check:

OpenSSF Scorecard

PackageVersionScoreDetails
actions/circlefin/circle-public-github-workflows/.github/workflows/attach-release-assets.yaml 1.*.* UnknownUnknown
actions/circlefin/circle-public-github-workflows/.github/workflows/pr-scan.yaml 1.*.* UnknownUnknown

Scanned Files

  • .github/workflows/scan.yml

@guangmian-circle
guangmian-circle force-pushed the publish/yaas-v2-20260810-074616 branch from 816dc1d to 7579000 Compare August 11, 2026 11:16
Source: .git-yaas-v2/main@378baa783b342783ce282b3eda15bf932e804d71

Replaces the previous PR head while preserving c16bd1d as the parent. The scan workflow targets Circlefin master; uncommitted local files are excluded.
@guangmian-circle
guangmian-circle force-pushed the publish/yaas-v2-20260810-074616 branch from 7579000 to dc4ea18 Compare August 11, 2026 11:26
@guangmian-circle

Copy link
Copy Markdown
Collaborator Author

@nexx88 As discussed, this PR is ready for the first public release. Would you be the right person to review and merge this?

guangmian-circle and others added 3 commits August 12, 2026 01:04
Source SHA: 4e0feedb677d76a98395e6c4e9ccac23b871145c (.git-yaas-v2/main).
One-commit snapshot; full local history stays on .git-yaas-v2/main.
Uncommitted and untracked working-tree files are excluded.
scan.yml branch filters remain [master] in this delivery branch only.

A watch that made no progress on three dispatches used to be parked as
misconfig and surfaced as an approval card, frozen until a human cleared
it. It now backs off (5m doubling to a 24h cap) and retries forever.

A tick with no network no longer runs at all: every dispatch it makes is
guaranteed to fail and charge a no-progress strike to a watch that did
nothing wrong. The probe targets the configured backend's API and fails
open on anything that is not unambiguously a network failure.

The dashboard attributes both kinds of backoff to their quest and labels
which kind, and review cards no longer render buttons that cannot work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Source SHA: 8be8a06a20b74668bb3b0b0722eeb0752dc65748 (.git-yaas-v2/main).
One-commit snapshot; full local history stays on .git-yaas-v2/main.
Uncommitted and untracked working-tree files are excluded.
scan.yml branch filters remain [master] in this delivery branch only.

An audit against the implementation found nine mismatches. Three would
have produced a broken quest: the thread-retirement default was
documented as 30 days in three places when the runtime uses 14;
telegram_chat was offered as a watch type that has no checker, so such a
quest scaffolds cleanly and is then skipped silently forever; and
one-shot schedules (next_fire_ts) were supported by the runtime but
undocumented and rejected by the scaffolder.

The rest align the documented contracts with the code: slack_dm needs a
channel_id, the validation section separates what the script enforces
from what the caller must check, and the ops and dispatch references no
longer describe an unacked watch as promoted to misconfig.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Watching a repo's issues needed a checker of its own: github_pr only sees pull
requests, and `gh search issues` excludes PRs, so the two watches cover the two
halves without ever double-reporting the same item.

github_issue.py inherits the watermark doctrine from github_pr.py wholesale --
the bounded ascending window, the tie-safety hold on a saturated page, and the
convention that `complete` means "everything up to advance_to", not "the backlog
is done". That doctrine is asserted again in its own suite rather than trusted
because the sibling is tested: a copied fix is exactly what rots when someone
simplifies the query back to a suffix scan and every count assertion still
passes.

Two things are new, and both are also backported to github_pr.py:

  * `gh_account` resolves a token per subprocess via `gh auth token -u`, for a
    repo that is private to a second GitHub account. Never `gh auth switch`,
    which mutates global state and breaks every other repo in the same tick.
  * `search` now refuses any token starting with `-`. The string is spliced into
    argv ahead of gh's own flags, so a leading dash lands as a real flag:
    `--include-prs` would put PRs back into an issues query and bill two
    dispatches for one event. An adversarial review of the new checker found
    this, along with a 404 classified as a retryable error rather than the
    misconfig it is -- both fixed here, both with tests.

The skill is the other half. A checker is registered in seven places, none
derived from the others, and a missed one fails quietly rather than loudly: the
type scaffolds fine and then no watch of it ever fires. yaas-checker-authoring
lists all seven with the consequence of skipping each, plus the seven docs that
go stale, the tests a checker has to pass, and the two incidents (the 14-hour
suffix-scan stall, the $1k dispatch storm) that motivate the rules.

Source commit: 92654cfb03e0bf2a236d2d14392d2ad943feed97
@guangmian-circle
guangmian-circle force-pushed the publish/yaas-v2-20260810-074616 branch 2 times, most recently from a2e85ad to 441374e Compare August 12, 2026 12:49
…ip, stalled-item reclaim

Acting on a review card now removes it immediately and offers a 6s undo
toast instead of leaving the card on screen until the next poll.

In-flight items (reviewed / executing / needs_reply) move out of the
carousel into the strip below it, which is read-only: a worker is either
mid-run or rewriting the text, so an edit box there is a lost edit. A
stalled item is promoted back into the carousel with a single Reclaim
control, since nobody is coming for it otherwise.

Also: draft edits POST to /api/edit instead of overloading /api/review,
the 'other' divider counts off surviving items so a dismissal cannot
shift it onto the wrong card, and a drained queue reports what is still
in flight instead of claiming 'All clear'.
@guangmian-circle
guangmian-circle force-pushed the publish/yaas-v2-20260810-074616 branch from d843f70 to 309aab8 Compare August 12, 2026 15:29
@guangmian-circle

Copy link
Copy Markdown
Collaborator Author

@nexx88 final changes made, ready for you to merge.

@nexx88 nexx88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated code review (Claude Code). No high-severity correctness bug; findings below range from unsafe-delete edge cases to doc/dead-code cleanups. The new log-event.py helper and its behaviour test (23/23 pass) are clean.

# A quest id is a directory name. Reject traversal before touching the path.
if "/" in quest_id or quest_id in ("", ".", ".."):
die(f"invalid quest id '{quest_id}'")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncaught crash on NUL byte in quest_id (low). The guard only rejects /, so a quest_id with an embedded NUL byte passes, reaches Path.is_dir(), and raises an uncaught ValueError('embedded null byte') — a raw traceback instead of the intended error: invalid quest id exit 1. Unlikely from real callers, but it's an unhandled crash at an input-validation trust boundary. Consider rejecting \x00 (or any control char) alongside /.

# checkers read them.
#
# Advisory, NOT a whitelist. Real timelines carry ~27 distinct event names,
# most of them quest-specific (brief_written, weekly_recap_posted, pr_merged,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three helpers duplicated from slack-send.py (low). _quest_dir, _append_timeline, and _utc_now are byte-for-byte copies. The docstring justifies duplicating _repo_root (its path-bootstrap depth dependency is the very bug being fixed), but these three don't participate in bootstrap — once REPO_ROOT is resolved they could live in a shared module. Two copies means a fix to timeline-locking or quest-lookup logic must land in both files and can silently drift.

#
# Advisory, NOT a whitelist. Real timelines carry ~27 distinct event names,
# most of them quest-specific (brief_written, weekly_recap_posted, pr_merged,
# tests_run). Rejecting those would push a worker straight back to hand-writing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 604 union without from __future__ import annotations (very low / portability). -> Path | None is evaluated at definition time and raises TypeError on Python < 3.10 at import. Runtime here is 3.14 and this matches slack-send.py's convention, so this only bites a 3.9 deployment — flagging for completeness.

- **A DM arrived from a watched partner** → read the thread context, compose a response (draft first unless quest explicitly authorizes `allow_send`), log action.
- **A new top-level message in a watched channel** → apply the quest's `context.md` decision rules. If the common fast-path is "log and ignore," just exit without any file edits.
- **A new email matching a watched query** → read the full message, apply the quest's `context.md` decision rules. **Always acknowledge the email** with a reply (via `yaas-triage/skills/yaas-gmail-reply/gmail-reply.py`) before or immediately after taking action — even if the action is just "request submitted, will follow up." Exception: bulk, automated, or notification emails where a human reply would be inappropriate. Log `info_received` or `message_sent` to `timeline.ndjson`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Residual "log to timeline.ndjson" guidance (low). Some instructions still tell workers to write timeline.ndjson directly, contradicting the PR's "never write the NDJSON line yourself" rule. It passes the behaviour test (no <utc_iso> template for the grep to catch), but still nudges a worker toward hand-writing an entry with an invented ts — the exact failure log-event.py was added to prevent. Route these through the helper.

"""A slack_thread whose parent is older than the cutoff. cutoff_epoch None → never."""
if cutoff_epoch is None:
return False
return w.get("type") == "slack_thread" and _thread_epoch(w) < cutoff_epoch

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retire_thread deletes on unknown age — fails UNSAFE (higher severity). A slack_thread watch with a missing or non-numeric thread_ts makes _thread_epoch(w) return 0.0, so 0.0 < cutoff_epoch (~now-14d) is always true and the watch is silently retired/deleted — dropping tracking of a possibly-live thread. This contradicts the file's own ephemeral rule (_created_epoch/retire_ephemeral), which deliberately backfills unknown age and keeps the watch. Deletion is the highest-consequence op in this file; unknown age should be treated as young, not infinitely old.

if not m or not page_ts:
break
cursor = m.group(1)
if pages >= MAX_PAGES:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reaction sweep never early-stops — burns rate-limit budget. Results are sorted timestamp desc, so all new reactions are on the first page(s) and everything older is already in known. But the loop only breaks on cursor exhaustion or MAX_PAGES (30), so on a busy workspace it walks ~30 pages × 4 emojis ≈ 120 Slack searches every run — exactly the budget the tick's fairness rotation tries to conserve. Break once a full page yields no unknown ts.

if not line:
continue
try:
ts = json.loads(line).get("ts", "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Archives untimestamped run-log events regardless of age. An event line that parses as JSON but has no ts yields ts="", and "" < cutoff is always true, so the line is moved to the monthly archive out of the live run log. spend-window.py reads only the live log for its 24h budget window, so any untimestamped dispatch/cost event silently drops out of spend accounting even if recent. Skip (or keep) lines with no ts rather than treating them as ancient.

breach = ""
if over(out["spend_1h"], cap_1h):
breach = f"1h spend ${out['spend_1h']:.2f} over cap ${float(cap_1h):.2f}"
elif over(out["spend_6h"], cap_6h):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring/behavior contradiction for --cap-6h. Line ~44 documents --cap-6h as "accepted but unused," yet the breach ladder here (elif over(out['spend_6h'], cap_6h)) actively enforces it. A caller relying on the documented no-op would get dispatch blocked with a "6h spend over cap" breach. Reconcile the docstring with the enforcement (or vice versa).

Comment thread yaas-triage/ledger/watch-guard.py Outdated
Comment thread yaas-triage/ledger/watch-guard.py

@nexx88 nexx88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the fixes against 6708d3a..c4be3cd — 10 of 11 findings resolved, the reactions early-stop correctly rejected, and the touched suites pass. One new gap the fixes introduced; otherwise everything is clean.

Comment thread yaas-triage/surfaces/timeline_io.py

@nexx88 nexx88 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Re-reviewed all fixes against the latest commit: 10 of 11 original findings resolved, the reactions early-stop correctly rejected as unsafe, and the follow-up PEP 604 gap in timeline_io.py is now fixed (annotation deferred, both CLIs import cleanly on the 3.9 floor). Touched suites pass; real test suites 44/44 + differential 29/0. Clean.

@guangmian-circle
guangmian-circle merged commit a4bba7c into master Aug 16, 2026
1 check passed
@guangmian-circle
guangmian-circle deleted the publish/yaas-v2-20260810-074616 branch August 16, 2026 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants