JSON-Rohbericht maschinenlesbar
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 795,
"has_wiki": true,
"homepage": null,
"languages": {
"Go": 1008269,
"Makefile": 1080,
"Dockerfile": 3321,
"Go Template": 4238
},
"pushed_at": "2026-07-24T23:15:33Z",
"created_at": "2026-07-17T20:44:02Z",
"owner_type": "User",
"updated_at": "2026-07-24T23:15:21Z",
"description": "Receipt Printer as a Service — a self-hosted daemon that turns any ESC/POS thermal printer into an API-addressable appliance.",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "Go",
"significant_languages": [
"Go"
]
},
"owner": {
"blog": null,
"name": null,
"type": "User",
"login": "harveysandiego",
"company": null,
"location": null,
"followers": 0,
"avatar_url": "https://avatars.githubusercontent.com/u/18230246?v=4",
"created_at": "2016-04-02T12:21:34Z",
"is_verified": null,
"public_repos": 6,
"account_age_days": 3770
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.5.1",
"kind": "patch",
"published_at": "2026-07-24T23:17:45Z"
},
{
"tag": "v0.5.0",
"kind": "minor",
"published_at": "2026-07-24T22:55:51Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-07-23T11:47:46Z"
},
{
"tag": "v0.3.1",
"kind": "patch",
"published_at": "2026-07-21T23:04:11Z"
},
{
"tag": "v0.3.0",
"kind": "minor",
"published_at": "2026-07-21T22:28:07Z"
},
{
"tag": "v0.2.0",
"kind": "minor",
"published_at": "2026-07-21T21:36:47Z"
},
{
"tag": "v0.1.1",
"kind": "patch",
"published_at": "2026-07-20T15:37:15Z"
},
{
"tag": "v0.1.0",
"kind": "minor",
"published_at": "2026-07-20T15:16:56Z"
}
],
"recent_commits": [
{
"oid": "dfb469e5a81180981029df42e8b2ef6af7f6f3fd",
"body": "PR #4 (Dependabot) bumped this to golang:1.26-bookworm, which I merged\nwithout first checking go.mod's own version-policy comment. go.mod's `go`\ndirective is deliberately pinned to oldstable, not the newest release —\nCI's \"stable\"/\"oldstable\" matrix (.github/workflows/ci.yml) currently\nresolves stab\n[…]\ne\nlockstep the comment describes; verified via a local `docker build`.\n\nBoth this project's images and go.mod should bump together, to 1.26, once\nGo 1.27 ships and 1.26 becomes oldstable — not before.",
"is_bot": false,
"headline": "revert(docker): keep golang base image at 1.25-bookworm",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:14:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3bae4e2f4d8bc819cb8b3abf3ebf3e2d9dbf65d6",
"body": "Two fixes since v0.5.0: the Docker build (web/ was excluded from the\nbuild context, so internal/webui's //go:embed dependency on it couldn't\nresolve inside the container) and a pre-existing intermittent data race\nin a cmd/receiptd test, found via the same CI run that caught the Docker\nissue. Both ar\n[…]\npolicy calls that a PATCH bump. Moves the Unreleased\nentry under a dated 0.5.1 heading and points README's \"is tagged and\npublished\" link at it, the same way 8c7130d cut v0.2.0 and 575b872 cut\nv0.3.1.",
"is_bot": false,
"headline": "docs(changelog,readme): cut v0.5.1",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:11:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d2d72a701eecd6029ef553a6f71dd86c17779dff",
"body": "TestDaemon_Run_ReconcilesOrphanedRunningJobBeforeAnyWorkerStarts spawns\nd.run() in its own goroutine, then read d.workerCancel directly from the\ntest's main goroutine to clean up the worker afterward. The test's\npolling loop only synchronizes on the Job's state, which Reconcile sets\nbefore startWork\n[…]\nefore spawning the ListenAndServe goroutine or entering\nthe signal-handling select, so shutdownOnSignal's own read of\nd.workerCancel/d.workers, in the same goroutine, is already safe by\nconstruction).",
"is_bot": false,
"headline": "fix(cmd/receiptd): fix data race in reconciliation-ordering test cleanup",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:11:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "544bb8fab831336945d2786220fc3b9d0d035402",
"body": "The v0.5.0 release build failed: internal/webui embeds web/'s templates\nand static assets via //go:embed (docs/adr/0022), but .dockerignore still\nexcluded web/ entirely, and the Dockerfile never COPYed it into the\nbuild stage — both predate Milestone 4, from when internal/webui had no\nsuch dependenc\n[…]\nn disk), which is why it went unnoticed through\nevery quality-gate run across all of Milestone 4 and its hardening pass\n— only the actual container build exercises .dockerignore/COPY's file\nselection.",
"is_bot": false,
"headline": "fix(docker): include web/ in the Docker build context",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:11:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6bac14eb78cf05739b2ec763afe269ef004c530c",
"body": "…26-bookworm\n\nchore(docker): bump golang from 1.25-bookworm to 1.26-bookworm",
"is_bot": false,
"headline": "Merge pull request #4 from harveysandiego/dependabot/docker/golang-1.…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:11:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "411638028a9abc8c9a3cc43fcfe0e9a65655d529",
"body": "…ithub-actions-86662e5b23\n\nchore(ci): bump the github-actions group with 6 updates",
"is_bot": false,
"headline": "Merge pull request #3 from harveysandiego/dependabot/github_actions/g…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T23:09:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c9bc3e50bfd743152337b6d0c9a5e7a0ea8e2b43",
"body": "Milestone 4 (Web UI: dashboard, printer settings, assets, preview, quick\nprint, client-side polling, CSRF protection, security headers) is new,\nbackwards-compatible functionality — VERSIONING.md's policy calls that a\nMINOR bump. Moves the Unreleased entry under a dated 0.5.0 heading and\npoints READM\n[…]\n575b872 cut v0.3.1.\n\nv0.4.0 predates all of this Web UI work (it shipped Receipt.copies, crash\nrecovery, idempotent requests, and graceful shutdown) — this is the first\nrelease to include Milestone 4.",
"is_bot": false,
"headline": "docs(changelog,readme): cut v0.5.0",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T22:52:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "25a119ee99922ccafa713d70413f62efb76fdae5",
"body": "…pass\n\nBrings docs/ARCHITECTURE.md's \"Milestone 4 as built\" section, README, and\nCHANGELOG up to date with 766605a's reliability/security hardening: CSRF\nprotection (ADR-0027), concurrent timeout-bounded printer status,\nsecurity headers, buffered rendering, request body limits on\npreview/print, status detail sanitization, and dashboard.js's\noverlap/visibility handling. No code changes.",
"is_bot": false,
"headline": "docs(architecture,readme,changelog): sync docs for release-hardening …",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T22:49:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f16bc377a7db1e0225bbada3612dcbbdb702cbd0",
"body": "…curity)\n\nRelease-hardening ahead of the next release (v0.5.0 — Milestone 4/Web UI\nis unreleased; the already-tagged v0.4.0 predates all of this work),\naddressing reliability and security issues found during Milestone 4\nreview. No new features; existing architecture and package boundaries\nunchanged.\n[…]\ne ./... are all clean. Manually verified against a running\ndaemon: security headers present on real responses, CSRF token rendered\nand enforced, Preview's embedded PNG unaffected by the tightened CSP.",
"is_bot": false,
"headline": "feat(app,webui): Milestone 4 release-hardening pass (reliability & se…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T22:49:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d08697f12b76e8ace98547c3d339ee7e8715fb8f",
"body": "…adiness pass\n\nIntegration/documentation/release-readiness review for Milestone 4 (Web\nUI), now that ADR-0025's dashboard polling has landed. No Go code\nchanged; docs, ADR status, templates, and stylesheet only.\n\ndocs/ARCHITECTURE.md's \"Milestone 4 as built\" section (§10) now describes\nthe shipped d\n[…]\nSS rules) — carried forward through the\nstash/restore around the ADR-0025 work.\n\ngofmt, goimports, golangci-lint run ./..., go test ./..., and\ngo test -race ./... are all clean (no Go source touched).",
"is_bot": false,
"headline": "docs(adr,architecture,readme,changelog),webui: Milestone 4 release-re…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T21:42:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e7097fe2ff02b1874702c15ede845a8621fb5192",
"body": "GET /status (internal/webui/status.go) now serves a JSON snapshot of the\ndashboard's Printers/Queue cards instead of the old 501 stub: printer\ncount/online count/status message plus the queue's Pending/Running/Done/\nFailed/Cancelled/Total counts. It reads through the exact same\nService.ListPrinters/\n[…]\nontent-Type,\nCache-Control, JSON shape) and a service-failure path via a dedicated\nqueue.Store double.\n\ngofmt, goimports, golangci-lint run ./..., go test ./..., and\ngo test -race ./... are all clean.",
"is_bot": false,
"headline": "feat(webui): implement ADR-0025 dashboard client-side polling",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T21:23:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3faebbfa81b9c7a17e66774fbf6762a943bc3c84",
"body": "Replaces the Print stub with a synchronous print-submission workflow:\nGET /print renders a printer/JSON form plus an explanatory note, POST\n/print decodes the submission, calls the existing app.Service.Print (the\nsame enqueue-only method internal/api's POST /api/v1/print already\nuses), and redirects\n[…]\nwith\nPRG redirect and confirmation, and (the architectural guarantee this\nslice exists to prove, mirroring Preview's own inverse guarantee) that a\nsuccessful submission always creates exactly one Job.",
"is_bot": false,
"headline": "feat(webui): implement Quick Print page (Milestone 4, slice 7)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T20:49:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "10d81617f60cf708be73644f81c5d2b168e6b6b4",
"body": "Bumps golang from 1.25-bookworm to 1.26-bookworm.\n\n---\nupdated-dependencies:\n- dependency-name: golang\n dependency-version: 1.26-bookworm\n dependency-type: direct:production\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "chore(docker): bump golang from 1.25-bookworm to 1.26-bookworm",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-24T19:57:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "02bd602e9f4f7f91c9aa7f5cf7ab6b6b41f15c8f",
"body": "Bumps the github-actions group with 6 updates:\n\n| Package | From | To |\n| --- | --- | --- |\n| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `5` | `7` |\n| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `3` | `4` |\n| [docker/setup-buildx-action](https\n[…]\non\n dependency-version: '7'\n dependency-type: direct:production\n update-type: version-update:semver-major\n dependency-group: github-actions\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "chore(ci): bump the github-actions group with 6 updates",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-24T19:56:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f3492dffdc99730314eed2e5baeff326145e2e4c",
"body": "Replaces the Preview stub with a synchronous receipt-preview workflow:\nGET /preview renders a JSON/printer form with a placeholder, POST /preview\ndecodes the submission and renders the resulting PNG as an embedded data\nURI. No new service-seam methods were needed — app.Service.Preview already\nexiste\n[…]\ny, malformed JSON, invalid receipt content,\nunknown printer, successful generation with a decodable PNG, and (the\narchitectural guarantee this slice exists to prove) that Preview never\nenqueues a Job.",
"is_bot": false,
"headline": "feat(webui): implement Preview page (Milestone 4, slice 6)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T19:41:57Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e4d2b278643e4806634282429cd7758157ae851e",
"body": "Replaces the Assets stub with full asset management: listing, upload\n(multipart/form-data per docs/adr/0026-asset-upload-multipart-form-data.md),\nand delete, all through the ListAssets/UploadAsset/DeleteAsset service\nseam. UploadAsset/DeleteAsset are new thin pass-throughs on app.Service,\nmirroring \n[…]\non\nsuccess.\n\nGET /assets and both POST routes move out of the generic 501-stub\ncoverage in router_test.go/daemon_test.go and get their own\nlisting/empty-state/upload/duplicate/delete/error/auth tests.",
"is_bot": false,
"headline": "feat(webui): implement Assets page (Milestone 4, slice 4)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T19:24:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "248bff4760508d0a7c88cef7b87225d41d159fbd",
"body": "Replaces the Printers stub with a read-only table of every configured\nprinter: name, transport, address, capability profile (width/DPI/cut\nsupport), and current reachability, sourced only through the\nListPrinters service seam (docs/adr/0024-printer-settings-screen-is-\nread-only.md). No forms, no edi\n[…]\ne render helper\ntripped last slice.\n\nGET /printers moves out of the generic 501-stub coverage in\nrouter_test.go/daemon_test.go and gets its own rendering/ordering/status/\nempty-state/error/auth tests.",
"is_bot": false,
"headline": "feat(webui): implement Printers page (Milestone 4, slice 4)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T17:32:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b09a622d70c5cc1b9f8b5dd788c352488326d050",
"body": "Replaces the Dashboard stub with a read-only overview: configured\nprinter/asset counts, a one-line printer reachability status, and the\nqueue's job-state summary, sourced only through the ListPrinters/\nListAssets/QueueSummary service seam. No forms, no JavaScript, no\npolling — this slice is delibera\n[…]\napi/job_status.go already applies to a Job's LastError.\n\nGET / moves out of the generic \"every route is a 501 stub\" coverage in\nrouter_test.go and daemon_test.go and gets its own rendering/auth tests.",
"is_bot": false,
"headline": "feat(webui): implement Dashboard page (Milestone 4, slice 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T17:19:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bbccb86d5652c33be12e568e0217fcbb8b0e7b74",
"body": "…Milestone 4, slice 2)\n\nExposes ListPrinters, ListAssets, and QueueSummary on app.Service, each\nreturning an app-owned presentation model (PrinterSummary, AssetSummary,\nQueueSummary) rather than printer.Connection, queue.Job, or config\ntypes. ConnectionSummary carries only the transport/address disp\n[…]\ning into the Store directly.\ncmd/receiptd's buildPrinters now also builds the Connections map from\nthe same construction loop as Printers/Profiles. The Web UI is\nuntouched — handlers remain 501 stubs.",
"is_bot": false,
"headline": "feat(app,queue,cmd/receiptd): add Service Seam presentation methods (…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T16:33:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8d18391ed3a4f5d5b4f5d90d346c2b017ca9a4da",
"body": "…ne 4, slice 1)\n\nAdds internal/webui's route table, base html/template layout, and\nembedded static assets, with every handler currently a 501 stub — no\npage logic lands until its own slice. cmd/receiptd now mounts the Web\nUI separately from /api/v1, behind auth.Basic (docs/adr/0023) instead\nof the API's auth.Bearer, gated by the existing web.enabled config\nflag.",
"is_bot": false,
"headline": "feat(webui,web,cmd/receiptd): scaffold Web UI infrastructure (Milesto…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-24T15:30:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0bc3d706d0122c965a603d7be69cb820feafbd5a",
"body": "Freezes five open design questions ahead of Web UI implementation:\nserver-rendered html/template over a JS SPA, reusing the existing\nshared-token auth with no session layer, read-only printer settings,\nclient-side polling over a push channel, and multipart/form-data for\nasset uploads. All Proposed, not Accepted, pending implementation.",
"is_bot": false,
"headline": "docs(adr): propose ADRs 0022-0026 for Milestone 4 Web UI design",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T15:46:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0352aab12b587c1baea367d86a7aa0e19fc4265a",
"body": "…d docs\n\nReceipt.Copies now rejects (and EffectiveCopies clamps) values above\nmaxCopies=100, so one request can't monopolize a printer's worker.\nCorrected two stale/misleading comments found during review:\nnetworkTimeout's rationale still described the pre-ADR-0016 single\nglobal worker, and shutdown\n[…]\ny cancellation, which isn't\ntrue — the actual safety property is that networkPrinter.Send ignores\nctx once its write loop starts. Also bumps CHANGELOG/README for the\nv0.4.0 release this rolls up into.",
"is_bot": false,
"headline": "fix(receipt,printer,docs): bound Receipt.Copies, clarify shutdown/Sen…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T11:45:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bf1ccbd3a447ac0e30d4451a7b2ce5a05042f125",
"body": "app.Service.Process now sends the rendered/encoded byte stream to the\nprinter Receipt.EffectiveCopies() times instead of once, rendering and\nencoding exactly once per Job. Receipt.Validate rejects a negative\nCopies; zero is treated as one for backwards compatibility. Previously\nthe field was decoded and round-tripped with no effect.",
"is_bot": false,
"headline": "feat(app,receipt): implement Receipt.Copies as multi-copy printing",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T11:06:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ca790584db67a82b6a04a4949632ee470585a2c1",
"body": "…7 Reconcile\n\nADR-0019 decided against caching rendered/encoded bytes across retry\nattempts, with no code change of its own. Confirmed that decision still\nholds after ADR-0016/0017/0018/0020: queue.Job has no cached-bytes field,\nand app.Service.Process still unconditionally redoes render+encode+send\n[…]\nthe single shared timestamp across\nevery Job one Reconcile call resolves (one restart event, not N).\n\ngo build, go vet, golangci-lint run ./..., gofmt/goimports, and\ngo test -race ./... are all clean.",
"is_bot": false,
"headline": "docs(adr): confirm ADR-0019 retry granularity, review pass on ADR-001…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T10:51:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cf788caa9a1ef09b9db96a84ece7f8b78dd2f539",
"body": "…ation\n\nAdd Queue.Reconcile, which resolves every Job found JobRunning at startup\ninto Pending (retry, if attempt budget remains) or Failed (budget\nexhausted), treating the interruption itself as one consumed attempt.\ncmd/receiptd's daemon.run now calls Reconcile synchronously, before any\nper-printe\n[…]\ndy\npartway to budget runs only its remaining attempts on reclaim, not a\nfresh full budget.\n\nFlips ADR-0017 to Accepted, documents reconciliation in\ndocs/ARCHITECTURE.md §6, and adds a CHANGELOG entry.",
"is_bot": false,
"headline": "feat(queue,cmd/receiptd): implement ADR-0017 crash-recovery reconcili…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T10:30:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8f889f4e3dae6a45fab42514d0dd2f6705e90178",
"body": "Per-printer worker concurrency, bounded graceful shutdown, and idempotent\nprint requests, implemented together and reconciled into one coherent\nqueue/daemon lifecycle since the three are genuinely interdependent (all\nthree reshape the same retry loop and worker topology, not just adjacent\nfeatures l\n[…]\ne-period requirement, and\nCHANGELOG.md's Unreleased section covers all three as user-facing changes.\n\ngo build, go vet, golangci-lint run ./..., gofmt/goimports, and\ngo test -race ./... are all clean.",
"is_bot": false,
"headline": "feat(queue,cmd/receiptd,app,api): implement ADR-0016, ADR-0018, ADR-0020",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T10:08:25Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b5fd8f99987f86b89c89503302f26c7732421b7b",
"body": "Comments across the codebase had grown to explain *what* the code does\nrather than *why*, often with doc comments longer than the functions they\ndocumented and ADR cross-references repeated in every paragraph. This is a\ncomment-only sweep: no behavior change.\n\n- CLAUDE.md: add a \"Comment length and \n[…]\npanic recovery, sanitized-error trust\n boundary, constant-time auth, path-traversal and stack-depth limits).\n\ngofmt/goimports clean, golangci-lint 0 issues, go vet clean, go test\n-race ./... passing.",
"is_bot": false,
"headline": "docs,refactor: trim over-long comments and add commenting guidance",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-23T09:44:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6ea9b10c6651dc85c2a3730d0ed39eea5cf98b52",
"body": "Documentation-only pass, no behavior change:\n\n- Removed Markdown as a described input format from the README's\n Philosophy section and architecture diagram — no parser exists anywhere\n in the codebase, and it isn't on any milestone.\n- Moved \"Web UI\" and \"server-side templates\" out of the Features \n[…]\nure work. CHANGELOG.md is a historical record and already accurate\nfor each past release. CONTRIBUTING.md's one Markdown mention is already\nframed as \"if/when it's built,\" not a current-feature claim.",
"is_bot": false,
"headline": "docs: audit README/SECURITY for accuracy against current implementation",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-22T16:10:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "807c3e7f1ef1332625a5406b632e0dba71c38429",
"body": "…dress\n\nTwo review-flagged issues fixed:\n\n- assets.FilesystemStore.Get/Delete wrapped the raw os error into their\n KindNotFound (a 4xx), leaking the full filesystem path to API clients;\n now constructs a clean \"asset %q not found\" message, matching\n memoryStore's existing convention.\n- JobStatusH\n[…]\nor both info-disclosure fixes against the real app.Service\npipeline (missing asset via Preview, transient printer failure via\nJobStatus), plus config validation tests for the new server.address\ncheck.",
"is_bot": false,
"headline": "fix(api,config): sanitize error/LastError leaks and require server.ad…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-22T15:58:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2beae4725f77c06cf4df7c1f518cfab164e4f13a",
"body": "Second review pass on the 0016-0020 ADR set, plus a new ADR:\n\n- 0021 (new): TLS termination is a deployment concern, delegated to a\n reverse proxy (Caddy/nginx/Traefik/SWAG/HAProxy/Apache), not something\n receiptd implements itself. Receiptd keeps owning application-layer\n auth via internal/auth.\n[…]\nHITECTURE.md: noted, as a future non-blocking consideration, that\n per-Element validation bounds a single Element's shape but not a\n Receipt's aggregate size.\n\nDocs-only: no production code changes.",
"is_bot": false,
"headline": "docs(adr): add ADR-0021 and tighten 0016-0018 after review",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-22T15:42:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a77977b12f4a1566afbb7ab378637763e44b611a",
"body": "Settles the architecture for the work flagged in the prior hardening PR\n(queue lifecycle, graceful shutdown, crash recovery, concurrency model,\nidempotency) before any of it is implemented:\n\n- 0016: per-printer worker concurrency and atomic Job claiming — a slow\n or offline printer must never block\n[…]\ngy, clean ownership boundaries\nbetween ADRs (notably 0016 vs. 0017), and implementation details trimmed\nback to behavioral invariants rather than concrete APIs.\n\nDocs-only: no production code changes.",
"is_bot": false,
"headline": "docs(adr): add ADRs 0016-0020 for the next architectural phase",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-22T15:16:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a8f321a51039240f1921d9ebce089a595d40d6b0",
"body": "Addresses several implementation-level gaps before the architectural work\n(queue lifecycle, graceful shutdown, crash recovery, concurrency model,\nidempotency) that needs ADRs first:\n\n- Bound Text.Size, Divider.Size, and Barcode.Height so an oversized value\n can no longer force an excessive allocati\n[…]\ne.\n- Add test coverage proving the existing HTTP error sanitisation is\n keyed off status code, not apperr.Kind, even for an error with no Kind\n at all.\n\nSee CHANGELOG.md for the user-facing summary.",
"is_bot": false,
"headline": "fix: harden implementation ahead of ADR-driven queue/shutdown work",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-22T15:08:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "575b872eb58ba41de01d1e60fd03b93845461fc0",
"body": "The 5xx error-sanitization fix (7832130) is a security hardening patch\nwith no compatibility impact on the REST API response shape — per\nVERSIONING.md, that's a PATCH bump. Moves the Unreleased entry under a\ndated 0.3.1 heading, filed under Security per Keep a Changelog's\nconvention for vulnerability-related fixes.",
"is_bot": false,
"headline": "docs(changelog): cut v0.3.1",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T23:01:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "78321304d967a9dd6a1419683ced3b7e31a9e7f4",
"body": "The REST API is the trust boundary, but writeError was returning\nerr.Error() verbatim for every status, including 500/503 — leaking\napperr.Error Op strings, filesystem/database paths, and raw network\nerrors to any caller who happened to trigger a server-side failure.\n\n5xx responses now get a fixed generic message; the real error is still\nlogged server-side first. 4xx behavior (validation, malformed JSON,\nnot found, unauthorized) is unchanged, since that detail is actionable\nfor the caller.",
"is_bot": false,
"headline": "fix(api): never leak internal error detail in 5xx responses",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T22:55:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a248a4f0f4a0634f3fa9e9d2c128f60619406f04",
"body": "pkg.go.dev only indexes a version once the proxy is asked for it, so\nwithout this every release required someone to manually hit \"Request\"\non the site. Add a step that warms proxy.golang.org for the tag that\ntriggered the workflow.",
"is_bot": false,
"headline": "ci(release): auto-notify the Go module proxy on tagged releases",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T22:30:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "66d6d89224fc4e4fd8ee9e82e6fb739adc86dc71",
"body": "…uration\n\nReplaces the flat width_mm printer config field with two mutually\nexclusive modes: model: (resolved against a new printer.ModelProfiles\ncatalogue, seeded with the hardware-validated epson-tm-m30ii) or an\nexplicit profile: block using printable_width_mm. A printer entry\ngiving both or neither is rejected with apperr.KindValidation. No\nalias or migration shim for width_mm - this is a breaking, pre-1.0\nschema change per the ADR.",
"is_bot": false,
"headline": "feat(config,printer): implement ADR-0015 model/profile printer config…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T22:25:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b3ac0b2f0ef35e6181995267aab02987edb8e049",
"body": "…euristic\n\nRecords the decision to fix printable-width misconfiguration (paper roll\nwidth vs. printhead width, found during real-hardware List testing) with\ntwo mutually exclusive printer config modes - a model: catalogue backed\nby verified printer.ModelProfiles entries, or an explicit profile: block\nwith printable_width_mm - rather than a paper-width heuristic that could\nsilently misconfigure hardware it hasn't verified.",
"is_bot": false,
"headline": "docs(adr): add ADR 0015, known-model printer catalogue over a width h…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T22:07:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8c7130d5d2a0c0200c690fb229ed7701370eec03",
"body": "The list Element type (docs/adr/0014-list-elements.md) is a new,\nbackwards-compatible schema addition — VERSIONING.md's policy calls that\na MINOR bump. Moves the Unreleased entry under a dated 0.2.0 heading and\npoints README's \"is tagged and published\" link at it, the same way\n5f7be37 cut v0.1.0.",
"is_bot": false,
"headline": "docs(changelog,readme): cut v0.2.0",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T21:34:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "edace9ab3d832709210a195d55873180d129a59d",
"body": "Adds bulleted, numbered, and checkbox lists as one receipt.List/ListItem\nElement type with a closed-enum Kind, implementing the design frozen in\ndocs/adr/0014-list-elements.md:\n\n- internal/receipt/list.go: List/ListItem, Validate() (closed Kind enum,\n non-empty Content/valid UTF-8, Indent bounds, C\n[…]\ns\nbitmap-equivalence proofs (ListLine renders pixel-identically to\nequivalent hand-written Text, both directly and end-to-end through\nBuild). Full suite, golangci-lint, gofmt, and goimports all clean.",
"is_bot": false,
"headline": "feat(receipt): implement List element per ADR-0014",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T21:33:57Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8697c6678d8b2ac28ae7910f6ccbb41582d39bc4",
"body": "Freezes the design for a single receipt.List element covering\nunordered, ordered, and checkbox lists before any implementation\nlands: one Kind-enum type instead of three, a flat semantic Indent\nlevel (no recursive nesting), ASCII-only bracket/hyphen markers (the\nembedded font can't render Unicode bu\n[…]\nion identifiers\n(internal type names, file names, defensive-limit constants) so it\ndocuments the architecture, not a plan — those are left to whichever\nchange actually implements it.\n\nNo code changes.",
"is_bot": false,
"headline": "docs(adr): add 0014 list elements (bullet/number/checkbox)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-21T21:03:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "38d792675329bd093cc61fe464870266f408758d",
"body": "… pending\n\ncmd/receiptd's package doc still said \"as of Milestone 2\" with a\nlog-file-stand-in printer and a \"will become\" claim about\nprinter.Connection; daemon.go's pollInterval comment referenced a\nfake-printer worker; status.go's maxRequestBodyBytes comment flagged\nMilestone 3's Image/Asset eleme\n[…]\n. All of\nthat shipped in v0.1.0 — pkg.go.dev renders these comments verbatim\nper tagged version, so the published godoc for cmd/receiptd was\ndescribing a plan rather than the code. No behavior change.",
"is_bot": false,
"headline": "docs: fix stale Milestone 2/3 doc comments describing shipped work as…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T15:34:57Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c9d30f316534379e667f1d90e518cb1d6b2ced25",
"body": "release.yml's first real run just finished — test, goreleaser, and the\nDocker publish job all succeeded. Verified directly (not assumed):\nghcr.io/harveysandiego/receiptd:{latest,0.1.0,0.1,0} all resolve to a\nproper linux/amd64+linux/arm64 OCI image index, and it's pullable with\nno authentication, so the \"private until manually flipped\" GHCR note\nwas wrong for a public repo — corrected it to what was actually\nobserved instead of what I'd assumed.",
"is_bot": false,
"headline": "docs(readme): flip pre-release wording now that v0.1.0 is live",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T15:23:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5f7be37a39e49280f6c3a44a2a9d1038e0c6039a",
"body": "The Unreleased section only ever accumulated three bullets (repo\nscaffolding + the two Docker entries), even though Milestones 1, 2, 3,\nand 5 have all actually shipped by this point — REST API, job queue,\nauth, ESC/POS printing to real hardware, every Element type, and now\ncontainer packaging. Filling in the real scope here rather than tagging\nagainst the stale three-bullet placeholder.",
"is_bot": false,
"headline": "docs(changelog): cut v0.1.0",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T15:14:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d0ad4ab64d97ce583df4ba74f60db47b8e7b21ba",
"body": "Adds a reusable Buildx workflow (.github/workflows/docker-image.yml)\nthat builds the existing Dockerfile for linux/amd64 and linux/arm64.\nci.yml calls it with push=false on every PR/push so a broken arm64\nbuild is caught before merge; release.yml calls it with push=true on\ntagged releases, publishin\n[…]\n tags (e.g. v0.2.0-rc1) only ever produce the\nfull-version tag, since metadata-action suppresses major/minor/latest\nfor prereleases itself.\n\nNo changes to the Dockerfile itself. Completes Milestone 5.",
"is_bot": false,
"headline": "feat(ci,release): publish multi-arch container images to GHCR",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T15:13:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8cdc27fedc62679f6d1fd74515b39389506bf120",
"body": "Static CGO_ENABLED=0 build layered onto a distroless, non-root runtime\nimage, buildx-compatible (TARGETOS/TARGETARCH, no arch-specific\nassumptions) so it cross-builds amd64/arm64 without changes. BuildKit\ncache mounts speed up repeated module downloads and Go builds without\naffecting the resulting i\n[…]\node changes.\n\nDocuments build/run instructions, required volume mounts, and the\nminimal config Docker needs (server.address bound to 0.0.0.0, paths\nunder /var/lib/receiptd) in README's Docker section.",
"is_bot": false,
"headline": "feat(docker): add multi-stage Dockerfile for local container builds",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T14:50:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8027d90abc2832f2222388309f89965b7a25ecf6",
"body": "….Align/Asset.Width (ADR-0013)\n\nCloses the last \"accepted but not rendered\" schema gap: Text.Align and\nAsset.Align become a closed left/center/right enum validated by both\ntypes, and Asset.Width becomes a real, clamped-to-page render dimension.\n\nText.Align is applied per wrapped line via a new align\n[…]\nssed entirely as padding on already-positioned content, consistent\nwith the existing Table/Columns/Barcode-caption technique. A Receipt that\nnever sets these fields renders byte-identically to before.",
"is_bot": false,
"headline": "feat(receipt,render/layout,render/canvas): implement Text.Align/Asset…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T13:52:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a85dbb8db347dfc00e06e2363d94f786c479928b",
"body": "…sign (ADR-0013)\n\nDesign-only ADR closing the last \"accepted but not rendered\" schema gap\nADR-0007 left open: a closed left/center/right alignment enum shared by\nText and Asset, expressed as leading-space padding for text and leading\nblank-pixel-column padding for Asset bitmaps, reusing the techniqu\n[…]\nf, and preserving receipt.Asset unresolved through to\nPaint, were both evaluated and rejected in the ADR's Alternatives section.\n\nNo implementation yet - render/layout and render/canvas are unchanged.",
"is_bot": false,
"headline": "docs(adr,architecture): propose Text.Align/Asset.Align/Asset.Width de…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T13:23:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "332d34ca7eb5f730689382b533f19b24691c0e77",
"body": "…d pattern and Barcode.ShowText caption\n\nDivider.Style \"dashed\" now paints a repeating on/off pattern\n(render/canvas.paintDivider) instead of always rendering solid;\nBarcode.ShowText now adds a centered caption line beneath the bars via a\nnew layout.BarcodeCaption Block, space-padded against the emb\n[…]\nd -\nBuild lowers Asset into a plain receipt.Image before Paint's independent\nre-decode, so there is currently no way to carry a per-Block width\noverride through without a schema or Block-shape change.",
"is_bot": false,
"headline": "feat(receipt,render/layout,render/canvas): render Divider.Style dashe…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T11:02:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c060a13a9b4100cc572eaabc6c27a17c62730cfc",
"body": "…ining Milestone 3 staleness\n\nCI already generated a coverage.txt via go test -coverprofile but only\nuploaded it as a build artifact, with nothing publishing the number\nanywhere badge-able. Add a Codecov upload step (stable Go leg only, to\navoid uploading the same coverage twice from the matrix) and\n[…]\nl — both left over from before Milestone 3 (real ESC/POS printing,\nconfirmed on hardware) was completed, and both contradicted the\nalready-accurate \"Current status\" section further down the same page.",
"is_bot": false,
"headline": "docs(readme,ci): add Codecov, CodeQL, and Go version badges; fix rema…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T10:10:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ffea41d61ddd81166ec781b57379485c635ead23",
"body": "… unbounded recursion, and stalled printer writes\n\nA milestone review turned up three real vulnerabilities and several\ncases of stale/missing documentation, all fixed together:\n\n- receipt.Image.Validate and render/layout's image decoding had no\n bound on a declared image's pixel dimensions, letting\n[…]\nlouts for\nDivider.Style and Barcode.ShowText, matching the convention already\nused for Text.Align and Asset.Width/Align; and fixes a dangling\ncross-reference in the Columns section of ARCHITECTURE.md.",
"is_bot": false,
"headline": "fix(receipt,render,printer,docs): harden against decompression bombs,…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T10:04:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a577f62c0d7bc1f2bfcde955f428156c0ae834ff",
"body": "…ipt element (Milestone 3)\n\nColumns lays its columns out side by side, weighted by Column.Weight,\nreusing exactly the technique Table already established: render/layout\nword-wraps each column's content to its share of the printable width\nand composes the wrapped lines into padded, column-aligned tex\n[…]\ner the frozen schema, but reported as\napperr.KindPermanent by Build until a real positioning or per-run\nstyling primitive is designed for it (see docs/ARCHITECTURE.md's new\n\"Columns\" section and §11).",
"is_bot": false,
"headline": "feat(receipt,render/layout,render/canvas): implement the Columns rece…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T09:19:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "42240643d80ef10f14b66a835ce12e586ced443e",
"body": "…nt (Milestone 3)\n\nAdds receipt.Asset (name/width/align) resolved by name via a new\nassets.Store (Get/Put/Delete/List), with FilesystemStore backing the\ndaemon (config.Assets.Path) and MemoryStore backing the CLI's offline\nrender path and tests. layout.Build gains ctx and an assets.Store\nparameter t\n[…]\nno render/canvas changes\nneeded. A nil Store remains valid for any Receipt with no Asset element;\nresolving an Asset against a nil Store now fails cleanly as\napperr.KindPermanent instead of panicking.",
"is_bot": false,
"headline": "feat(receipt,assets,render/layout): implement the Asset receipt eleme…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-20T08:36:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6e01e6282cc249bcfb6a715148d622bfd7fdc704",
"body": "…factor\n\nPhysical printing at DividerThickness=4 (ADR-0011) showed the default\nrule reading as a heavy bar rather than a line. Lowers the default to 2\ndots and adds receipt.Divider.Size, an integer thickness scale factor\nmirroring Text.Size's \"0/omitted means unscaled\" convention, so a\nreceipt autho\n[…]\npersedes ADR-0011 with this default and the new field.\n\nVerified via daemon preview and a physical print against the front-desk\nTM-m30II showing four visibly distinct thicknesses (size 1/omitted/2/3).",
"is_bot": false,
"headline": "fix(receipt,render): lower default divider thickness, add Size scale …",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T22:51:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "440f62e9ce2dcdcc944eda963251d89f4bdbc0c4",
"body": "…t element (Milestone 3)\n\nTable is a flat grid of headers/rows (receipt.Table), validated for\nnon-empty headers/rows, row/header column-count agreement, and UTF-8\ncontent. layout.Build derives column widths from the printer's\nprintable width, word-wraps each cell via the existing wrapText\nprimitive,\n[…]\nnted bitmap is unaffected (verified by two tests asserting\nbyte-identical Canvas.Bits against equivalent hand-written Text).\n\nrender/escpos needed no changes: it only ever consumes canvas.Canvas.Bits.",
"is_bot": false,
"headline": "feat(receipt,render/layout,render/canvas): implement the Table receip…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T22:33:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a6493ab374b0a24e9bffb324f90a57d55d917121",
"body": "…ments\n\nThe Feed/Cut and divider-thickness comments added over the last two\ncommits re-derived design rationale inline (ADR-0002/0008-level reasoning\nduplicated in godoc), risking drift between the comment and the actual\ndecision record. Writes ADR-0010 (Canvas.Controls as the mechanism for\nposition\n[…]\nlly non-obvious bits. Also fixes render/escpos's now-stale\ndependency line in docs/ARCHITECTURE.md §1/§11 (it gained a receipt/\ndependency in the Feed/Cut work but the doc wasn't updated at the time).",
"is_bot": false,
"headline": "docs(adr): promote inline design rationale to ADR-0010/0011, trim com…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T22:07:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8ae9826c8c5e1a492842026af28656d9c18c9c68",
"body": "…dware\n\nA physical print showed the one-dot receipt.Divider rule to be nearly\ninvisible on the 203 DPI TM-m30II, the same hardware-legibility problem\ndocs/adr/0008-embedded-font-legibility.md found for the unscaled font.\nBumps DividerThickness from 1 to 4 dots (~0.5mm).",
"is_bot": false,
"headline": "fix(render/layout): thicken the default divider for legibility on har…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T22:00:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7862b987bff2494f5c48d68cc0b698564d60c448",
"body": "… (Milestone 3)\n\nAdds receipt.Feed and receipt.Cut, the two remaining printer-control\nElement types from the v0.1 schema. Both occupy zero height in\nrender/layout.Build and paint no pixels in render/canvas.Paint; Paint\ninstead records each as a canvas.Control (position + a Terminal flag),\nwhich rend\n[…]\n/escpos.Encode reads to interleave explicit feed/cut bytes\nat the right point in the raster stream and to suppress the automatic\ntrailing cut only when the Receipt genuinely ends with an explicit Cut.",
"is_bot": false,
"headline": "feat(receipt,render): implement Feed and Cut printer-control elements…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T21:55:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dfb780181c4f02525076a7561b0fc6fb15fd9eee",
"body": "… (Milestone 3)\n\nAdds the Barcode receipt element following the same pipeline QRCode\nalready established: Receipt -> Layout -> generated bitmap -> existing\nraster renderer -> Canvas -> ESC/POS, with no barcode-specific drawing\nlogic in render/canvas.\n\nreceipt.Barcode.Validate() and .Encode() support\n[…]\n's uniform square scaling).\nShowText is accepted and validated but not yet rendered, the same\n\"schema ahead of its rendering\" position already held by Text's\nItalic/Underline/Strikethrough (ADR-0007).",
"is_bot": false,
"headline": "feat(receipt,render): implement Barcode element with raster rendering…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T21:25:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8ce6f6a8dd1688bb0a5282d7b311f71282179fd1",
"body": "Barcode.Symbology's valid values were undefined in the architecture,\nso the Barcode element slice stopped before implementation to avoid\ninventing public API. Adds ADR-0009 defining the six supported v1\nsymbologies (code128, ean13, ean8, upca, code39, itf) and their wire\nstrings, and corrects a stale go-qrcode reference left over from the\nearlier QR library swap to boombuler/barcode.",
"is_bot": false,
"headline": "docs(architecture): freeze the barcode symbology public API (ADR-0009)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T21:07:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "03775b68d5b0c0428009dad7310aef146526fea2",
"body": "… (Milestone 3)\n\nQRCode is generated to an image.Image during layout.Build and flows\nthrough the same raster/paintGlyph pipeline receipt.Image already uses,\nso canvas.Paint gains no QR-specific drawing logic. Uses\ngithub.com/boombuler/barcode/qr for encoding (actively maintained, and\nalready the library docs/ARCHITECTURE.md names for the future Barcode\nelement).",
"is_bot": false,
"headline": "feat(receipt,render): implement QR Code element with raster rendering…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T20:54:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c9e432c30953a6ecdddda627c3f53afe153022ed",
"body": "…ort (Milestone 3)\n\nreceipt.Image joins Divider/Spacer/Text/Heading as a first-class Block:\nrender/layout.Build decodes just enough of Image.Data (image.DecodeConfig)\nto advance Y, scaling to fit the printable width like text already does;\nrender/canvas.Paint decodes the full pixels (layout.DecodeIm\n[…]\ndient threshold, checkerboard,\ntransparency, oversized scale-to-fit) and real internet images (the Go\ngopher logo, NASA's Earthrise photo) all printed correctly on the\nTM-m30II via the daemon's queue.",
"is_bot": false,
"headline": "feat(render): implement Image rendering with multi-format raster supp…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T18:56:18Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d397df7578eb158ebc8d4d8021ef9d4837ebecb1",
"body": "render/layout.Build and render/canvas.Paint now support\nreceipt.Divider: it occupies a fixed 1-dot line (layout.DividerThickness,\nshared by both stages), advances Y like any other Block, and paints as a\nfull-width horizontal line via the existing paintHLine primitive\nunderline/strikethrough already \n[…]\ned end to end against real hardware: offline render, daemon\npreview at the printer's real 576-dot width, and a physical print job\n(max_image_height_dots: 0, single raster) all produced correct output.",
"is_bot": false,
"headline": "feat(render): implement Divider rendering (Milestone 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T18:01:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "68facfa62190931bf955ab0066802b3c17ae2df4",
"body": "Printing every style/size combination on real 203 DPI thermal hardware\nsurfaced two legibility problems the on-screen PNG preview didn't catch:\n\n- EmbeddedFont's native 7x13 glyphs were too small to read reliably on\n paper. It now bakes a fixed 2x upscale into its own native resolution\n (14x26) — \n[…]\nction displacement,\n which resolved it. (A pipeline reorder was tried first and reverted —\n proven to have zero effect, since clipped per-row translations compose\n identically regardless of order.)",
"is_bot": false,
"headline": "fix(render): improve text legibility found in real-hardware review",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T17:42:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6e93981f3c52047f53f7d930dfca01209a5b4435",
"body": "…block\n\nPaint's content-fit width computation (doc.WidthDots <= 0) stopped\nchecking every block's measured width once the first one made width\npositive, so a later, wider block silently got clipped instead of\ngrowing the Canvas. Found via a real-hardware print review.",
"is_bot": false,
"headline": "fix(render/canvas): fix content-fit width considering only the first …",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T17:42:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "03ef500727b61fa140a5f079eb2fe9971dfb72a3",
"body": "…Milestone 3)\n\nCompletes the bitmap text styling system started in 6efc09e: italic is a\ndeterministic bitmap shear appended to styleGlyph's scale-then-bold\npipeline, while underline and strikethrough are decorations painted\ndirectly onto the Canvas after glyphs, never mutating the glyph bitmap\nitself, per docs/ARCHITECTURE.md §3.",
"is_bot": false,
"headline": "feat(render/canvas): implement italic, underline, and strikethrough (…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T16:55:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6efc09e7e30d1fec6540d6a5e542e379cebefe09",
"body": "Implements the styling system docs/adr/0007-bitmap-text-styling.md\nspecifies: receipt.Text.Size now affects rendering, Heading renders\nthrough the same pipeline as Text, and bitmap bold is implemented.\n\nlayout.Build resolves every Text/Heading into a layout.Style carried on\nBlock (Size always normal\n[…]\npt.Text/receipt.Heading fields to decide\nstyling, which is what keeps Heading from needing a second rendering\npath (verified directly: a Heading and a Text given the same Style\nrender identical bits).",
"is_bot": false,
"headline": "feat(render): implement bitmap text styling (Milestone 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T16:30:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5e03330548e8b7b2ca119a21bc371dee8554d22e",
"body": "receipt.Text.Size was frozen as an unconstrained string with no defined\nvalues, discovered as a genuine gap while implementing bitmap text\nstyling (the previous slice correctly stopped rather than invent one).\n\nDefines Size as an integer bitmap scale factor (0/omitted = 1, not a\npoint size, not prin\n[…]\nlayout.Style, resolved once by Build and carried on Block, so\nrender/canvas.Paint styles by Block.Style alone and Heading never needs\na second rendering path. Per docs/adr/0007-bitmap-text-styling.md.",
"is_bot": false,
"headline": "docs(architecture): specify the public text-styling API",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T16:29:57Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "25601700fb356cecd5685f22a808467e8da9d93a",
"body": "layout.Build now word-wraps receipt.Text/receipt.Heading content to\nprinter.Profile.WidthDots, emitting one Block per wrapped line instead\nof one per element. Explicit \"\\n\"s always split lines; word-wrapping\nonly applies when a positive width is available (Build's documented\n\"no printer configured\" \n[…]\nas.Paint and escpos.Encode are unchanged: a Document's height\nalready falls out of however many Blocks Build produces, so wrapping\nmore lines just means more Blocks, not a separate height calculation.",
"is_bot": false,
"headline": "feat(render/layout): wrap text to the printable width (Milestone 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T09:41:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "64cc360f6ed710bfbe26c138e976248fa464ad55",
"body": "…ring (Milestone 3)\n\nrender/layout.Build now takes a printer.Profile and sets it on\nDocument.WidthDots; render/canvas.Paint sizes the Canvas to that width\ninstead of always shrinking to fit content, so a receipt narrower than\nthe paper still renders at the printer's full printable width. A zero\nWidt\n[…]\newHandler's\nwire format, the CLI's preview command (new required --printer flag),\nand the REST examples in README, whose print example was already\nstale before this change and is corrected in passing.",
"is_bot": false,
"headline": "feat(render,app,api): thread printer.Profile through layout and rende…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T09:15:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f5e9d2209f879ed2a4b043bde1909e1e3b6000e0",
"body": "Threading printer.Profile through rendering (canvas width sized to\nprofile.WidthDots) means a preview is only meaningful relative to a\nspecific printer. Preview's frozen signature had no printer name at\nall, and nothing in the schema defines a default printer to fall back\non, so this was a genuine a\n[…]\nd the templates/weather\nwalkthrough accordingly. Code (Preview, PreviewHandler, the CLI's\npreview command) is unchanged in this commit - it lands in the\nprofile-threading slice this decision unblocks.",
"is_bot": false,
"headline": "docs(architecture): require an explicit printer target for Preview",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T08:55:57Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c872d4c768eb46cc64d4b8bc0ff0f4b0e0dc3c04",
"body": "…e 3)\n\nescpos.Encode now emits one GS v 0 command per profile.MaxImageHeightDots\nrows instead of a single command for the whole Canvas, improving\ncompatibility with real ESC/POS printers that reject an oversized raster\ncommand. A zero (or sufficiently large) MaxImageHeightDots still produces\nexactly\n[…]\nHITECTURE.md and the escpos package doc comment to describe the\nshipped banding behavior. docs/adr/0002-raster-rendering.md is left\nuntouched as a historical record of the earlier sequencing decision.",
"is_bot": false,
"headline": "feat(escpos): split large raster images into multiple bands (Mileston…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T08:25:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "593d762955ccc71072e4ed2ab304d6c708290f9b",
"body": "…mposition root (Milestone 3)\n\nbuild() now constructs a printer.Printer and resolves a printer.Profile for\nevery configured entry in cfg.Printers, keyed by name, and populates\nService.Printers/Profiles from them. This makes the existing\nReceipt->Layout->Canvas->ESC/POS->Printer pipeline operational in production\nfor the first time -- app.Service.Process required no changes.",
"is_bot": false,
"headline": "feat(cmd/receiptd): wire configured printers and profiles into the co…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T08:15:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "421a1b46587bb0c61eb23e764fd34cc03c344724",
"body": "…estone 3)\n\nescpos.Encode now takes a printer.Profile and appends a feed+cut\nsequence when the Profile supports cutting, matching the frozen\nEncode(canvas, profile) signature (ARCHITECTURE.md §4, ADR-0002)\ninstead of a parallel functional-options API.\n\nService gains a Profiles map mirroring Printers; Process resolves\nthe Job's Profile before encoding, so the feed/cut behavior is now\nreachable end-to-end rather than fed a hard-coded zero value.",
"is_bot": false,
"headline": "feat(escpos): add Profile-driven feed/cut to the ESC/POS encoder (Mil…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T08:05:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "eb7e715d89583a79d7fc3da92e754bf8f6cc7c69",
"body": "…ilestone 3)\n\nService.Process now composes the existing rendering, encoding, and\ntransport layers into the real print path: render -> escpos.Encode ->\nPrinters[j.PrinterName].Send, propagating each stage's error unmodified.\nAn unconfigured PrinterName fails with apperr.KindNotFound instead of\nsilent\n[…]\n-in now that migration is complete:\nService.LogSink, daemon's logSink file handle and close-on-shutdown\nlogic, and the printerLogPath helper are all gone, leaving exactly one\nproduction printing path.",
"is_bot": false,
"headline": "feat(app): wire Receipt->Layout->Canvas->ESC/POS->Printer pipeline (M…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T00:31:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f8fbb480c492baee43bc00fcc1aac9c138499d45",
"body": "printer.NewNetworkPrinter(conn Connection) Printer implements Printer over\nraw TCP: Send dials conn.Address fresh per call, writes data in full (looping\npast short writes per the io.Writer contract, guarding against a zero-byte/\nnil-error write spinning forever), and closes the connection. Status re\n[…]\nes on Queue.sleep) to\nscript multi-call short-write sequences, write failures, and the zero-byte\nno-error edge case, capped so a regression fails fast instead of hanging.\n100% coverage on the package.",
"is_bot": false,
"headline": "feat(printer): add TCP network transport (Milestone 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T00:15:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bfd949d569715aaa932bb6fbd9b3a160b43dceae",
"body": "render/escpos.Encode(c *canvas.Canvas) ([]byte, error) turns a painted\nCanvas into ESC/POS bytes: ESC @ init followed by one GS v 0 raster\nimage command carrying c.Bits verbatim, per ADR-0002 (raster-first — no\nnative text/QR/barcode commands). No feed, no cut, no chunking yet\n(docs/ARCHITECTURE.md \n[…]\nnput Canvas, both\nbitmap-size mismatch directions, and a full receipt -> layout -> canvas\n-> escpos pipeline test proving the encoder contains no text-specific\nlogic. 100% coverage on the new package.",
"is_bot": false,
"headline": "feat(escpos): add raster encoder skeleton (Milestone 3)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-19T00:02:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f6456f78d2edcd9a5c836c4ceebb26c755d04218",
"body": "… scan\n\nProcessNext previously called Store.List and scanned every Job for the\nfirst JobPending one, per docs/ARCHITECTURE.md-noted stopgap deferred\nuntil a persistent Store existed. bbolt landed a few slices ago, so that\nprecondition is now met: Store gains NextPending, implemented by\nmemoryStore a\n[…]\nursor, stopping at the first pending Job instead of\nunmarshaling the whole bucket.\n\nUpdates every queue.Store test double (retryStore, fakeStore x2,\nfakeCancelStore) to satisfy the extended interface.",
"is_bot": false,
"headline": "perf(queue): add Store.NextPending, replacing ProcessNext's full-list…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T23:46:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bed1d32c3c140317091bd2fa4874683ec8a98cf8",
"body": "…cer height\n\n- config: auth.enabled now defaults to true when the auth: block is\n omitted or doesn't mention \"enabled\" (Config's custom UnmarshalYAML\n pre-populates the default before decoding). Only an explicit\n \"enabled: false\" opts out — closes a gap where a config that simply\n forgot the aut\n[…]\n(10,000 dots) so one Spacer can't\n force a disproportionate canvas.Paint allocation.\n\nAlso brings README.md and docs/ARCHITECTURE.md up to date with Milestone\n1/2 completion and the new auth default.",
"is_bot": false,
"headline": "fix(config,api,receipt): harden auth default, request bodies, and Spa…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T23:46:30Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "88aee51daa8377677a4cecff384d038b1c3ad11f",
"body": "Adds preview, print, and jobs subcommands backed by a small apiClient\n(cmd/receipt/client.go): reads the same config.yaml receiptd loads,\nresolves the bearer token via the existing auth.ResolveToken, and POSTs\nGETs to /api/v1/preview, /api/v1/print, and /api/v1/jobs/{id}. render\nis untouched — it st\n[…]\nThe wire request/response shapes are duplicated locally in cmd/receipt\nrather than imported from internal/api, so the CLI depends on the\ndocumented HTTP contract, not on the server package's Go types.",
"is_bot": false,
"headline": "feat(cmd/receipt): switch the CLI to calling receiptd's REST API",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T23:20:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9cbe9c1c14f18b466a26c05e462c7e2b87a7a093",
"body": "Replaces the placeholder main with the daemon's actual wiring: config.Load,\nthe configured queue.Store, app.Service (LogSink pointed at a fake-printer\nlog file standing in for a real printer until Milestone 3), the versioned\n/api/v1 routes, Bearer middleware when auth.enabled, and a background\nworke\n[…]\nonfig\nfield for it either). Both are cheap to revisit later.\n\nbuild() opens the printer-log file; serve() closes it once\nListenAndServe returns, so the daemon that opens the file also owns\nclosing it.",
"is_bot": false,
"headline": "feat(cmd/receiptd): implement the Milestone 2 composition root",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T22:58:53Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ccc96b1d3e929555df09645b8da728bb18c042bf",
"body": "Basic checks the password against the same single shared token Bearer\nuses (docs/ARCHITECTURE.md §1: \"one shared token check\"), reusing the\nexisting constant-time tokensEqual helper. The username is required to\nbe non-empty but is otherwise not checked, matching the common\ntoken-as-password conventi\n[…]\ne frozen config schema has no separate\nusername field to check it against.\n\nBuilt on stdlib r.BasicAuth() rather than a hand-rolled parser, so\nthere's no duplicated Authorization-header-parsing logic.",
"is_bot": false,
"headline": "feat(auth): add HTTP Basic-Auth middleware",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T22:39:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dce965fb450de4e7b7b9bbe812d02640cc897b38",
"body": "Milestone 2's stand-in for a real printer (docs/ARCHITECTURE.md §10):\nProcess renders a Job's Receipt as before, then writes the result as PNG\nbytes (reusing the same canvas.EncodePNG call Preview already uses) to\nService.LogSink, an io.Writer. Sink write failures are wrapped\napperr.KindPermanent, c\n[…]\new, so a freshly constructed Service\nbehaves exactly as before this change until the composition root\nconfigures a real log destination in a later slice. The public\nconstructor signature is unchanged.",
"is_bot": false,
"headline": "feat(app): write Process's rendered output to a fake printer log sink",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T22:27:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ad5e2d65eabc5685a7fa14ed9a6db722164af2a1",
"body": "Protects an http.Handler behind Authorization: Bearer <token>, checked\nagainst a caller-supplied expected token (typically the output of\nResolveToken). Missing, malformed, or incorrect credentials get 401\nwith a WWW-Authenticate: Bearer challenge and never reach the wrapped\nhandler; the expected tok\n[…]\n per RFC 9110\n§11.1. The comparison helper is unexported but deliberately factored\nout so a future Basic-Auth middleware can share it, per the shared\ntoken check called for in docs/ARCHITECTURE.md §1.",
"is_bot": false,
"headline": "feat(auth): add Bearer-token HTTP middleware",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T16:42:48Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5b932c1ab4203eafbcb66993e5deb79092dd1993",
"body": "Add auth.ResolveToken, resolving the shared token from config.AuthConfig\nper the frozen precedence rule in docs/ARCHITECTURE.md §7:\nRECEIPTD_AUTH_TOKEN, when set (even to \"\"), is authoritative over\nauth.token_file; otherwise the token file is read, with only its trailing\nnewline/whitespace trimmed. \n[…]\nsing token file wraps apperr.KindNotFound, any other read failure\n(e.g. a directory given as token_file) wraps apperr.KindPermanent, and an\nabsent or empty effective token wraps apperr.KindValidation.",
"is_bot": false,
"headline": "feat(auth): add configured-token resolution",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T16:32:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "43b640690b3002d0beb5c82fa29098fd0c810e95",
"body": "Implements internal/config as the sole owner of the printer.Profile /\nprinter.Connection flat-YAML split described in docs/ARCHITECTURE.md §7,\nplus loading and validation for the full documented top-level schema\n(server, auth, logging, assets, queue, printers, providers.weather, web).\n\nValidation is\n[…]\nented in v0.1 — usb/\nbluetooth/serial are documented as future), and no duplicate printer\nnames. width_mm -> Profile.WidthDots uses round(width_mm / 25.4 * dpi),\nthe standard dots-per-inch definition.",
"is_bot": false,
"headline": "feat(config): add YAML configuration loading for the frozen schema",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T16:23:28Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c5035c43802031817243a3323dc81a9f59ff091c",
"body": "Introduces the printer package's capability/transport/interface split\nexactly as frozen in docs/ARCHITECTURE.md §2: Profile (paper geometry,\ncut capability), Connection (transport details), and the Printer\ninterface (Send/Status/Close). Data only — no validation, width-helper\nmethods, or transport implementation yet, per the raster-first design's\n\"discover interfaces at the second real use\" principle (no second\nPrinter implementation exists in this slice to generalize against).",
"is_bot": false,
"headline": "feat(printer): add frozen core contracts (Profile, Connection, Printer)",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T16:05:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4b81192c6a9a68aa0748e5c2024137f582fb6670",
"body": "Replaces the cmd/receipt skeleton with the architecture-mandated Cobra\nCLI (docs/ARCHITECTURE.md §1) and implements the offline\n`receipt render <receipt.json> --out <preview.png>` path: read JSON,\nValidate(), then the existing local pipeline (layout.Build with\nEmbeddedFont, canvas.Paint, EncodePNG),\n[…]\ndiffer\" — that flag predated this change and applied to every prior\ngo.sum-touching commit; removing it is a repo-wide convention change\nmade with explicit approval, not scoped narrowly to this slice.",
"is_bot": false,
"headline": "feat(cmd/receipt): implement offline `receipt render` CLI command",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T15:52:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "94ec5c0bf0e7c558afe0af334676789dc6b16082",
"body": "Thin adapter over Service.JobStatus using the Go 1.22+ stdlib\nr.PathValue(\"id\") for the path parameter, per the routing decision\nmade for this slice: no router dependency, no mux wiring, and no\nchange to main.go. Missing/unknown ids get no special-casing in the\nhandler — they fall through to Service\n[…]\nnrecognized id.\n\nResponse body is a bespoke jobStatusResponse mirroring queue.Job\nfield-for-field, matching the existing printResponse convention\nrather than marshaling the internal Job type directly.",
"is_bot": false,
"headline": "feat(api): implement HTTP GET /api/v1/jobs/{id}",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T15:28:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a40b8d2fbec6bd2d7dce2dbe33bd189f4e4135f5",
"body": "…ookup\n\nJobStatus needed a read-only Job-by-ID lookup that didn't exist on Queue\n(only Enqueue, Cancel, and ProcessNext were exposed). Added Queue.Get as\na thin forward to the existing Store.Get, mirroring how Cancel already\nuses it internally, so app.Service.JobStatus can expose queue state\nwithout a second access path into Store.",
"is_bot": false,
"headline": "feat(queue,app): implement Service.JobStatus with minimal Queue.Get l…",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T15:17:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b9d29f14d9d36fb8f1107c5f93e55ad02dcc1368",
"body": "Adds PreviewHandler, adapting the endpoint onto the existing\napp.Service.Preview method: decode the request body as a Receipt, call\nPreview, and write back the resulting PNG bytes with a 200 and\nimage/png Content-Type. Mirrors PrintHandler's structure and reuses\nthe existing writeError/statusForError error mapping.",
"is_bot": false,
"headline": "feat(api): implement POST /api/v1/preview HTTP adapter",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T15:07:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dab4293ce572d60e7f2717d219a2fc82b427dd91",
"body": "Adds app.Service.Preview, reusing the existing render/EncodePNG\npipeline already used by Process, so a Receipt can be rendered to PNG\nbytes for inspection without enqueuing a Job or touching a printer.",
"is_bot": false,
"headline": "feat(app): implement Service.Preview PNG rendering",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T15:00:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cbf61b16e7a3fae020d361fc8da2f00bb5c7228b",
"body": "Adds queue.NewBoltStore as a drop-in Store implementation backed by\ngo.etcd.io/bbolt, so Job state survives a receiptd restart, per\ndocs/adr/0003-print-queue.md.",
"is_bot": false,
"headline": "feat(queue): implement bbolt-backed persistent Store",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T14:52:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "075ccff2f830f4345791ba39be252b1a5d6b2823",
"body": "A Job that fails with apperr.KindTransient is now retried up to 3 total\nattempts, with exponential backoff starting at 5s, entirely within a\nsingle ProcessNext call. Any other error kind still fails the Job\nimmediately with no retry, per docs/adr/0005-error-handling.md.",
"is_bot": false,
"headline": "feat(queue): implement bounded retry/backoff for ProcessNext",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T14:15:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "382b978527e7fb1951655d8a6d33a0f0e9b5e08e",
"body": "A Pending Job can now transition to Cancelled via Cancel, using only the\nexisting Store.Get/Save. Any other current state (Running, Done, Failed,\nCancelled) is rejected with apperr.KindValidation and left unchanged, per\ndocs/adr/0003-print-queue.md's pending -> cancelled transition.",
"is_bot": false,
"headline": "feat(queue): implement Queue.Cancel",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T14:03:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "993f375536799a39604ae3cd26429a4c9d7982c2",
"body": "Build and Paint now accept receipt.Spacer alongside Text and Heading.\nUnlike Text/Heading, a Spacer block advances the vertical cursor by its\nown Height (dots) rather than f.LineHeight(), and paints no glyphs -\nPaint's sizing pass now asks each block its own vertical extent\n(blockHeight) instead of assuming every block is one font line tall.",
"is_bot": false,
"headline": "feat(layout,canvas): extend rendering pipeline to support Spacer",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T09:46:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "01282a7677085daaf9d4c71c1d409c3fe7fabc07",
"body": "Build and Paint now accept receipt.Heading alongside receipt.Text,\npositioning and painting it identically to Text (same font, same\ncontent) since Font has no weight/size variation yet to realize\nHeading's documented \"bold + large\" styling.\n\nFour pre-existing tests (two in render/layout and render/canvas, two\nin app) had been using receipt.Heading as their \"unsupported element\"\nfixture; switched them to receipt.Divider, which remains unsupported.",
"is_bot": false,
"headline": "feat(layout,canvas): extend rendering pipeline to support Heading",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T09:35:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d77407e1c5209bb79fc3e116f1e4340ebd12a129",
"body": "Adds Process(ctx, j) error, satisfying queue.Processor exactly as frozen\nin docs/ARCHITECTURE.md, plus an unexported render helper that composes\nlayout.Build and canvas.Paint. Process performs no printer I/O; a\nsuccessful render's Canvas is discarded until a later slice adds\nsomething downstream to \n[…]\nure conflicts with returning a\nrendered result directly, so render is tested via one disclosed\nwhite-box exception (render_internal_test.go) to the codebase's\notherwise black-box-only test convention.",
"is_bot": false,
"headline": "feat(app): implement Service.Process rendering orchestration",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T09:23:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "835e8ccd8f8db8aee4351122bddb771c1315d5f5",
"body": "Adds PrintHandler, a thin adapter decoding {\"printer\",\"receipt\"} into a\ncall against a Service.Print-shaped interface and encoding the result\nas {\"job_id\"} (202 Accepted) or an apperr.Kind-mapped error status.\n\nThe wire format (request wrapper shape, response field name, status\ncode) was not specified anywhere in the architecture docs; resolved\nvia explicit discussion before implementation rather than invented\nsilently, since it becomes the de facto contract future endpoints\nwill match.",
"is_bot": false,
"headline": "feat(api): implement POST /api/v1/print HTTP adapter",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T09:09:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2195d9f968c7bda8305fd7ba1469d155c006c0a7",
"body": "Element decoding uses a map[string]decoder registry populated by each\nconcrete type's own init() (the image.RegisterFormat/database/sql.Register\npattern docs/adr/0001-receipt-model.md specifies), so a new Element type\ntouches exactly one new file. Encoding needs no registry: Text, Heading,\nDivider, \n[…]\n from\nJSON had never actually been implemented - Receipt had no UnmarshalJSON\nand no Element type carried a \"type\" field, so a POST /print handler\nwould have had nothing to decode a request body into.",
"is_bot": false,
"headline": "feat(receipt): implement registry-based JSON polymorphism for Receipt",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T08:56:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "82dfd19f18d05e4b1ca45d6f1f7f38df4c6f50df",
"body": "Service and Print provide the first Milestone 2 slice proving\napplication code can accept a validated Receipt and hand it to the\nqueue: Print validates the Receipt via its existing Validate(),\nconstructs a queue.Job targeting the given printer name, and enqueues\nit via Queue.Enqueue, returning the g\n[…]\nemplates, and\nassets don't yet define the types (Profile, Printer, Registry, Store)\nthe remaining fields depend on. Those fields, and Preview/RunTemplate/\nJobStatus/Process, are left for later slices.",
"is_bot": false,
"headline": "feat(app): implement Service.Print to enqueue validated Receipts",
"author_name": "harveysandiego",
"author_login": "harveysandiego",
"committed_at": "2026-07-18T08:38:54Z",
"body_truncated": true,
"is_coding_agent": false
}
],
"releases_count": 8,
"commits_last_year": 114,
"latest_release_at": "2026-07-24T23:17:45Z",
"latest_release_tag": "v0.5.1",
"releases_from_tags": false,
"days_since_last_push": 4,
"active_weeks_last_year": 2,
"days_since_latest_release": 4,
"mean_days_between_releases": 0.6
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": true,
"health_percentage": 100,
"has_issue_template": false,
"has_code_of_conduct": true,
"has_pull_request_template": true
},
"ecosystem": {
"packages": [
{
"name": "github.com/harveysandiego/receiptd",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/harveysandiego/receiptd",
"is_deprecated": false,
"latest_version": "v0.5.1",
"repository_url": "https://github.com/harveysandiego/receiptd",
"versions_count": 8,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-24T23:14:11Z",
"latest_version_yanked": null,
"days_since_latest_publish": 4
}
]
},
"popularity": {
"forks": 0,
"stars": 0,
"watchers": 0,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": {
"days": [],
"complete": true,
"collected": 0,
"total_stars": 0,
"collected_at": null
},
"open_issues_and_prs": 0
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": true,
"has_mcp_signal": false,
"bootstrap_files": [
"Makefile"
],
"api_schema_files": [],
"has_devcontainer": true,
"typecheck_configs": [],
"toolchain_manifests": [
"go.mod"
],
"largest_source_bytes": 47990,
"source_files_sampled": 189,
"oversized_source_files": 0,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 18527
},
"dependencies": {
"manifests": [
"go.mod"
],
"advisories": {
"error": null,
"scope": "repository_graph",
"source": "osv",
"findings": [],
"collected": true,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 8,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"go"
],
"dependencies": [
{
"name": "github.com/boombuler/barcode",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.1.0"
},
{
"name": "github.com/spf13/cobra",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.10.2"
},
{
"name": "go.etcd.io/bbolt",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.5.0"
},
{
"name": "golang.org/x/image",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v0.44.0"
},
{
"name": "gopkg.in/yaml.v3",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v3.0.1"
}
],
"all_dependencies": {
"error": null,
"source": "github-sbom",
"packages": [
{
"name": "github.com/boombuler/barcode",
"direct": true,
"version": "v1.1.0",
"ecosystem": "go"
},
{
"name": "github.com/spf13/cobra",
"direct": true,
"version": "v1.10.2",
"ecosystem": "go"
},
{
"name": "go.etcd.io/bbolt",
"direct": true,
"version": "v1.5.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/image",
"direct": true,
"version": "v0.44.0",
"ecosystem": "go"
},
{
"name": "gopkg.in/yaml.v3",
"direct": true,
"version": "v3.0.1",
"ecosystem": "go"
},
{
"name": "github.com/inconshreveable/mousetrap",
"direct": false,
"version": "v1.1.0",
"ecosystem": "go"
},
{
"name": "github.com/spf13/pflag",
"direct": false,
"version": "v1.0.10",
"ecosystem": "go"
},
{
"name": "golang.org/x/sys",
"direct": false,
"version": "v0.45.0",
"ecosystem": "go"
}
],
"collected": true,
"truncated": false,
"total_count": 8,
"direct_count": 5,
"indirect_count": 3
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 3,
"open_issues": 0,
"closed_ratio": null,
"closed_issues": 0,
"closed_unmerged_prs": 1
},
"bus_factor": 1,
"bot_contributors": 1,
"top_contributors": [
{
"type": "User",
"login": "harveysandiego",
"commits": 111,
"avatar_url": "https://avatars.githubusercontent.com/u/18230246?v=4"
}
],
"contributors_sampled": 1,
"top_contributor_share": 1
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"codeql.yml",
"docker-image.yml",
"labels.yml",
"pr-title-lint.yml",
"release.yml"
],
"has_docs_dir": true,
"linter_configs": [
".golangci.yml"
],
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": true
},
"security_signals": {
"lockfiles": [
"go.sum"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 3,
"reason": "branch protection is not maximal on development and all release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 10,
"reason": "2 out of 2 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 0/26 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 0,
"reason": "project has 0 contributing companies or organizations -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 0,
"reason": "project was created within the last 90 days. Please review its contents carefully",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 0,
"reason": "dependency not pinned by hash detected -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 10,
"reason": "SAST tool is run on all commits",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 10,
"reason": "security policy file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": 0,
"reason": "Project has not signed or included provenance with any releases.",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 10,
"reason": "0 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "dfb469e5a81180981029df42e8b2ef6af7f6f3fd",
"ran_at": "2026-07-29T17:15:18Z",
"aggregate_score": 5.2,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": true,
"has_security_policy": true,
"has_dependabot_config": true
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-27T08:00:28Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-07-24T23:11:10Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": []
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/harveysandiego/receiptd",
"host": "github.com",
"name": "receiptd",
"owner": "harveysandiego"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 62,
"inputs": {
"security": 62,
"vitality": 69,
"community": 43,
"governance": 45,
"engineering": 90
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "moderate",
"name": "Vitality",
"value": 69,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "moderate",
"name": "Development activity",
"note": null,
"notes": [],
"value": 55,
"inputs": {
"commits_last_year": 114,
"human_commit_share": 0.98,
"days_since_last_push": 4,
"active_weeks_last_year": 2
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 4 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 4
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "2/52 weeks with commits",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 2
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "114 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 114
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"releases_count": 8,
"latest_release_tag": "v0.5.1",
"releases_from_tags": false,
"days_since_latest_release": 4,
"mean_days_between_releases": 0.6
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "8 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 8
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 4 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 4
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~0.6 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 0.6
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 43,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 1,
"inputs": {
"forks": 0,
"stars": 0,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "0 stars",
"points": 0,
"status": "missed",
"details": [
{
"code": "stars",
"params": {
"count": 0
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "0 forks",
"points": 0,
"status": "missed",
"details": [
{
"code": "forks",
"params": {
"count": 0
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "excellent",
"name": "Community health",
"note": null,
"notes": [],
"value": 92,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": true,
"has_pull_request_template": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 18,
"status": "met",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 13.5,
"status": "met",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 6.3,
"status": "met",
"details": [],
"max_points": 6.3
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "at_risk",
"name": "Sustainability & Governance",
"value": 45,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 10,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 1,
"top_contributor_share": 1
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 100% of commits",
"points": 0,
"status": "missed",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 100
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "1 contributors",
"points": 1.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 1
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "moderate",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"issue_resolution"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 54,
"inputs": {
"merged_prs": 3,
"open_issues": 0,
"closed_issues": 0,
"issue_closed_ratio": null,
"closed_unmerged_prs": 1
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "no issues or no data",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_issues_or_data",
"params": {}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "3/4 decided PRs merged",
"points": 28.7,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 3,
"decided": 4
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 0/26 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "at_risk",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 35,
"inputs": {
"followers": 0,
"owner_type": "User",
"is_verified": null,
"owner_login": "harveysandiego",
"public_repos": 6,
"account_age_days": 3770
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "0 followers of harveysandiego",
"points": 0,
"status": "missed",
"details": [
{
"code": "owner_followers",
"params": {
"count": 0,
"login": "harveysandiego"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "6 public repos, account ~10 yr old",
"points": 18.2,
"status": "partial",
"details": [
{
"code": "public_repos",
"params": {
"count": 6
}
},
{
"code": "account_age_years",
"params": {
"years": 10
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"github.com/harveysandiego/receiptd"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 4
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on go",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "go"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 4 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 4
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "8 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 8
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "excellent",
"name": "Engineering Quality",
"value": 90,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "excellent",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": true
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "6 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 6
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": ".golangci.yml",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml"
}
}
],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 9.6,
"status": "met",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 6.4,
"status": "met",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "2 out of 2 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "good",
"name": "Documentation",
"note": null,
"notes": [],
"value": 75,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": null,
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "moderate",
"name": "Security",
"value": 62,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "moderate",
"name": "Security posture",
"note": null,
"notes": [],
"value": 52,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 18,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 0,
"scorecard_aggregate": 5.2
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection is not maximal on development and all release branches",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "2 out of 2 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 0/26 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is run on all commits",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "0 existing vulnerabilities detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "dependency_advisories",
"band": "excellent",
"name": "Dependency advisories",
"note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories, No advisories left outstanding. Remaining weights renormalized. Matched 8 resolved dependencies against OSV. This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"indirect_dependencies_free_of_known_advisories",
"no_advisories_left_outstanding"
]
}
},
{
"code": "weights_renormalized",
"params": {}
},
{
"code": "advisories_scope_repository",
"params": {
"assessed": 8
}
},
{
"code": "advisories_repo_graph_caveat",
"params": {}
},
{
"code": "advisories_reachability",
"params": {}
}
],
"value": 100,
"inputs": {
"source": "osv",
"advisories": 0,
"affected_packages": 0,
"assessed_packages": 8,
"unassessed_packages": 0,
"affected_by_severity": "none",
"direct_affected_packages": 0
},
"components": [
{
"key": "direct_dependencies_free_of_known_advisories",
"name": "Direct dependencies free of known advisories",
"detail": "no direct dependency carries a known advisory",
"points": 35,
"status": "met",
"details": [
{
"code": "no_direct_advisories",
"params": {}
}
],
"max_points": 35
},
{
"key": "indirect_dependencies_free_of_known_advisories",
"name": "Indirect dependencies free of known advisories",
"detail": "transitive set not separable from development and test dependencies in this scope",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_scope_not_separable",
"params": {}
}
],
"max_points": 25
},
{
"key": "no_advisories_left_outstanding",
"name": "No advisories left outstanding",
"detail": "no advisory carries a publication date",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_no_publication_date",
"params": {}
}
],
"max_points": 40
}
]
},
{
"key": "malicious_dependencies",
"band": "excellent",
"name": "Malicious dependencies",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"source": "osv",
"meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
"packages": [],
"red_flag": false,
"assessed_packages": 8,
"malicious_packages": 0,
"direct_malicious_packages": 0,
"withdrawn_malicious_packages": 0,
"installable_malicious_packages": 0
},
"components": [
{
"key": "no_dependency_reported_as_a_malicious_package",
"name": "No dependency reported as a malicious package",
"detail": "no dependency is reported as a malicious package",
"points": 100,
"status": "met",
"details": [
{
"code": "no_malicious_dependencies",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "excellent",
"name": "AI Readiness",
"value": 85,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 1,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 18527
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "CLAUDE.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "CLAUDE.md"
}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "98 of 98 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 98,
"sampled": 98
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "good",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 80,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum"
],
"has_dockerfile": true,
"typed_language": true,
"bootstrap_files": [
"Makefile"
],
"has_devcontainer": true,
"has_linter_config": true,
"typecheck_configs": [],
"agent_commit_share": 0,
"toolchain_manifests": [
"go.mod"
],
"dependency_bot_commit_share": 0.02
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": "Makefile",
"points": 18,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Makefile"
}
}
],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": ".golangci.yml",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "Go (statically typed)",
"points": 11,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "devcontainer, Dockerfile, lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "devcontainer, Dockerfile, lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "2 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 2,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "Go",
"largest_source_bytes": 47990,
"source_files_sampled": 189,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Go (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/189 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 189,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [],
"report_type": "repository",
"generated_at": "2026-07-29T17:15:26.299260Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/h/harveysandiego/receiptd.svg",
"full_name": "harveysandiego/receiptd",
"license_state": "standard",
"license_spdx": "MIT"
}