Task board — one company to-do list, by department, on the home page¶
Status: plan. Nothing here is built yet. Extends the task register shipped 2026-09-13 (see meetings-and-tasks Phase 1), which gave us the table, the admin card and the assistant tools but no department, no assignee, no board and no history.
What Angel asked for¶
Open home.embeddedera.com, and because the gate already knows who you are, your tasks are right there: yours, your team's, what is overdue. Assign a task to a person or a group, give it a due date, put it in a calendar, close it and keep the record of who closed it and how long it took. SOP audit findings should become tasks the same way, routable to a team. And a button on any task that hands it to the AI to just fix.
The one decision that shapes everything else¶
Home stops being a static page and moves onto the assistant service, the way
chat. and admin. already are: home.embeddedera.com → 127.0.0.1:8200, its audience
added to CF_ACCESS_AUD. Reasons, in order of weight:
- A board scoped to the viewer needs verified identity server-side. The service already
does this properly, from the signed Access JWT. A Pages site would have to fetch
cross-origin into
chat.withcredentials: 'include'— the feedback modal proves that works, but it fails on first visit if the browser has no Access cookie for that audience, and a home page that intermittently says 401 is worse than no board. departments.ymlalready has atiles:list per department and the static page ignores it — everyone sees all eight tiles today, including ones they cannot open. Moving home onto the service fixes that in the same pass, for free.- One fewer deploy path.
home-deployand the Pages project retire.
Cost: if the server is down, home is down. Accept it — every tile on that page points at the server anyway, so a static home during an outage is a page of dead links.
Decided: adopt Kanboard (2026-09-13)¶
Angel's call, against my recommendation to extend our own register, and it settles the open question above: the kanban board itself is wanted, not just a list on the home page. On that reading the trade changes — a board UI, an iCal feed, an activity log and a built-in email notification system all arrive for free, and the sync script becomes a fair price rather than a needless one.
Kanboard: MIT, PHP + sqlite, ~128 MB, on this server as a container behind the existing gate. Everything below is written for that.
The two things this decision obliges us to get right¶
1. The header must not be forgeable. Kanboard trusts REMOTE_USER
(REVERSE_PROXY_AUTH=true, REVERSE_PROXY_USER_HEADER). Nginx Proxy Manager sets it from
the verified Cf-Access-Authenticated-User-Email — and must strip any inbound
REMOTE_USER and Cf-Access-* first, in the same custom location block, not at server
level. We have already been bitten by exactly this: a proxy_set_header at server level is
not inherited once a location sets its own. Anyone on the shop LAN who can reach the
container directly would otherwise be whoever they claim. Bind the container to localhost
and treat this as the security review item for the whole phase.
2. Departments must not become a second registry. departments.yml stays the one file
that decides who is what. onboard grows a Kanboard reconcile step alongside the existing
Access one: departments become Kanboard groups, each group gets its project, people are
added and removed to match the registry, and a drift alert fires when they disagree — the
same self-reconciling pattern that fixed the Access gate. Never hand-manage users inside
Kanboard's admin.
Auto-provisioning must also set each user's email address on creation, not just their
name — Kanboard's notifications are worthless to a user with no email on file, and
REMOTE_USER gives us the address for free.
What stays ours¶
Kanboard holds tasks and sends mail. It does not hold: the home board's rendering, the per-item AI proposal review, the proposal and outcome reports, the vault mirror, or the Grafana panels. Those read and write through its JSON-RPC endpoint (single endpoint, not REST — more wiring than a REST API, entirely workable).
Tweaks stay plugins. Template forks mean owning a merge conflict at every upgrade, and that is how a free tool becomes the most expensive thing on the server.
Migrating what we already have¶
The 112 tasks in ~/.local/state/assistant/assistant.sqlite move across via JSON-RPC, one
project per department, source/source_ref preserved in task metadata. Then:
tasks.pyin the assistant stops being a store and becomes a thin client.- The admin Tasks card points at Kanboard rather than duplicating its CRUD.
tasks-exportkeeps writing~/Notes/tasks.md, now reading Kanboard — the plain-text mirror in the vault is the copy that survives the server, and it is not negotiable.- The old sqlite table is left in place, read-only, until the mirror has matched for a week.
Notifications¶
Use Kanboard's, do not build one. It has per-user notification settings out of the box: email or web, and a filter for all tasks in my projects / only tasks assigned to me / only tasks I created. Wired to Resend over SMTP, which we already pay nothing for.
What that gives immediately, with no code:
- assigned a task → email
- a task you own is commented on, moved, or closed → email
- Kanboard's daily
cronjobcommand → overdue task notifications, plus recurring tasks
What it does not give, and we add:
- "Due soon". Kanboard notifies on overdue, which is already too late. A small morning digest (the one planned for Phase 4) reads the API and mails each person what is due today and this week, one message rather than a stream. This is the reminder that actually works.
- Push to a phone. Kanboard webhooks can drive
ntfyfor anyone who wants it on their phone rather than in their inbox. Optional, per person, and worth offering to Yara — she lives in a messaging app, not an email client.
Rules so the mail stays worth reading: default every new person to tasks assigned to me, never all activity; the digest is one email a day, not one per task; and nothing sends from a noreply address — mail comes from the shop address so a reply reaches a human.
Adding a task by hand¶
Four doors, one register behind them. Nobody should have to learn Kanboard to raise a task.
- The home board — a + Task button next to the lists. A short form: title, department (defaults to yours), assignee (defaults to nobody), due date, priority. Posts through JSON-RPC. This is the door most people will use, and it is deliberately smaller than Kanboard's own form.
- Kanboard itself — the full board, drag-and-drop, columns, comments, attachments, subtasks. For whoever wants the real thing. Linked as a tile.
- The assistant — "add a task for Purchasing to re-price the LCSC line by Friday".
create_taskalready exists and already drafts-then-confirms; it gets repointed at Kanboard. Useful on a phone, where a form is a chore. - The SOP audit and friends — automatic, covered below.
Whichever door, the same minimum: a title that says what done looks like, and a department. Assignee and due date may be empty — an unassigned task in the right team's column is honest, and a fake owner is not.
Data¶
Kanboard's own task fields cover title, description, due date, priority, assignee, column and project. What it has no first-class notion of, we keep in task metadata (its key/value store, reachable over the API):
| Column | Why |
|---|---|
department |
the project already encodes it; mirrored in metadata so exports do not have to resolve project ids |
ai_state |
queued / proposed / applied. Kanboard has an assignee but no notion of handing work to a machine |
dedupe_key |
UNIQUE, nullable. The whole automatic-task mechanism depends on this |
accepted_until |
a finding we have consciously decided to live with, and when to re-ask |
The record Angel asked for — who closed it, how long it took — is Kanboard's own activity stream plus its time tracking, which is one of the better reasons to have adopted it. We read that stream rather than keeping a parallel log. The one thing we log ourselves is the AI proposal trail (proposed / reviewed / applied, per item, with the deciding person), because that is ours and has no home in Kanboard.
Shortcuts: the home page as the one front door¶
Today's tiles are hardcoded and everybody sees all eight, including the ones they cannot
open. Once home is served by the assistant service it renders from each person's
tiles: list in departments.yml, and the list grows to cover the outside services we
actually use — the things Angel currently reaches by hunting a bookmark.
Two kinds of tile, and they should look different:
- Ours — behind the Access gate, one Google login already spent: ERP, Assistant, SOPs, Reports, Admin, Vault, NAS, Sandbox.
- Outside — someone else's login, opened in a new tab. Grouped under a quieter heading so nobody thinks the gate covers them.
Outside services worth a tile, from what is actually configured on this server
(~/.config/) and in the project notes:
| Tile | Who needs it |
|---|---|
| Business Gmail / Google Workspace admin | Administration, Sales |
| Cloudflare dashboard (DNS, Pages, Zero Trust, R2) | Administration |
GitHub — EmbeddedEra org |
Engineering, Administration |
| DigiKey · Mouser · LCSC · JLCPCB | Purchasing, Engineering |
| eBay Seller Hub · Amazon Seller | Sales |
| Resend (email delivery) | Administration |
| Uptime Kuma | Administration |
| The public site + the quote inbox | Sales, Administration |
And one new one of ours: a Notebook tile for the plans, decisions and hardware notes published from the vault — see notebook-site.
Rules for the tile list, so it stays honest:
- It lives in
departments.ymlnext to everything else that decides who sees what. One registry, not a second list in the HTML. - Every tile says which host it goes to, as they do now, and outside tiles are marked as outside. No pretending a third-party login is covered by ours.
- A tile a person cannot open is not shown. Half of what makes the current page feel like a patch is offering people doors that are locked.
- LAN-only tiles (the NAS) keep the existing shop-network-only treatment.
The board¶
Per person, three lists (stacked on a phone, columns on a laptop):
- Yours — assigned to you
- Your team — your departments, unassigned or on someone else
- Due and overdue — across your departments, overdue first and in red
Each row: title · department chip · due date · who has it. Row actions: Done (demands a note, same as the admin card already does), Reassign, Due date, Hand to AI. One + Task button. Administration gets a fourth view: everything, plus closed history and the numbers.
Phone-first at 375 px, three-state theme, Home link, brand tokens — the house rules for internal pages, no exceptions for this one.
SOP findings become tasks¶
sop-audit already emits stable check ids (SOP-002.no-image) with a count and a sample.
That is the whole hook.
- One task per failing check, never per failing item. 43 parts with no image is one task that says 43, not 43 tasks. The sample goes in the detail and the count updates.
dedupe_key = "sopaudit:<check_id>". Each nightly run: failing and no open task → create; failing and already open → refresh count and detail, leave the due date and assignee alone; passing and open → close it automatically, actorsop-audit. The board empties itself as the shop gets fixed, which is the only way people keep trusting it.- Routing needs no new registry. Each check carries its SOP number; each SOP's
front-matter
owneris already a department. Finding → SOP → owner → team. - Severity sets priority sets the default due date (high 7 days, normal 30, low none), from one dict that lives next to the check table.
- Accepted risk is mandatory. A finding we have decided to live with gets a note and an expiry, and the audit will not recreate it until then. Without this escape hatch a permanent nag trains everyone to ignore the board — that is what kills audit dashboards.
The same hook takes any source with stable keys: backup canaries, the Access drift alert
from onboard access-sync, SMART warnings. Worth naming now, not worth building yet.
Hand it to the AI¶
The interesting one, and the one to go slowest on.
assignee = 'ai' queues the task. A worker picks it up and runs a Claude Agent SDK session
with a tool set chosen by task class — not a general agent with a shell. It works
against the sandbox, produces a proposal, and a person confirms before anything
reaches production. Same draft-then-confirm contract as the rest of the assistant; this
feature does not get an exemption from it.
Classes worth doing first, because the tools already exist:
- parts with no description → drafts them, shows a diff
- SOP front-matter stale or version out of step with its history → drafts the edit
- PO line with no price → looks the price up and drafts it
Classes it must refuse, out loud, with the reason:
- parts with no image — somebody has to take a photograph
- anything not in the class table. An unrecognised task is not an invitation to improvise.
The proposal is a document, not a chat message¶
An AI run does not end in a dialogue box. It ends in a proposal: a durable, dated artifact a person can read away from the screen that produced it.
- A report file, written to the proposal store and downloadable from the task: a self-contained HTML page (opens offline, no styling to fetch) plus a CSV of the same rows for anyone who would rather read it in a spreadsheet. Retained after the decision — the report is the evidence of what was proposed, separate from what was applied.
- An email via Resend to the task's assignee and to Administration: what the task was, how many items, the headline counts, and a link back to the review page. The summary and the link, never the full contents, and never anything a mail server should not hold.
- The sandbox result is shown, not claimed. The report carries the outcome of the sandbox run — what it changed there and whether it succeeded — so "it worked in the sandbox" is something the reviewer can see rather than take on trust.
Every item gets its own decision¶
The thing to defend against is a person clicking one big green button on forty-three changes they did not read. So there is no big green button.
- One row per proposed change, showing the current value, the proposed value, and the AI's one-line reason. A row is undecided until a person makes it Accept, Reject or Edit.
- Edit is first-class. The common case is "nearly right", and forcing a reject-and-rerun for a wording change is what teaches people to just accept everything.
- No select-all, no approve-all. A running counter reads "12 of 43 reviewed", and the Implement button stays disabled until that reaches all of them. Long batches paginate, so there is no single scroll in which forty rows can be waved through.
- The decision is attributed. Each item records who decided it and when, as its own
task_eventsrow. Not one approval for the batch — one per item, because that is the claim being made. - Reject needs no reason; accept carries the weight. The reviewer owns what they accepted. That belongs in SOP-017 in those words.
Applying, and what happens when the world moved¶
- A proposal expires (seven days), because a stale proposal reviewed carefully is still a wrong change.
- Each row remembers the value the AI actually read. At apply time, if the live record no longer matches, that row fails loudly and is skipped — never overwritten. Somebody edited it in the meantime, and their edit wins.
- Items apply one at a time through the same allow-listed actions the assistant already uses. One failure does not abandon the rest.
- The run produces an outcome report — applied, failed, skipped, with reasons — stored next to the proposal and emailed the same way. The proposal says what was intended; the outcome says what happened; both are kept.
- Only then may a person close the task. The AI still never closes it.
Hard limits: never delete, never touch a person's account, never write to production, and
never close its own task. It does the work; a human marks it done. Otherwise the board
is a machine grading its own homework. Every run writes task_events rows, a transcript
and its dollar cost, against the existing monthly cap.
Dates, calendars and reminders¶
The server still cannot write to Google Calendar — Workspace blocks service-account keys (task #113, meetings-and-tasks). Two things that work without a key:
- A per-person iCal subscription feed Google can poll by URL. It has to be reachable without the Access gate, so it is an unguessable secret path, revocable from admin, and it carries titles and dates only — never the detail. Refresh is on Google's schedule, which is hours, so it is a calendar view, not a reminder.
- A morning digest email (Resend, already planned): what is due today, what is overdue, per person. This is the reminder that actually works.
Record and reporting¶
task_events is the record. tasks-export keeps writing ~/Notes/tasks.md so a plain-text
copy survives the server. Grafana Quality folder gets: opened vs closed per week by
department, median days to close, overdue count, SOP findings open vs closed over time —
which is exactly the real data SOP-016 Quality Objectives is currently missing. The
quarterly pack reads the same numbers.
SOP¶
One new document, four small amendments. The test each time is the house rule: one procedure, one home, cross-reference instead of restating.
New: SOP-017 Task Register¶
There is no SOP today that says how work gets tracked, and the moment tasks carry assignees, due dates and email notifications, that becomes a procedure people can follow wrongly. It owns:
- Raising a task — the four doors, and the minimum: a title that says what done looks like, and a department. Why an unassigned task is acceptable and a fake owner is not.
- Assigning and dating — who may reassign, what a due date means (the date it must be finished, not the date somebody hopes to start), and that moving a due date leaves a trace.
- Closing — closing note required, what it must say, and that closing is a person's act. Reopening and what it costs.
- Accepted risk — a finding we have consciously decided to live with, the note it needs, the expiry, and who may grant one.
- Reviewing an AI proposal — the per-item review, that Edit is expected rather than exceptional, and the sentence that matters: the reviewer owns what they accept.
- Notification settings — the default (assigned to me), and that turning on all activity is a personal choice, not a requirement.
Owner: Administration. Audit checks it should carry from day one, so it is
machine-checkable the way SOP-000 demands: SOP-017.overdue-unassigned (an overdue task
with nobody on it), SOP-017.closed-without-note, SOP-017.accepted-risk-expired,
SOP-017.ai-applied-unreviewed (an AI change applied without a per-item decision recorded
— should be impossible, so if it ever fires, something is broken).
Amended¶
- SOP-009 Personnel Onboarding — joining now also creates a Kanboard account by reconcile, sets the person's email and their notification default; leaving removes it and reassigns their open tasks. Cross-references SOP-017 rather than describing tasks.
- SOP-010 Assistant Use — gains the AI task handoff: what may be handed over, that it works in the sandbox and proposes rather than applies, and that it never closes its own task. The review of a proposal stays in SOP-017; this document only says what may be handed over.
- SOP-015 Quarterly Review — the review already reads open actions; it now reads them from the register and turns meeting decisions into tasks there. Points at SOP-017 for how, and drops any of its own wording about tracking actions.
- SOP-016 Quality Objectives — its objectives finally get real numbers: findings open versus closed, median days to close, overdue count. Names them as measures and points at the Grafana panel; the definitions live with the panel, not in two places.
Not amended, deliberately¶
SOP-000 Document Control needs nothing — SOP-017 is an ordinary document under the existing rules, numbered in sequence, no new class of thing. And no per-department SOP gains a "tasks" section; if Purchasing needs to know how to raise one, it reads SOP-017.
Gaps found on review (2026-09-13)¶
Read back against the house rules and the way this server actually fails. Twelve things the plan did not say, roughly in the order they would hurt.
- Kanboard is not backed up. A new service is a new thing to lose. Its sqlite file and its file attachments must join the age-encrypted R2 run, and the restore has to be rehearsed once — an untested backup is a rumour. Until that rehearsal passes, the vault mirror is the only real copy.
- Nothing watches the notifier. A notification system that silently stops is worse than none, because people stop checking the board and never learn why. Kanboard's cronjob and the morning digest each need a canary: if no digest sent today, alert. Uptime Kuma gets a monitor for the service itself in the same pass.
- The gate work is under-specified, and this is where we have been bitten. A new host
means a DNS record, a tunnel route, a Cloudflare Access app built by
onboard access-appfrom the registry — never cloned from a saved policy — and a newtaskstoken in every department'saccess:list indepartments.yml. Plus a card inNotes/map/for the container. If it is not in the registry and the map, it drifts. - An unassigned task belongs to nobody, so nobody is nagged. I allowed unassigned tasks
on purpose, then gave the digest no one to tell. Fix: each department names a default
owner in
departments.yml, and unassigned overdue work goes to them. Without this, unassigned is where tasks go to die. - No sandbox copy of Kanboard. The house rule is try it on the sandbox first. Changing notification rules, permissions or a plugin against the live board breaks it. A second container on the sandbox side, reset from a snapshot, same as the ERP.
- Nobody decided who can see whose tasks. Default: a department sees its own projects, Administration sees all. State it now, because at two people everything-visible feels fine and it is the wrong habit to grow into. Separately: no personnel or pay matters in Kanboard at all — it is not that kind of system and never becomes one.
- Recurring work is not mentioned and Kanboard does it for free. Backup restore checks, SnapRAID scrub review, quarterly review prep, gate drift review. These are exactly the jobs that get forgotten, and they are a built-in feature we would otherwise hand-roll.
- Tasks do not link back to the thing they are about. A finding about a part, a PO or a build should deep-link into InvenTree, and an SOP finding to the SOP section. Small work, and the difference between a board people use and one they close.
- Feedback and RMAs do not reach the register. SOP-013 Feedback and SOP-014 Returns and Repairs both end in "and then somebody should do something", which is what this register is for. Same dedupe hook as the audit. It closes the loop Angel asked for when he wanted feedback captured.
- No trial period and no kill criterion. Adopting a service deserves an honest exit: six weeks, and if the board is not being opened without being asked, we go back to the list on the home page. The vault mirror and the read-only sqlite are what make that exit cheap — keep both until the six weeks are up.
- Kanboard's own UI on a phone is mediocre. The home board is phone-first, but the "full board" tile lands a phone on a cramped page. Say so on the tile rather than discovering it in the shop.
- Mail plumbing is assumed. Resend's free tier is finite, and mail from the shop address must be covered by the existing SPF and DKIM or the notifications land in spam — which looks exactly like the system not working.
Items 1, 2 and 3 are not phase-6 polish. They belong in phase 1, with the service itself.
Close-out: a Done comment is a claim, not a fact (2026-09-14)¶
Angel's rule: assume nothing. A task closed with a comment still gets rechecked; a task that does not check out gets bounced back with a specific "you missed this"; and whatever the work produced (the source file, the vendor, the order, the spec) is kept somewhere it can be changed later, not just photographed once.
The loop¶
- Nobody closes a task. People move it to a
Reviewcolumn. Kanboard gets a fifth column between Work in progress and Done in every project. Closing is reserved for the verifier; the native automatic action TaskCloseColumn onDonemakes Done and closed the same thing, so "closed silently" becomes impossible by construction. - Every task carries what "done" means, up front. A
Verify:block at the end of the description — three to five lines, each a check that can be made without trusting the closer: where the record should now be, what it should say, what file should exist. Claude writes it for tasks it raises (sop-tasks, intake, proposals). A task raised by hand without one gets the default: a Done comment that names the record location, plus at least one attachment. Example for "new business cards": Verify:- source: artwork/business-card/ has the editable source (SVG/AI/PDF-with-outlines)
- spec: record.yml names vendor, stock, finish, quantity, order number, unit price
- proof: a photo or scan of the delivered card is attached
- record: ERP purchase order exists for the vendor with that order number
task-verifyruns every five minutes (user timer; no webhook needed, the container cannot reach 127.0.0.1:8200 and a five-minute delay is fine). For each task inReviewit:- reads the Verify block, the comments since the last move, and the attachment list;
- runs the machine checks it can (file exists at the path, ERP record via API, site or
notes contain the string, attachment count) — the same style as the
sop-auditchecks; - hands the remainder to Haiku with the Verify block and the Done comment, asking only "which lines are satisfied, which are not, and what exactly is missing" — a checklist answer, never a judgement call;
- pass → moves the card to Done (which closes it), adds a comment "Verified: …" listing
each line and the evidence, records the closure with evidence paths in
task_closures; - fail → moves the card back to Work in progress and comments Missing: with one line per unmet check and why it matters ("no order number, so Purchasing cannot match the invoice"). Kanboard emails the assignee because a comment on their task is an event they already subscribe to. Reopening is logged, so the register shows how many times a task bounced.
- cannot tell (a check Claude cannot run, an ambiguous comment) → the card stays in Review and it appears in Angel's Waiting on you as "needs a human check", with the specific question. It never guesses in either direction.
- The closer can answer. They add the missing thing and move the card to Review again; the loop reruns. Nothing is ever silently accepted after N bounces — it stays in the loop or reaches Angel.
Keeping the source, not the photo¶
The photo proves the thing was made; the source lets it be made again or changed. Every kind of record gets one home, and the Verify block points at it. First table, to be extended as cases appear:
| What the task produced | Where the source lives | Where the fact lives |
|---|---|---|
| Company address, phone, hours | — | ERP company record; Notes/brand/README.md; site footer |
| Artwork: business cards, labels, packaging, stickers | Notes/brand/artwork/<item>/ — editable source + record.yml (vendor, order, stock, finish, price, date) |
the ERP purchase order |
| A document sent outside (quote, terms, letter) | the ERP attachment on the order or company | ERP |
| A product's board, firmware, enclosure | the product repo (product.yml) |
ERP part |
| A credential | Vaultwarden | Notes/roles/credentials.yml register |
| A decision | Notes/decisions/ |
the SOP it changed |
| Anything else | precious/EmbeddedEra/<area>/ — the on-NVMe irreplaceable copy |
— |
record.yml is the small manifest that makes "tweak it later" possible: who printed it, on what,
from which file, at what price — so the next order is a re-order, not a hunt. Sources in the vault
are small (SVG, PDF); anything large goes to precious/ and the vault holds the pointer.
Closing out from a phone (the intake page)¶
The intake page gets a Close out a task mode next to receipt / document / voice:
- pick the task from my tasks in progress;
- add what you have: photo, file, voice note, or a few words;
- mark each upload source or proof (a toggle; default proof for photos, source for files);
- Claude files the sources in the home the table gives, writes
record.ymlfrom what it can read (OCR on an order confirmation, the voice note for vendor and price), writes the Done comment on the task with the paths, moves the card to Review, and runstask-verifyat once instead of waiting for the timer; - the page answers on the spot: "Verified" or "Missing: the source file and the order number" — the same list that lands as a comment on the task, so the person can add it while they still have it in their hand. That is the "you missed this, you missed that" reply.
What this changes elsewhere¶
- SOP-017 Task Register gains the close-out section: Review column, the Verify block, that
closing is the verifier's act, the bounce, and the source table (cross-referenced, not copied,
from wherever it ends up living). Audit check
SOP-017.closed-without-notebecomesSOP-017.closed-unverified— a closed task with no "Verified:" comment, which should be zero. tasks-metricsrecords bounces per task; the Tasks dashboard gets "bounced this month" and "in Review > 2 days".sop-tasksandintakewrite a Verify block on every task they raise.- Kanboard: the Review column and the TaskCloseColumn action per project — done by
onboard kanboard-sync, so a new project gets them too.
Order¶
- Review column + TaskCloseColumn in all seven projects; SOP-017 close-out section with the Verify-block convention and the default. (Half a day, no engineer.)
task-verifywith the machine checks and the Haiku checklist, timer every five minutes, the Waiting-on-you hook for "cannot tell". Try it on the sandbox against three real tasks Yara has in flight before it touches production.- The source table and
Notes/brand/artwork/+record.yml; backfill the business cards and the address as the first two records. - Intake close-out mode with the on-the-spot answer.
- Dashboard and metrics additions.
Decided 2026-09-14: a "cannot tell" goes to a person in the task's department who is
not the closer. Four-eyes, never self-verification. When the department has nobody else
(today: every department but Administration), it falls through to Administration. The code reads
departments.yml and the task's assignee, so it needs no change when a third person joins.
Kill criteria¶
Every new component gets one before it is built, or the estimate quietly becomes a commitment.
- Kanboard — six weeks. If the board is not being opened without being asked, go back to the list on the home page. The vault mirror and the read-only sqlite keep that exit cheap.
- Home on the assistant service — this one has no clean exit once the Pages project retires, so the criterion is a gate rather than a rollback: it does not ship until the per-department tiles work and the board renders correctly at 375 px. If it cannot do both, home stays where it is.
- The AI proposal pipeline — three job classes, thirty proposals. If more than a third of proposed items are rejected rather than accepted or edited, the pipeline is generating work instead of saving it. Stop and fix the class, or drop it.
- The notebook site — see notebook-site.
Filtering and scope¶
Asked for 2026-09-13: the list on the home page must be filterable — by project, by assignment, and more.
The rule that comes first¶
Visibility is not a filter. What a person may see is decided server-side from their departments before any filter runs; filters only narrow what they already may see. If a filter widget can reveal a task, the widget has become a permission control, which is how these things quietly break. The default scope for everyone is their own departments; Administration alone may scope to everything.
Scope, then filters¶
A scope selector first — Mine · My team · Everything I can see — because that is the question people actually ask, and it is server-enforced. Then filters, ANDed together:
| Filter | Values |
|---|---|
| Department | the departments you hold; Administration sees all |
| Assignee | a person, unassigned, or AI — unassigned needs to be one click, because that is where work rots |
| Project | the plan or note that raised it (source_ref), the SOP check that raised it, or entered by hand |
| State | open · done · dropped, open by default |
| Due | overdue · this week · this month · no date. Overdue is the one that matters and is always offered first |
| Priority | low · normal · high |
| Text | plain search over title and detail |
Every filter shows its count, so an empty result is explained before it happens, and an empty list says which filter to clear rather than just being blank.
Two details that make it usable¶
- The filter lives in the URL. "Purchasing, overdue" must be a link somebody can send, bookmark, or put in the morning digest. This is most of the value and costs almost nothing.
- Each person's last view is remembered, defaulting to mine, open, overdue first. On a phone the filters collapse into one sheet with active ones shown as chips.
Where this lands¶
Kanboard has its own filter language and saved views, which covers the full board. The home board is the narrow, fast view for the question "what do I owe today", and it reads the same data. Both are blocked behind phase 1. Until then the same filters are being added to the admin Tasks card, which exists today, so the register is usable before the board exists.
Build status (2026-09-13)¶
- Done: departments split into functions with the Quality function tags, verified with no effective permission change; SOP-017 written and published with its four audit checks and the four amendments; kill criteria for every component; the whole plan loaded into the task register.
- Blocked, not started: phase 1 itself. Creating
tasks.embeddedera.com— DNS, tunnel route, Access app, proxy rules, a new container — was refused as too much to do unattended on a live server. Nothing was created. It needs a session Angel is watching, or his hands. The plan below is unchanged and ready to run.
Order¶
| Phase | What | Rough |
|---|---|---|
| 1 | Kanboard up behind the gate, header hardening, onboard reconcile, Access app + registry token, map card, backup + restore rehearsal, monitors, migrate 112 tasks |
a day and a half |
| 2 | Home onto the service: per-department tiles, outside shortcuts, the board, + Task | a day |
| 3 | SOP findings → tasks, with dedupe, auto-close and accepted risk | half a day |
| 4 | Notifications: Resend SMTP, defaults per person, overdue cronjob, morning digest, iCal | half a day |
| 5 | Hand to AI: sandbox run, proposal report + email, per-item review, apply with outcome report | two days |
| 6 | Grafana panels, SOP-017, the four amendments, new audit checks | a day |
Phases 1 and 2 are the ask. Phase 3 makes the board fill itself. Phase 5 goes last, once the board is trusted — an AI button on a list nobody believes is a way to make bad changes faster.
Related: meetings-and-tasks, roles-and-skills, reporting.
Log¶
- 2026-09-13: plan written.
- 2026-09-13: Angel chose Kanboard over extending our register — he wants the board itself. Plan rewritten around it; notifications and manual entry added.
- 2026-09-14: close-out verification planned — Review column, Verify block, task-verify loop with bounce-back, source table with record.yml, intake close-out mode. Angel's rule: a Done comment is a claim, recheck it, reject with specifics, keep the source.
- 2026-09-14: decided — human verification of a 'cannot tell' is someone in the same department other than the closer; falls through to Administration when there is nobody else.
- 2026-09-14: steps 1–2 of Close-out built and live; 177 verified by task-verify itself, 178 left for the timer. Steps 3–5 (record.yml + backfill, intake close-out mode, dashboard) remain as tasks 179–181.
- 2026-09-14: step 4 (intake close-out mode) live in production; tested pass and bounce paths on a dev-identity copy. Step 5 (metrics, dashboard, digest lines) is task 181.