Repository navigation
Releases: intelligo-dev/intelligo
Release list
v1.3.0-next.1
- Upgrade: recommended — the chat transport no longer reads client-sent history on a new conversation's first turn
- Breaking: the admin plugin's user writes answer 404 over HTTP, and the chat item's
saveMessageAsArtifacttakes{ conversationId, messageId, title? }(steps under Upgrading) - Migrations: none
Added
### Securityis a changelog category. From 1.2.5, each of its entries
links the GitHub security advisory published for it, and from 1.3.0 a
section that has one says Upgrade: required;
scripts/release-notes.mjsrefuses a release whose section does not.- The
admin-consoleregistry item: the platform operator's console under
/admin, installed as source the application owns
(intelligo sync admin-console). An overview (the product's own panels
first, then the last 24 hours and integration health), a searchable,
paged user directory with a record panel, a workspace's support page
(plan, credit, members, payments, executions, and grant-plan, add-credit
and impersonate actions that each need a reason), revenue and costs (the
billing settings, the payment ledger and the usage ledger, paged), and
operations (failed jobs, unsettled executions and the audit trail, with
actors and resources by name). A platform admin only; anyone else gets
the app's 404. Every string is in theadmin-consolemessage namespace,
and dates and money are written by next-intl's formatter in the
reader's time zone. - Its seams:
lib/admin-nav.ts(the sidebar,titleKeys resolved like
lib/settings-nav.ts) andlib/admin-config.tsx(brandKey,
backHref,overviewPanels,workspacePanels(workspaceId),
auditFilterPlaceholderKey). - Its components serve a product's own console pages too:
components/admin/page-title.tsx(title,description,crumbs,
actions),panel.tsx(Panel,Stat,Status,PaymentStatus),
action-form.tsx,data-list.tsx(a URL-state list overdata-table
andrecord-panel, withbulkactions andUrlTabs),properties.tsx
(property-listwithout the server and browser formatting a value two
ways),settled.tsx, andlib/admin-page.ts/lib/admin-list.ts/
lib/admin-format.ts. @intelligo-dev/admin:isPlatformAdmin(), the admin check without an
audit row;listUserSummaries({ query, limit, offset }),
getWorkspace(id),listWorkspaceMembers(id),
listPayments({ workspaceId, status, limit, offset }),
getSignupsByDay({ days, timeZone }),resolveDisplayNames(),
countAuditEvents()andcountUsageRecords();listUsageRecordstakes
an offset.@intelligo-dev/audit:queryAuditEventstakes anoffset, and
countAuditEventscounts the same filters.- From this release on, every GitHub release carries a CycloneDX 1.6
SBOM per published package (intelligo-dev-<package>-<version>.cdx.json):
its runtime dependencies as the release's lockfile resolved them, each
with its registry hash, and the package's tarball hash in hex — the
sha512 npm serves as integrity. Earlier releases carry none. - From this release on, every GitHub release carries each package's SLSA
provenance as npm serves it: the Sigstore bundle
(intelligo-dev-<package>-<version>.sigstore.json) and the attested
statements (intelligo-dev-<version>.intoto.jsonl). - A
nextchannel: the next minor ships asX.Y.0-next.N, and
npx @intelligo-dev/cli@next createscaffolds it with its own pages
from/r/<version>/(the scaffold template is 1.23.0). A maintainer's
node scripts/npm-maintain.mjskeepsnextat or abovelatestand
removes the retiredbetatag; the release run says whennextis
behind.
Changed
- Calling
checkQuota, or checking out a credit bundle with
credits/priceUsd, logs a one-timeDeprecationWarning
(INTELLIGO_DEP0001 to INTELLIGO_DEP0003) naming the replacement. - The admin plugin's user writes —
create-user,update-user,
set-role,set-user-password,ban-user,unban-user,
remove-user,revoke-user-session(s)— answer 404 over HTTP, like
impersonation and the user listings already did. They leave no audit
row, and the platform role holds no permission for them; closing them
keeps a role that gains one from opening them to the browser.
Server-side calls throughauth.apiare unaffected. @intelligo-dev/authsets the rate limits of its credential
endpoints itself rather than inheriting Better-Auth's: 3 attempts per
10 seconds at signing in, signing up and changing a password or email,
3 reset or verification emails per minute, and 100 requests per 10
seconds elsewhere. The numbers are unchanged; on the paths
AUTH_RATE_LIMIT_RULESnames, a dependency update can no longer move
them, and any other path keeps Better-Auth's built-in rule. They, the
session lifetime and the impersonation length are named constants in
request-hardening.ts.@intelligo-dev/billing's admission rule and settlement's pool split
are pure internal modules, with no change in behaviour or API.applyRate's documentation states its rounding bound in the
deployment's own currency: underusdRateMicros / 1e6 + 1micros
over the exact charge, which is two micros only in a USD deployment..env.example(app-scaffold 1.23.0) and the Environment page list
STORAGE_*, read once a storage adapter is bound, and
ACCOUNT_DELETION_GRACE_DAYS, and say production refuses to start
without an email provider. The docs guard now finds a variable read
from an injectedenvas well as fromprocess.env.- The deploy guide says production refuses to start without an email
provider unlessEMAIL_PROVIDER=console, and lists the warnings
assertEnv()logs, separating those logged only in production from
those logged in every environment. - The billing README (and
/docs/packages/billing) describes registering
a local payment provider once, with what itscurrencydoes. - The docs have a Security section, opening at
/docs/securitywith what
the framework enforces and what stays the application's. - A production checklist at
/docs/security/production-checklist: the
environment, proxies, rate limits, platform admins, the maintenance
cron, Stripe, the AI surface, the edge and database, and what to watch. - Threat models at
/docs/security/:threat-model-auth,
threat-model-billing,threat-model-chat,threat-model-executions,
threat-model-storage(file storage and chat uploads) and
threat-model-admin(the admin console and support impersonation). - intelligo.dev and
/rserve the release npm tagslatest; a
prerelease keeps its own copy at/r/<version>/, frozen once npm has
it, and its publish does not redeploy the site. - Every fixed vulnerability gets a published GitHub security advisory,
with its affected and patched versions, a CVSS score and a CVE where
one is assigned, so Dependabot andnpm auditreport it to projects on
an affected version.SECURITY.mdsays so and links the published
advisories; its fix goes under### Security, linking the advisory,
and npm marks the affected versions deprecated.
docs/incident-response.mdwrites the advisory in the private report
and publishes it once the fixed version is on npm. RELEASES.md: what a patch, minor and major may change, what counts as
public API, which packages are stable and which experimental, the
release cadence, the support window and the deprecation rule.
SECURITY.mdsays how fast a report is acknowledged, triaged and fixed
by severity, that disclosure is coordinated within 90 days, the safe
harbour for good-faith research, a scope that includes the registry
items, the CLI scaffold and what client data may reach server state,
and a supported-versions table that an architecture test holds to the
released minor.docs/incident-response.md: the maintainers' runbook for a report or an
incident — roles, severity and timelines, containing a compromised npm
account, trusted publisher or Cloudflare token and a leaked
BETTER_AUTH_SECRET, how the fix release is cut, and how users are told.GOVERNANCE.md: roles, review rules, and how a contributor becomes a
maintainer.CONTRIBUTING.mdis written for people: where to start, the boundary
rule, setup, the steps of a change and what review checks. It no
longer sends a newcomer toAGENTS.mdfirst.SUPPORT.mddrops the
beta wording.CONTRIBUTING.mdand the pull request template say what an
AI-assisted change passes and which lines a person reads..github/CODEOWNERSmarks the high-risk source paths, and a test keeps
every pattern there matching a tracked file.- The
review-windowstatus holds a pull request that touches a
high-risk path, or is labelledsecurity, for 24 hours. Its workflow
stays inactive until the repository variableREVIEW_WINDOW_CLIENT_ID
is set. - The new-issue chooser links the Show and tell discussions, for a
product or demo built on Intelligo. node scripts/npm-maintain.mjsdeprecates, per package, the versions a
published security advisory affects (vulnerable.fixedInin
scripts/npm-deprecations.json), so installing one warns and points at
the advisories; nothing is deprecated for a package until npm has its
fixed version.- CI fails when a workflow uses an action by tag instead of commit SHA,
grants a write scope at the top level, leaves the token in a checkout,
hands a checkout, an env var or a step the triggering event's head
(aworkflow_run, a pull request's head,head_ref) or triggers on
pull_request_target; CI's checkouts no longer keep the token. - OpenSSF Scorecard runs weekly and on every push to main; its results are
public at scorecard.dev and in the repository's code-scanning alerts. - The README shows the OpenSSF Scorecard score.
- A dependency-audit workflow, separate from the CI run the release waits
on, fails...
v1.2.4
Fixed
property-listrendered an element that a server component passed as
a value — a link, a badge — as React's internal fields laid out like a
record. Such an element reaches the client as a lazy reference, and
PropertyValuenow renders it as itself.
v1.2.3
Added
- Three registry primitives for a product's console, installed with
shadcn add @intelligo/<name>:data-table: a list the server pages, sorts and filters — rows with
a pinned header and keyboard steps (↑ ↓, Enter, Space), a toolbar
with search, facet filters and a column and density menu, removable
filter chips and pagination. It is controlled: every change is a
callback, so the product decides how list state reaches the server
(usually the URL).record-panel: one record opened in a sheet beside its list, with
previous and next (the arrow keys, or J and K), a link to its full
page, actions and a pinned footer.property-list: a record's fields as label and value. Dates,
numbers, lists and nested records are formatted for a reader rather
than shown as JSON, and empty fields fold behind a toggle.
- Their copy comes in through a
labelsprop with English defaults, as
the other primitives take theirs.
v1.2.2
1.2.1 was set on main but never published: its release commit failed CI
on formatting. 1.2.2 is that release.
Added
setConversationSharing("public" | "workspace" | "off")in
@intelligo-dev/corechooses who a share link opens for.public
(the default) is unchanged;workspaceopens a link only for members
of the conversation's workspace;offhides the share dialog and
refuses to share (sharing_off). The public read honours it on the
server.- Invitations carry a product's own fields:
inviteMembertakes
productFields, accepting the invitation copies them to the new
membership, andmemberProductFieldsreads them back. isImpersonating(session)in@intelligo-dev/auth.- The chat config takes
linkHosts, andMarkdowntakes
linkPrefixes: a product that names them links only to those hosts,
and other links show as text.MarkdownLinkis the confirming link a
components.aoverride falls back to. DOCUMENT_LIMITSbounds a saved document's id, title, content and
kept versions.
Changed
- Production refuses to start without an email provider: set
RESEND_API_KEYorLOOPS_API_KEY, or chooseEMAIL_PROVIDER=console
explicitly. - A chat opened with
?query=puts the text in the composer instead of
sending it. A same-origin handoff still sends. - For a persisted conversation, the model reads the stored transcript:
an earlier message the client sends is replaced by the stored message
with the same id and role.persist: false, or a product's own
persist, keeps the old path. hasFeatureanswersfalsewithdegraded: truewhile the feature
flags cannot be read, instead of falling back to the plan matrix.- Platform admins list users, read a user, list a user's sessions and
start or stop impersonating only through the admin console's server
calls, not the raw auth endpoints. - Checkout, local payment and credit purchases refuse while an admin is
acting as the user (errors.impersonating). - Reply links open only http(s),
mailto:andtel:addresses. - An HTML document preview runs under a content security policy that
reaches no server, and onlycodedocuments are previewed as HTML.
Fixed
- Tokens
prepareMessagesspent are charged when admission then
refuses the turn. - A saved document links only the actor's own conversations, and old
versions beyond the limit are pruned. Too long or foreign input shows
actions.invalidArtifact. - The prompt sanitizer strips role prefixes, chat-template tokens,
[INST]and prompt tags in any language. - Links next to citations still confirm before opening.
Upgrading
Run intelligo migrate before deploying:
0009_invitation_product_fields adds a nullable product_fields jsonb
column to invitation and member. Nothing is rewritten.
Set RESEND_API_KEY, LOOPS_API_KEY or EMAIL_PROVIDER=console in
production, or the app does not start.
Run intelligo sync for every item (chat, chat-share, dashboard,
pricing, payment-poll, ai-markdown, document-viewer and
artifacts changed). Add the chat keys actions.invalidArtifact,
actions.sharingOff and share.descriptionWorkspace, and the pricing
and payment-poll key errors.impersonating, to any locale you own.
v1.2.0
Added
- Account deletion with a grace period. Deleting an account schedules it
for 30 days (ACCOUNT_DELETION_GRACE_DAYS); signing in during that
time leads to a "Restore your account" screen, and afterwards
purgeDeletedAccounts, called from the maintenance route, removes the
user's private conversations, facts, memories and profile snapshots
while usage, billing and execution rows stay with the user cleared.
Deletion is refused while the user is the only owner of a workspace
other people belong to; workspaces only they belong to are deleted
with the account and their subscriptions ended. - Audit trails can be pseudonymized:
eraseUserFromMemoryAuditand
eraseActorFromAuditEventsclear what the trails hold about a person
(ids, recorded values, the actor's email) and keep what happened. The
append-only triggers allow exactly that change. consumeFeatureQuotain@intelligo-dev/billingchecks a feature
quota and counts the use in one statement, so concurrent requests
cannot exceed the limit.- Credit purchases are reversed when refunded or disputed:
charge.refundedandcharge.dispute.createdtake the refunded or
disputed share back from the balance, never below zero, as acredit
usage row and abilling.credit_reversedaudit event. runInBackgroundandsetBackgroundTaskRunner, with
nextBackgroundTasksfrom@intelligo-dev/next: work the framework
starts without awaiting (auth emails, a new workspace's bootstrap,
notification emails) keeps running after the response on a serverless
host.- The chat upload route takes
upload.rateLimit,maxUnclaimedBytes
andadmitUpload, and honours the chat feature gate. The chat route
takesmaxBodyBytes. run.progress({ usage })records a running execution's usage so far,
andreconcile()charges an abandoned run that recorded usage instead
of releasing its hold.pruneNotificationsremoves old read notifications;
pruneJobs(before, { failedBefore })also deletes failed jobs.summarizeExecutionsandsummarizeExecutionsByDaytake an optional
userId.- intelligo.dev/r keeps every release's items at
/r/<version>/<item>.json, and the site sends a content security
policy, refuses framing and serves the registry as JSON to any origin.
Changed
- intelligo.dev/r serves the items of the latest release on npm instead
of main. - The usage page follows the caller's role: owners and admins see the
workspace's usage, spend, quota and trial credit; members see only the
runs they started. - Older reply versions are read-only: the composer, edit and regenerate
return on the latest version, which is the one stored. @intelligo-dev/authand@intelligo-dev/billingdeclarereactas
a peer dependency, since they send email rendered with React.- The workspace limit counts the workspaces a user owns, not the ones
they were invited into. Workspace names cannot contain links, and
invitations are rate-limited per sender and per workspace. - The release workflow publishes from a job that runs no install, build
or test and holds only npm's OIDC identity, in dependency order;
tagging and the GitHub release run in a job that holds only the write
token, and point at the release commit. A hand-pushedv*tag
publishes only when it points at the release commit on main.
Fixed
- A tool-approval continuation resumes the assistant message the model
wrote. The handler takes the stored message and copies only the
client's answers onto it: an approval or denial for a call awaiting
one, under that call's approval id and with the input the model
proposed, or the output of a tool the client runs. Any other change,
a call the model never made, or a message stored under another role
refuses the turn, and the finished reply is persisted from the stored
message rather than the client's copy. A call that already ran or was
denied can no longer be approved again. - A user message carries text, files and
data-*parts only. A file
part'sproviderReferenceandproviderMetadataare dropped, so the
model reads the file whose URL was checked; an assistant message's
file must carry its bytes as a data URL. An elided file is restored
only from a stored message of the same role, and only when the
attachment policy still accepts it. upsertMessagesin@intelligo-dev/core/conversationsno longer
rewrites a stored message under a different role.- Usage and money counters no longer overflow: a month's token count and
Stripe, local-payment and credit-pack amounts are 64-bit. - Deleting a user or a workspace no longer fails because of the memory
audit, and deleting a fact no longer keeps its content there.
Notifications are deleted with their user or workspace. customer.subscription.updatedwrites the subscription's current
state, read back from Stripe, so an older update retried after a newer
one no longer restores the earlier plan. A local payment refuses to
sell a plan to a workspace a live Stripe subscription bills
(subscription_active), a days grant after a canceled subscription
starts from now, and two subscription checkouts started together no
longer both open a session. Deleting a workspace cancels its
subscription only once the workspace is gone.hasFeatureno longer caches an answer decided while the database
could not be read, and its cache drops expired entries.
getBillingSettingsthrows instead of answering with USD defaults
when the row has never been read. Settlement treats a usage row in
another currency as an exhausted allowance. The first quota row for a
user is created once;recordFeatureUsageworks on Postgres again.- A trial's warning and remaining share follow its money grant; trial
reminders are sent at most 50 per run and retried when the email was
not sent; a usage or trial notice that could not be delivered is
retried by the next check. The rate-limit and reservation cleanups
delete in batches. - A chat file part is estimated by what the model reads, not its base64,
so an inline image no longer inflates the admission hold, empties the
window or inflates a stopped reply's charge. The default window keeps
the user message an over-budget continued assistant message answers. A
step the provider fails partway through is charged from what ran.
streamTurnreceives stored files as the model sees them. - The chat route authenticates before reading the body. A first turn
refused at admission leaves no empty conversation (turn.conversation
isnullon a first turn, as documented). The share page leaves out a
failed call's raw input and provider metadata, and no longer carries
the conversation id. Withpersist: false, uploads are no longer
swept as unclaimed.sendWithoutEarlierFileskeeps the bytes of a
message no reply answered. - Editing a message drops it and everything after it on the server,
including the first message of a conversation or of the loaded page.
Reopened chat panels and widgets continue from what is stored.
chat-eve stores a stopped turn's cursor, settles steps finished before
a failure, closes eve's stream after each turn and reads "Don't allow"
as a denial. A malformed percent escape in a source URL no longer
breaks rendering; an attachment can be sent on its own; the dashboard
prompt no longer travels in the URL. mastrasettles against the admitted model; a differing reported id
is kept asmetadata.reportedModel.- A job claimed in a batch runs once, and a failed status write no
longer re-runs a handler or stops the batch. - Requests to the bucket and the email providers time out. An upload
whose row cannot be written removes its object, and a key the bucket
keeps refusing no longer holds back other deletions. Request headers
stay with their own call when two request contexts start at once
outside Next. - A shared conversation stops being public when its author leaves the
workspace or deletes their account. Admins cannot remove themselves
from a workspace; they leave it. intelligo synckeeps the app's seams and messages when an install is
interrupted (under.intelligo/sync-restore/), backs up shadcn's own
files an install replaced, and runs on Windows from a path with a
space;intelligo removeandintelligo add --forceback up what
they replace.intelligo migratetakes an advisory lock;migrate --checkreports an adopted pre-1.0 database asup_to_dateor
pending.intelligo doctoraccepts every client-safe framework
module.getIntegrationHealthrefuses a caller who is not a platform admin.- Signup's verification link lands on /verify-email; a failed fact
deletion comes back into the privacy list with the error; plan prices
show their cents; the pricing, checkout, artifacts and invitation
error boundaries renderRouteError; a timed-out QR payment checks
the same invoice again; the login page confirms a password reset and
billing settings confirm a credit purchase; the trial banner survives
storage that throws. - Accessibility: settings tabs are single links, the onboarding card
group has a name, paywalled content is inert, a press-and-hold button
confirms on a screen reader's click, andSpinnertakes a translated
labeland is otherwise decorative.
Upgrading
Run intelligo migrate before deploying:
0008_wide_counters_kept_ledgers widens four counters to bigint, keeps
memory-audit and usage rows when a user or workspace is deleted, adds
foreign keys on notifications and eight indexes. Nothing is renamed or
removed; widening a column rewrites its table under a lock, so run it in
a quiet window.
Bind setBackgroundTaskRunner(nextBackgroundTasks) in
lib/intelligo.ts (app-scaffold 1.22.0 does). Call
purgeDeletedAccounts and pruneNotifications from the maintenance
route, as the reference app does. Enable charge.refunded and
`...
v1.1.2
Added
-
definePlansandforPlansin@intelligo-dev/billing/plansmake a
catalogue's slugs a type: a plan'sslugmust equal its key, and a
feature matrix or per-plan map that names a plan the catalogue lacks
(chat: ["free", "por"]) fails to compile instead of denying the
feature at runtime. The scaffold'slib/plans.tsuses them. -
Each config seam declares where it runs (
boundariesin the
registry'srequires.json):clientwhen the browser bundles it,
serverotherwise, derived from the items' imports by an architecture
test.intelligo doctorwarns when a client seam imports server code
(server-only, a server@intelligo-dev/*module, a Node built-in) or
reads a non-public environment variable, and the config-seams page says
which seams the browser bundles. -
intelligo --version,intelligo <command> --helpfor one command's
usage, and documented exit codes: 0 done, 1 a check found problems or
the command failed, 2 the command was used wrongly (an unknown command
or a missing argument now exits 2). -
intelligo doctorasks the database whenDATABASE_URLis set (and
--offlineis not): whether the framework's and the app's migrations
are applied, whether pgvector is available, and whether the billing
row's currency is the onelib/billing-config.tsformats. It accepts
AUTH_SECRETas the app does (with a note to rename it), warns about a
Resend key withoutEMAIL_FROMand, in production, a non-https URL or a
test Stripe key. Its migrations line now says what it checks: the
framework chain's integrity, not the database. -
--jsonondoctor,upgrade --checkandsync --check, for CI. -
intelligo upgrade --diff <path>shows how the app's copy differs from
the current template, andupgrade --accept <path>keeps the app's copy
and stops reporting it as a conflict until the template changes again. -
intelligo syncrecords each seam's shipped default, andsync --check
reportsseam-changedwhen a release changes it (the app's copy is still
kept);sync --diff <path>shows the change. Files an earlier release
shipped and none of the app's items do now are reportedorphaned. -
intelligo remove <item>deletes the files only that item ships and
forgets it, keeping its seams; it refuses an item another needs. -
intelligo sync --forcecopies the files it replaces to
.intelligo/backup/<time>/first. -
executionRefusalResponse(run)in@intelligo-dev/next/route: the chat
route's answer to a refused execution for any route — 503
MODEL_UNAVAILABLEorBILLING_NOT_CONFIGURED, 402QUOTA_EXCEEDED.
The scaffold's assistant route uses it withwithWorkspace(template
1.20.0). -
assertEnv({ extra })andvalidateEnv({ extra })check a product's own
variables beside the framework's; the thrown error names the missing
variables, andvalidateEnvwarns about a pair set by half (OAuth id
without secret, a Stripe key without its webhook secret, a storage
bucket without credentials). -
LOG_LEVELsets how much the server logs. -
clearUpgradeMessagesandclearActionLabels, for tests.
Fixed
-
A conversation with an inline image no longer sends the image again
with every later message.useChatsends the whole transcript each
turn, so a 3 MB image added about 4 MB of base64 to every request
after it, past a serverless body limit for the rest of the chat. The
chatitem's transport usessendWithoutEarlierFilesfrom
@intelligo-dev/chat/client, which replaces earlier messages' inline
bytes with a placeholder, and the handler puts the stored file back
(or drops the part when that message was never stored). -
A conversation opens on its latest 100 messages, with "Show earlier
messages" for the rest (loadEarlierMessages), instead of reading its
whole transcript on every load. An approval continuation reads the one
message it checks.@intelligo-dev/core/conversationsadds
getRecentMessages(a page, oldest first, withhasEarlier),
getMessageandgetMessagesByIds. A turn on a reopened long
conversation shows the model what the thread holds, windowed as
before;turn.history()still reads everything. -
Registry items name a version range for every npm dependency. The
build pins each one to the range the registry is developed and tested
against (its ownpackage.json), so an item installs the major it was
written for rather than whatever is newest that day;@intelligo-dev/*
stays unversioned, sinceintelligo createinstalled it andsync
refuses another release's pages. -
intelligo syncdelivers reworded registry copy. Message files kept
the app's value for every key, so a changed sentence or argument never
arrived and nothing said so. A sync now records a short hash of each
message it shipped (registry.messagesin the manifest): a value the
app never changed takes the new text, and a key the registry dropped
goes unless the app reworded it.sync --checkreports
messages-changedfor the app's own wording of a key whose registry
text changed, and fails withmessages-argumentswhen that wording
uses an ICU argument the registry's text no longer passes. The first
sync after upgrading records the hashes and changes nothing. -
The scaffold registers per-plan chat request limits (
RATE_LIMITSin
lib/plans.ts, 10 a minute on free and 60 on pro). It registered
none, so every plan got the conservative default of 10. -
A key a new release adds arrives in English only, and an app whose
default locale is not English threw on it until translated: the
scaffold'si18n/request.tsfalls back from the locale to the default
locale to English (app-scaffold template 1.21.0). -
The
pricingandbilling-settingspages readactionRolesfrom their
config seams within, so an app whoselib/plan-card-config.tsxor
lib/credit-bundle-config.tsxpredates the field —intelligo sync
keeps seams as they are — compiles again. Since 1.1.0 it failed to
type-check. -
A chat turn holds what it can spend. Admission held one model call
against a fixed 16K-token prompt, while a turn with tools may call the
model once per step and a long conversation sends more than 16K; the
shortfall was forgiven at settlement.executions.begintakes a
workload(inputTokens,steps),estimateWorstCaseChargescales by
it, and the chat handler passes the prepared prompt's estimate and, for
an agent with tools,maxSteps. -
A chat turn that errors after its first steps is charged for them. The
stream error released the hold, so every step the provider had already
billed was free; the finished steps (and a step the provider had begun
to answer) are now settled. -
Tokens a tool spent on another model are recorded however low the
balance has run:executions.begin({ alreadySpent: true })skips
admission for work that already happened, and the chat handler uses it
for that usage, which a refusal used to drop. -
A product's
deriveTitleruns once the turn is admitted, so a refused
turn no longer pays a model to name its conversation. -
Two plan grants to one workspace at the same moment — two local
invoices settled together — both count: grants take turns on a
per-workspace lock, so the second adds its days to the first's end
instead of both starting from the same one. -
A job whose handler kills its worker every time stops being reclaimed
once it has used its attempts, and is recordedfailed.draintakes
deadlineMs: jobs it claimed but had no time to start go back with the
attempt unspent, reported asdeferred. -
An execution that receives usage after it was failed — abandoned by the
stale sweep while it still ran — logs it and records an
execution.usage_after_failureaudit event instead of dropping it
without a trace. -
A local invoice its provider's callback names is asked about again
even when it was recorded failed, and granted if it was paid after all
(settleLocalInvoice({ recheckFailed })). -
Uploaded files are deleted. Nothing called
listOrphanAttachments, and
a deleted workspace or user took its attachment rows with it and left
the objects in the bucket. The database now queues an object's key
whenever its row is deleted (storage_deletions), and
sweepAttachments({ storage, olderThan })from
@intelligo-dev/core/attachmentsdeletes uploads no conversation
claimed, then the queued objects; a key leaves the queue only once
storage has deleted it. The maintenance route runs it when a storage
adapter is bound (maintenance template 1.3.0). -
Looking up a request's usage by workspace and request id uses an
index (usage_records_workspace_request_idx) instead of scanning the
workspace's records.
Security
- The
platform-adminrole keeps only what the console uses of
Better-Auth's admin plugin — finding users and impersonating one — and
/admin/impersonate-useris closed over HTTP
(SERVICE_ONLY_ADMIN_PATHS): setting roles, passwords and emails,
banning, deleting and raw impersonation went around the reason and the
audit event. - An impersonation's reason is recorded on its
admin.impersonation.started
event, and one that the session then refused leaves an
admin.impersonation.refusedevent instead of a start that never
happened (requireAdminOrRefusetakesmetadata). - A promotion from
PLATFORM_ADMIN_EMAILSis audited
(admin.platform_admin.granted, via the allowlist), and
intelligo admin revoke <email>takes the role away, ends the user's
sessions and audits it, warning when the allowlist would promote the
address again.requirePlatformAdminreturnspromoted. @intelligo-dev/admin's cross-tenant read models check that a platform
admin is asking, so a page that forgotrequireAdminrefuses instead of
listing every tenant. Calling them outside a request (a script) reads the
tables...
v1.1.1
Fixed
- The logger writes an
Errorpassed in its data as its type, message,
stack,codeandcausechain. It wrote{}, so a logged failure such
as the scaffold's seeding error carried no detail. settlePendingLocalInvoicespicks its invoices at random rather than
oldest first, so an invoice that fails to settle on every run (an
amount the provider disputes, a grant that throws) no longer holds back
every invoice behind it, and starts no settle afterdeadlineMs
(default 45 s), reporting the rest asdeferred.- The
payment-pollitem's callback route answers{ ok }only. It
returned the settle summary, with each failure's message, to an
unauthenticated caller. - The scaffold's assistant route answered every request with 429: its
stand-in modelexample/echo-1had no registered price, so admission
refused it asunknown_model. The scaffold registers the stand-in at
no cost, and the route answers a refusal only the deployment can fix
with 503 and one the workspace can fix by paying with 402, and lets a
failure other than a refused guard through instead of answering 401. - The stub chat model refuses to reply in a production build, so a
deployment that never bound a real model inlib/chat-model.tsfails
loudly instead of billing its users for an echo.
INTELLIGO_ALLOW_STUB_MODEL=1, orallowInProductionin code, lets a
deployment meant to run on it do so. - Stripe amounts for ISK, UGX and MNT. Stripe takes these in hundredths
although none are in use, and they were sent as whole units — a charge
at a hundredth of its price, or refused under Stripe's minimum. Credit
checkout now converts through the newtoStripeMinor, and amounts read
back from Stripe (the payment-failed email, the admin revenue totals)
throughfromStripeMinor, both in@intelligo-dev/core/money.toMinor
keeps the whole units local payment rails take. - A Stripe subscription that ended stays ended. A payment failure that
arrives aftercustomer.subscription.deletedno longer marks it
past_due(which kept the paid plan), and the deletion drops the row's
Stripe id. A plan granted to a workspace whose Stripe subscription
ended — a local payment,grantPlan— now gets its period and lapses,
instead of being taken for one Stripe still bills. - Quota admission no longer needs a second database connection inside its
transaction. A billing settings cache miss read the settings through the
shared pool while the transaction held its connection, so a pool full of
admissions waited on each other forever. The settings are read first. - The database pool waits at most 15 seconds for a connection, so an
exhausted pool fails requests instead of hanging the process, and logs
an idle connection the server dropped instead of letting the unhandled
error end the process.DATABASE_POOL_MAXsizes it (default 10). - A chat turn stopped mid-reply is charged for the step that was running.
The finished steps were the only usage settled, so a reply stopped
before its first step finished — every one-step turn — settled at
zero while the provider billed it. The step in flight is now estimated
from its prompt and what it had streamed.
Security
- Better-Auth's organization endpoints that the team and workspace
services wrap (create, update, delete, invite, accept, reject, cancel,
remove, update role, leave) answer 404 over HTTP. Called directly they
skipped the plan's member and workspace limits and the
beforeDeleteWorkspacehook that cancels the subscription. The
services, andset-activefrom the workspace switcher, are unaffected.
A product that called these throughauthClient.organizationcalls the
services instead. - A password reset ends every session that was open before it
(revokeSessionsOnPasswordReset). AUTH_IP_HEADERSandAUTH_TRUSTED_PROXIEStell the auth rate limiter
where the client address is: without them, an app behind more than one
proxy resolved no address and put every client in one bucket.- The scaffold's
next.config.mjssendsframe-ancestors 'none',
X-Frame-Options,X-Content-Type-Options,Referrer-Policyand
Strict-Transport-Securityon every response. An existing app adds the
headers()block to its own config (intelligo upgrade --checkshows
the template changed).
Changed
- A workspace that is its owner's only one cannot be deleted, and no one
can leave their only workspace (last_workspacefrom the workspace and
team services, with messages inworkspace-settingsand
team-settings). With none left, the next page created a new personal
workspace, and with it a new start for whatever the product grants one.
Upgrading
Run intelligo sync team-settings workspace-settings app-shell payment-poll chat for the new error messages, the callback route and the seam notes. Copy
the headers() block from the scaffold's next.config.mjs into your own.
A production deployment that runs on the stub chat model sets
INTELLIGO_ALLOW_STUB_MODEL=1; one behind more than one proxy sets
AUTH_IP_HEADERS / AUTH_TRUSTED_PROXIES. Code that called Better-Auth's
organization endpoints from the browser (authClient.organization.*,
other than setActive) calls the team and workspace services instead.
v1.1.0
Added
actionRoleson thepricingitem'slib/plan-card-config.tsxand the
billing-settingsitem'slib/credit-bundle-config.tsx: the workspace
roles shown a plan's or a bundle's other ways to pay (default
["owner"]). Bind it topaymentPollConfig.payerRolesso the pages
offer the QR payment to the roles its server action accepts.PaymentProvider.callbackResponse({ settled }): the answer a provider
expects to its callback. Thepayment-pollitem's callback route returns
it when the provider has one, and its JSON summary otherwise.
Changed
PricingContent,PlanCardandCreditBundlestake the caller'srole
instead of acanCheckoutflag. Card checkout stays owner-only; an
admin or member sees the bundles on billing settings when
actionRolesincludes their role.- A local invoice is recorded before its provider is asked.
openLocalInvoice
writes thepaymentsrow asopening, hands its id to
createPaymentas the newpaymentIdparameter, and fills in the
provider'sinvoice_idandexpires_atfrom the answer; a provider that
refuses leaves the rowfailed. A provider can usepaymentIdas its
merchant-side invoice number or in its callback URL, and
invoiceIdFromCallbackmay return either id. - An invoice's expiry is kept and acted on.
settleLocalInvoicecancels an
invoice the provider still reports unpaidLOCAL_INVOICE_EXPIRY_GRACE_MS
(15 minutes) after itsexpiresAtand marks it failed, and
settlePendingLocalInvoiceskeeps settling an invoice with a recorded
expiry until then rather than only for a day after it was opened. - A local invoice keeps what it was sold for.
openLocalInvoicetakes the
offer'sgrantand stores it in the newpayments.grant_terms, and
settleLocalInvoiceapplies that grant rather than askingfulfil,
which now decides only for an invoice opened without one. An offer
changed or removed after the invoice was opened no longer changes or
blocks what a paid invoice grants. Thepayment-pollitem's
startLocalPaymentpasses the grant. Payment.invoiceId(thepaymentsrow type) isstring | null: a row
isopening, with no invoice id yet, while its provider is asked. Code
that readspaymentsdirectly checks for it.- Migrations
0005_local_invoice_expiry(payments.invoice_idnullable,
payments.expires_atadded) and0006_local_invoice_grant
(payments.grant_termsadded). Nothing is renamed or removed, so
intelligo migratecan run ahead of the deploy.
Upgrading
Run intelligo migrate, then intelligo sync pricing billing-settings payment-poll to take the new pages and route; bind actionRoles in
lib/plan-card-config.tsx and lib/credit-bundle-config.tsx if a role
other than the owner pays locally.
v1.0.1
Fixed
- The
auth-loginitem'sreturnPathvalidates the post-sign-in
destination more strictly and returns the path resolved against the
site. Re-syncauth-login. - In the chat's
inlineattachment mode a file part must be adata:
URL; any other URL is refused asattachmentRejected.storedmode
is unchanged.
v1.0.0
Added
intelligo admin grant <email>: makes a signed-up user a platform admin
by writingusers.role, and records anadmin.platform_admin.granted
audit event in the same transaction. WithNODE_ENV=productionit
refuses unless--forceis passed.PLATFORM_ADMIN_EMAILScounts only
a verified address, which a development setup without an email provider
never has.- A granted plan can end.
grantPlan({ …, endsAt })or{ …, days }
records the grant's period (dayscounts from the end of a running
grant of the same plan, so renewals stack), and a local payment's plan
grant takesdaystoo:{ plan: slug, days: 30 }. Without either, a
grant has no end, as before, and clears an end an earlier grant left.
GrantPlanResultcarriesendsAt. On a workspace Stripe bills, the
plan moves and the period stays Stripe's. processExpiredPlanGrants()in@intelligo-dev/billingreturns every
lapsed grant to the free plan, audited asbilling.plan_grant.expired,
and themaintenancetemplate (1.2.0) runs it after trial expiry.
A one-time QR payment therefore no longer grants its plan forever.- Local invoices settle without the buyer's tab.
settlePendingLocalInvoices
in@intelligo-dev/billingsettles every unfulfilled invoice opened in
the last day, or the one a provider's callback names, each against the
workspace that recorded it and by asking the provider — a callback's
body is never taken as payment.PaymentProvidergains an optional
invoiceIdFromCallback(request); the mock reads?invoice=. The
payment-pollitem shipsapp/api/payments/local/settle/route.ts
(schedule it withCRON_SECRET, beside maintenance) and
app/api/webhooks/local-payment/route.ts(the provider's callback URL),
both granting through the newlib/local-payment-grant.ts, which the
poll action now uses too. withCronSecret(handler)in@intelligo-dev/next/route: 401 without
Authorization: Bearer $CRON_SECRET, 403 to everyone while the secret
is unset or shorter than 32 characters.@intelligo-dev/core/storage/s3: aStorageAdapterfor any
S3-compatible bucket — AWS S3, Cloudflare R2, MinIO.createS3Storage
takes the bucket, region, keys and an optionalendpointand
pathStyle, and signs with Signature Version 4 overfetchand Web
Crypto, so it adds no dependency;s3StorageFromEnv()builds
one fromSTORAGE_BUCKET,STORAGE_ACCESS_KEY_ID,
STORAGE_SECRET_ACCESS_KEY,STORAGE_REGIONandSTORAGE_ENDPOINT, or
returns null without a bucket. The storage port's example no longer
names an adapter that did not exist.
Changed
- The scaffold's
i18n/request.tslays a locale's messages over the
default locale's: a namespace or key a locale has not translated yet
renders in the default language instead of throwing, so a locale can
be translated a namespace at a time.intelligo sync --checkstill
reports what is behind.app-scaffold1.16.0. intelligo migrate --checkalso checks the app's own chain: the
migrationsdrizzle-kit generatewrote to./drizzleagainst what
drizzle-kit migraterecorded indrizzle.__app_migrations. A pending
or unknown app migration exits 1, so the scaffold'sdb:checkdeploy
gate no longer passes with the app's schema behind.--jsoncarries it
asapp(nullwhen the app owns no chain);statestill describes
the framework's chain andexitCodecovers both.- Outside production, the console email provider logs the links a message
carries (verificationUrl,resetUrl, …) beside its recipient and
subject, so an address can be verified and a password reset without an
email provider.
Fixed
@intelligo-dev/core'sdbmaps a column declared without a name to
snake_case, as the scaffold'sdrizzle.config.tsdoes: a consumer table
writtenworkspaceId: text()is queried asworkspace_id, the column
drizzle-kit generated. The framework's own columns are all named, so
their SQL is unchanged.- The
chatitem'slib/chat-server-config.tsshows Mastra's
handleChatStreamwithversion: "v7", the stream AI SDK 7 reads. - The
usageitem's chart renders the same scale on server and client.
UsagePageformats the axis labels in compact notation and passes them
down (UsageCharttakesmaxandscaleLabels), as the trial banner
does: Node's and a browser's ICU spell compact numbers differently for
many locales, which React reported as a hydration mismatch. An
architecture test now refuses compact notation in any client component
an item ships. intelligo createinside a pnpm workspace no longer gives the app a
package name another package has:create apps/app --name Acmein a
workspace whose root isacmenames the app@acme/app(the root's
scope or name, then the directory). The page title and billing product
still come from--name. The package name is its own template variable,
__PACKAGE_NAME__(app-scaffold1.17.0); an app recorded before it
reads as named__APP_NAME__, so itspackage.jsonstays current.intelligo doctorwarns when the app's package name is also another
workspace package's —pnpm --filterandworkspace:*resolve only one.