Documentation
¶
Overview ¶
Package engine drives the archive: which families to read, in what order, how far back, and what to do with what comes off the wire.
Index ¶
- Constants
- Variables
- func Archivable(meta freeagent.ResourceMeta) bool
- func Deferred() map[string]Class
- func Probeable(meta freeagent.ResourceMeta) bool
- func SelectFamilies(names []string) ([]freeagent.ResourceMeta, error)
- type BlobOptions
- type BlobResult
- type Check
- type CheckStatus
- type Class
- type Engine
- func (e *Engine) FetchBlobs(ctx context.Context, blobs *blob.Store, opts BlobOptions) (BlobResult, error)
- func (e *Engine) Probe(ctx context.Context, names []string) ([]ProbeResult, error)
- func (e *Engine) Pull(ctx context.Context, opts Options) (Result, error)
- func (e *Engine) RenderDocuments(ctx context.Context, blobs *blob.Store, opts RenderOptions) (RenderResult, error)
- func (e *Engine) Verify(ctx context.Context, opts VerifyOptions) (VerifyResult, error)
- type FamilyResult
- type Options
- type ProbeResult
- type RenderOptions
- type RenderResult
- type Result
- type UnknownFamilyError
- type UnsupportedFamilyError
- type VerifyOptions
- type VerifyResult
Constants ¶
const ( // ProbeHonoured means the filter was applied. ProbeHonoured = "honoured" // ProbeIgnored means records came back that the filter excluded, so an // incremental read of this family would silently be a full one. ProbeIgnored = "ignored" // ProbeNoData means the family is empty, so nothing can be concluded. ProbeNoData = "no-data" // ProbeFailed means the probe itself errored. ProbeFailed = "failed" // ProbeNotApplicable means the family is not a paged collection, so // updated_since has nothing to filter. ProbeNotApplicable = "not applicable" ProbeUnavailable = "unavailable" )
Probe results.
const DefaultBlobConcurrency = 8
DefaultBlobConcurrency is how many attachments download at once. Higher than the API concurrency because these go to a third-party host and spend no FreeAgent rate budget.
const DefaultConcurrency = 4
DefaultConcurrency is how many jobs are read at once. The rate limiter is the binding constraint, so this only hides latency; more workers would just queue against the same budget.
const DefaultOverlap = time.Hour
DefaultOverlap is how far back an incremental run reaches beyond its stored cursor. It absorbs clock skew and any lag between a record's updated_at and its visibility. Upserts are idempotent, so overlap costs requests, nothing else.
const DefaultPayrollYears = 7
DefaultPayrollYears is how far back to look when the company record does not say when the books start. Seven years is the UK record-retention window, so it covers what anyone is obliged to keep.
const DefaultReconcileInterval = 7 * 24 * time.Hour
DefaultReconcileInterval is how stale a family's last full sweep may get before --reconcile-if-due picks it up. FreeAgent has no deletions feed, so this cadence is the only thing that ever notices a removal.
const DefaultReportWindow = 365 * 24 * time.Hour
DefaultReportWindow is how far back a report is taken when the run gives no window. A rolling year covers the current accounting period for most companies without asking the caller to know their own year end.
const ProbeUpdatedSince = "updated_since"
ProbeUpdatedSince is the capability key recorded in the archive.
Variables ¶
var BlobUserAgent = "fasync-attachments"
BlobUserAgent identifies the downloader to the content host. Deliberately separate from the API user agent, because this client is not talking to FreeAgent and carries none of its credentials.
var ErrBudgetExhausted = errors.New("engine: run budget exhausted")
ErrBudgetExhausted ends a run that hit its request or time limit. It is not a failure: the archive is consistent, there is simply more to do.
var PDFFamilies = []string{"invoices", "estimates", "credit_notes"}
PDFFamilies are the records FreeAgent renders a document for.
These are the sales-side documents. Their PDF is the thing that was actually sent to a customer, so for an archive it is the counterpart of the scanned receipt on a bill: the evidence, not just the figures.
Functions ¶
func Archivable ¶
func Archivable(meta freeagent.ResourceMeta) bool
Archivable reports whether this build can archive a family. The remaining classes need their own strategies and are not skipped silently: the engine reports them as unsupported so the gap is visible in the run output.
func Deferred ¶
Deferred lists the families this build cannot archive yet, with the reason, so a run can report the gap rather than leaving the user to notice it.
func Probeable ¶
func Probeable(meta freeagent.ResourceMeta) bool
Probeable reports whether asking about updated_since means anything for a family. A singleton has nothing to filter, a report is recomputed on every request, and a year-addressed endpoint is not a collection at all. Probing them produced failures that said nothing about the API.
func SelectFamilies ¶
func SelectFamilies(names []string) ([]freeagent.ResourceMeta, error)
SelectFamilies resolves the caller's --family list against what this build can archive. An unknown or unsupported name is an error naming the reason, never a silent omission from the run.
Types ¶
type BlobOptions ¶
type BlobOptions struct {
// Concurrency is how many downloads run at once.
Concurrency int
// Limit caps how many attachments this pass takes on. Zero means all
// outstanding.
Limit int
// Deadline stops the pass at a wall-clock time.
Deadline time.Time
}
BlobOptions configures a download pass.
type BlobResult ¶
BlobResult is what a download pass achieved.
type Check ¶
type Check struct {
Name string
Status CheckStatus
Summary string
Detail []string
}
Check is one verification result.
type CheckStatus ¶
type CheckStatus string
CheckStatus is how one check came out.
const ( // CheckPass means the check ran and found nothing wrong. CheckPass CheckStatus = "pass" // CheckFail means the check found something that should not be true. CheckFail CheckStatus = "fail" // CheckAdvisory means the check found a difference that may be legitimate. // Reported so a human can judge, not treated as a failure. CheckAdvisory CheckStatus = "advisory" // CheckSkipped means the check could not run: usually missing data. CheckSkipped CheckStatus = "skipped" )
type Class ¶
type Class int
Class is how a family has to be read. It is derived from the SDK's own registry rather than from a list maintained here, so a family added upstream is classified automatically instead of being silently dropped.
const ( // ClassCollection is a plain paged collection: the common case. ClassCollection Class = iota // ClassGrouped splits its records across several envelope keys. ClassGrouped // ClassBankScoped rejects a request without a bank_account filter. ClassBankScoped // ClassSingleton has no id segment and returns one document. ClassSingleton // ClassReport is a derived, point-in-time answer rather than a record. ClassReport // ClassYearScoped is addressed by tax year, with no endpoint listing which // years exist. ClassYearScoped // ClassParentScoped rejects a request without a contact or project. ClassParentScoped // ClassUserScoped is nested under a user, so its own path does not exist. ClassUserScoped // ClassCustomEnvelope answers with a shape of its own. ClassCustomEnvelope // ClassChildOnly is reached through a parent record, never enumerated. ClassChildOnly )
func Classify ¶
func Classify(meta freeagent.ResourceMeta) Class
Classify decides how a family is read. Order matters: the flags are not mutually exclusive, and the most restrictive one wins.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine archives one account.
func New ¶
New builds an engine. The client must be read-only, which is the only kind the api package can produce.
func NewOffline ¶
NewOffline builds an engine with no API client, for the work that reads only the archive. Only Verify is safe to call on one: everything else needs a client, and the absence is deliberate so a local command cannot quietly start making requests.
func (*Engine) FetchBlobs ¶
func (e *Engine) FetchBlobs( ctx context.Context, blobs *blob.Store, opts BlobOptions, ) (BlobResult, error)
FetchBlobs downloads outstanding attachments into the blob store.
Downloads go through a plain HTTP client that carries no OAuth token: the content URLs point at a third-party host, and sending FreeAgent credentials somewhere that is not FreeAgent would be a real leak. It also means these fetches cost none of the API rate budget.
func (*Engine) Probe ¶
Probe establishes which families actually honour updated_since.
This exists because a family that ignores the filter returns everything, which looks exactly like success. Two cheap requests settle it: one to see whether the family has any records at all, and one asking for records changed after a date in the far future. Anything that comes back proves the filter was ignored.
func (*Engine) Pull ¶
Pull archives the selected families. A job that fails does not stop the others; the run reports partial and names what broke, because one flaky endpoint should not cost a night's sync of everything else.
func (*Engine) RenderDocuments ¶
func (e *Engine) RenderDocuments( ctx context.Context, blobs *blob.Store, opts RenderOptions, ) (RenderResult, error)
RenderDocuments fetches the PDF for every record whose document is missing or was rendered for an older version of it.
Unlike attachments, these come from the API and so cost rate budget: one request per document. That is why the pass is incremental rather than wholesale, keyed on the parent's modification time, and why it is not part of a routine pull by default.
func (*Engine) Verify ¶
func (e *Engine) Verify(ctx context.Context, opts VerifyOptions) (VerifyResult, error)
Verify checks the archive against itself and against FreeAgent's own arithmetic.
The checks are ordered by how much they prove. Internal consistency and dangling references are local and unambiguous. The reconciliation against the trial balance is advisory, because a trial balance total is cumulative from the start of an accounting period and a difference on a balance-sheet code can be an opening balance rather than a missing record.
type FamilyResult ¶
type FamilyResult struct {
Family string
Scope string
Label string
Pages int
Stats store.UpsertStats
Deleted int64
Cursor time.Time
CursorAdvance bool
Swept bool
FullScan bool
// Unavailable marks a family this company does not have: the API answered
// 403 or 404. A fact about the company, not a failure.
Err error
// contains filtered or unexported fields
}
FamilyResult is what happened to one job: a family, or one scope of one.
func (FamilyResult) Name ¶
func (f FamilyResult) Name() string
Name is how this result is displayed.
type Options ¶
type Options struct {
// Mode is one of the store.Mode constants. It decides whether the stored
// cursor is read, advanced, or left entirely alone.
Mode string
// Families limits the run. Empty means every archivable family.
Families []string
// Window is the caller's explicit time bounds, recorded on the run.
Window store.RunWindow
// Overlap is how far back beyond the cursor an incremental run reaches.
Overlap time.Duration
// Concurrency is how many jobs are read at once.
Concurrency int
// MaxRequests stops the run once this many API calls have been made.
// Zero means no limit.
MaxRequests int64
// Deadline stops the run at a wall-clock time. Zero means no limit.
Deadline time.Time
// Reconcile sweeps each family for records the far end no longer has.
Reconcile bool
// ReconcileIfDue sweeps only families whose last sweep is older than
// ReconcileInterval, so one scheduled command covers both cadences.
ReconcileIfDue bool
// ReconcileInterval overrides DefaultReconcileInterval.
ReconcileInterval time.Duration
}
Options configures one run.
type ProbeResult ¶
ProbeResult is what one family answered.
type RenderOptions ¶
type RenderOptions struct {
// Families limits which record types are rendered. Empty means all of
// PDFFamilies.
Families []string
// Limit caps how many documents this pass renders. Zero means all
// outstanding.
Limit int
// Deadline stops the pass at a wall-clock time.
Deadline time.Time
// MaxRequests stops the pass after this many API calls.
MaxRequests int64
}
RenderOptions configures a rendering pass.
type RenderResult ¶
RenderResult is what a rendering pass achieved.
type Result ¶
type Result struct {
RunID int64
Mode string
Families []FamilyResult
Stats store.UpsertStats
Deleted int64
Requests int64
Outcome string
Deferred map[string]Class
}
Result is what happened to the run.
func (Result) Unavailable ¶
func (r Result) Unavailable() []FamilyResult
Unavailable lists the families this company does not have.
type UnknownFamilyError ¶
type UnknownFamilyError struct{ Name string }
UnknownFamilyError names a family the SDK has no entry for.
func (*UnknownFamilyError) Error ¶
func (e *UnknownFamilyError) Error() string
type UnsupportedFamilyError ¶
UnsupportedFamilyError names a family that exists but needs a strategy this build does not have yet.
func (*UnsupportedFamilyError) Error ¶
func (e *UnsupportedFamilyError) Error() string
type VerifyOptions ¶
type VerifyOptions struct {
// Blobs enables re-hashing every stored attachment.
Blobs *blob.Store
// FromDate and ToDate bound the reconciliation against the trial balance.
// Empty means use the newest snapshot's own window.
FromDate string
ToDate string
// MaxDetail caps how many lines a check reports, so one broken family
// cannot bury the others.
MaxDetail int
}
VerifyOptions configures a verification.
type VerifyResult ¶
type VerifyResult struct {
Checks []Check
}
VerifyResult is the whole verification.
func (VerifyResult) Failed ¶
func (r VerifyResult) Failed() bool
Failed reports whether any check found something that should not be true. Advisory findings do not fail: they are differences a human has to judge.