v0.2.0: human-confirmed grade and announcement writes
Added — write operations, human-confirmed (Foresay protocol)
preview_grade_change/post_grades— set grades on an assignmentpreview_announcement/post_announcement— publish a course announcement
Both follow the Foresay confirmation
protocol. Foresay's operation rule is that an irreversible operation always
takes the confirm branch, and these are irreversible in its sense — students are
notified and a notification cannot be un-sent — so there is no scope threshold
below which the gate is skipped.
The gate has two independent layers, because they catch different failures:
- Elicitation (
elicitation/create) puts the confirmation in front of the
user through the client's own dialog. The model cannot skip it or answer it,
because the answer does not pass through the model. This is what Foresay's own
case study called its biggest gap — "Foresay is a protocol, not a runtime" —
and it closes it, because this project controls the server rather than only
the skill layer. - Payload-bound token. A preview mints a single-use, 10-minute token bound
to the exact payload it rendered; the write refuses any payload whose hash
differs. This catches what the dialog structurally cannot: the human approved
[85, 92, 78]and[85, 92, 87]reached the API. A mismatch burns the token,
so the correction must be re-previewed rather than riding in on the old
approval.
Fail-closed in every direction — no elicitation support, decline, cancel, or
transport error all refuse the write.
Reversibility is read from the assignment rather than assumed: an assignment on
a manual posting policy genuinely is recoverable (grades stay hidden until
posted), and the confirmation says so instead of crying wolf. An unreadable
posting policy is treated as automatic — unknown fails toward the more cautious
warning.
Fixed
Servernow runs withConfiguration.strict. The SDK's
validateClientCapabilityis a no-op unless strict mode is on, and the
default is off — so the elicitation capability check that this feature's
safety was assumed to rest on was not actually running. Caught by reading the
SDK rather than trusting the assumption.- The elicitation capability is additionally checked directly by this server
(captured at the initialize handshake) rather than relying only on the SDK's
strict-mode path, so the safety property does not hinge on a global config
flag that could later be flipped for an unrelated reason. NTUCOOL_API_BASEnow overrides the Canvas base URL.CanvasAPIClient
documented this as configurable "for testing", but the server hardcoded it,
making the claim untrue and the write gate unverifiable offline.
Verification
The gate was exercised end-to-end against a stub Canvas server: a valid token
was minted by a preview, the write was attempted from a client that declared no
elicitation support, the write was refused rather than hung, and the stub
recorded zero write requests. 16 unit tests cover replay, expiry, payload
drift, cross-payload-type redemption, and delimiter-collision resistance.