Skip to content

Releases

v0.7.3

Choose a tag to compare

@github-actions github-actions released this 23 Oct 12:34

Added

  • 两套前端新增简约品牌登录页和概览登录页,保留原有认证流程,并支持主题、语言及窄屏布局。

Changed

  • 拆分 Vue、React 工作流配置与任务操作组件,集中处理流程状态展示,便于扩展和维护。
  • 优化两套前端的图标加载,首屏直接使用常用离线图标,完整图标集按需加载。

Fixed

  • 用户详情、更新、删除、重置密码和启停用等按 ID 操作均校验用户当前机构的数据范围;批量删除先完成范围校验再执行。
  • 数据库就绪日志不再输出连接字符串,避免敏感配置进入日志。
  • Vue 请求遇到格式错误的 CSRF Cookie 时仍能完成请求处理。
  • 修复工作流表单提示标题不受支持,以及登录预览残留和无效占位路由问题。

v0.7.2

Choose a tag to compare

@github-actions github-actions released this 22 Oct 07:44
a069899

Added

  • 工作流实例详情展示实际参与人、当前与历史审批轮次、会签进度,以及转办、委托、加减签和自动处理记录;Vue、React 两套模板同步接入。
  • 新增工作流参与人查询和管理员干预接口,支持强制转办、终止实例与跳过节点,保留权限校验和操作日志。

Changed

  • MVC API 与 SignalR 对 JavaScript 安全整数范围内的 long 保持数字输出,超出范围使用十进制字符串;OpenAPI 和 Vue、React 的 ID 类型同步支持 number | string。
  • 两套前端统一页面、面板、标签页与登录页动效,提供标准、轻量和关闭三档设置,并尊重系统减少动态效果的偏好。
  • Vue 模板采用 vuejs-ai/skills 开发规范,拆分登录能力、导入预览与工作流任务操作职责,统一适用的响应式、模板引用和双向绑定写法。

Fixed

  • 将 Oracle 驱动已有的 System.Security.Cryptography.Pkcs 传递依赖最低版本提升到 8.0.2,修复两项拒绝服务漏洞;安全版本约束随 TenonAdmin.SqlSugar NuGet 包传递给消费方。
  • 修复 WorkerId 仅在宿主后台服务运行时才受租约保护的问题。默认发号器与分配结果共同持有数据库租约生命周期,未确认、丢失、过期或已释放的租约会阻止继续发号;完善 SQLite 建表锁冲突、MySQL 死锁、重复键分类、取消和接管处理。
  • 为 sys_job_log 补充 (JobId, EndTime) 与 (EndTime, StartTime) 复合索引,减少串行任务检查和失联执行回收对历史日志的扫描;保留原有日志查询索引和调度语义。
  • 加强工作流定义发布、发起、审批写入、办理人、必填意见、表单权限和历史快照校验,拒绝空条件组及无效办理人配置;修复 SQL Server 中文参与人排除条件。
  • 修复工作流大整数 ID 在表单、路由、监控和 outbox 重放中的精度丢失,完善阻断、部分阻断和退回发起人状态展示,两套模板的待办列表随工作流实时通知刷新。
  • 修复 Vue 角色授权、设计器加载及保存发布、远程选择器的请求竞态;初始化服务暂时失败时保留有效会话,支持重试恢复原深链。
  • 修复 Vue 导入重新校验覆盖正在编辑的内容、监控轮询重叠、验证码与短信倒计时清理、缓存页弹窗生命周期,以及关闭最后一个深链标签页后的导航问题。

Upgrade notes

  • 自有客户端必须同时接受数字和十进制字符串形式的 ID,避免对字符串 ID 使用 Number()、parseInt() 等可能丢失精度的转换;升级后端时同步更新所用前端模板。默认雪花号仍在 JavaScript 安全整数范围内,历史导入或自定义发号器生成的大整数也按新契约处理。
  • 使用 CodeFirst 的环境会补齐任务日志索引;生产环境需同时允许 CodeFirst 和生产结构同步。由 DBA 管理的 MySQL 数据库可执行 定向升级脚本,步骤和已有索引处理见 索引升级说明。
  • 使用默认发号器的部署需保证 WorkerId 数据库租约能够持续续租;升级前检查数据库访问和 sys_worker_lease 权限。旧版已发布工作流快照也会在发起时按当前规则校验,应先修正无效办理人等配置并重新发布。

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 30 Sep 11:23

