Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions scripts/pm/check-half-states.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@
* own state model (maintainer ruling 2026-08-19/20, verbatim: 「同意 并
* 存」) has it COEXIST with the card's standing `pm:*` label rather than
* replace it: the objecting seat applies it alongside its evidence
* comment, and the triage Routine — which re-judges every `pm:retriage`
* card each fire, high priority (SKILL.md) — is the only remover. That
* comment, and the triage Routine — which answers every `pm:retriage`
* card's question each fire (SKILL.md) — is the only remover. That
* division of duties is H13's healing-loop shape again: a label nobody
* re-checks for age is the next "state nobody is watching", and without
* this item nothing here would say so. Aged past one cycle the row names
Expand All @@ -329,11 +329,11 @@
* disputed grading is unidentifiable (异议对象不明) from the label set
* alone. Age is read from `updated_at`, the same proxy H13 uses for the
* same reason: this sweep makes no per-card timeline fetch for the
* label-APPLICATION event, and every triage re-judgement (grade kept or
* changed) bumps `updated_at`, so a stale reading means nothing touched
* the card since some triage pass — which is exactly the failure this
* item exists to name. Report-only and NOT loud, like H14–H16: the
* remedy is the triage Routine's next fire, never a label written here.
* label-APPLICATION event, and every triage answer (whatever it decides)
* bumps `updated_at`, so a stale reading means nothing touched the card
* since some triage pass — which is exactly the failure this item exists
* to name. Report-only and NOT loud, like H14–H16: the remedy is the
* triage Routine's next fire, never a label written here.
*
* ## H19 — the question that ENDS a block
*
Expand Down Expand Up @@ -3086,17 +3086,17 @@ function readTrackedFiles() {

/**
* H18 threshold — one triage-Routine cycle, the same reasoning as
* `DOMAIN_HALF_STATE_STALE_HOURS`: the Routine fires HOURLY and re-judges
* every `pm:retriage` card "each fire, high priority" (SKILL.md, 「`pm:retriage`
* 重判每 fire 高优先处理」), so a card still carrying the label after 2h has
* survived at least one re-judgement pass it should not have. Age is read
* from `updated_at` rather than a per-card timeline fetch for the
* label-APPLICATION event: this sweep makes that fetch for no item (H13's
* same proxy choice, for the same reason — see `h13DomainWithoutPmState`),
* and every triage write on the card (grade kept or changed) bumps
* `updated_at`, so a stale reading here means nothing touched the card since
* any triage pass at all, which is exactly the failure this item exists to
* name.
* `DOMAIN_HALF_STATE_STALE_HOURS`: the Routine fires HOURLY and ANSWERS the
* question on every `pm:retriage` card each fire (SKILL.md, 「`pm:retriage`
* fire 先答异议评论所求,答后同笔摘标;须维护者答的进收件箱,标照摘」), so a card
* still carrying the label after 2h has survived at least one answer pass it
* should not have. Age is read from `updated_at` rather than a per-card
* timeline fetch for the label-APPLICATION event: this sweep makes that fetch
* for no item (H13's same proxy choice, for the same reason — see
* `h13DomainWithoutPmState`), and every triage write on the card (whatever
* the answer) bumps `updated_at`, so a stale reading here means nothing
* touched the card since any triage pass at all, which is exactly the failure
* this item exists to name.
*/
export const RETRIAGE_STALE_HOURS = 2;

Expand Down Expand Up @@ -3127,10 +3127,11 @@ export function h18RetriageAged(issue, nowMs = Date.now()) {
? `alongside its standing ${coexisting.map((l) => `\`${l}\``).join(', ')}`
: 'ALONE, with no coexisting standing `pm:*` label — the disputed grading is unidentifiable (异议对象不明)';
return (
`\`pm:retriage\` carried ${carrying}, ${reading} — the objecting seat's grade is still undecided past ` +
`one triage cycle. The triage Routine re-judges every \`pm:retriage\` card each fire (SKILL.md); a card ` +
`still here past the threshold is a re-judgement pass that did not run, not inventory: resolve the ` +
`grade (keep or change) and drop the label in the same write, oldest first.`
`\`pm:retriage\` carried ${carrying}, ${reading} — the question the objecting seat put to triage is ` +
`still unanswered past one triage cycle. The triage Routine answers every \`pm:retriage\` card's ` +
`question each fire (SKILL.md); a card still here past the threshold is an answer pass that did not ` +
`run, not inventory: answer what the comment asks and drop the label in the same write (an answer that ` +
`needs the maintainer goes to the inbox with the label still removed), oldest first.`
);
}

