278 Commits
Author SHA1 Message Date
pnokerandClaude Code 3fb43c4239 feat(web): unify three-terminal control density and close audit defects
Land the v7 three-terminal UX decision: drop the width/pointer-based
44px touch enlargement so one 32px density holds across desktop,
tablet, and mobile, and rebuild entity pages on a responsive record
list (ResponsiveRecordList + EntityMobileCard + per-entity mobile
summaries). Tighten the nginx security headers (CSP frame-ancestors
'none', X-Frame-Options DENY) to match the static e2e server.

Complete the dc3-web audit remediation, including the remaining LOW
items from docs/dc3-web-low-defects-handoff.md: streaming-safe
message loads in the agentic store, mock menu reparent cycle
rejection, paged-list page clamping, fetch/axios mock request
normalization, per-table attribute catalogs, post-flush alarm
watchers, rowKey-agnostic delete state, localized card text
fallbacks, and e2e-server port fail-fast.

Gate: vue-tsc, eslint, vitest 687 passed, responsive e2e 64 passed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-07 19:33:17 +08:00
pnoker 66aab58f20 build: standardize repository tooling and quality gates 2026-09-02 20:51:10 +08:00
pnoker 6591982b8a test: run broker and database contracts as integration tests 2026-09-01 23:41:40 +08:00
pnoker eae86b58fc refactor(storage)!: unify platform persistence on reactive postgresql
- migrate auth, manager, data, and agentic persistence to R2DBC
- hard cut pagination, facade, gateway, web, and CLI contracts
- remove legacy database and TSDB adapters and document validation gates

BREAKING CHANGE: platform persistence now requires PostgreSQL 18 with TimescaleDB and the new reactive API contracts.
2026-09-01 22:03:16 +08:00
pnoker 16733c806b docs(design): close /mcp verification debt; greenlight oauth gateway flag 2026-09-01 22:03:15 +08:00
pnoker 81d4fe2c37 docs(design): record live regression results for token unification 2026-09-01 22:03:14 +08:00
pnoker 0cc4635f2a docs(design): note first phase-3 slice in cli dual transport 2026-09-01 22:03:14 +08:00
pnoker 61db55da6c docs(design): record phase 2 partial landing (projection + ttl ladder) 2026-09-01 22:03:14 +08:00
pnoker 2d2654dddf docs(design): mark phase 1 of token unification implemented 2026-09-01 22:03:14 +08:00
pnoker 6c16dfd5f8 docs(design): fix scope projection and consistency gaps in token unification
- projection is additive per risk class: a mixed LOW/HIGH principal
  keeps tools_call; MEDIUM (hidden-until-enabled) now accounted
- qualify high-risk 'no standing grant' to the default path so it no
  longer contradicts the approved --approve-window escape hatch
- note phase-1 ordering: --oauth login can land before the gateway
  resolver chain, behind a flag until REST accepts RS256
2026-09-01 22:03:14 +08:00
pnoker 35e625de9a docs(design): plan console cli onboarding; restore dc3-cli package name
Revert the premature McpServer.vue change (implementation was not
requested). Record the onboarding UX contract in
token-unification-mcp-first-cli.md §4.1 instead, and rename the CLI
package back to dc3-cli per review.
2026-09-01 22:03:14 +08:00
pnoker d5e993bdd7 docs(design): record phase-0 decisions on token unification
Q1-Q4 resolved 2026-08-27: scope vocabulary kept as RBAC projection,
step-up via all three channels (web approval default, CLI tty,
explicit approve-window), cached-JWKS gateway verification, web
retains cookie sessions. Q5 threshold documented as proposed.
2026-09-01 22:03:14 +08:00
pnoker 3d65e0949d docs(design): propose token unification and mcp-first cli rebuild
Add design draft covering:
- verified state of the two parallel auth systems (HS256 login vs
  RS256 OAuth/MCP) and their incompatible verdict points
- gateway TokenResolver chain over an extended X_AUTH_PRINCIPAL so
  both tickets converge on one RBAC-backed permission model
- scope-as-projection of dc3_resource bindings, risk-layered TTLs
  with step-up for high-risk calls
- CLI rebuild around the MCP/OAuth credential mechanism (supersedes
  dc3-client-sdk.md Phase 4) with five open decision questions
2026-09-01 22:03:14 +08:00
pnoker 843ee444ca docs: translate design and selection documents to Chinese
Translate the seven remaining English documents under docs/ in place,
preserving all technical content, code blocks, identifiers, links, and
decision IDs (D1-D19, F1-F15, P0-P5). Add a categorized Chinese
navigation index (docs/README.md) with per-document summaries, the
storage supersession chain, and task-based reading paths.
2026-08-28 02:07:45 +08:00
pnoker 8be0bbc73d docs(design): add relational R2DBC access layer design
Approved design for replacing MyBatis-Plus + JDBC with Spring Data R2DBC,
auth center as the pilot. Records the verified fact base (F1-F15), the
D1-D19 decision checklist (per-dialect repository modules, TCK discipline,
compile-time tenant scoping, facade/gRPC blocking edge, timestamptz codec,
workerId allocation rule), and the P0-P5 phased plan with the P2-P3
go/no-go gate. Supersedes storage-abstraction.md section 3 (relational
half); its TSDB half was already superseded.
2026-08-28 01:19:20 +08:00
pnoker a7ca489b9b style(repo): reformat java, xml, yaml, markdown and frontend sources 2026-08-25 21:27:49 +08:00
pnoker 56169842eb docs(design): record three-terminal v5 revision 2026-08-25 21:02:04 +08:00
pnoker d90021ce57 fix(tsdb): capability and consistency fixes across store adapters
- let TSDB failures propagate in the alarm window data source: swallowing
  them made an outage read as no-data and silently missed alarms
