Documentation
¶
Overview ¶
Package task is the module manifest: what the kernel needs to know about tasks that it cannot learn from a function call.
It is the shape every module follows: contracts/, internal/, and one exported function taking a struct of typed dependencies and returning a module.Module. The application constructs it in dependency order. Compilation checks the dependency types; composition tests exercise the values supplied.
Index ¶
- Variables
- func Catalogue() xtext.Source
- func New(deps Deps) module.Moduledeprecated
- func NewChecked(deps Deps) (module.Module, error)
- func NewLockedReader() contracts.LockedReader
- func NewReader() contracts.Reader
- func NewService() contracts.Service
- func NewServiceWithPolicy(policy tenancy.Policy) contracts.Service
- func NewServiceWithPolicyChecked(policy tenancy.Policy) (contracts.Service, error)
- func NewWriter() contracts.Writer
- type Deps
Constants ¶
This section is empty.
Variables ¶
var Migrations = db.MigrationSource{ Owner: "task", Files: db.Sub(schema, "migrations"), Adopts: []db.Adoption{{Owner: "platformkit", Versions: []int64{4}}}, }
Migrations is this module's SQL as one source — the tasks table and its indexes — owned as "task". The manifest hands the kernel the same files and adoption, and a test composes the schema it needs beside the foundation: dbtest.Schema(t, task.Migrations).
The files keep the version numbers they had when the foundation applied them under its own name, so an installation migrated before this module owned its SQL is adopted by checksum rather than migrated again (see db.Adoption and docs/adr/0011). New files continue from the highest number.
var Module = pkit.NewModule("task", wire, pkit.Needs[jobs.TenantLister](), pkit.Optional[tenancy.Policy](), pkit.Optional[rest.Gate](), pkit.Provides[taskcontracts.Service](), pkit.Provides[taskcontracts.Creator](), pkit.Provides[taskcontracts.Reader](), pkit.Provides[taskcontracts.LockedReader](), pkit.Contributes[personaldata.Part](), )
Module is the task desk as the resolver sees it, for an application that names it in Use: `Use(task.Module)`.
The desk's lifecycle service comes out as a contract for the first time. It was never put anywhere: nothing in the reference application takes it, which is exactly the case Explain's "no module in platformkit needs it" note exists for — a reader is told rather than left in silence (0074 rule 4). A product that composes a desk of its own takes it from here rather than rebuilding one.
Four contracts come out of that one service, because a module that needs part of a task desk asks for that part: Service for the three lifecycle commands, Creator to raise a task, Reader to read one, LockedReader to read the row a diff is made against. They are one value under four keys, so no composition can be handed a creator that is not the service its own routes write through. contracts.Writer is deliberately not among them: Save is the one door that moves a protected field with no gate in front of it, and handing it to every composition that asked would widen that door by composition rather than by decision. A product that composes the change-control subject builds it by name.
Which task a coordinator may resolve is decided by an object-scope policy, and a policy is this product's object — a rego file it wrote, embedded, priced in its own repository. So it is Optional: a composition that names none has the generic desk, with route grants and the tenant floor and nothing above them.
The gate is Optional for the same kind of reason, and it is kit/rest's own type because the door belongs to the routes a Spec mounts: this module hands it to its own mount rather than writing around them, and which of a task's fields an installation will not let one account move alone is no fact this module owns.
var Spec = rest.Spec[*contracts.Task]{ Module: "task", Entity: "task", Path: "/tasks", Read: contracts.PermissionTaskRead, Write: contracts.PermissionTaskUpdate, SoftDelete: true, Immutable: contracts.CommandOwnedFields, HookEvents: []string{contracts.EventSLABreached}, Present: entity.EntryHints{ Description: "Set priorities, assign work and keep track of what is due.", EmptyDescription: "Create a task to give the work a clear next step.", Singular: "task", Plural: "tasks", Icon: "task", Group: &entity.ResourceGroup{Key: "work", Label: "Work"}, PrimaryField: "title", PreviewField: "description", StatusField: "status", SummaryFields: []string{"priority", "dueAt", "slaBreached"}, Sortable: []string{"title", "dueAt", "updatedAt"}, TrendField: "createdAt", Highlight: &entity.EntryHighlight{ Label: "Due today", Filter: []entity.EntryPredicate{ {Field: "dueAt", Op: "gte", Value: "today"}, {Field: "dueAt", Op: "lt", Value: "tomorrow"}, }, }, }, }
Spec is the entity's presence in the application: five routes, two permissions, three events and the schema a generated screen reads. Everything generic about a task is this value; internal/ holds only what is not.
It is exported for one consumer and one reason: a write made outside an HTTP request — the application's own seed — still owes this entity's validation, its list schema and its event, and the alternative was a second write core beside this one. Module below mounts a copy of it with one more hook.
Functions ¶
func NewChecked ¶
NewChecked mounts one shared lifecycle implementation for routes and jobs, and answers as an error the two ways of naming a policy at once: the shared service already carries one, and the Desk would then answer with the other. A zero SweepEvery keeps the interval beside it. It opens nothing, runs no query and is safe to call twice.
func NewLockedReader ¶
func NewLockedReader() contracts.LockedReader
NewLockedReader returns the same task service Module builds, as the one interface a caller about to diff a task needs (see contracts.LockedReader for why that is a second interface and not a method on Service). It costs a second value because the service has no fields — everything it needs arrives with the transaction — so there is nothing here to keep in step with anything else.
func NewReader ¶
NewReader returns the plain read of a task by id (contracts.Reader), for a module that links to another module's task without locking it.
func NewService ¶ added in v1.1.0
NewService constructs task lifecycle commands for application composition. Pass the returned value through Deps.Service to share it with task routes.
func NewServiceWithPolicy ¶ added in v1.1.0
NewServiceWithPolicy adds resource decisions to assignment and resolution. The caller still owns route grants, current identity and tenant transactions. A nil policy is refused with a panic; NewServiceWithPolicyChecked answers it as an error.
func NewServiceWithPolicyChecked ¶
NewServiceWithPolicyChecked answers a nil policy provider as the error NewServiceWithPolicy panics with: the point of the policy is to refuse a resource the caller does not hold, and a service with no policy answers that question by allowing everything it was asked.
func NewWriter ¶
NewWriter returns that service again, as the whole-row write an approved proposal comes through (contracts.Writer). The one door of its kind: the gate stands in front of the five routes and never here, because a gate on both sides is a wall — the approved change could be proposed and never applied. What makes it safe is that it is single, named, and audited, and a caller that reached for it outside an apply would be composition code, which is the trust boundary the tenant model already draws.
Types ¶
type Deps ¶
type Deps struct {
// Service shares one lifecycle implementation with product callers. A
// policy-enabled composition supplies NewServiceWithPolicy here and hands
// the same service to its other modules.
Service contracts.Service
// Policy optionally refines assignment and resolution for the locked task.
// Existing route grants still apply. System SLA maintenance is independent.
// Supply either Policy or a preconfigured Service, never both.
Policy tenancy.Policy
// Tenants is how the SLA sweep reaches active tenants. The application
// supplies tenant/contracts.Active over its tenant service.
Tenants jobs.TenantLister
// SweepEvery is how often the sweep runs; zero means a minute. The promise
// this module keeps is measured in hours, so a minute of lag on noticing a
// breach is free and anything faster is a query per tenant per tick for
// nobody's benefit. A test sets it lower.
SweepEvery time.Duration
// Gate is the door in front of the five routes this module mounts: asked once
// the row is locked and the body merged, before anything is written, and never
// at all when nothing is wired here. It answers whether these fields may be
// written directly, which is not this module's question to answer — it owns the
// task's invariants, and the answer belongs to an installation with an opinion
// about which of its promises need a second pair of eyes.
//
// The reference application wires one over modules/change; a composition that
// wires nothing writes tasks exactly as it always did. This is the shape
// modules/site's Deps.Gate takes, one level out from the singleton's Save closure
// it had to be written into there: a Spec mounts its own routes, so the door has
// to be handed in rather than written around them.
Gate rest.Gate
}
Deps is what this module cannot make for itself: a struct rather than a parameter list, so adding one is a named field at every call site rather than a fourth positional argument. Every module follows this, and an empty Deps is a module that needs nothing.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package contracts is everything another module, an app or a test may know about tasks: the entity, the events, the permissions and the Service interface.
|
Package contracts is everything another module, an app or a test may know about tasks: the entity, the events, the permissions and the Service interface. |
|
tasktest
Package tasktest is the conformance suite for contracts.Service, and a fake that passes it.
|
Package tasktest is the conformance suite for contracts.Service, and a fake that passes it. |
|
Package domain owns portable task decisions.
|
Package domain owns portable task decisions. |
|
This file is this module's answer to one contract: personaldata.Data (ADR 0020).
|
This file is this module's answer to one contract: personaldata.Data (ADR 0020). |