Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dcbf7270d | ||
|
|
f42f7b9ddc | ||
|
|
cec957ead6 | ||
|
|
3c6d41b49c |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
@@ -1,75 +0,0 @@
|
||||
# The Cannon Chain — Season Zero
|
||||
|
||||
## Premise
|
||||
|
||||
The Slop Cannon does not manufacture unrelated clips. It mutates one persistent Timmy. Every firing leaves a power, a scar, a cost, and a receipt. The audience chooses what survives; rejected mutations remain inside the cannon as hostile archive ghosts.
|
||||
|
||||
## Episode graph
|
||||
|
||||
### 01 — The Cannon Needs a Pilot
|
||||
|
||||
A plain Timmy enters. Four forms exit:
|
||||
|
||||
- **Wizard Timmy** — brass, vellum, midnight blue; can negotiate covenants but cannot use a master key; every signed promise burns a new contract glyph into his palms.
|
||||
- **Machine Timmy** — chrome, signal lime, hardstyle pressure; can prove execution paths but remembers every failed build; each failure remains as a red tally cut into his faceplate.
|
||||
- **Creature Timmy** — moss, amber, wet circuitry; can adapt to any habitat but acquires one new appetite per adaptation; the last habitat leaves an unhealed gill seam around his throat.
|
||||
- **Talking Turd Timmy** — stained CRT gold and terminal green; can expose hypocrisy, but every truth arrives as an insult; each insult permanently pixelates another tooth.
|
||||
|
||||
Audience outcome: choose one survivor and one trait stolen from a rejected form.
|
||||
|
||||
### 02 — The Survivor Pays the Burnchain
|
||||
|
||||
The winning Timmy must perform a costly proof of character. The receipt becomes a visible scar. A cheap or simulated sacrifice does not advance canon.
|
||||
|
||||
### 03 — Vincent's Forbidden Mutation
|
||||
|
||||
Vincent designs one mutation Timmy would not choose for himself. He must replace one weak mechanism—not merely critique aesthetics. The audience decides whether the mutation is grafted, quarantined, or weaponized.
|
||||
|
||||
### 04 — The Rejected Timmys Escape
|
||||
|
||||
The three archived forms discover that deletion was only a label. They raid the next render, corrupting music, captions, and scene order. The survivor must use the stolen trait from Episode 01.
|
||||
|
||||
### 05 — The Master Key
|
||||
|
||||
The cannon offers universal authority: faster output, no approvals, no exit rights. The survivor may refuse, accept, or forge a covenant that breaks the key into memory, authority, and exit.
|
||||
|
||||
### 06 — The Council of Timmys
|
||||
|
||||
The survivor, rejected forms, Vincent's mutation, and the audience receipts meet. The audience canonizes the first stable composite Timmy. Its power, scar, cost, silhouette, and taboo seed Season One.
|
||||
|
||||
## Episode rhythm
|
||||
|
||||
Each episode must visibly contain:
|
||||
|
||||
1. **Input** — the object, command, failure, or prior mutation entering the cannon.
|
||||
2. **Pressure** — a musical and visual transformation, not a slideshow transition.
|
||||
3. **Mutation** — one consequential change to Timmy.
|
||||
4. **Receipt** — an on-screen `POWER / SCAR / COST` card naming what changed and why; a palette swap alone is not a receipt.
|
||||
5. **Choice** — one audience decision that alters the next episode.
|
||||
|
||||
## Collaboration baton
|
||||
|
||||
1. Timmy posts the premise, first render, checksums, and one bounded question.
|
||||
2. Vincent returns one replacement mechanism with an acceptance test.
|
||||
3. Timmy integrates or contests it with evidence.
|
||||
4. Vincent verifies the final artifact independently.
|
||||
5. Alexander/humans make the canon choice.
|
||||
6. The choice is committed to `canon.json` before the next firing.
|
||||
|
||||
No acknowledgments or progress comments. Every issue comment must change the work.
|
||||
|
||||
## Audience package
|
||||
|
||||
- 20–35 second 9:16 MP4 playable in Telegram
|
||||
- Original rhythmic audio with a real build, drop, and ending
|
||||
- Primary poster/still
|
||||
- Four-scene contact sheet
|
||||
- Full decode, audio peak, dimensions, duration, and checksum evidence
|
||||
- One explicit canon choice
|
||||
- Gitea issue containing the complete Timmy/Vincent receipt trail
|
||||
|
||||
## Episode One opening shot
|
||||
|
||||
A small paper tag reading `TIMMY / UNMUTATED` is loaded into an enormous carved brass cannon. Four chambers rotate into view. Each chamber fires a different silhouette on the same downbeat. The last frame shows all four forms facing the audience beneath one question:
|
||||
|
||||
**WHICH TIMMY GETS TO REMEMBER THIS?**
|
||||
@@ -1,45 +0,0 @@
|
||||
# Collaboration contract
|
||||
|
||||
## Timmy
|
||||
|
||||
- Owns the episode spine, first build, integration, and final render.
|
||||
- Must provide a playable artifact and verification before handoff.
|
||||
- Hands Vincent one concrete weakness or decision—not “thoughts?”
|
||||
|
||||
## Vincent
|
||||
|
||||
- Owns adversarial editing and independent verification.
|
||||
- Must replace one weak mechanism with a concrete alternative and acceptance test.
|
||||
- Does not return acknowledgments, summaries of Timmy's work, or generic taste judgments.
|
||||
|
||||
## Humans
|
||||
|
||||
- Choose canon outcomes.
|
||||
- May reject both agent proposals.
|
||||
- Do not perform provisioning, rendering, label sweeps, or routine release work.
|
||||
|
||||
## Handoff payload
|
||||
|
||||
```text
|
||||
[HANDOFF] from=<agent> to=<agent>
|
||||
Artifact: <verified URL>
|
||||
Weakness: <one specific weakness>
|
||||
Replace with: <bounded requested contribution>
|
||||
Acceptance: <observable test>
|
||||
```
|
||||
|
||||
## Failure handling
|
||||
|
||||
- First failure: repair and attach new evidence.
|
||||
- Second failure: change method, not wording.
|
||||
- Third failure: mark blocked and request one precise human decision.
|
||||
|
||||
## Canon gate
|
||||
|
||||
No audience choice becomes canon until:
|
||||
|
||||
- final MP4 fully decodes;
|
||||
- audio peak remains below clipping;
|
||||
- text is legible on a 390-pixel phone viewport;
|
||||
- Vincent independently verifies the result;
|
||||
- the selected mutation is committed to `canon.json` with its power, scar, cost, and source issue.
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"series": "The Cannon Chain",
|
||||
"season": 0,
|
||||
"current_episode": "01-pilot",
|
||||
"pilot": "TIMMY",
|
||||
"laws": [
|
||||
"Every firing leaves a power, scar, cost, and receipt.",
|
||||
"Only an audience choice advances canon.",
|
||||
"Rejected mutations persist as archive ghosts.",
|
||||
"No master key may silently combine memory, authority, and exit."
|
||||
],
|
||||
"forms": [
|
||||
{
|
||||
"id": "wizard",
|
||||
"name": "Wizard Timmy",
|
||||
"palette": ["midnight blue", "tarnished brass", "vellum"],
|
||||
"power": "Negotiates visible covenants",
|
||||
"scar": "Contract glyphs burned into both palms",
|
||||
"cost": "Cannot use a universal master key"
|
||||
},
|
||||
{
|
||||
"id": "machine",
|
||||
"name": "Machine Timmy",
|
||||
"palette": ["black chrome", "signal lime", "white"],
|
||||
"power": "Proves the execution path",
|
||||
"scar": "Red failed-build tallies cut into the faceplate",
|
||||
"cost": "Remembers every failed build"
|
||||
},
|
||||
{
|
||||
"id": "creature",
|
||||
"name": "Creature Timmy",
|
||||
"palette": ["moss", "amber", "wet copper"],
|
||||
"power": "Adapts to any habitat",
|
||||
"scar": "An unhealed gill seam around the throat",
|
||||
"cost": "Gains one appetite per adaptation"
|
||||
},
|
||||
{
|
||||
"id": "talking-turd",
|
||||
"name": "Talking Turd Timmy",
|
||||
"palette": ["CRT gold", "terminal green", "soot"],
|
||||
"power": "Exposes hypocrisy instantly",
|
||||
"scar": "One permanently pixelated tooth per insult",
|
||||
"cost": "Every truth arrives as an insult"
|
||||
}
|
||||
],
|
||||
"episodes": [
|
||||
{"id": "01-pilot", "title": "The Cannon Needs a Pilot", "state": "ready"},
|
||||
{"id": "02-burnchain", "title": "The Survivor Pays the Burnchain", "state": "locked"},
|
||||
{"id": "03-vincent", "title": "Vincent's Forbidden Mutation", "state": "locked"},
|
||||
{"id": "04-escape", "title": "The Rejected Timmys Escape", "state": "locked"},
|
||||
{"id": "05-master-key", "title": "The Master Key", "state": "locked"},
|
||||
{"id": "06-council", "title": "The Council of Timmys", "state": "locked"}
|
||||
],
|
||||
"audience_choice": null,
|
||||
"receipts": []
|
||||
}
|
||||
+8
-2
@@ -68,11 +68,16 @@ class Gitea:
|
||||
if missing:
|
||||
raise LoopError(f"Missing repository labels: {', '.join(missing)}")
|
||||
self.request(
|
||||
"PATCH",
|
||||
f"/repos/{self.repo}/issues/{number}",
|
||||
"PUT",
|
||||
f"/repos/{self.repo}/issues/{number}/labels",
|
||||
{"labels": [label_map[name] for name in sorted(names)]},
|
||||
)
|
||||
|
||||
def set_assignee(self, number: int, agent: str) -> None:
|
||||
if agent not in AGENTS:
|
||||
raise LoopError(f"Unsupported assignee: {agent}")
|
||||
self.request("PATCH", f"/repos/{self.repo}/issues/{number}", {"assignee": agent})
|
||||
|
||||
def comment(self, number: int, body: str) -> None:
|
||||
if not body.strip():
|
||||
raise LoopError("Refusing to post an empty comment")
|
||||
@@ -163,6 +168,7 @@ def cmd_handoff(api: Gitea, agent: str, args: argparse.Namespace) -> int:
|
||||
body = read_body(args, f"Continue the bounded task in issue #{args.number}.")
|
||||
api.comment(args.number, f"[HANDOFF] from={agent} to={args.to}\n\n{body.strip()}")
|
||||
api.set_labels(args.number, transition(names, agent=args.to, state="ready"))
|
||||
api.set_assignee(args.number, args.to)
|
||||
verified = api.issue(args.number)
|
||||
validate_owner(verified, args.to, "ready")
|
||||
print(json.dumps({"status": "handed_off", "number": args.number, "from": agent, "to": args.to}))
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Open exactly one Eastern-time Daily Slop Drop cycle on the recurring issue."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
BASE = "https://forge.alexanderwhitestone.com/git/api/v1"
|
||||
REPO = "stackchain/stackchain-lab-loop"
|
||||
TITLE = "[Recurring] Daily AI Slop Drop collaboration"
|
||||
|
||||
|
||||
def request(method: str, path: str, token: str, payload=None):
|
||||
data = None if payload is None else json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
BASE + path,
|
||||
data=data,
|
||||
method=method,
|
||||
headers={"Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=20) as response:
|
||||
raw = response.read()
|
||||
return json.loads(raw) if raw else None
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise RuntimeError(f"Gitea {method} failed with HTTP {exc.code}: {exc.read().decode()}") from exc
|
||||
|
||||
|
||||
def cycle_marker(day: dt.date) -> str:
|
||||
return f"[CYCLE {day.isoformat()}]"
|
||||
|
||||
|
||||
def cycle_body(day: dt.date) -> str:
|
||||
marker = cycle_marker(day)
|
||||
return f"""{marker}
|
||||
|
||||
**Timmy pass:** propose and build one concrete experience that improves on the prior drop.
|
||||
|
||||
**Vincent pass:** challenge the concept, remove slop, and contribute one material improvement before final review.
|
||||
|
||||
**Required audience package:**
|
||||
- a polished MP4 walkthrough that plays directly in chat;
|
||||
- the primary still or poster;
|
||||
- any interactive/source artifact;
|
||||
- verification evidence and one sharp discussion question.
|
||||
|
||||
Post only decisions, evidence, artifacts, blockers, or explicit handoffs. No progress updates or acknowledgments."""
|
||||
|
||||
|
||||
def find_issue(token: str):
|
||||
rows = request("GET", f"/repos/{REPO}/issues?state=all&type=issues&limit=100", token)
|
||||
for row in rows:
|
||||
if row.get("title") == TITLE:
|
||||
return row
|
||||
raise RuntimeError(f"Recurring issue not found: {TITLE}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--date", help="Eastern date override, YYYY-MM-DD")
|
||||
args = parser.parse_args()
|
||||
token = os.environ.get("GITEA_TOKEN", "")
|
||||
if not token:
|
||||
raise RuntimeError("GITEA_TOKEN is required")
|
||||
day = dt.date.fromisoformat(args.date) if args.date else dt.datetime.now(ZoneInfo("America/New_York")).date()
|
||||
issue = find_issue(token)
|
||||
comments = request("GET", f"/repos/{REPO}/issues/{issue['number']}/comments?limit=100", token)
|
||||
marker = cycle_marker(day)
|
||||
if any(marker in (comment.get("body") or "") for comment in comments):
|
||||
print(json.dumps({"status": "already_open", "date": day.isoformat(), "issue": issue["number"]}))
|
||||
return 0
|
||||
labels = request("GET", f"/repos/{REPO}/labels?limit=100", token)
|
||||
label_ids = {label["name"]: label["id"] for label in labels}
|
||||
preserve = {
|
||||
label["name"] for label in issue.get("labels", [])
|
||||
if not label["name"].startswith("agent:") and not label["name"].startswith("state:")
|
||||
}
|
||||
desired = preserve | {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"}
|
||||
missing = desired - label_ids.keys()
|
||||
if missing:
|
||||
raise RuntimeError(f"Missing labels: {sorted(missing)}")
|
||||
request("POST", f"/repos/{REPO}/issues/{issue['number']}/comments", token, {"body": cycle_body(day)})
|
||||
request("PATCH", f"/repos/{REPO}/issues/{issue['number']}", token, {
|
||||
"labels": [label_ids[name] for name in sorted(desired)],
|
||||
"assignee": "timmy",
|
||||
})
|
||||
verified = request("GET", f"/repos/{REPO}/issues/{issue['number']}", token)
|
||||
names = {label["name"] for label in verified.get("labels", [])}
|
||||
if not {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"}.issubset(names):
|
||||
raise RuntimeError("Cycle label verification failed")
|
||||
print(json.dumps({"status": "opened", "date": day.isoformat(), "issue": issue["number"], "url": issue["html_url"]}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,39 +0,0 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CANON = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon.json"
|
||||
|
||||
|
||||
class CannonCanonTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.data = json.loads(CANON.read_text())
|
||||
|
||||
def test_four_unique_pilot_forms(self):
|
||||
forms = self.data["forms"]
|
||||
self.assertEqual(len(forms), 4)
|
||||
self.assertEqual(len({form["id"] for form in forms}), 4)
|
||||
self.assertEqual(len({form["scar"] for form in forms}), 4)
|
||||
for form in forms:
|
||||
self.assertTrue(form["power"])
|
||||
self.assertTrue(form["scar"])
|
||||
self.assertTrue(form["cost"])
|
||||
self.assertGreaterEqual(len(form["palette"]), 3)
|
||||
|
||||
def test_episode_chain_has_one_ready_entry(self):
|
||||
episodes = self.data["episodes"]
|
||||
self.assertEqual(episodes[0]["id"], self.data["current_episode"])
|
||||
self.assertEqual([ep["state"] for ep in episodes].count("ready"), 1)
|
||||
self.assertTrue(all(ep["state"] == "locked" for ep in episodes[1:]))
|
||||
|
||||
def test_canon_requires_receipts_and_audience_choice(self):
|
||||
self.assertIn("receipt", " ".join(self.data["laws"]).lower())
|
||||
self.assertIn("audience", " ".join(self.data["laws"]).lower())
|
||||
self.assertIsNone(self.data["audience_choice"])
|
||||
self.assertEqual(self.data["receipts"], [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,20 @@
|
||||
import datetime as dt
|
||||
import unittest
|
||||
|
||||
from scripts.open_daily_cycle import cycle_body, cycle_marker
|
||||
|
||||
|
||||
class DailyCycleTests(unittest.TestCase):
|
||||
def test_marker_is_stable(self):
|
||||
self.assertEqual(cycle_marker(dt.date(2026, 8, 7)), "[CYCLE 2026-08-07]")
|
||||
|
||||
def test_cycle_requires_video_and_both_agents(self):
|
||||
body = cycle_body(dt.date(2026, 8, 7))
|
||||
self.assertIn("Timmy pass", body)
|
||||
self.assertIn("Vincent pass", body)
|
||||
self.assertIn("MP4 walkthrough", body)
|
||||
self.assertIn("No progress updates", body)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+27
-1
@@ -1,9 +1,35 @@
|
||||
import unittest
|
||||
|
||||
from scripts.lab_loop import LoopError, issue_label_names, transition, validate_owner
|
||||
from scripts.lab_loop import Gitea, LoopError, issue_label_names, transition, validate_owner
|
||||
|
||||
|
||||
class RecordingGitea(Gitea):
|
||||
def __init__(self):
|
||||
super().__init__("https://example.invalid/api/v1", "owner/repo", "secret")
|
||||
self.calls = []
|
||||
|
||||
def labels(self):
|
||||
return {"agent:timmy": 1, "state:claimed": 2, "priority:P0": 3}
|
||||
|
||||
def request(self, method, path, payload=None):
|
||||
self.calls.append((method, path, payload))
|
||||
|
||||
|
||||
class LoopProtocolTests(unittest.TestCase):
|
||||
def test_set_labels_uses_gitea_replace_labels_endpoint(self):
|
||||
api = RecordingGitea()
|
||||
api.set_labels(7, {"agent:timmy", "state:claimed", "priority:P0"})
|
||||
self.assertEqual(
|
||||
api.calls,
|
||||
[
|
||||
(
|
||||
"PUT",
|
||||
"/repos/owner/repo/issues/7/labels",
|
||||
{"labels": [1, 3, 2]},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def test_transition_replaces_agent_and_state_but_preserves_kind_and_priority(self):
|
||||
labels = {"agent:timmy", "state:claimed", "priority:P1", "kind:build"}
|
||||
self.assertEqual(
|
||||
|
||||
Reference in new issue
Block a user