Fixed

  • 修复普通管理员已获工作流菜单授权后,定义详情、版本历史、删除及 outbox 重放仍因路由约束权限码漂移而返回 403 的问题;同步 Vue/React 按钮权限,升级结构种子并隔离旧权限缓存,保留已有角色授权。升级步骤与验证记录见 修复记录。
  • 修复多副本 Docker smoke 加载 Compose 文件的方式。

升级到 0.7.1 前须先停止所有旧版副本,避免旧版将结构种子回刷;启用种子时自动更新已有菜单并保留角色授权。关闭种子的部署须按修复记录更新四条菜单权限码,并同步所用前端模板。

Changed

  • README 使用当前品牌标识。

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 08:08

Added

  • Added the optional TenonAdmin.Integration package for third-party integration: integration apps with API keys that can be issued, rotated with an overlap window, expired and revoked; default-deny open API endpoints with declared data scopes, per-app rate limits, a separate OpenAPI document and inbound call records; outbound calls to configured targets with address checks, per-call credentials and no automatic retries; and reliable delivery enqueued in the business transaction, dispatched under leases, recovered according to the partner's deduplication and query capabilities, with reconciliation, alerts and audited manual actions. The Vue and React templates both include the management pages.
  • Added dotnet new tenon-app --integration, the IntegrationSample consumer sample with the IntegrationMockPartner local third party, the wire-integration skill, and the third-party integration guide.
  • Added ISkipOperationLogMetadata so endpoints can opt out of user operation logs; open API endpoints use it to record inbound calls instead.
  • Added a workflow engine and Vue/React interfaces for design, organization-leader and branched approvals, delegation, timeouts, CC, monitoring, and history.
  • Added idempotent workflow commands and replay, operation receipts, extensible node handlers, leased execution and retry history, an outbox, and Webhook nodes.
  • Added shadow-only AI decision nodes and an OpenAI-compatible provider; decisions are evaluated and audited but never approve automatically. Added expense-decision evaluation and sample-collection tools.

Changed

  • Rebuilt the bilingual documentation around practical learning paths, with separate Vue and React first-page tutorials, system integration guidance, and real application screenshots.
  • Adopted the Tenon-Brand blue artwork across both frontends, the documentation site, and NuGet package icons.
  • Upgraded both frontend templates to Vite 8 and refreshed their dependencies. Use Node.js 22.12 or later for development and builds.

Fixed

  • Hardened workflow approval concurrency, participant isolation, stale Webhook results, and MySQL outbox replay; an unset outbox transport now fails explicitly.
  • Fixed job-capacity reservation, unique WorkerId allocation and dead-process lease recovery, session revocation and cache atomicity, Markdown rendering, and asynchronous frontend interactions.
  • Fixed the Vue workflow monitor build error caused by an unused table reference, stabilized job cancellation and slow SQL logging tests, and allowed Playwright workers to borrow their run port leases.

v0.7.0-preview.1

v0.7.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 27 Sep 04:47

Added

  • Added a workflow engine and Vue/React interfaces for design, organization-leader and branched approvals, delegation, timeouts, CC, monitoring, and history.
  • Added idempotent workflow commands and replay, operation receipts, extensible node handlers, leased execution and retry history, an outbox, and Webhook nodes.
  • Added shadow-only AI decision nodes and an OpenAI-compatible provider; decisions are evaluated and audited but never approve automatically. Added expense-decision evaluation and sample-collection tools.

Fixed

  • Hardened workflow approval concurrency, participant isolation, stale Webhook results, and MySQL outbox replay; an unset outbox transport now fails explicitly.
  • Fixed job-capacity reservation, unique WorkerId allocation and dead-process lease recovery, session revocation and cache atomicity, Markdown rendering, and asynchronous frontend interactions.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 00:33