- dedupe ingest batches by natural key before store upserts (same-row
  writes poison timescale ON CONFLICT batches)
- turn unsupported countByDimension into a structured refusal (analytics)
  or capability-honest empty panel (dashboard) instead of a raw 500
- pin LocalDateTime.now to DEFAULT_ZONEID on the tsdb read paths
- adapter fixes: influxdb/iotdb/tdengine/timescale store and auto-config
  corrections, iotdb system properties, capability declarations on
  TsdbStore, new timescale store unit tests
2026-08-25 19:27:51 +08:00
pnoker 7610b8dadd fix(db): harden mysql/mariadb dialects and seed conversion
- derive pagination DbType from the selected dc3-db adapter with a fail-fast
  when a non-default dc3.db.type lacks its adapter jar
- skip postgres profile activation for mariadb, not just mysql
- fork OAuthMcpMapper/AlertMapper statements for mysql and mariadb
  (CAST AS JSON, ::text casts, ODKU value references)
- honor GET_LOCK verdict in resource registry sync instead of assuming
  acquisition on every engine
- pg2mysql_seed.py: convert partial unique indexes to stored generated guard
  columns instead of dropping them; add 99-iot-dc3-grants.sql
- add DC3_DB_URL/DC3_DB_HISTORY_URL per-service overrides in center configs
- cover the new mapper forks in the db TCK; fix AGPL headers in spring
  metadata files
2026-08-25 19:27:44 +08:00
pnoker dc0282c3db fix(mq): centralize key routing and harden broker adapters
- add KeyMatcher/KeyRoutes: client-side topic router for brokers without
  binding-level key filtering, with wildcard unit tests in dc3-mq-core
- stamp a dc3-partition-key header mirror for brokers with no native key
  field (mqtt user property, jms string property)
- drop the unused RetryPolicy; batch redelivery is governed by the batch
  consumer retry settings
- align adapter configurations, error messages, and the Pulsar contract
  test with the dc3-mq-* module names
2026-08-25 19:27:38 +08:00
pnoker 769d1cb977 style: apply repository-wide formatting
- expand single-line Javadocs to multi-line blocks, rewrap Markdown prose, and reflow POM, YAML, and TypeScript formatting
- formatting only: no Java code lines changed, no build-file semantics changed
- verified: backend compile and check_documentation.py pass, pnpm check and pnpm lint:check pass, and the dc3 YAML files parse
2026-08-25 13:35:00 +08:00
pnoker 5a89b39ec1 refactor(build): move the relational core into the db family as dc3-db-core
dc3-common-jdbc was the family's core in disguise — MyBatis-Plus wiring,
the tenant-line handler, the databaseIdProvider, the jdbc profile. It now
lives as dc3-db/dc3-db-core, matching the dc3-mq-core / dc3-tsdb-core
shape: one aggregator, port first, adapters after, tck last. Artifact
renamed, packages deliberately untouched (same convention as the mq
family move); twelve poms rewired, the family aggregator puts core
first, and the db-tck now depends on the core explicitly instead of
riding transitives.

dc3-common-sql stays where it is on purpose: it is not platform dialect
plumbing but the base class of the four JDBC *device drivers*
(mysql/oracle/postgresql/sqlserver drivers that connect to external
databases being collected from) and depends on the driver SDK — under
dc3-db it would claim a meaning it does not have.

Gates: db-tck 24/24 (postgres + mysql + mariadb), data 274/274,
full-repo compile green.
2026-08-24 20:19:06 +08:00
pnoker de656d293c feat(db): add the mariadb adapter — third engine certified, 24/24 across the family
dc3-db-mariadb brings MariaDB 10.6+ behind dc3.db.type=mariadb with the
official mariadb-java-client and the same dual-dialect contract suite.
MariaDB shares the MySQL seed wholesale (utf8mb4_unicode_ci — the 0900
collation is MySQL-8-only and MariaDB rejects it outright, so the seed
translator and the hand-written 05 both moved to the portably-supported
collation, re-verified against mysql:8.4).

The mapper forks differ from the mysql twins in exactly one dimension,
verified against a live 10.11 container first: MariaDB never adopted
the AS-new ODKU row alias (syntax error on 10.11), so its 15 twins
reference VALUES(col) instead — everything else (GET_LOCK/RELEASE_LOCK,
FOR UPDATE SKIP LOCKED, recursive CTEs, INTERVAL ? SECOND, the
JSON_UNQUOTE(JSON_EXTRACT(...)) double-hop, REGEXP_REPLACE without the
g flag, DAYOFWEEK/HOUR units) proved compatible and rides the same
statement bodies.

Scope decision recorded in docs/db-dialects.md: DB2, H2 and HSQLDB are
explicitly out (dialect cost with zero demand; embedded test databases
unfit for multi-service concurrent IoT writes), SQLite equally unfit
for the multi-service shape (database-level write lock, no network
listener). Oracle and SQL Server stay on the roadmap with their real
obstacles spelled out (MERGE-heavy rewrite; no ARM64 mssql image for
local certification on Apple Silicon) — not started without a demand
signal.

