tom_core_kernel 1.1.16
tom_core_kernel: ^1.1.16 copied to clipboard
Core kernel library for the TOM framework containing base modules.
1.1.16 #
This release is built from the repository at the state the entries below describe (an Unreleased section included); its own section is in the full history linked at the end.
Unreleased #
Added #
-
Preference defaults and the bulk write (
preferences/).TomUserPreferenceDto.origin(tomUserPreferenceOriginUser'user',tomUserPreferenceOriginRole'role',tomUserPreferenceOriginDeployment'deployment'; defaultuser) and its getterisDefaultsay whether a value is the caller's own or a default the server answers under a key the caller has not written;listPreferencesandreadPreferenceinclude those defaults, and the read answerspreferences.not_foundonly for a key with neither.TomUserPreferenceBulkWriteRequest(preferences: key →{valueType, value};.of(writes);writesin key order, refusing a malformed entry aspreferences.bulk.malformed=tomUserPreferencesBulkMalformedErrorKey) is the request of the endpointtomUserPreferencesBulkWriteEndpointId(writePreferences,POST /preferences), which writes the batch all or none and counts its new keys againsttomUserPreferencesMaxPerPrincipaltogether;TomUserPreferencesApiService.writePreferences.doc/preferences/preferences.md§ Defaults beneath the caller's own values, § Writing several preferences at once; tests intest/preferences_test.dart. -
Job priority, pausing and checkpoints (
scheduling/).TomJobDefinition.priorityandTomScheduledJob.priority(default 0) are copied onto every run the scheduler plans (TomJobRun.priority), andTomJobStore.claimDuetakes due runs by descending priority, in queue order among equals.TomScheduler.pauseJob/resumeJob/pausedJobs(store contractpauseJob(jobName, now),resumeJob,pausedJobs) hold a job's runs out ofclaimDueandbacklogon every node sharing the store; a paused clock-driven job is not planned, so on resume its missed windows follow itsTomMissedWindowPolicy, and a paused event job's events wait in the queue.TomJobRunContext(current,require(),runId,jobName,checkpoint,saveCheckpoint) lets a running body save an opaque JSON map as its progress (TomJobRun.checkpoint,TomJobStore.saveCheckpoint, fenced on the claim andfalseonce the claim no longer holds the run); the checkpoint is kept across retries and recovery and handed to the next attempt, and recovery still re-queues a started run only when its job setsrequestsRecovery. The scheduler dispatches every body inside aTomJobRunCommand, whose port carries a save from a worker isolate back to the scheduler. Error keysschedule.job.checkpoint(not JSON-encodable,TomJobStore.encodeCheckpoint),schedule.job.checkpoint_failed(the store could not write it) andschedule.run_context.missing(require()outside a job body).doc/scheduling/scheduling.md§ Priority, § Pausing a Job, § Checkpoints; sampleexample/samples/scheduling/06_priority_pause_checkpoint_example.dart; tests intest/scheduling_test.dart,test/scheduled_job_test.dart,test/scheduling_error_keys_test.dartand the store contract suitetest/contracts/scheduling_contract.dart. -
HTTP client resilience (
http_connection/,resilience.dart):TomResilientClientwraps apackage:httpclient and applies theTomHttpResiliencePolicyof each call's destination (scheme://host:port) — a timeout per attempt (30 s by default), aTomHttpRetryPolicy(3 attempts, jittered exponential backoff from 200 ms to 5 s,Retry-Afterin seconds honoured up to that ceiling; only for idempotent methods or anIdempotency-Keyheader, and buffered bodies; on a connection failure, a timeout,429,502,503or504), aTomCircuitBreakerPolicy(opens after 5 consecutive errors, timeouts or5xx, half-opens after 30 s) and aTomBulkheadPolicy(32 concurrent, 64 queued; a permit is held until the response body has been read). Breakers (TomCircuitBreaker,TomCircuitState) and bulkheads (TomBulkhead) are shared per destination across the isolate.TomHttpResilienceholds thedefaults, per-destination policies (setDestinationPolicy,policyFor),configurefor a configuration map (timeoutMs,retry,circuitBreaker,bulkhead,destinations;false/0switches a part off),resetandclockfor tests. Refusals raiseTomHttpResilienceException(http.client.timeout,http.client.circuit_open,http.client.bulkhead_full,http.client.config.invalid). Seriestom_http_client_attempts,tom_http_client_retries,tom_http_client_rejections,tom_http_client_circuit_open.doc/http_connection/http_connection.md§ Resilience; sampleexample/samples/http_connection/09_http_resilience_example.dart; tests intest/http_resilience_test.dart. -
TomTraceContext(context/): the W3C Trace Context a piece of work belongs to, carried in the zone —traceId,spanId,parentSpanId,sampled.parsereads atraceparentheader and answersnullfor an invalid one (as the W3C specification requires),newRootstarts a trace,childcontinues one with a new span,traceparentrenders the header for an outgoing call, andrun/currentinstall and read it. The constructor throwsFormatExceptionfor an invalid id.doc/context/context.md§ Trace Context; sampleexample/samples/context/05_trace_context_example.dart; tests intest/structured_logging_test.dart. -
Structured log output (
logging/):TomJsonLogOutputwrites one JSON object per line —timestamp(UTC, ISO 8601),level(lower-case),message,logger,isolate(fixedFields), then the context fields of the current zone — with errors and status to stderr asTomConsoleLogOutputroutes them, or through awriter.TomLogContextholds the context fields (trace_id,span_id,tenantby default;register/unregisteraTomLogFieldProvider,names,fields()); a provider that throws is skipped, and registering a fixed field name throwsArgumentError. Fixed fields carry ids only, never personal data.doc/logging/logging.md§ Structured JSON Output; sampleexample/samples/logging/07_json_log_output_example.dart. -
Metrics module (
metrics/):TomMetricRegistryholds counters (TomCounter), gauges (TomGauge), histograms (TomHistogram, withtomLatencyBucketsandtomLagBuckets) and observed gauges (observe,TomMetricObserver,TomObservedValue,TomMetricObservation) per isolate;collect()answers a sorted snapshot ofTomMetricFamily/TomMetricPoint, andtomPrometheusTextrenders it in the Prometheus text format 0.0.4 (tomPrometheusContentType).TomMetrics.registryis the registry the framework records into; misuse raisesTomMetricsException(metrics.name.invalid,metrics.name.conflict,metrics.label.invalid,metrics.counter.negative). A failing or slow observer is skipped and counted intom_metrics_observation_failures_total.doc/metrics/metrics.md; samples inexample/samples/metrics/; tests intest/metrics_test.dart. -
Scheduler metrics.
TomSchedulerrecordstom_job_start_lag_seconds,tom_job_run_duration_seconds,tom_job_runs_total{job,outcome}andtom_job_last_success_timestamp_seconds, and fromstart()toclose()observestom_job_backlogandtom_job_backlog_age_secondsfor its own registered jobs.doc/scheduling/scheduling.md§ Metrics. -
TomScheduler.started(scheduling/): the schedulers of this isolate that were started and not yet closed, as an unmodifiable snapshot.start()adds a scheduler,close()removes it,stop()leaves it listed. A graceful shutdown stops every one, drains them within its deadline and closes them so their leases are released.doc/scheduling/scheduling.md§ The Scheduler; test intest/scheduling_test.dart. -
TomShutdownCleanup.exitAfterSignalShutdownandexitProcess(shutdown_cleanup/): with the flag set (defaultfalse), a shutdown started by a signal callsexitProcess(0)once every phase has run; a shutdown started by callingperformShutdown()never exits.exitProcessdefaults todart:io'sexitand is replaceable for tests.doc/shutdown_cleanup/shutdown_cleanup.md§ Exiting after a signal; sampleexample/samples/shutdown_cleanup/03_shutdown_execution_example.dart(example 11); tests intest/shutdown_cleanup_test.dart(groupexitAfterSignalShutdown). -
TomJobStore.backlog(now)andTomJobBacklog: the due, unclaimed runs per job — how many, and since when the oldest has been due. A new abstract member: an implementation ofTomJobStoreoutside this package must add it. Implemented byTomMemoryJobStoreandTomFileJobStore, and held to the store contract suite. -
TomItemType.lowestLevelGranting(grade)— the weakest level whose grade is at leastgrade, the minimum level of a "every item this principal may read (or write)" query. A type whose stronger level stands for a weaker grade is now refused at declaration, so that threshold holds for every level above it.doc/security/security.md§ Item Permission Levels. -
Item permission levels.
TomItemType(ordered levels, each mapped to aTomAuthState;standardis view < comment < edit < manage;fromJsonreads a manifest declaration),TomItemTypeRegistryandTomItemPermissions(levelOn,allows,gradeOn) insecurity/item_permissions.dart. Items are named at run time —<type>:<id>— and a level held on a pattern<type>:<prefix>*covers every item whose id starts with the prefix, so a category or folder needs no item list.TomResourceRoleAdapter.resolveAuthresolves an item key through the level's grade, andTomResourceRoleRegistry.stateOn/strongerOnapply the type's grades and order on item keys.doc/security/security.md§ Item Permission Levels; tests intest/item_permissions_test.dart. -
Tenant context in
context/tenant_context.dart:TomTenantContextholds the tenant the current zone belongs to (currentId,isSet,run,require,validate,qualify,idPattern— a lower-case identifier of at most 32 characters);TomTenantExceptionraisestenancy.id.invalidandtenancy.context.missing;TomTenantScopedCommandruns aTomCommandinside a tenant wherever it executes, a worker isolate included;TomTenantEventnames the tenant of an event-triggered job's run. A tenant is an isolation boundary and is distinct from the organization, a business unit inside it. -
TomPrincipal.tenant, written into and read back from the token as thetenantclaim byTomBearerAuthentication, independently oforganization. -
Runs of a tenant.
TomJobRun.tenant(serialized bytoJson/fromJson);TomJobStore.findWindowtakestenant:, so a window is per tenant.TomSchedulerrecords the tenant of aTomTenantEvent, builds and runs the run's body inside the tenant (wrapping the command in aTomTenantScopedCommand), and serialisesallowOverlap: falseper job and tenant — the cluster lease isjob:<name>@<tenant>— so one tenant's run never waits for another's. -
package:tom_core_kernel/tom_core_kernel_io.dart, the native-only entry library, exporting the file guard the kernel's file-backed stores use:tomWithFileLock, the newtomWithFileLockAsync— the same guard, waiting withFuture.delayedso a UI isolate is not blocked — andtomFileLockPath/tomFileLockStaleAfter. The two waiters share one non-blocking acquisition attempt, so they are one lock. -
lookupObjectInTreeinjson/map_merge_and_process.dart: the walk behindgetObjectFromTree, reporting a path it cannot resolve as(found: false, value: null)instead of throwing. Both now share one walk. -
Passkey wire types in
security/webauthn.dart:TomWebAuthnCreationOptions,TomWebAuthnRequestOptions,TomWebAuthnRegistrationResponseandTomWebAuthnAuthenticationResponse, each serialising to WebAuthn Level 3's JSON form, withtoSubmission()/fromSubmission()carrying a response through the second factor'sStringseam; plusTomWebAuthnUserVerification,TomWebAuthnAlgorithmsand the unpadded base64url codecTomBase64Url. Shared bytom_core_server'sTomWebAuthn2FAServiceandtom_core_flutter's passkey mechanism.
Changed #
-
TomFileJobStorewrites a JSON object. The queue file is{"runs": [...], "paused": {"<job>": "<ISO instant>"}}; a file holding a bare array is treated as damaged — moved to<queue>.corruptand started empty. -
TomDefaultHttpClientFactory.getClient()returns the platform client inside aTomResilientClient. EveryTomServerChannelcall — and with it everyTomServerEndpointcall andTomHttpFileTransferClienttransfer — runs under its destination's resilience policy, so an attempt that receives no status and headers within 30 seconds fails withhttp.client.timeoutwhere it used to wait indefinitely. A customTomHttpClientAbstractFactoryreplaces the default whole and wraps its own client to keep the policies. An upload slower than the timeout needs a destination policy with a longertimeoutMs, or0. -
The principal id is a string, unique within its tenant.
TomUser.idandTomPrincipal.idareString(defaultTomUser.noId,''), so a deployment can use a UUID, a directory's object id or a database key rendered as text; the token'suserIdclaim is a string, and a claim of another shape reads asnoId. Another tenant may hold the same id — a different person.TomPrincipal.qualifiedIdis the id qualified by the principal's tenant, the key per-principal rows are filed under, andTomTenantContext.qualifyCurrentqualifies a name by the tenant in context. -
TomJobStore.findWindow(jobName, scheduledFor, {String? tenant})— every store implementation takes the namedtenantand matches a window of that tenant only (nullmatching runs of no tenant).TomMemoryJobStoreandTomFileJobStoredo. -
TomPrincipal.organizationis documented as the business unit inside the tenant, no longer as the multi-tenant identifier; the tenant isTomPrincipal.tenant. -
Every sample group runner frames its output the same way, through
sampleGroupHeader/sampleGroupFooterinexample/sample_bootstrap.dart(Running <Group> Examples...and a rule; the rule andAll <Group> examples completed successfully!). Eight runners printed a box-drawn title instead.tool/run_standalone_examples.shfails a runner that does not use the pair. -
A resource miss no longer constructs an exception. Both resource providers resolve dotted keys through
lookupObjectInTreeinstead of catchinggetObjectFromTree'sTomJsonPathException. Constructing aTomExceptionrenders a stack trace, and underflutter test's stack-chaining zone each render parses the whole chain; since a widget probes a key for every optional colour, style and text, misses dominated widget builds under test — one two-hundred-widget demo screen built in seventy seconds, andtom_flutter_ui_test's overflow suite took about twenty-five minutes. It now runs in about twenty seconds. Lookup results are unchanged. -
The API summaries moved from
doc/api_reference/todoc/api/, where every other package and the workspace guideline already kept them. The folder held only the markdown summaries and their index; the nameapi_referencenow means only the built PDF,doc/{package}_api_reference.pdf. -
TomLoginFailurelives here, and a failedTomAuthenticationResultcarries it. The enum moved fromtom_core_serverbeside the result, which gainsloginFailure(the value's name, on the wire) and a typedloginFailureReasonaccessor. A client switches on the closed set whether a login failure was thrown (the server's envelope) or returned as a result, without depending on the server package. -
A mixin scope keeps its path.
TomScopeMixin.getScopePathcomposes the path from the parent's and keeps it once the parent has kept its own (or there is no parent), so on a chain of mixin scopes each path is built once. A parent that is not aTomScopeMixinfalls back tobuildScopePathon every call, and nothing below it is kept, so a hand-rolled scope whose parent changes is still followed.buildScopePathitself stays uncached. -
The isolate log sinks take their transport as an interface.
TomIsolateLogOutput(TomCommandDispatcher dispatcher)replaces theTomWorkerPool workerPoolfield;TomWorkerPoolimplements the newTomCommandDispatcher(execute,close).TomCreatorLogOutputtakes an optionalTomCreatorChannel channel, defaulting to the newTomCreatorChannel.current(the running isolate's back-channel). Tests now assert all seven fields of the record each sink dispatches, which a level/message transposition at either call site fails. -
schedule.cron.empty_fieldis gone. No cron field that parses can match nothing — every item either raises its own key or adds at least the low end of its range — so the raise was unreachable and is removed with its documented entry. -
Discovered components may take their dependencies through the constructor. Discovery plans every key first and then builds each component through its unnamed constructor, supplying every required parameter — positional or
requirednamed — with the bean bound under exactly the parameter's type, building that component first. Construction follows the constructors' dependency order. Three new keys refuse what cannot be built, each naming the class and the parameter:beanlocator.initialization.constructor_dependency_unsatisfied,…constructor_dependency_filtered(the provider is left out by this deployment's@TomEnvironment/@TomPlatform) and…constructor_dependency_cycle. They replacebeanlocator.initialization.constructor_takes_arguments.TomBeanfield handles are unchanged, and remain the form for mutual dependencies. -
TomBean.withTypetakes its key as a type argument. It is nowstatic TomBean<B> withType<B extends Object>(B bean)(andoverrideWithType<B>), soTomBean.withType<UserService>(impl)compiles only whenimplis aUserService. TheType-value forms are renamedwithRuntimeType(Type, Object)/overrideWithRuntimeType, unchecked, for a key known only at runtime.installByEnvironment<T>now requiresT extends Object. Breaking: everyTomBean.withType(X, bean)becomesTomBean.withType<X>(bean), and every generated reflection artifact that capturesTomBeanmust be regenerated. The change found three registrations in this package's own suite that keyed anAnotherServiceunderTestService.
Tests #
- Every error key the package raises is named by a test. New
*_error_keys_test.dartfiles pin 43 keys across scheduling, HTTP connection, isolate pooling, observables and scope ids, each asserting the exact key. The beanlocator coverage test no longer carries its own "named by a test" check;tool/check_doc_error_keys.pyasks it for every package. test/ai_citation_test.dartfails the suite when an artefact of the package cites a document in the AI-state mount. It runstom_ai/core/tool/check_doc_ai_citations.pyover the package throughtom_ai/core/tool/package_gate.dart; the rule and its one exemption (the runtime_ai/trail/directory) are stated in the checker alone.test/formatting_test.dartdelegates to the shared guard. The walk, the generated-file exclusion, the coverage floor and the choice of formatter moved totom_ai/core/tool/package_format_guard.dart, which every package undertom_ai/coreshares; the file keeps only this package's floor, landmarks and generated-output flag.
Added #
@TomRecommendedField()andTomReflectionInfo.missingRecommendedFields. The softer twin of@TomRequiredField(): a field a bound map should supply, reported under the same rules, for a caller that warns rather than refuses.
Fixed #
-
TomBearerAuthentication.setTokenschecks the authentication token's lifetime as well as the authorization token's. It passed only the authorization token toconvertTokenPayloadToPrincipal, so an expired authentication token beside a live authorization token was accepted. Both tokens are now passed, and an expired authentication token throwsTomTokenLifetimeExceptionand leaves the session as it was. -
TomServerEndpoint.createApiServerEndpoint<requestType, responseType>accepts a declaration written inTomApiEndpoint's own order. The class declares<ReturnType, RequestType>, but the factory tested forTomApiEndpoint<requestType, responseType>, so every declaration whose two types differed was refused withapis.endpoint.invalid_type_parametersunless it was written backwards. A declaration written backwards to get past the check is now refused; reverse its type arguments. -
TomClass.setValuesdeserializes collections of same-named classes. ATomList<X>orTomMap<String, X>member resolvedXthroughsingleElementType/mapValueElementType, which see only the runtime collection and so lookXup by simple name. TwoTomClasssubclasses sharing a simple name in different libraries made every such member throwreflector.classmirror.ambiguous_simple_name.Xis now read from the member's declaration, and the name lookup is only the fallback for an element type the reflector cannot describe. -
The internal file guard no longer lets concurrent breakers in together. Breaking an abandoned guard was check-then-delete, and a release deleted whatever guard stood at the path, so several waiters reaching the staleness boundary at once could each delete a fresh guard and hold the file together. Breakers now take turns through a sibling
<lock>.breakguard (itself cleared once stale), and a writer releases only a guard carrying its own acquisition token. Found bytest/file_primitives_test.dart, the first tests ofatomic_file.dartandfile_mutex.darton their own. -
A
TomList<TomString>now binds to a multi-select field.getValues()serializes an observable list as a plainList<dynamic>, and a member that holds a whole list as one value —TomObject<List<String>>, which is what every multi-select form field is — rejected it inset, sobind()failed and took the whole form's bind with it.TomClass.setValuesnow re-types a list for aTomObject<List<E>>member for each primitiveEthe framework serializes (String,int,double,bool, nullable or not), widening integral elements intoList<double>as the scalar rule already did. An element of the wrong type is still refused. See the observable guide, "Lists: A Serialized List Re-Types Into a List-Holding Scalar". -
Three broken markdown reference links in
doc/scope_ids/scope_ids.md.Zone,idandparentwere written with dartdoc's square-bracket syntax around a code span, which markdown renders as literal brackets rather than as a link. No anchor existed to point at, so the brackets are gone and the code spans remain.
Earlier entries #
The full history is in the repository's CHANGELOG.md; this file holds the newest entries that fit pub.dev's size limit.