A 36-finding QA sweep of the kernel, closed in five batches. Most of it is authorization boundaries: the data-scope guarantee that only ever covered business DataEntity tables now also covers user, org and file management, and the role system grew an explicit delegation boundary so a non-superadmin holding the role menu can no longer mint themselves a privileged role.

Four changes alter behaviour that existing installs depend on. Read Changed before upgrading — one of them stops the app from starting.

Changed

  • AdditionalDatabases[].DbType is now required. It used to default to "Sqlite", so copying a MySQL connection string into a secondary connection and forgetting the type produced a dialect mismatch that only surfaced on the first query. Startup validation now rejects an empty value. An install that omits DbType will fail to start until the type is filled in (QA29).
  • The user import template addresses references by code, not display name. OrgCode / PositionCode / RoleCodes / DirectorAccount replace the old name columns; an unresolvable value is now a cell error (ImportCellRefNotFound) instead of silently binding to whichever duplicate name sorted first. Overwrite also checks that the target user's current org is inside the caller's scope, and role grants run through the same delegation policy as the UI. Templates downloaded from an older version no longer parse — re-download from the template endpoint (QA19).
  • Assigning a role now requires that role to be marked delegatable. SysRole.IsDelegatable plus IRoleGrantPolicy split the role definition surface from the role granting surface: defining, renaming, deleting a role and configuring its menus or data scope are superadmin-only, and a non-superadmin may only grant roles explicitly marked delegatable, and only to users inside their own data scope. The column is nullable and NULL counts as not delegatable, so every role that exists today becomes non-delegatable on upgrade — a superadmin has to tick the box for any role that non-superadmin administrators are expected to hand out (QA09, QA36).
  • User and org management honour the caller's data scope. The user list and add/update paths filter by OrgId ∈ scope, and the org tree returns in-scope orgs plus their ancestors so the tree still has a root to render from. Positions stay global, being a company-level vocabulary rather than an org asset. A non-superadmin who could previously see every account now sees only their own scope (QA08).

Removed

  • The anonymous POST /api/v1/auth/mfa/challenge/verify endpoint. Neither template ever called it; TOTP login goes through POST /api/v1/auth/login/totp. It could burn an in-flight MFA challenge and disclosed a userId on an unauthenticated surface, so it is gone rather than fixed (QA03).

Fixed

Authorization and disclosure:

  • File management is owner-only for non-superadmin: the list filters by uploader, download and delete validate ownership, batch delete checks every target before touching anything, and a cross-owner reference answers FileNotFound so the endpoint doesn't confirm whether the file exists. The check applies to authenticated callers only — anonymous signed /view links are a capability URL guarded by their signature, and have nothing to filter by (QA15).
  • [ActiveSession] endpoints now resolve and write the data scope, the same way [RolePermission] does. Previously the scope context was simply unset on those routes, and an unset context reads as unrestricted — so a consumer querying a DataEntity from a login-only endpoint saw every org (QA07).
  • Self-service password change revokes the account's other sessions, keeping the current one (QA04).
  • Marking a notice read checks that the notice is visible to the caller, instead of accepting any id (QA24).
  • The SignalR hub validates the session id at connection time and aborts without joining any group, so a force-logged-out client with an unexpired JWT stops receiving pushes rather than staying connected until the token expires (QA25).
  • Avatar URLs are validated as local signed file links through the new IAvatarUrlValidator; the field previously accepted any string and rendered it into an img src (QA25).
  • Passwordless SMS login returns one error code whether the phone belongs to an enabled account or not. The wrong-code and expired-code paths were distinguishable, which turned the login step into a phone-number oracle even though code sending was already enumeration-safe. Password login's SMS challenge keeps its remaining-attempts feedback, the caller's identity being established there already (QA06).
  • MFA bind and recovery reuse the cached dummy hash for unknown accounts, matching the login path, so response time no longer separates "no such account" from "wrong secret" (QA05).
  • A disabled role no longer contributes to the portal's module and menu tree. Permission codes already excluded it, so the sidebar offered entries whose endpoints answered 403 (QA11).
  • Import row limits apply to the JSON validate and commit entry points and to the error-report endpoint, not just to the streaming preview that well-behaved clients happen to go through first (QA17).
  • Overwrite import with a blank role column keeps the target's existing roles instead of clearing them; an optional column means "don't change", not "set to empty" (QA18).
  • Export cells starting with =, +, - or @ are escaped in the writer, covering every export path at once (QA19).

