1package timelock_guardian23import (4 "chain"5 "chain/runtime/unsafe"6 "strconv"7 "strings"8 "time"9)1011// TargetConfig binds a named target to the only address allowed to12// schedule actions against it, an enforced minimum delay, and an13// optional guardian who can veto pending actions. Without this binding14// a timelock attests nothing: anyone could schedule their own15// short-delay action against any name and "execute" it.16type TargetConfig struct {17 Name string18 Owner address19 Guardian address // empty = no guardian20 MinDelay int64 // seconds; every action for this target waits at least this21 // PendingOwner is the offered-but-not-accepted new owner (fix Y5:22 // ownership moves in two steps, so a stranger can never have a23 // target — and its quota slot and pending obligations — dumped on24 // them without consenting).25 PendingOwner address26}2728// Action represents a scheduled operation that can only execute after29// its delay has elapsed, and only within its grace window.30//31// State model (re-audit 2026-09-02): only PENDING and EXECUTED actions32// are stored. Executed records are permanent attestations consumers33// check via IsExecuted. Cancelled, vetoed, and expired actions are34// REAPED from state — their history lives in emitted events — so the35// live-action cap bounds live exposure and can never be consumed36// permanently by schedule/cancel cycling.37type Action struct {38 ID string39 Target string40 Creator address41 Data string // encoded call data or description of the action42 ScheduledAt time.Time43 ExecuteAfter time.Time44 ExpiresAt time.Time // ExecuteAfter + GracePeriod; not executable after45 Executed bool46}4748const (49 // MinDelayFloor is the smallest minimum delay a target may register.50 // A timelock with a 1-second delay protects nothing.51 MinDelayFloor = int64(60)5253 // MaxDelay bounds both registered minimum delays and per-action54 // delays. Also the overflow guard: MaxDelay seconds in nanoseconds55 // is far below int64 range, so ExecuteAfter arithmetic cannot wrap56 // into the past.57 MaxDelay = int64(10 * 365 * 24 * 3600) // 10 years5859 // GracePeriod is how long an action stays executable once ready.60 // After it, the action expires: something scheduled and forgotten61 // cannot be sprung on a target years later.62 GracePeriod = int64(30 * 24 * 3600) // 30 days6364 // Quotas are PER-OWNER only (re-audit round 3): any global cap is a65 // shared resource a few sybil accounts can exhaust forever (10y66 // delays defeat expiry sweeping), bricking every other tenant. With67 // per-owner quotas an attacker only ever consumes their own budget;68 // state growth is priced in gas and funded accounts.69 MaxTargetsPerOwner = 1070 MaxPendingPerTarget = 2071 MaxTargetNameLen = 6472 MaxDataLen = 200073 MaxRenderActions = 10074 // RenderIndexCap bounds the render-ordering index (fix Y4): the75 // index self-trims to this size, so reaping an entry from it is a76 // bounded scan no matter how many actions the realm has ever seen.77 // Records older than the window stay in state (GetAction/IsExecuted78 // are map reads and permanent); they only leave the front page.79 RenderIndexCap = 2 * MaxRenderActions80)8182var (83 targets map[string]*TargetConfig84 targetNames []string // insertion-ordered for deterministic iteration85 ownerTargets map[address]int86 actions map[string]*Action87 actionIDs []string // insertion-ordered render window, self-trimmed to RenderIndexCap88 // pending holds each target's LIVE pending action IDs in insertion89 // order (fix Y4): every list is bounded by MaxPendingPerTarget, so90 // scans, sweeps, and removals are bounded per call regardless of how91 // much state OTHER tenants have grown — queue position in a global92 // list is no longer a shared resource.93 pending map[string][]string94 // pendingTargets lists the targets holding >= 1 live pending action,95 // maintained with O(1) swap-remove via ptIndex (round-2 fix Y-1): no96 // write path ever scans a list another tenant can grow, and97 // GetPending/Render iterate only targets that actually have work.98 pendingTargets []string99 ptIndex map[string]int100 nextID int101)102103func init() {104 targets = make(map[string]*TargetConfig)105 targetNames = []string{}106 ownerTargets = make(map[address]int)107 actions = make(map[string]*Action)108 actionIDs = []string{}109 pending = make(map[string][]string)110 pendingTargets = []string{}111 ptIndex = make(map[string]int)112 nextID = 1113}114115func now() time.Time {116 return time.Now()117}118119// ---------- helpers ----------120121// rejectStraySend aborts when coins are attached to a call (fix Y6):122// this realm handles no funds and holds no banker, so an attached send123// would strand on the realm address forever. Aborting reverts the124// transfer back to the sender. Fails open for realm-routed calls, whose125// attached send lands on the intermediary realm, never here.126func rejectStraySend(cur realm) {127 if cur.Previous().IsUserCall() && len(unsafe.OriginSend()) > 0 {128 panic("this realm does not accept coins")129 }130}131132func mustGetTarget(name string) *TargetConfig {133 t, ok := targets[name]134 if !ok {135 panic("target not found: " + name)136 }137 return t138}139140func mustGet(id string) *Action {141 a, ok := actions[id]142 if !ok {143 panic("action not found: " + id)144 }145 return a146}147148func isValidName(name string) bool {149 if name == "" || len(name) > MaxTargetNameLen {150 return false151 }152 for _, c := range name {153 if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {154 return false155 }156 }157 return true158}159160func expired(a *Action) bool {161 return !a.Executed && now().After(a.ExpiresAt)162}163164func removeID(list []string, id string) []string {165 for i, x := range list {166 if x == id {167 return append(list[:i], list[i+1:]...)168 }169 }170 return list171}172173func addPendingTarget(target string) {174 if _, ok := ptIndex[target]; ok {175 return176 }177 ptIndex[target] = len(pendingTargets)178 pendingTargets = append(pendingTargets, target)179}180181// removePendingTarget drops a target from the with-pendings list in182// O(1) by swapping the last entry into its slot. Order afterwards is183// deterministic (a pure function of the operation history), which is184// all iteration needs.185func removePendingTarget(target string) {186 i, ok := ptIndex[target]187 if !ok {188 return189 }190 last := len(pendingTargets) - 1191 moved := pendingTargets[last]192 pendingTargets[i] = moved193 ptIndex[moved] = i194 pendingTargets = pendingTargets[:last]195 delete(ptIndex, target)196}197198// dropPending removes id from its target's pending list. Bounded: the199// list never exceeds MaxPendingPerTarget.200func dropPending(target, id string) {201 l := removeID(pending[target], id)202 if len(l) == 0 {203 delete(pending, target)204 removePendingTarget(target)205 } else {206 pending[target] = l207 }208}209210// reap removes a non-executed action from state entirely; its history211// is the emitted event. Executed records are permanent attestations:212// reap refuses them outright (round-2 hardening), so no future caller213// can erase one by mistake.214func reap(id string) {215 a := actions[id]216 if a != nil && a.Executed {217 return218 }219 if a != nil {220 dropPending(a.Target, id)221 }222 delete(actions, id)223 actionIDs = removeID(actionIDs, id)224}225226// sweepExpired reaps the expired pending actions of ONE target (fix227// Y4/Y2: the old global budget-windowed sweep let long-delay entries at228// the front of a shared queue starve everything behind them). A229// target's list is bounded by MaxPendingPerTarget, so the sweep is a230// bounded scan. Expiry history is the emitted event.231func sweepExpired(target string) {232 var live []string233 for _, id := range pending[target] {234 a := actions[id]235 if a == nil {236 continue237 }238 if expired(a) {239 chain.Emit("timelock_expired", "id", id, "target", a.Target)240 delete(actions, id)241 actionIDs = removeID(actionIDs, id)242 continue243 }244 live = append(live, id)245 }246 if len(live) == 0 {247 delete(pending, target)248 removePendingTarget(target)249 } else {250 pending[target] = live251 }252}253254// pendingCount is the length of the target's live list. The counter it255// replaces (fix Y4) could desync from the lists it mirrored; a length256// cannot. It may briefly include not-yet-swept expired actions; those257// are reaped by the next Schedule's sweep or by anyone's Expire, and in258// the worst case an owner briefly under-uses their own quota — never259// another tenant's.260func pendingCount(target string) int {261 return len(pending[target])262}263264// sanitize makes attacker-controlled text safe to embed in markdown and265// single-line summaries: backticks, pipes, newlines, link syntax, and266// raw HTML brackets (fix Y3: gnoweb has no HTML sanitization layer, so267// a literal <script> tag must never reach the page) are replaced so a268// Data string cannot break out of its cell, inject rows or markup, or269// render a live link.270func sanitize(s string) string {271 r := strings.NewReplacer(272 "`", "'", "|", "/", "\n", " ", "\r", " ",273 "[", "(", "]", ")", "*", "·", "_", "-",274 "<", "(", ">", ")",275 )276 return r.Replace(s)277}278279// truncate shortens s to at most n runes without splitting a multibyte280// character. n is clamped to a minimum of 4 (re-audit: n<=2 sliced out281// of bounds).282func truncate(s string, n int) string {283 if n < 4 {284 n = 4285 }286 runes := []rune(s)287 if len(runes) <= n {288 return s289 }290 return string(runes[:n-3]) + "..."291}292293// ---------- target registration ----------294295// RegisterTarget creates a named target. The caller becomes its owner —296// the only address that may schedule actions against it. minDelay is297// the enforced floor for every action's delay. guardian may be empty298// (no guardian) or an address empowered to veto pending actions.299func RegisterTarget(cur realm, name string, minDelay int64, guardian address) {300 rejectStraySend(cur)301 owner := cur.Previous().Address()302 if !isValidName(name) {303 panic("target name must be 1-" + strconv.Itoa(MaxTargetNameLen) +304 " chars, lowercase alphanumeric with underscores only")305 }306 if _, exists := targets[name]; exists {307 panic("target already registered: " + name)308 }309 if ownerTargets[owner] >= MaxTargetsPerOwner {310 panic("per-owner target limit reached")311 }312 if minDelay < MinDelayFloor {313 panic("minimum delay must be at least " + strconv.FormatInt(MinDelayFloor, 10) + " seconds")314 }315 if minDelay > MaxDelay {316 panic("minimum delay exceeds the maximum of " + strconv.FormatInt(MaxDelay, 10) + " seconds")317 }318 if guardian != "" && !guardian.IsValid() {319 panic("invalid guardian address: " + string(guardian))320 }321322 targets[name] = &TargetConfig{323 Name: name,324 Owner: owner,325 Guardian: guardian,326 MinDelay: minDelay,327 }328 targetNames = append(targetNames, name)329 ownerTargets[owner]++330 chain.Emit("timelock_target_registered", "target", name,331 "owner", string(owner), "guardian", string(guardian),332 "min_delay", strconv.FormatInt(minDelay, 10))333}334335// SetGuardian changes (or clears, with "") the target's guardian.336// Owner only, and REFUSED while the target has pending actions: the337// guardian's veto power exists precisely to check the owner during a338// delay window, so the owner must not be able to strip it mid-window.339//340// KNOWN LIMIT (documented, round-3 audit): the owner can cancel all341// pending actions, change the guardian, and reschedule — the price is342// a full fresh MinDelay on every rescheduled action, and every step343// emits an event (cancellations + the guardian change below), so344// observers always get MinDelay of warning under the new guardian345// regime. Guardians protect open windows, not the owner's future.346func SetGuardian(cur realm, targetName string, guardian address) {347 rejectStraySend(cur)348 t := mustGetTarget(targetName)349 if cur.Previous().Address() != t.Owner {350 panic("only the target owner can set the guardian")351 }352 // expired-but-unswept actions protect nothing; sweep them so they353 // cannot block a guardian rotation (round-2 hardening)354 sweepExpired(targetName)355 if pendingCount(targetName) > 0 {356 panic("cannot change the guardian while actions are pending")357 }358 if guardian != "" && !guardian.IsValid() {359 panic("invalid guardian address: " + string(guardian))360 }361 t.Guardian = guardian362 chain.Emit("timelock_guardian_changed", "target", targetName,363 "guardian", string(guardian))364}365366// TransferTargetOwnership OFFERS a target to a new owner; the nominee367// must AcceptTargetOwnership to complete it (fix Y5: a one-step368// transfer let anyone fill a stranger's per-owner quota and dump369// pending obligations — with an attacker-chosen guardian — on an370// address that never asked). Owner only. Pass "" to clear a pending371// offer. Nothing changes hands until the nominee accepts.372func TransferTargetOwnership(cur realm, targetName string, newOwner address) {373 rejectStraySend(cur)374 t := mustGetTarget(targetName)375 if cur.Previous().Address() != t.Owner {376 panic("only the target owner can transfer ownership")377 }378 if newOwner == "" {379 t.PendingOwner = ""380 chain.Emit("timelock_ownership_offer_cleared", "target", targetName)381 return382 }383 if !newOwner.IsValid() {384 panic("invalid new owner address: " + string(newOwner))385 }386 if newOwner == t.Owner {387 panic("new owner is already the owner")388 }389 t.PendingOwner = newOwner390 chain.Emit("timelock_ownership_offered", "target", targetName,391 "pending_owner", string(newOwner))392}393394// AcceptTargetOwnership completes a pending ownership offer; only the395// nominee can accept. The nominee's quota is checked HERE — consent396// time — so an offer can never overfill an account that did not agree397// to carry it.398func AcceptTargetOwnership(cur realm, targetName string) {399 rejectStraySend(cur)400 t := mustGetTarget(targetName)401 a := cur.Previous().Address()402 if t.PendingOwner == "" || a != t.PendingOwner {403 panic("caller is not the pending owner")404 }405 if ownerTargets[a] >= MaxTargetsPerOwner {406 panic("accepting would exceed the per-owner target limit")407 }408 ownerTargets[t.Owner]--409 if ownerTargets[t.Owner] <= 0 {410 delete(ownerTargets, t.Owner)411 }412 ownerTargets[a]++413 t.Owner = a414 t.PendingOwner = ""415 chain.Emit("timelock_ownership_transferred", "target", targetName,416 "new_owner", string(a))417}418419// ---------- write operations ----------420421// Schedule creates a new timelocked action against a registered target.422// Only the target's owner may schedule. The delay must be at least the423// target's registered minimum and at most MaxDelay. Returns the action ID.424func Schedule(cur realm, targetName, data string, delay int64) string {425 rejectStraySend(cur)426 t := mustGetTarget(targetName)427 caller := cur.Previous().Address()428 if caller != t.Owner {429 panic("only the target owner can schedule actions for " + targetName)430 }431 if data == "" {432 panic("data must not be empty")433 }434 if len(data) > MaxDataLen {435 panic("data exceeds " + strconv.Itoa(MaxDataLen) + " bytes")436 }437 if delay < t.MinDelay {438 panic("delay below the target's minimum of " + strconv.FormatInt(t.MinDelay, 10) + " seconds")439 }440 if delay > MaxDelay {441 panic("delay exceeds the maximum of " + strconv.FormatInt(MaxDelay, 10) + " seconds")442 }443444 sweepExpired(targetName)445 if pendingCount(targetName) >= MaxPendingPerTarget {446 panic("too many pending actions for target " + targetName)447 }448449 id := "action_" + strconv.Itoa(nextID)450 nextID++451452 ts := now()453 execAfter := ts.Add(time.Duration(delay) * time.Second)454 actions[id] = &Action{455 ID: id,456 Target: targetName,457 Creator: caller,458 Data: data,459 ScheduledAt: ts,460 ExecuteAfter: execAfter,461 ExpiresAt: execAfter.Add(time.Duration(GracePeriod) * time.Second),462 }463 actionIDs = append(actionIDs, id)464 // self-trim (fix Y4): the render index stays bounded, so reaping an465 // entry from it can never become an unbounded scan. Trimmed entries466 // remain in the actions map; only front-page ordering forgets them.467 if len(actionIDs) > RenderIndexCap {468 actionIDs = append([]string{}, actionIDs[len(actionIDs)-MaxRenderActions:]...)469 }470 pending[targetName] = append(pending[targetName], id)471 addPendingTarget(targetName)472 // full-attribute event (round-3 audit): reaped records must be473 // reconstructible from events alone474 chain.Emit("timelock_scheduled", "id", id, "target", targetName,475 "creator", string(caller), "data", data,476 "delay", strconv.FormatInt(delay, 10),477 "execute_after", execAfter.Format(time.RFC3339),478 "expires_at", execAfter.Add(time.Duration(GracePeriod)*time.Second).Format(time.RFC3339))479 return id480}481482// Execute marks an action as executed. Anyone can call this — the483// timelock is the protection, not the executor's identity. The action484// must exist, be pending, its delay elapsed, and its grace window not485// yet expired.486func Execute(cur realm, actionID string) string {487 rejectStraySend(cur)488 a := mustGet(actionID)489 if a.Executed {490 panic("action already executed")491 }492 if now().Before(a.ExecuteAfter) {493 remaining := a.ExecuteAfter.Sub(now())494 panic("too early: " + strconv.FormatInt(int64(remaining/time.Second), 10) + "s remaining")495 }496 if now().After(a.ExpiresAt) {497 panic("action expired: the grace window of " +498 strconv.FormatInt(GracePeriod, 10) + "s after readiness has passed")499 }500501 a.Executed = true502 dropPending(a.Target, actionID)503 chain.Emit("timelock_executed", "id", actionID, "target", a.Target)504 return "executed: " + actionID505}506507// Cancel removes a pending action. Only the target's CURRENT owner can508// cancel (re-audit: the scheduling creator's rights must not survive an509// ownership transfer). The record is reaped; history is the event.510func Cancel(cur realm, actionID string) string {511 rejectStraySend(cur)512 a := mustGet(actionID)513 t := mustGetTarget(a.Target)514 if cur.Previous().Address() != t.Owner {515 panic("only the target owner can cancel")516 }517 if a.Executed {518 panic("cannot cancel an already-executed action")519 }520 if expired(a) {521 panic("action expired: nothing to cancel (use Expire)")522 }523524 chain.Emit("timelock_cancelled", "id", actionID, "target", a.Target)525 reap(actionID)526 return "cancelled: " + actionID527}528529// Veto cancels a pending action as the target's guardian. This is the530// guardian's whole power: it can stop a scheduled action during the531// delay window, never create or execute one. The record is reaped;532// history is the event.533func Veto(cur realm, actionID string) string {534 rejectStraySend(cur)535 a := mustGet(actionID)536 t := mustGetTarget(a.Target)537 if t.Guardian == "" {538 panic("target has no guardian: " + a.Target)539 }540 if cur.Previous().Address() != t.Guardian {541 panic("only the target guardian can veto")542 }543 if a.Executed {544 panic("cannot veto an already-executed action")545 }546 if expired(a) {547 panic("action expired: nothing to veto (use Expire)")548 }549550 chain.Emit("timelock_vetoed", "id", actionID, "target", a.Target)551 reap(actionID)552 return "vetoed: " + actionID553}554555// Expire reaps a provably expired action. Permissionless (fix Y2): an556// expired action decides nothing — reaping it only writes down what the557// clock already decided — so anyone may free the quota slot it holds.558// This is the recovery valve that makes a wedged target impossible:559// before it, a phantom expired entry consumed quota and blocked560// SetGuardian until a global sweep happened to reach it; now its own561// target's owner — or anyone else — reaps it directly.562func Expire(cur realm, actionID string) string {563 rejectStraySend(cur)564 a := mustGet(actionID)565 if a.Executed {566 panic("action already executed")567 }568 if !expired(a) {569 panic("action is not expired")570 }571 chain.Emit("timelock_expired", "id", actionID, "target", a.Target)572 reap(actionID)573 return "expired: " + actionID574}575576// ---------- read-only queries ----------577578// GetTarget returns a formatted summary of a registered target.579func GetTarget(targetName string) string {580 t := mustGetTarget(targetName)581 var b strings.Builder582 b.WriteString("Target: " + t.Name + "\n")583 b.WriteString("Owner: " + string(t.Owner) + "\n")584 guardian := "none"585 if t.Guardian != "" {586 guardian = string(t.Guardian)587 }588 b.WriteString("Guardian: " + guardian + "\n")589 b.WriteString("Min delay: " + strconv.FormatInt(t.MinDelay, 10) + "s\n")590 if t.PendingOwner != "" {591 b.WriteString("Pending owner: " + string(t.PendingOwner) + "\n")592 }593 return b.String()594}595596// GetAction returns a formatted summary of a single pending or executed597// action. Cancelled/vetoed/expired actions are reaped — their history598// is in emitted events.599func GetAction(actionID string) string {600 a := mustGet(actionID)601 return formatAction(a)602}603604// GetPending returns the IDs of all pending, non-expired actions,605// grouped by target, insertion-ordered within a target. Only targets606// that actually hold live pendings are visited (round-2 fix Y-1), so607// the scan cannot be inflated by registrations alone.608func GetPending() string {609 var results []string610 for _, name := range pendingTargets {611 for _, id := range pending[name] {612 a := actions[id]613 if a == nil || a.Executed || expired(a) {614 continue615 }616 results = append(results, a.ID)617 }618 }619 if len(results) == 0 {620 return "none"621 }622 return strings.Join(results, ", ")623}624625// IsReady returns true if the action exists, is pending, its delay has626// elapsed, and it has not expired.627func IsReady(actionID string) bool {628 a, ok := actions[actionID]629 if !ok || a.Executed || expired(a) {630 return false631 }632 return !now().Before(a.ExecuteAfter)633}634635// IsExecuted returns true if the action exists and was executed. This is636// the consumer-side check: combined with target registration it attests637// that the target's registered owner scheduled the action, it waited at638// least the registered minimum delay, no guardian vetoed it, and it was639// executed within its grace window. Executed records are permanent.640func IsExecuted(actionID string) bool {641 a, ok := actions[actionID]642 return ok && a.Executed643}644645// ---------- render ----------646647// Render returns a markdown overview. Never panics. Cancelled, vetoed,648// and expired actions are reaped from state; their history is in649// events. The page shows the most recent actions only (the ordering650// index is bounded — fix Y4); older executed records stay queryable651// via GetAction/IsExecuted forever.652func Render(path string) string {653 if actions == nil || len(actions) == 0 {654 return "# Timelock Guardian\n\nNo actions scheduled.\n"655 }656657 // The Pending section reads the live per-target lists, so flooding658 // the realm with executed churn can never push a still-live pending659 // action off the page during its delay window (round-2 fix Y-2).660 var pendingList []*Action661 truncated := false662 for _, name := range pendingTargets {663 for _, id := range pending[name] {664 a := actions[id]665 if a == nil || a.Executed || expired(a) {666 continue667 }668 if len(pendingList) >= MaxRenderActions {669 truncated = true670 break671 }672 pendingList = append(pendingList, a)673 }674 if truncated {675 break676 }677 }678679 // The Executed section reads the bounded render index: recent680 // attestations only; older ones stay queryable via GetAction.681 var executedList []*Action682 show := actionIDs683 if len(show) > MaxRenderActions {684 show = show[len(show)-MaxRenderActions:]685 truncated = true686 }687 for _, id := range show {688 a := actions[id]689 if a == nil || !a.Executed {690 continue691 }692 executedList = append(executedList, a)693 }694695 var b strings.Builder696 b.WriteString("# Timelock Guardian\n\n")697 if truncated {698 b.WriteString("_Showing at most " + strconv.Itoa(MaxRenderActions) + " actions per section._\n\n")699 }700701 section := func(title string, list []*Action) {702 if len(list) == 0 {703 return704 }705 b.WriteString("## " + title + " (" + strconv.Itoa(len(list)) + ")\n\n")706 for _, a := range list {707 b.WriteString(renderActionRow(a))708 }709 b.WriteString("\n")710 }711 section("Pending", pendingList)712 section("Executed", executedList)713714 return b.String()715}716717// ---------- formatting helpers ----------718719func formatAction(a *Action) string {720 var b strings.Builder721 b.WriteString("ID: " + a.ID + "\n")722 b.WriteString("Target: " + sanitize(a.Target) + "\n")723 b.WriteString("Creator: " + string(a.Creator) + "\n")724 b.WriteString("Data: " + sanitize(a.Data) + "\n")725 b.WriteString("Scheduled: " + a.ScheduledAt.Format(time.RFC3339) + "\n")726 b.WriteString("Execute after: " + a.ExecuteAfter.Format(time.RFC3339) + "\n")727 b.WriteString("Expires: " + a.ExpiresAt.Format(time.RFC3339) + "\n")728729 switch {730 case a.Executed:731 b.WriteString("Status: executed\n")732 case expired(a):733 b.WriteString("Status: expired\n")734 case now().Before(a.ExecuteAfter):735 remaining := a.ExecuteAfter.Sub(now())736 b.WriteString("Status: locked (" + strconv.FormatInt(int64(remaining/time.Second), 10) + "s remaining)\n")737 default:738 b.WriteString("Status: ready\n")739 }740 return b.String()741}742743func renderActionRow(a *Action) string {744 data := truncate(sanitize(a.Data), 40)745746 status := "pending"747 switch {748 case a.Executed:749 status = "executed"750 case expired(a):751 status = "expired"752 case !now().Before(a.ExecuteAfter):753 status = "**ready**"754 }755756 return "- **" + a.ID + "** | target: `" + sanitize(a.Target) + "` | " + data + " | " + status + "\n"757}758AcceptTargetOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string)
Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string
Execute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string
Expire(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string
GetAction(actionID string) string
GetPending() string
GetTarget(targetName string) string
IsExecuted(actionID string) bool
IsReady(actionID string) bool
RegisterTarget(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, minDelay int64, guardian string)
Render(path string) string
Schedule(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, data string, delay int64) string
SetGuardian(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, guardian string)
TransferTargetOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, newOwner string)
Veto(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.