Expand Down Expand Up @@ -4081,10 +4082,10 @@ export function h21NegatedClosingKeyword(pr) {
* itself, whose label is what makes the seat list page a board.
* A closed seat card keeps it as identity, not as state.
* `pm:epic` a delegation marker on a parent, the same kind of identity.
* `pm:retriage` a request for re-judgement. Plausibly residue too, and it did
* not appear in the census — so it stays out until something
* measures it, rather than being widened in on a hunch. The
* set is one edit away when that measurement exists.
* `pm:retriage` a question put to the triage seat. Plausibly residue too, and
* it did not appear in the census — so it stays out until
* something measures it, rather than being widened in on a
* hunch. The set is one edit away when that measurement exists.
*/
export const PM_RESIDUE_LABELS = [
'pm:dispatched',
Expand Down Expand Up @@ -14307,6 +14308,16 @@ function selfTest() {
t('H18: …and the finding names the threshold', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes(`${RETRIAGE_STALE_HOURS}h`), true);
t('H18: …and names the coexisting standing label', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('`pm:queue`'), true);
t('H18: multiple coexisting labels are all named', h18row(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocked`') && h18row(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocking`'), true);
// The label is a QUESTION put to the triage seat, not a request to re-grade
// (SKILL.md 「`pm:retriage` 每 fire 先答异议评论所求,答后同笔摘标;须维护者答的进收件箱,标照摘」):
// the row says the question is unanswered, and the remedy is to answer what
// the objection comment asks and drop the label in the same write.
t('H18: the row reads the label as an unanswered question', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('is still unanswered past one triage cycle'), true);
t('H18: …and the remedy is answer + drop the label in the same write', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('answer what the comment asks and drop the label in the same write'), true);
t('H18: …and an answer that needs the maintainer still drops the label', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('needs the maintainer goes to the inbox with the label still removed'), true);
// The retired re-grade vocabulary is gone from BOTH row shapes.
t('H18: no re-grade vocabulary in the coexisting row', /re-judg|grade \(keep or change\)/.test(h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW)), false);
t('H18: …nor in the ALONE row', /re-judg|grade \(keep or change\)/.test(h18row(domainCard(['pm:retriage'], hoursAgo(3)), NOW)), false);
// Under-threshold: fresh objection is normal intake latency, not a finding.
t('H18: retriage under the threshold -> clean', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(1)), NOW), null);
t('H18: exactly at the threshold -> clean (strictly beyond fires)', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(RETRIAGE_STALE_HOURS)), NOW), null);
Expand Down
29 changes: 17 additions & 12 deletions scripts/pm/ensure-pm-labels.sh
Original file line number Diff line number Diff line change
Expand Up @@ -255,19 +255,24 @@ for R in objectstack-ai/objectstack objectstack-ai/objectui objectstack-ai/cloud
# and list-page scans. A hand-set instance is mislabeling — the sweep
# corrects it against the index.
gh label create pm:blocking -R "$R" -c 8250df -d "Derived cache from the Blocked-by reverse index: open card with open dependents — never hand-set" 2>/dev/null || true
# pm:retriage COEXISTS with the standing pm:* label and ⛔ never replaces it
# pm:retriage is a QUESTION put to the triage seat (a re-grade, a lane for a
# cross-domain PR, a re-route, a split, a disposition fork a dev report left
# open); it COEXISTS with the standing pm:* label and ⛔ never replaces it
# (maintainer ruling 2026-08-19/20, 「同意 并存」): the objecting seat sets it in
# the same write as its evidence comment, and the triage Routine is the only
# remover — hanging and removing belong to two different parties, which is
# what makes the state safe. Named consumers: the dispatchable-candidate
# query (a pm:queue card carrying it is skipped — objection undecided, no
# dispatch) and the triage round's high-priority re-judgement pass. It is in
# this five-repo loop because the state-model row requires the label to exist
# in all five; where a first application already auto-created the object,
# creation here is a no-op and `--reconcile` is what gives it this colour and
# description (measured 2026-08-20: grey with an empty description in
# objectstack, and absent entirely from objectui).
gh label create pm:retriage -R "$R" -c d4c5f9 -d "Awaiting triage re-judgement — coexists with the standing pm:* label; queued cards skip dispatch" 2>/dev/null || true
# the same write as its objection comment, which states what it asks, and the
# triage Routine is the only remover — hanging and removing belong to two
# different parties, which is what makes the state safe. Named consumers: the
# dispatchable-candidate query (a pm:queue card carrying it is skipped — the
# question is unanswered, no dispatch) and the triage round's answer pass,
# which answers what the objection comment asks each fire and drops the label
# in the same write (SKILL.md 「`pm:retriage` 每 fire 先答异议评论所求,答后同笔
# 摘标;须维护者答的进收件箱,标照摘」). It is in this five-repo loop because the
# state-model row requires the label to exist in all five; where a first
# application already auto-created the object, creation here is a no-op and
# `--reconcile` is what gives it this colour and description (measured
# 2026-08-20: grey with an empty description in objectstack, and absent
# entirely from objectui).
gh label create pm:retriage -R "$R" -c d4c5f9 -d "Question for triage, answered each fire; coexists with the standing pm:* label; no dispatch" 2>/dev/null || true
gh label create finding -R "$R" -c c2e0c6 -d "Recorded observation — held, not dispatchable until the findings triage round grades it" 2>/dev/null || true
gh label create pm:epic -R "$R" -c 5319e7 -d "Reserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue" 2>/dev/null || true
done
Expand Down
Loading