Gates: db-tck 24/24 (postgres + mysql + mariadb, 8 each),
data/auth/manager suites green (274 in data), full-repo compile green.
2026-08-24 20:01:48 +08:00
pnoker 20c97709fe docs(web): document three-terminal UX architecture and SDK design 2026-08-24 19:39:35 +08:00
pnoker 0ddb60f31c test(db): add the dual-dialect contract suite — postgres and mysql, 16/16
R2c of docs/design/storage-abstraction.md §3. dc3-db-tck runs one set of
mapper-level assertions against both engines with the real mapper XML —
the databaseIdProvider routing is part of what gets certified, not just
the SQL: the fenced latest-value upsert's three-level tiebreak (fencing
token, then time, then sequence, then message id), the state upsert +
same-transaction re-select shape, the three-step expired-lease claim
(lock-and-read FOR UPDATE SKIP LOCKED, flip offline, derive the
post-update view), the lease upserts' row-local increments (token moves
only on ownership change), the row-level revision triggers (insert and
meaningful update bump, trivial update does not), and the tool-catalog
query's encoded-JSON double-hop.

Harness notes worth keeping: one SqlSessionFactory per database mirrors
the production @DS wiring (state/latest/leases/catalog span four
databases with unqualified table names); the mapper XML loads through
XMLMapperBuilder with the affectData mybatis-plus DTD extension
stripped; postgres registers the timestamptz type handler. Both
harnesses learned to distrust podman: log-following never fires, so
mysql waits on the listening TCP port and postgres polls a real JDBC
handshake (the port opens during initdb's local-only phase and closes
again for the restart). The postgres harness mounts seed files 01..07
individually — 00's age/pgvector extensions exist only in the
production base image — with 05 trimmed at runtime to the
dc3_point_latest projection (the hypertable half needs the base
image's TimescaleDB function set; the dialect contract doesn't touch
it).

