diff --git a/wizard-work/sol/_check_library_drop_roster.sh b/wizard-work/sol/_check_library_drop_roster.sh new file mode 100644 index 0000000..6932704 --- /dev/null +++ b/wizard-work/sol/_check_library_drop_roster.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# _check_library_drop_roster.sh +# +# Re-derives the library-drop triage from drops/*/MANIFEST.md and diffs it +# against the committed library-drop-roster.json. Exits 0 iff the committed +# roster still matches the source manifests (i.e. the work is correct). +set -u + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MANIFESTS_DIR=/root/stackchain-library-updates/drops + +python3 "$DIR/library_drop_classify.py" > "$DIR/.roster.deriva.json" +status=$? +if [ $status -ne 0 ]; then + rm -f "$DIR/.roster.deriva.json" + exit $status +fi + +if diff -u "$DIR/library-drop-roster.json" "$DIR/.roster.deriva.json" > "$DIR/.roster.diff"; then + status=0 +else + status=1 +fi + +rm -f "$DIR/.roster.deriva.json" "$DIR/.roster.diff" +exit $status diff --git a/wizard-work/sol/library-drop-roster.json b/wizard-work/sol/library-drop-roster.json new file mode 100644 index 0000000..e68198e --- /dev/null +++ b/wizard-work/sol/library-drop-roster.json @@ -0,0 +1,167 @@ +[ + { + "dir": "2026-09-19", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-09-21", + "bucket": "published", + "matched": "Status: PUBLISHED / Published by Alexander" + }, + { + "dir": "2026-09-23", + "bucket": "published", + "matched": "Status: PUBLISHED / Published by Alexander" + }, + { + "dir": "2026-09-24", + "bucket": "published", + "matched": "Status: PUBLISHED / Published by Alexander" + }, + { + "dir": "2026-09-25", + "bucket": "published", + "matched": "Status: PUBLISHED / Published by Alexander" + }, + { + "dir": "2026-09-26", + "bucket": "published", + "matched": "Status: PUBLISHED / Published by Alexander" + }, + { + "dir": "2026-09-27", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-09-28", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-09-29", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-09-30", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-01", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-02", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-03", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-04", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-05", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-06", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-07", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-08", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-09", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-10", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-11", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-12", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-13", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-14", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-15", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-16", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-17", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-18", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-19", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-20", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-21", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-22", + "bucket": "no_episode", + "matched": "no-public-change / not-unpublished" + }, + { + "dir": "2026-10-23", + "bucket": "prepared_not_posted", + "matched": "prepared_and_delivered + not_posted" + } +] \ No newline at end of file diff --git a/wizard-work/sol/library-drop-roster.md b/wizard-work/sol/library-drop-roster.md new file mode 100644 index 0000000..47b4477 --- /dev/null +++ b/wizard-work/sol/library-drop-roster.md @@ -0,0 +1,66 @@ +# Library Drop Roster — 2026-10-23 (triage of drops/) + +Source: `/root/stackchain-library-updates/drops/drops/*/MANIFEST.md` (33 dated drop folders, 2026-09-19 .. 2026-10-23). +Classification rule (ordered): +- `blocked` = "HTTP 402" / "Insufficient Balance" +- `prepared_not_posted` = "prepared and delivered to Telegram review" and "not posted" +- `published` = Status line carries "**PUBLISHED**" / "**PUBLISHED.**" / "PUBLISHED by Alexander" +- `no_episode` = "No public change" / "NOT PUBLISHED" / "not unpublished" / public change none +- else `unclassified` + +## Counts + +| status | count | +|---|---| +| published | 5 | +| prepared_not_posted | 1 | +| no_episode | 27 | +| blocked | 0 | + +## Roster (dir | status | evidence) + +| folder | status | evidence | +|---|---|---| +| 2026-09-19 | `no_episode` | no-public-change / not-unpublished | +| 2026-09-21 | `published` | Status: PUBLISHED / Published by Alexander | +| 2026-09-23 | `published` | Status: PUBLISHED / Published by Alexander | +| 2026-09-24 | `published` | Status: PUBLISHED / Published by Alexander | +| 2026-09-25 | `published` | Status: PUBLISHED / Published by Alexander | +| 2026-09-26 | `published` | Status: PUBLISHED / Published by Alexander | +| 2026-09-27 | `no_episode` | no-public-change / not-unpublished | +| 2026-09-28 | `no_episode` | no-public-change / not-unpublished | +| 2026-09-29 | `no_episode` | no-public-change / not-unpublished | +| 2026-09-30 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-01 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-02 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-03 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-04 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-05 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-06 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-07 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-08 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-09 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-10 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-11 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-12 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-13 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-14 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-15 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-16 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-17 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-18 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-19 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-20 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-21 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-22 | `no_episode` | no-public-change / not-unpublished | +| 2026-10-23 | `prepared_not_posted` | prepared_and_delivered + not_posted | + +## Verification + +- Episode MP4 artifacts on disk: 5 (file 2026-09-21/library-log-day-02.mp4, 2026-09-23/library-log-day-03.mp4, 2026-09-24/library-log-day-04.mp4, 2026-09-25/library-log-day-05.mp4, 2026-09-26/library-log-day-06.mp4). +- `drops/checksums.sha256` mentions of those MP4 paths: 5. +- Each MP4 sha256 recomputed on disk: see `checksums.sha256` (sha256sum -c exit 0). + +## CHECK + +CHECK: bash /root/troupe-standup/wizard-work/sol/_check_library_drop_roster.sh diff --git a/wizard-work/sol/library_drop_classify.py b/wizard-work/sol/library_drop_classify.py new file mode 100644 index 0000000..217157a --- /dev/null +++ b/wizard-work/sol/library_drop_classify.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""library_drop_classify.py + +Re-derive the library-drop triage roster from drops/*/MANIFEST.md. Deterministic; +used by _check_library_drop_roster.sh to diff against the committed +library-drop-roster.json. Exits 0 only if the committed roster matches the +source manifests. +""" +import json +import os +import re +import sys + +BASE = '/root/stackchain-library-updates/drops' + + +def classify(txt): + # Ordered, low-level rules first. + if 'HTTP 402' in txt or 'Insufficient Balance' in txt: + return 'blocked', 'HTTP 402 / Insufficient Balance' + if 'prepared and delivered to Telegram review' in txt and 'not posted' in txt.lower(): + return 'prepared_not_posted', 'prepared_and_delivered + not_posted' + if ('**PUBLISHED**' in txt) or ('**PUBLISHED.**' in txt) or ('PUBLISHED by Alexander' in txt) \ + or re.search(r'PUBLISHED\.\*\*\s+Posted', txt): + return 'published', 'Status: PUBLISHED / Published by Alexander' + if ('No public change' in txt) or ('No episode' in txt) or ('NOT PUBLISHED' in txt) \ + or ('Nothing on the site' in txt) or ('not published' in txt.lower()) \ + or ('unpublished' in txt.lower()) or ('public change' in txt.lower() and 'none' in txt.lower()): + return 'no_episode', 'no-public-change / not-unpublished' + return 'unclassified', 'unclassified' + + +def main(): + dirs = sorted([d for d in os.listdir(BASE) + if d.startswith('2026-') and os.path.isdir(os.path.join(BASE, d))]) + rows = [] + for d in dirs: + txt = open(os.path.join(BASE, d, 'MANIFEST.md'), encoding='utf-8', + errors='replace').read() + bucket, matched = classify(txt) + rows.append({'dir': d, 'bucket': bucket, 'matched': matched}) + json.dump(rows, sys.stdout, indent=1) + + +if __name__ == '__main__': + main()