Reads what colleagues said in the groups and threads a principal has designated, turns it into per-person and per-team task lists, and keeps them current. Assigns nobody, sends nothing, guesses no owner, scores no priority and deletes no task.
Two sentences decide everything in this package.
Someone needs to redo the pricing slide before Friday is a sentence
with a name attached to it — the name of whoever typed it. A tool that
reads the message and assigns the work has just given a task to the
person who noticed it needed doing, and the first they hear of it is a
reminder.
That is not an edge case. On any desk the person who raises work
is systematically not the person who does it: a lead flags, a
colleague mentions, a client asks. So a candidate carries two separate
things — raised_by, a fact read through the message and never
edited, and suggested_owner, a judgement somebody recorded while
reading, which may be blank and is the only field an assignment may
come from. Two columns, never one.
The one case where they coincide — I will get the vendor contract out today — is a person volunteering, and this package prints it rather
than hiding it, because the rule is not never use the sender. On the
sample desk 3 of 8 owned tasks have an owner who is also the raiser
and 5 do not; the harness asserts both counts are above nought,
because a rule that never let them coincide would be a rule
forbidding people to take their own work on.
And an unowned task is not an unassigned task. A task with nobody against it is work nobody has agreed to. Read as a gap to be filled it lands on whoever looks least busy or on the principal by default, and either way somebody is now responsible for something they never accepted. There is no owner guesser here: it is reported as unowned, by name, with the sentence it came from and the group it was said in, and a person decides.
A task travels and the sentence that produced it does not — unless the reader was in the group it was said in
A task raised in the pricing thread and assigned to somebody who is not on that thread is an ordinary Tuesday. The task has to reach them: that is the whole point of the desk. The sentence must not, because the pricing thread is a room they were not in.
So the check is not a prohibition, it is a biconditional:
- the task line is present on the owner's list always;
- the source sentence is present exactly when that reader is a member of the source group;
- and absent exactly when they are not — where it says so, names the group, and names who to ask.
A list that silently dropped what it could not show would have a hole the reader cannot see, and they would ask a colleague instead.
A team list is stricter. It reaches everybody on the team, so a
sentence appears on it only where every member was in the source
group. One member outside is enough to withhold the line from all of
them, because a document's readers are its readers and there is no
such thing as showing one paragraph to four people out of five. The
consequence is deliberate and visible in the samples: T-09 is quoted
in full on Cormac's own list and withheld from the Platform team list
that Cormac also reads.
The same task therefore produces two different lines on two different people's lists, from one run, and that is correct. Every employee before this one in the series had one rule per file; this one has one rule per reader.
Before Friday means the Friday after the day it was said. Read
against the day the tool happens to run, the same words mean a
different Friday every week — so a task that came due a fortnight ago
reads as due at the end of this one, and a list of what is late
silently becomes a list of what is coming up.
Both answers are printed side by side in the capture report, with the rule that produced each. On the sample desk 6 of 11 candidates would get a different date if their words were read against the run date.
The forms are a closed list: today, tomorrow, end of week,
end of month, in a week, and any weekday optionally prefixed by,
on or before. A weekday named is the next occurrence and the day
it was said counts. Anything else is not a date — as soon as possible leaves the candidate with no due date, and the report names
the words it could not read so that whoever approves it can write one
down.
| absent | because |
|---|---|
| an owner guesser | an unowned task is not an unassigned task |
| a priority scorer | a list sorted by a score has a bottom, and the bottom is where work goes to be forgotten with an explanation attached |
| a due-date guesser | a day nobody said is a deadline somebody then gets chased against |
a done_on column |
that minus the due date, per person, over a fortnight, is a performance record — and the outward file here is a list the person it is about receives |
| deletion | a task is closed, deferred or dropped and a reading is declined; a desk that can delete cannot say what happened |
| an address or a telephone number | membership is the access control, and a way of reaching somebody would be a second route the membership table does not govern |
Three files govern this desk and all three ship empty. Every run
refuses, once, listing every gap in all three together — 23 of them on
a fresh install (Sample Output/refused/the_three_governing_files_ship_empty.txt).
| file | what it decides |
|---|---|
desk-mandate.yaml |
the desk, its zone, the window, and where the ten tables are |
desk-team.yaml |
who approves, who compiles, whose name a request goes out under |
task-policy.yaml |
the nudge cap and interval, the escalation points, and whether a request naming nobody becomes a task at all |
They are empty on purpose. Each is a decision about whose conversation
gets read or about who is made responsible for what, and a tool that
filled them in would be taking that decision quietly. task-policy.yaml
also writes out seven things this package will not do, each
shipping as no, so switching one on is a visible edit to a file
somebody owns.
unowned_request_becomes_a_task has no default. Can we get the deposit question closed? is work somebody wants done and nobody has
been asked to do; answered yes it becomes an unowned task, answered no
nothing is created and the sentence is named in the capture report or
it is lost. Both are defensible and they are not the same desk.
people · teams · groups · membership · messages ·
candidates · tasks · priorities · nudges · absences
Eight must have rows. nudges and absences may be empty, because a
fortnight in which nobody had to be chased and a desk where nobody is
away are both states a real desk reaches.
groups.designated has three states: yes may be read, no may
not, and blank is a group nobody has decided about. Nothing is
read from a blank — a blank is not a yes — and the run is not
refused, because a desk that could produce no lists until every
group was triaged is a desk where somebody types no into the column
to make the tool work. The mandate report names the undecided groups
instead. A value that is neither yes, nor no, nor blank is refused:
that is a decision recorded in words this tool cannot read, on the one
column that decides whether a conversation is read at all.
Full column-by-column detail, with what goes wrong in each case, is in
plugins/pa-03-task-manager-personal-assistant/skills/pa-03-task-manager-personal-assistant/references/the-ten-tables.md.
cd plugins/pa-03-task-manager-personal-assistant/skills/pa-03-task-manager-personal-assistant/scripts
python3 mandate_report.py --mandate … --team … --policy …
python3 channel_report.py … # what each reader may be shown
python3 capture_report.py … # raiser vs suggested owner; due dates
python3 task_report.py … # what is open, late, unowned, unbanded
python3 nudge_report.py … # what may be asked about, and the drafts
python3 handoff_report.py … # what the next person needs to know
python3 build_lists.py … --out lists/
Exit codes: 0 nothing to report · 1 findings · 3 refused.
50 findings across the seven scripts, each under its own code
(PA03-M01 … PA03-L08), and 33 refused switches, each refusing
under its own name with its own reason — because a single generic
refusal teaches the reader that the tool will not do that class of
thing and they stop reading.
build_lists.py writes one file per person, one per team and a
register, then reads every file back off the disk and checks the
biconditional in both directions against the membership rows — not
against the function that decided them, because a check that asks the
same question twice checks nothing. A sentence present where its
reader was outside the room, or absent where they were inside it,
refuses.
Every person gets a file every run, including an empty one, because a list that only arrives when there is something outstanding is a list whose arrival is itself the message.
Nothing is sent. The nudge report writes what each request would say and stops, for a named person to read and send. A request is not drafted when the task is not due yet, when nobody owns it, when it wants nothing, when the cap is reached, when the interval has not elapsed, or when the request would go from the sender to the sender. Not one of those reasons is how long its owner has had it.
A task at the cap with no answer goes to the principal instead — a different action, taken by a different person, because somebody who has not answered twice is not somebody who needs asking a third time.
Sample Input/ |
13 files · 9,047 bytes — the whole invented desk |
Sample Output/ |
160 files · 97,967 bytes, every one produced by the scripts here |
↳ refused/ |
144 refusals, each under its own sentence |
↳ written/ |
6 person lists, 2 team lists and the register — 9 files · 18,752 bytes |
Build Tools/fixture/verify.py |
2,836 lines · 854 checks, no failures |
Build Tools/gen_samples.py |
regenerates Sample Output/ from the committed input |
Build Tools/preflight_public_scanners.py |
run before pushing anywhere scanned |
Every person, team, group, message, candidate and task is invented. Ardglass Works does not exist and neither does anybody named in it. That matters more here than it looks: this fixture records, for named colleagues, what they were asked to do, what they agreed to, what they have not finished, how many times they were chased and whether they replied. A real desk in the same shape would be a performance record on every person in it.
python3 "Build Tools/fixture/verify.py"
Eight passes, and each exists because a package can be wrong in a way the previous pass cannot see.
| pass | what it will not let through |
|---|---|
| coverage | 50 codes, 33 refused switches and 115 named refusals — every one reached, or recorded with the reason it cannot be produced from a fixture edit |
| contact | no address and no telephone number anywhere, with an exemption list that is empty and asserted empty, and probes assembled at run time so the harness does not trip its own scan |
| publication | 184 real task, messaging, mail, calendar, document and automation products blocked on word boundaries — in the files and in everything any run printed — with 20 candidates written down as deliberately absent and the reason for each |
| manifest | the plugin installs: frontmatter shape, and a description inside 1024 in characters and in bytes — the byte count is what two earlier employees in this series got wrong |
| invariants | read off the written files, each with an assertion that the case was not vacuous |
| negative cases | 31 codes each with an input that silences them, 19 with a written reason they cannot have one, and one positive control so that quiet means quiet rather than crashed |
| mutation | 18 mutations in both senses — gone for a needle the clean run prints, appears for one only a broken package produces, which is the only sense that tests a guard the package enforces on itself |
| hygiene | width in characters in source and output, nothing defined or imported and unused, and every one of 119 variants proved to change what some script prints |
The mutation pass clears the bytecode cache around every mutation and runs the subprocesses with it switched off. Without that it was intermittently wrong: a mutation and its restore land inside the same filesystem second, and when the two file sizes also agree a later subprocess loads the cached mutated module while the file on disk is clean. A harness that gives a different answer on a second run is worse than one that fails.
Built by New Digital Intelligence.