Data integrity and operations:

  • Seed dictionaries, dictionary items and config keys are protected from deletion (Id < 1000 marks kernel seed data). Deleting the gender dictionary used to be one click, and it silently emptied user-form dropdowns; deleting a sys.security.* key silently fell back to Options defaults while the config centre still looked authoritative. Consumer seeds must therefore use Id >= 1000 — now written down in skills/create-entity.md (QA13).
  • Dictionary item values are unique per type (QA13).
  • The dictionary lookup used by every form dropdown is [ActiveSession] rather than a permission-coded route. It is a cross-module hot path, and granting it per-menu meant every new module that used a dictionary had to remember to re-grant it. Write operations stay permission-coded (QA12).
  • A disabled dictionary type returns no items, instead of continuing to serve its enabled entries (QA14).
  • Deleting an org or position is refused while active users reference it, and users can no longer delete or disable themselves. A batch containing a superadmin reports SuperAdminProtected ahead of the self-deletion guard, the more specific answer of the two (QA10).
  • A job's panic alert sends its in-app notice and its e-mail independently. They shared one try, so once QA25 started validating notice recipients, a rejected notice target also swallowed the e-mail — the channel most likely to actually reach someone at 3am.
  • Soft-deleting a user or role preserves its associations, so restoring from the recycle bin restores the permissions too; the associations are cleaned on permanent deletion instead (QA23).
  • With the SQL job gate closed, existing SQL jobs can still have their schedule and name edited — only payload changes are refused. System jobs now lock HandlerKind, HandlerName, Props and Name while leaving trigger and run configuration editable (QA20, QA21).
  • Snowflake WorkerId collisions fail fast. A new SysWorkerLease table backs WorkerIdLeaseGuard, which claims the id at startup, renews it, and releases it on shutdown; a second instance configured with the same id refuses to start with a readable error instead of silently minting colliding ids (QA27).
  • AddTenonAdminWorker scans the Services assembly for entities, matching the HTTP composition root. A Worker deployment with CodeFirst enabled used to create only the SqlSugar-layer table and then fail on every query against a missing kernel table (QA28).
  • WorkerIdLeaseGuard.StopAsync no longer lets a logging failure escape. Lease release is best-effort, but the warning it logged on failure could itself throw during shutdown once the logging providers were disposed, and Host.StopAsync then aggregated it into whichever test was disposing its host.

Frontend, both templates:

  • The recycle bin has a job tab; the backend supported restoring soft-deleted jobs but the UI listed no way to reach them (QA22).
  • LoginOutput carries isSuperAdmin, and the stores fall back to it when the profile request fails. A superadmin usually holds no explicit permission codes, so a failed profile call used to hide every write button until a refresh (QA31).
  • The v-auth directive is reactive. It removed the element once on mount, so a permission refresh could neither restore a hidden button nor hide a newly-revoked one until the route remounted — the React <Can> component already re-rendered (QA32, QA35).
  • Login skin names come from i18n keys rather than hardcoded Chinese (QA32, QA34).
  • vite preview pins its port in both templates, matching the dev server. Without strictPort it silently moves to the next free port, and the docs and scripts that name a fixed port then open a different application (QA30, QA33).

Added

  • job gets its own structured tab in the config centre of both templates, instead of appearing among free-form "other" keys where its retention setting could be deleted like a custom row (QA13).
  • ReplaceabilityTests covers a core service registered before AddTenonAdmin(). The suite mostly used post-registration Replace, which stays green even if a TryAdd regresses to a plain Add — the guarantee the suite exists to protect (QA29).
  • FileOwnerTests runs. All four tests covering the QA15 owner isolation fix shipped as [Fact(Skip = ...)], leaving a P1 control with no executing ...