The catalog fixture plants its api row through a parameterized insert:
the escaped-JSON value must not travel through SQL text (MySQL's
default backslash escapes eat the \" the value depends on) nor bind
uniformly (PG rejects varchar->json, MySQL rejects the binary
Types.OTHER binding) — the fixture branches on the engine.

Selection guide published at docs/db-dialects.md (dialect matrix,
swap procedure, the semantic decisions the suite locks); the storage
design's status header moves to Implemented with an R1/R2 closeout
note.

Gates: db-tck 16/16 (8 postgres + 8 mysql), data/auth/manager suites
green (274 in data), e2e 26/26, full-repo compile green.
2026-08-24 19:13:24 +08:00
pnoker f64dccfc78 docs(design): add mcp runtime overhaul proposal — cohesive authorization contract
Proposed (not implemented): collapse the 5 internal runtime RPCs to 3,
carry inputSchema end-to-end so tools/list stops shipping an empty
envelope, and unify the authorize/confirm/audit seam. Aligned with the
S19 analytics endpoints (nine REST tools) which already match its target
dispatch shape; the s19 work noted the empty-schema gap this proposal
closes.
2026-08-24 16:21:05 +08:00
pnoker 79362fd3ed docs(analysis): add competitor and thing-model review notes
Three research notes from the platform-comparison track: DG-IoT and
supOS-Free feature-by-feature comparisons against IoT DC3 with borrow
lists, and a code-level review of the DC3 profile (thing model) design
with a final verdict on its current shape.
2026-08-24 16:20:56 +08:00
pnoker d83ead1f64 feat(tsdb): complete the store lineup — influxdb 3 and iotdb adapters, published capability matrix
Phase 3 of docs/design/tsdb-abstraction.md: the store family reaches four
certified adapters, the selection guide publishes the real capability
matrix, and external stores become first-class compose services.

dc3-tsdb-influxdb (3.11.2-core) talks the documented v3 HTTP APIs
directly — line protocol writes, query_sql CSV reads, zero client
dependencies. Integer fields carry the i suffix from the first write (a
bare number binds the column to Float64 forever and destroys ns
timestamps); timestamps cross the wire as CAST(... AS BIGINT)
nanoseconds because JSON renders them in scientific notation; PERCENTILE
is approximate-only and deleteRange does not exist in Core — both
declared honestly (false), the analytics facade computes exact
percentiles from bounded pulls.

dc3-tsdb-iotdb (2.0.10-standalone) maps series onto tree paths
root.dc3.t<tenant>.d<device>.p<point> — path nodes cannot be purely
numeric, so the design's raw-id mapping is amended. The server must run
with timestamp_precision=us and dn_rpc_address=0.0.0.0 (a two-line
properties override shipped under dc3/dependencies/iotdb); WHERE time
literals must use the 2026-08-20T12:59:59+00:00 form because bare epoch
numbers are parsed as milliseconds regardless of the server precision —
a microsecond literal silently matches nothing. Sessions disable
redirection: node discovery hands out internal cluster addresses that
break behind port mappings. GROUP BY LEVEL columns come back
wildcard-shaped and are parsed per column; COUNT rides the INT32 quality
measurement (never null); null measurements are omitted per record — a
null value in insertRecords makes the whole record vanish.

Capability matrix published in docs/tsdb-stores.md per adapter-declared
values (skip counts in the TCK are declared-unsupported cases, degraded
by facades, never wrong data). External store services (tdengine,
influxdb, iotdb) join the optional compose stack with healthchecks and
DC3_TSDB_* passthrough in the app stack.

Gates: timescale 24/24, tdengine 24/24 (2 skips), influxdb 24/24 (2
skips), iotdb 24/24 (3 skips); full-repo compile green.
2026-08-24 16:06:44 +08:00
pnoker 284c8187bf feat(tsdb): land s16 tiered retention and rollup — transparent tier reads on the shared caggs
The timescale adapter now owns the S16 lifecycle: raw 30 days (seed 05,
was 180) -> 1-minute tier 1 year (configurable via
dc3.tsdb.timescale.rollup.minute-keep-days) -> 1-hour tier forever.
bucketedAggregate and bucketedCount serve bucket widths at or above a
tier's granularity from that tier with exactly composable expressions
(AVG recombines as SUM(num_sum)/SUM(num_count), COUNT as
SUM(sample_count)); PERCENTILE and FIRST/LAST stay on the raw path — the
shared caggs carry textual first/last only, and tiered numeric first/last
would version-skew deployments created before such columns existed.
Single-window aggregate() stays raw by design: alarm windows must never
read through materialization lag.

The tiers are the observability pipeline's existing real-time continuous
aggregates (cagg_point_value_1m/1h, queried by Grafana) — one structure
serves both consumers; the adapter bootstraps the same names with an
identical shape via IF NOT EXISTS, so embedded seed boots, standalone
adapter boots and already-running deployments all converge on it without
rebuilding anything. Real-time mode (materialized_only=FALSE, no longer
the TS 2.13+ default) keeps tier reads correct immediately after an
append; the refresh policies only move aggregation work off the read
path. deleteRange refreshes both tiers afterwards.

Also fixed along the way: TDengine's bucketedAggregate PERCENTILE hit the
same "single table query" limit as aggregate() and now routes per-series
subtables.

Gates: timescale TCK 24/24 including the new rollup case (tiered
COUNT/AVG/LAST/bucketedCount and bucketed P50 all verified against raw
scans before any refresh materialized); TDengine TCK 24/24 with the same
case passing via honest raw degradation (2 declared-capability skips).
2026-08-21 13:01:45 +08:00
pnoker 8163aefde6 feat(data): add the s19 ai analytics facet — nine conclusion-shaped tools over the tsdb port
DataAnalyticsService (biz/analytics) composes TSDB port primitives with
relational metadata into the nine coarse-grained reads of
docs/design/tsdb-abstraction.md §9.7: latest values, raw/M4 history,
per-series stats with percentiles, period comparison, entity ranking
(activity or mean/max/min), least-squares trends, threshold exceedance
reports with merged intervals, Pearson correlation, and a tenant-level
quality overview (coverage, silence, quality-code census).

Exposed as nine /analytics/* REST endpoints. The MCP gateway's tools/call
dispatches over HTTP to catalog rows synthesized from OpenAPI snapshots,
so these endpoints (x-dc3-ai annotated, fully @Schema-documented — the
data annotation gate enforces it) become the MCP tool surface after
make openapi + refreshToolCatalog; no Java tool classes are needed.

Hard guarantees: the tenant id always comes from the authenticated
principal, never the body, and the facade re-validates it (S11 double
gate); scan volume is bounded (20 series, 5000 samples per series, 90-day
windows) with structured over-budget rejections and honest degradation
notes instead of silent truncation; ambiguous name resolution fails with
the candidate list so an agent can disambiguate on the next turn.
correlate is dual-path — the store primitive when declared, otherwise
Pearson over aligned bucket pulls (verified against TDengine's
capability=false, exact on a perfectly-correlated fixture).

Gates: dc3-common-data 274/274 including 12 new analytics tests;
dc3-center-data assembly compiles.
2026-08-21 11:53:37 +08:00
pnoker 20ae3fbf33 feat(tsdb): add the tdengine adapter — second store certified against the contract suite
dc3-tsdb-tdengine maps the port onto TDengine 3.x: one supertable
point_value tagged by (tenant, device, point), one deterministic subtable
pv_<t>_<d>_<p> auto-created on first insert via USING TAGS, database
created with PRECISION 'us' and KEEP 180. Connection settings live under
dc3.tsdb.tdengine.* (REST driver, own Hikari pool); selected by
dc3.tsdb.type=tdengine. Like the broker family, only the default adapter
(timescale) ships inside consumers — switching deployments add this
dependency plus DC3_TSDB_TYPE/DC3_TSDB_TDENGINE_URL.

The supertable mapping grilled four real issues out of the port
surface, each fixed and locked by the TCK:

- Timestamps never touch string form. The REST driver serializes
  Timestamp parameters in the client JVM zone while the server parses
  them as UTC — every write would shift by the deployment zone and
  cursor pagination would drift per page without end. Instants now
  travel as epoch-micro integer literals and reads return
  CAST(ts AS BIGINT) — verified symmetric against the image.
- AS value collides with a reserved word (agg_value now);
  INTERVAL takes a bare number read in the database (micro) precision.
- PERCENTILE only runs on single tables, so single-series percentiles
  query the deterministic subtable directly; tenant-wide percentile is
  refused rather than approximated.
- REST readiness must be probed with POST /rest/sql + basic auth; the
  GET path-style route 404s even on a healthy server.

Honest capabilities: latencyHistogram=false (dashboard degrades to
zero-filled bins via a capability check) and correlation=false (future
analytics facade computes from bucketed pulls); rollups stay NONE until
S16 lands stream computing. TCK result: 23 tests, 0 failures, 0 errors,
2 capability-gated skips; the timescale suite re-ran 23/23 unchanged.

The design doc's capability matrix now reflects the adapter's declared
values instead of the pre-implementation estimates.
2026-08-21 09:01:24 +08:00
pnoker edadfa0a06 feat(tsdb): rewire the data center onto the tsdb port and retire the repository module
Phase 1b of docs/design/tsdb-abstraction.md: every point-value read and
write now flows through the dc3-tsdb port, and dc3-common-repository is
deleted outright (no compat aliases).

Write path — new PointValueIngestService orchestration in dc3-common-data:
the stale-owner lease guard leaves the history INSERT's cross-schema join
and resolves the active owner per distinct device through the existing
DeviceFacade.getActiveOwner chain; a caffeine-backed ingest idempotency
window (dc3.data.ingest.*, marked only after both writes commit) absorbs
MQ redeliveries now that the message_id unique index is retired; the
batch lands via TsdbStore.append (natural series+time upsert, INGEST_ORDER
kept for the fenced dc3_point_latest upsert).

Read path — history via last(), page() resolves name/enable filters to
series keys through relational metadata (tenant-wide when unrestricted,
cursor offset emulation capped at 10k) with count() for totals; the alarm
window backend becomes TsdbWindowDataSource over aggregate()/history().

Dashboards — all nine data-side hypertable statements move to the S13
analytics facet (count/bucketedCount/countByDimension/latencyHistogram/
lastSeenPerSeries) plus the dc3_point_latest projection for latestStream;
the manager topology statement crosses into the data center via a new
ListSeriesVolumes RPC backed by the new S13-5 seriesCounts primitive (a
point shared by several devices needs per-series counts, which
single-dimension grouping cannot reconstruct); both cross-schema joins
are gone.

Port/adapter — seriesCounts added with a TCK case (23 total); series
filters compile to row-value IN lists chunked at 500 pairs; bootstrap
adds the quality column idempotently and retires uk_point_value_event so
existing deployments converge on the new upsert; the adapter binds the
application-provided tsdbDataSource bean (the history dynamic-datasource
entry) instead of the routing primary.

Timestamps — BO<->Instant conversion is pinned to
TimeConstant.DEFAULT_ZONEID (the platform canonical zone the latest
projection type handler already writes with); the design note's "lock
UTC" is amended accordingly, together with the lease-guard and S13-5
deviations recorded in §6.2.

Seed/deploy — 05-iot-dc3-history.sql gains the quality column and swaps
the unique index; compose carries DC3_TSDB_TYPE; make deploy publishes
dc3-tsdb.

Also fixed along the way: root pom never managed dc3-mq-core (latent,
masked by full-reactor builds); PointValueMapper ran unrouted on the
master datasource (now @DS("history")); latestStream name enrichment
looked up a Long-keyed map with String keys.

Gates: tsdb TCK 23/23, dc3-common-data 262/262, e2e 26/26 including the
unmodified PostgresHypertableIT; full reactor green except dc3-mq-tck's
kafka/pulsar container-startup flake, reproduced identically twice and
unrelated to this change.
2026-08-20 21:58:01 +08:00
pnoker eba91ad2ed docs(design): add ai analytics query facet s19 to the tsdb design
The agentic center (sessions/messages/actions/model providers) and the
mcp gateway tools/call path already exist, but the only data surface
today is the human CRUD api (PointValueController latest/list/history)
— nothing answers analytic questions. An agent's access pattern is
different: exploratory, combinational, cross-device comparisons, and
it needs a few coarse tools with typed json-schema inputs returning
self-contained statistical conclusions, not raw sample pages.

Three-layer design (§9.7):

- MCP tool layer: nine coarse tools (query_latest, query_history,
  compute_stats, compare_periods, rank_entities, trend_analysis,
  threshold_report, correlate, data_quality_report) — deliberately
  capped; typed inputs; audited into the existing dc3_action table
- DataAnalyticsFacade (new, in the data center): resolves names to
  series keys via relational metadata, composes port primitives into
  conclusions, enriches names, injects tenant context from the mcp
  auth (ai can never cross tenants), enforces scan budgets with
  structured downgrade advice
- TsdbStore port: principle 'no new ops for ai' — everything composes
  from S5/S6/S7/S13/S14/S15 except one capability-gated primitive,
  correlation(a, b, window, alignBucket), which the three sql stores
  express natively and iotdb degrades to facade-side bucketed pearson

Resolves open question 11: threshold-duration analysis is served by
threshold_report's bucket-locate + precise-history + merge composition
instead of a state-duration port op. TCK to 23 cases; phase 2 carries
the facade + mcp wiring.
2026-08-20 14:37:45 +08:00
pnoker 1d5e6120ef docs(design): add beyond-parity tsdb capabilities s14-s18
The extraction is also the moment to outgrow the current logic, not
just replicate it. Five new capability groups, all default-off so the
phase-1 behavior-parity gate stays intact:

- S14 multi-series reads: history/last/aggregate take a unified
  SeriesFilter (single series, series set, tenant-wide are one shape),
  collapsing the single-series and analytics facets into one read API
- S15 chart-grade aggregates: FIRST/LAST join the enum (M4 rendering
  over bucketed aggregates), PERCENTILE capability-gated
- S16 tiered retention + rollups: raw 30d -> 1m 1y -> 1h forever
  (configurable), reads tier-transparent (bucketedAggregate serves from
  the coarsest materialized tier), RollupSupport NATIVE (timescale
  caggs, tdengine streams) / MANUAL (influx tasks) / NONE (iotdb
  degrades to raw scan honestly); alarm windows always read raw
- S17 quality flag: OPC-UA-style int quality rides every sample now
  (one int today, a whole-store migration later if skipped)
- S18 ops surface: listSeries for the migration CLI and coverage
  audits, deadlines on every read, adapter-declared maxAppendBatch
  with port-side chunking

API unified around the filter shape; TsdbCapabilities grows five
fields; TCK to 22 cases; matrix gains four rows; open questions to
eleven (rollup materialization set, state-duration analytics).
2026-08-20 14:29:48 +08:00
pnoker d6ad519f2b docs(design): harden tsdb abstraction after gap review
Re-verified every dashboard statement against the actual SQL and closed
the gaps the review found:

- S13 tenant-wide analytics facet: the dashboard statements are all
  tenant-scoped (no per-series filter) — timeseries is tenant-wide
  bucketed COUNT, top groups by device/point/driver, silentSources is
  per-series lastSeen with a HAVING threshold, latencyHistogram bins a
  derived expression, coverageGapItems joins dc3_manager cross-schema.
  The single-series port API could not express any of these; the port
  gains bucketedCount / countByDimension / lastSeenPerSeries /
  latencyHistogram (capability-gated) with CursorPage and the supporting
  records defined
- fixed dangling cross-references (§10.x -> §12.x) from the translation
- new §6.1 runtime surface: selection key, per-adapter config namespaces,
  idempotent schema bootstrap responsibility, whole-batch append failure
  semantics, health indicators, port-level metrics
- new §6.2 caller migration table incl. the LocalDateTime -> Instant
  timezone hazard called out as a Phase 1 gate
- TCK extended to 16 cases (analytics facet coverage), capability matrix
  gains the analytics and latency rows, open questions to nine (external
  timescale topology, per-tenant retention)
2026-08-20 14:18:17 +08:00
pnoker 7d06f4e1b2 docs(design): translate tsdb abstraction design to chinese
Full Chinese rewrite of the tsdb port design (same structure and
content: verified inventory, S1-S12 semantics, port API, per-store
mappings, capability matrix, hard-parts arguments, 12-case TCK,
three-phase migration, seven open questions). Code identifiers, SQL and
enum names stay in English as is conventional.
2026-08-20 13:16:44 +08:00
pnoker 7bf80cfb85 docs(design): add tsdb abstraction design superseding the storage t-track
Design the time-series store as the second pluggable family, following
the executed mq playbook (port + adapters + tck + dc3.tsdb.type).

Grounded in a verified inventory of today's TimescaleDB usage — the
lease-fenced transactional write path, the fencing-guarded latest
projection, the SQL-shaped RepositoryService, and the ten dashboard
statements that bypass the port (one cross-schema) — the design's two
load-bearing decisions are:

- the latest-value projection stays relational (dc3_point_latest): it is
  OLTP state with a fencing-tuple guard that no TSDB expresses
  uniformly; adapters store history only, which removes the hardest
  cross-store semantic instead of negotiating it
- idempotency moves to an ingest-layer window; stores do natural
  (series, timestamp) upsert with per-adapter duplicate policies

The port adds what the dashboards had to bypass for: bucketed
aggregation, cursor pagination and count; all ten bypassing statements
are mapped onto port primitives. Per-store mappings cover TimescaleDB,
TDengine 3, InfluxDB 3 (version strategy argued) and IoTDB, with a
capability matrix, a 12-case TCK, a three-phase migration (timescale
extraction first, behavior-identical) and seven open questions.
2026-08-20 12:44:18 +08:00
pnoker d003291f81 refactor(build): extract the mq family into a top-level dc3-mq aggregator
The broker-selection family (port + six certified adapters + tck) is a
deployment selection, not shared plumbing, so it moves out of dc3-common
into a top-level dc3-mq aggregator beside dc3-api — the same standing as
the gRPC contract layer, and a template for future pluggable families
(storage is next on the design board).

- dc3-common-mq -> dc3-mq/dc3-mq-core (port: API + core runtime)
- dc3-common-mq-{rabbitmq,kafka,rocketmq,pulsar,activemq,mqtt,tck} ->
  dc3-mq/dc3-mq-{...}; no compatibility aliases — every coordinate is
  renamed everywhere in one commit (business poms, coverage, root
  dependency management)
- dc3-common sheds eight modules back to plain shared plumbing; the
  device-access dc3-common-mqtt stays by design (protocol stack, not a
  broker selection)
- Java packages deliberately stay io.github.pnoker.common.mq.* — the
  facade modules set the precedent that packages do not track module
  homes, keeping this a pom-only, zero-source refactor
- make deploy now publishes dc3-api + dc3-common + dc3-mq (third-party
  driver authors compile against the port transitively)
- docs/mq-brokers.md and the design's module layout follow the new home

Verified: full-repo test-compile green; constant/data/driver/manager
unit suites unchanged and green (255/144/291); the rabbitmq contract
suite passes 12/12 from the new coordinates against a live broker,
proving adapter wiring survived the move.
2026-08-20 12:23:09 +08:00
pnoker 95431e097f test(e2e): lock device-access mqtt vendor neutrality
Answer the broker-selection question for the device-access plane
mechanically: MQTT is an open wire protocol and the driver talks a
standard Paho client, so any conformant broker is a drop-in selection —
EMQX, Mosquitto, HiveMQ, NanoMQ, VerneMQ — via one URL
(dc3.driver.mqtt.url), no adapter layer needed.

MqttVendorNeutralityIT proves the property: the production option
builder (MqttUtil) plus the same client code performs a
publish/subscribe round trip against HiveMQ CE and Eclipse Mosquitto,
differing only in the broker URL. The compose default (dc3-emqx) is a
deployment choice; the guide's new device-access section documents the
URL-only selection, the no-vendor-code guardrail and the MQTT 3.1.1
dialect note (MQTT 5-only features belong to the internal-plane
adapter).

Full gated E2E now 26/26.
2026-08-20 11:26:24 +08:00
pnoker 1ece8d6732 docs(mq): publish broker selection guide and wire dc3.mq.type through deploy config
Close out the mq abstraction rollout:

- docs/mq-brokers.md: user-facing broker selection guide — how to pick
  (DC3_MQ_TYPE + connection settings), the six certified brokers, the
  capability matrix, and the at-least-once/outbox notes
- .env.example + dc3/env/dev.env: DC3_MQ_TYPE and the per-adapter
  connection variables with safe rabbitmq defaults
- docker-compose.yml: DC3_MQ_TYPE and adapter endpoints flow through the
  shared x-app-runtime-env anchor so every service sees the selection
- adapter configurations resolve connection settings from the canonical
  dc3.mq.* property first, then the DC3_MQ_* environment variable, then
  the transport-standard property (spring.kafka.bootstrap-servers),
  making containerized selection deterministic

The gated E2E gate passes unmodified through the port: RabbitDeliveryIT
6/6, CommandCallE2eIT 4/4, EventReportE2eIT 3/3 (full suite 24/24
including postgres and harness ITs) against testcontainers brokers.
2026-08-20 11:10:44 +08:00
pnoker cc655699fd feat(mq): add pulsar adapter, completing broker coverage
Phase 3 (4/4) and the final piece of the mq abstraction: every broker
named in the design is now a TCK-certified selection behind
dc3.mq.type. Pulsar suite 11 pass + 1 documented skip on the first run.

- topics map to persistent://public/default/dc3-<topic>, logical dead
  topics to dc3-<topic>-dlq; LOAD_BALANCE rides a durable shared
  subscription named after the consumer group (competing consumers,
  retained while offline), BROADCAST rides an exclusive per-instance
  subscription
- pulsar subscriptions start at the latest position natively, matching
  the rabbit fresh-queue / kafka latest semantics with no seeding
- acknowledge(List<MessageId>) commits deliveries, negativeAcknowledge
  requeues; reject(false) republishes to the -dlq topic and commits;
  poison and exhausted batches dead-letter through the shared
  synchronous bounded-retry semantics
- sends complete with the broker-issued message id (publisher
  confirmation); batchReceive is the native batch (capability true);
  arbitrary delays ride the port fallback like the other adapters
- tck harness against apachepulsar/pulsar standalone, or TCK_PULSAR_URL
  for an externally managed broker
2026-08-20 10:56:04 +08:00
pnoker fb703f5b64 feat(mq): certify the rocketmq adapter on the contract suite
Phase 3 (3/4): RocketMQ adapter (classic client) for
dc3.mq.type=rocketmq, TCK 11 pass + 1 documented skip against a live
nameserver/broker pair.

The 5.x classic client has two behaviors that had blocked certification,
both now handled in the adapter:

- brand-new consumer groups replay the topic backlog regardless of
  consumeFromWhere (LAST and TIMESTAMP both ignored), so fresh groups
  are seeded explicitly: a short-lived pull consumer sets every queue
  without a committed offset to the current max offset before the push
  consumer starts, matching the rabbit fresh-queue / kafka latest
  semantics; groups with a real offset trail are untouched
- subscribing to a topic that was never published to waits out the 30s
  route refresh, so subscribe-time seeding also warms up missing topics
  with a publish (the warm-up lands before the seeded offset, invisible
  to every fresh group)

Further fixes surfaced by the suite: dead-letter republishes only the
standard envelope headers (RocketMQ system properties like
CONSUME_START_TIME are rejected on re-publish), and requeues request the
1s delay level so bounded retry exhaustion dead-letters within the
contract window instead of the 10s default.

Topics map to dc3-<topic> (no dots), dead letters to dc3-<topic>-dlq;
LOAD_BALANCE rides CLUSTERING consumer groups, BROADCASTING fans out per
instance; sync sends are the publisher confirmation; arbitrary delays go
through the port fallback (delay levels would quantize). The tck harness
is opt-in via TCK_ROCKETMQ_NAMESRV (testcontainers cannot manage the
nameserver/broker pair); a fresh-group isolation probe rides along in
the adapter module, also opt-in.
2026-08-19 22:42:00 +08:00
pnoker cdfdff00d8 docs(design): record adapter certification status in the capability matrix
Rabbitmq, kafka, activemq and mqtt 5 adapters are implemented and
TCK-certified against live brokers; the matrix now reflects implemented
behavior including the group-durability row (mqtt documents the §13.8
variance) and the rabbit delay note (intrinsic TTL+DLX topics stay
server-side, arbitrary delays ride the port fallback). Rocketmq is
implemented but pending certification; pulsar pending.
2026-08-19 21:48:46 +08:00
pnoker eeffe6fa59 docs(design): add mq abstraction design with mqtt boundary
Add the pluggable message broker ADR (docs/design/mq-abstraction.md):

- port + one adapter per broker, selected by dc3.mq.type (default rabbitmq)
- two subscription modes (LOAD_BALANCE / BROADCAST), batch consumption with
  post-commit batch ack, bounded redelivery, lease-coupled instance expiry
- eight broker-specific semantics mapped per target broker with documented
  fallbacks (delay -> local scheduler, JMS confirm -> driver outbox)
- capability matrix, 13-case TCK as the community adapter acceptance bar,
  three-phase wire-compatible migration plan
- mqtt boundary (7.1): device-access plane stays vendor-neutral by protocol,
  internal plane may run over any mqtt 5 broker via a dedicated adapter

Status: proposed; phase 1 (port + rabbitmq adapter + business migration)
implemented in the follow-up commits.
2026-08-19 18:47:15 +08:00
Henry Zhang d06abff597 chore(docs): move docs site to standalone pnoker/iot-dc3-docs repo (#177)
* chore(docs): move docs site to standalone pnoker/iot-dc3-docs repo

The VitePress docs site now lives in pnoker/iot-dc3-docs and serves docs.dc3.site from there. Remove the in-repo docs/ and all docs-only build/deploy wiring so nothing dangles.

- delete docs/ (source moved to pnoker/iot-dc3-docs)

- delete .github/workflows/docs.yml (deploy owned by iot-dc3-docs)

- drop npm /docs dependabot block; clean codeql/.gitignore docs paths

- remove Makefile docs-* targets

- relocate AI testing policy into dc3-web/tests/ so the required Web guardrail test is self-contained (was reading ../docs/...)

- rewire README image refs (7 READMEs) and in-repo doc links (README.ai.md, AGENTS.md, dc3/doc/README.md) to https://docs.dc3.site / the new repo

dc3/doc/{TITLE,CHANGE,USAGE}.md STAY - release tooling (make changelog, docker-ci) regenerates them; the docs repo snapshots them.

* fix(docs): repair pre-existing broken doc links found during migration

- 8 READMEs: /en|zh/introduction/technology-stack -> /en|zh/development/technology-stack (page lives under development/, not introduction/)

- dc3-web/tests/README.md: ../docs/test-debugging.md (missing) -> https://docs.dc3.site/en/frontend/test-debugging
2026-08-06 14:54:17 +08:00
Henry ZhangandClaude Fable 5 73ff48bcb9 style: reformat codebase (backend java/xml, docs, web) (#175)
* style(backend): reformat and rearrange java/xml sources

Apply code cleanup across dc3-common and dc3-driver: whitespace and
indent normalization, import sorting, member rearrangement, and javadoc
normalization. No semantic changes; compilation verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(docs): reformat vitepress vue diagram components

Expand compact svg templates and normalize formatting across diagram
components, the seo module, and the theme stylesheet. No behavior
changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(docs): reformat markdown pages

Normalize markdown formatting. Restore the <script setup> component
imports that an earlier unused-import pass had wrongly stripped; every
<Diagram> tag now resolves to a backing import again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: reformat point view and trim pom whitespace

Normalize whitespace in the point view and drop trailing whitespace in
pom.xml. No behavior changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 12:13:36 +08:00
Henry Zhang 22263fc48a fix(security): patch postcss/brace-expansion CVEs + link SECURITY.md (#168)
- VulnerabilitiesID: pnpm update postcss/brace-expansion (transitive)
  — dc3-web: postcss 8.5.19+/8.5.25, brace-expansion 2.1.4/5.0.9
  (CVE path-traversal / DoS); docs: postcss 8.5.25
- SecurityPolicyID: SECURITY.md add GitHub Security Advisory
  (private vulnerability reporting) link as preferred channel

Fixes 4 dependabot alerts (PostCSS ×2, brace-expansion ×2).
2026-08-05 01:46:00 +08:00
Henry ZhangandClaude Fable 5 4b8a18b100 fix(docs): restore 5-pillar nav and fix version switcher position (#167)
* fix(docs): restore consolidated 5-pillar navigation

The promote-develop-to-main merge (6a4bef72c) fused main's consolidated 5-pillar nav with develop's flat 6-pillar layout into 7 scattered pillars, undoing the consolidation in 261af5721.

Restore the 5-pillar design: architecture absorbs the Overview pillar (project-overview group); operations merges into Develop (运营 + deploy-ops groups); AI stands alone; quickstart moves under Develop. Drop the now-unused pillar.overview/pillar.operations i18n keys, which also removes the overview/operations English fallback in the header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(docs): position version switcher between lang and theme toggles

The version switcher is injected via the nav-bar-content-after slot and rendered after the social links at the far right. Add flex order on desktop (>=960px) so it sits between the language switcher and the appearance toggle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 01:29:39 +08:00
pnoker 6a4bef72c5 chore: promote develop to main and fuse docs navigation
Brings develop's compiler/CI fixes into main (main was red; the fixes
in PR#88-91 lived only on develop). This unblocks the dependabot PRs
that target main.

Resolves the docs/.vitepress/config.mts merge conflict by fusing both
navigation designs into 7 pillars (总览·架构·驱动·AI·基础·开发·运维):
develop's overview/operations pillars + main's standalone AI pillar.
- Keeps develop's version-switcher (version.mts/VersionSwitcher/VersionBanner)
- AdSense stays dropped per main's earlier perf decision (drop AdSense)
- technology-stack remains under 开发, since the develop-side rename to
  introduction/ was not carried through the merge (file still at development/)
2026-08-03 11:24:25 +08:00
pnoker e3d335b56d build(docs): add makefile targets and pin node version
Add docs-dev, docs-build, docs-preview, and docs-version make targets. Pin
Node 22 LTS via .nvmrc. Bump pnpm packageManager to 11.17.0.
2026-07-24 12:08:08 +08:00