Read more

v0.5.4

Choose a tag to compare

@github-actions github-actions released this 06 Aug 07:59

Fixed

  • CodeFirst column upgrades on tables that already have data (#31). SysUser.ForceTotp/TotpEnabled and SysSession.AbsoluteExpiresAt are now nullable database columns — SQL Server rejects ADDing a NOT NULL column without a DEFAULT to a non-empty table, so upgrading a live kernel install used to fail on this dialect. Existing rows read back as NULL; the read path treats that as the pre-upgrade default (MFA flags false, absolute expiry falling back to ExpiresAt) without changing the CLR type of the already-published public properties. Covered by a new regression test, CodeFirstNullableUpgradeTests (drops the columns from a seeded table, then asserts CodeFirst re-adds them and old rows still read correctly), now also run in the SqlServer dialect-sensitive CI subset.
  • Documented the underlying rule for anyone adding columns to an already-shipped entity: docs/coding-standards.md §1.3, a new "已有表加列" section in skills/create-entity.md, and a tip box in the deployment guide explaining why evolved columns must be nullable.

v0.5.3

Choose a tag to compare

@github-actions github-actions released this 03 Aug 08:55

0.5.3 - 2026-08-03

Two consumer-facing surfaces land on the same patch: same-process secondary databases (SqlSugar multi-ConfigId) and branded external login (GitHub / personal WeChat packages + dual-template UI, with pending-link hardening).

Added

  • Same-process additional databases (multi-ConfigId) (#28). Configure TenonAdmin:AdditionalDatabases with a unique ConfigId (reserved name TenonAdmin is the main DB). Access secondaries with db.AsTenant().GetConnection(configId); IRepository<T> and CodeFirst/seed still hit main only. Soft-delete, data-scope, and audit AOP are off by default on secondaries and opt-in per connection (ApplySoftDeleteFilter / ApplyDataScopeFilter / ApplyAuditAop). Site guide: Configure Multiple Databases.
  • Optional satellite packages TenonAdmin.Auth.GitHub and TenonAdmin.Auth.WeChat. Same shape as WeCom/DingTalk: install the package, call AddTenonAdminGitHubAuth / AddTenonAdminWeChatAuth before AddTenonAdmin(). GitHub uses OAuth App with fixed code github and scope read:user; personal WeChat uses website-app qrconnect with code wechat and Subject = unionid only (no openid fallback — empty unionid fails the exchange).
  • Branded external-login UI on both templates (login strip + personal bindings, zero-shared). Known provider codes map to local SVG brand icons; system config gains a third-party login tab driven by GET /api/v1/auth/external/providers/all for enable/display toggles. Frontend brand map also reserves icons for codes without a kernel package yet (e.g. gitee/qq placeholders).
  • Pending-link claim for unbound SSO. When an external identity is resolved but not bound, the callback issues a short-lived pending-link plus a browser-only binder cookie (tn_oauth_pending); after password/SMS login the client calls POST /api/v1/auth/external/pending-link/claim. Claim requires the same browser (binder match); wrong binder does not burn the ticket. Covered by integration tests and ADR 0007 / docs/external-login-brand/.

Fixed

  • WeCom / DingTalk providers use IHttpClientFactory and clearer exchange error mapping (aligned with the new GitHub/WeChat packages).
  • MultiConfigIdTests on non-SQLite CI legs: dual-connection fixtures now force the active TestDb dialect onto secondary AdminDatabaseConnectionOptions (default DbType is Sqlite and was pairing with MySQL/Postgres connection strings).

Changed

  • OpenAPI schemas on web/ and web-react/ regenerated for external-auth providers/all and pending-link/claim; temporary path casts in the API clients dropped.
  • Construction-only external-login review artifacts removed; keep decisions, ledger, and QA checklist under docs/external-login-brand/.

v0.5.2

Choose a tag to compare

@github-actions github-actions released this 31 Jul 09:49

0.5.2 - 2026-07-31

Optional application security lands as independent, off-by-default switches — not a full MLPS Level-3 profile. Also fixes SQL Server long-text mapping that broke Chinese error text in the job scheduler suite.

Added

  • Optional TOTP (authenticator) second factor, off by default. Runtime master switch sys.security.totp.enabled (config center, same style as captcha) plus deploy floor Security:Totp:*. Self-service bind (POST /api/v1/auth/mfa/bind/start|complete) with one-time recovery codes; admin force-TOTP and clear MFA; login signals 40018 / 40020 and recovery path. Both Vue and React templates: Account Security page, bind UI with QR, login Modal for forced unbound users (no permanent login-page bind link), re-auth modal for sensitive writes.
  • Optional Cookie + CSRF session mode (Security:Session:CookieMode, default false). HttpOnly refresh cookie, double-submit CSRF (tenon_csrf / X-Tenon-CSRF), dual-template silent refresh. Configurable idle / absolute session caps when you want them.
  • Narrow secret protection for TOTP seeds (IDataProtectionKeyProvider / ISecretProtector) and diagnostic security baseline precheck API (not a certification report).
  • Product direction documented as general admin kernel + optional security (ADR 0006); full Level3 multi-phase roadmap retired. Site page Auth & Security covers TOTP and CookieMode.

Fixed

  • SQL Server long-text columns map to Unicode nvarchar(max) via SqlSugar CodeFirst_BigString (#26). Bare text was non-Unicode and corrupted Chinese ErrorText from job orphan reaping on the full SqlServer suite (#25). Affects job / op-log / notice / exception-log large string columns.
  • React offline menu icons load after the lazy icon pack finishes (AppIcon re-render + test mock hoist), so sidebar icons no longer stay blank until a folder is expanded.
  • User-admin UX polish: wider edit form, operations column order (edit / delete / more), force-TOTP and clear-MFA copy; personal-security admin hints gated by permission.

Changed

  • Removed product paths for MFA bind invites, InitGrant, emergency reset, and idle-account auto-disable heavy plays. Historical Profile=Level3 is transition-only; new deploys should use the independent keys above.
  • README / CONTEXT.md terminology updated for optional security; agent config notes under docs/agents/security-optional-config.md.

Removed

  • Construction-only agent docs for the retired Level3 phase-1 program (review/closeout prompts). Keep ADR 0006 and the optional-security config / UI checklist for maintainers.

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 29 Jul 06:33

0.5.1 - 2026-07-28

A patch for the scheduled-jobs surface that shipped in 0.5.0: an unset snowflake WorkerId no longer crashes every scheduler tick, and job PUT on PostgreSQL no longer returns a broken body.

Fixed

  • Scheduler node heartbeat no longer throws when TenonAdmin:Id:WorkerId is unset (#24). SqlSugar evaluates expression-tree members when building SetColumns; the previous idOptions.WorkerId ?? 0 ran against a null options object and flooded the tick loop with exceptions. Locals (workerId, pid, hostName, startTime) are hoisted before the expression so the tree only sees constants and locals.
  • Job update (PUT) on PostgreSQL no longer returns a non-JSON body. JobService.UpdateAsync switched to the same Updateable(entity).IgnoreColumns(...) pattern used elsewhere (Dict/User), and the nullable NextRunTime CAS is split into two branches so a ternary does not land in the expression tree.
  • Multi-replica smoke is less flaky after failover. Caddy upstreams gain health checks and lb_try_duration; the multi-smoke script polls until the 5s job has three runs instead of a fixed 25s wait. Both frontend OpenAPI clients pick up the missing nodeInstanceId field that had drifted from the contract.

Changed

  • Runtime architecture diagrams and README previews refreshed for in-kernel jobs, optional WorkerHost, and the SignalR hub path.
  • Documented the MLPS assessment boundary (ADR-0005) and matching domain terms in CONTEXT.md.