A developer adds one remote MCP server to VS Code, JetBrains, Claude Code or any MCP client. From then on, their coding agent can ask the architecture why a call is asynchronous, list exactly what talks to a service, read a whole ADR, and build one component from its build pack — every contract byte for byte, every answer citing the version it came from.
Alia serves the sa-index/1 bundles that the
Solutions Architect Skills plugin exports from a versioned
ARCHITECTURE.md. The plugin produces data only. This server embeds it, answers exact questions from the facts ledger,
and serves build packs as stored. It is self-hosted: no architecture text leaves your network.
flowchart LR
repo["📐 Architecture repo<br/>ARCHITECTURE.md · ADRs · handoff kit"]
ci["⚙️ CI<br/>export-index.js"]
ide["💻 IDE<br/>VS Code · JetBrains · Claude Code"]
subgraph net["Self-hosted"]
server(["🧭 Alia server"])
qdrant[("Qdrant<br/>dense + sparse")]
ollama["Ollama<br/>Qwen3-Embedding 0.6B"]
disk[("Bundles on disk<br/>ledger · build packs")]
end
repo -->|"tag"| ci
ci -->|"POST /bundles/{key}/{version}"| server
server --> qdrant
server --> ollama
server --> disk
ide <-->|"MCP · Streamable HTTP /mcp"| server
classDef store fill:#15222A,stroke:#0C9DA3,color:#C9D1D9
classDef core fill:#0D1418,stroke:#2EE6E6,color:#C9D1D9,stroke-width:2px
class qdrant,disk store
class server core
- Why · Quick start · Connect your IDE · Tools
- How it works — publishing, downloads, indexing, escalations
- Deploy · Configuration
- Development · Roadmap
- Contributing · Security · License
Coding agents guess when they have nothing to read. An architecture they cannot query becomes a guess too: an invented timeout, a renamed field, an endpoint left open "for now". Alia gives the agent the architecture itself:
- Exact answers where exactness matters. Lists and relations — components, integrations, flows, ADRs, TDRs — come from the facts ledger, complete, never from search. A component is matched by exact name, never partially.
- Search for the reasons. Hybrid retrieval (meaning plus exact identifiers such as
ADR-112orI-07) for the why, each hit citing its file, lines and version. - Contracts are never paraphrased. OpenAPI, AsyncAPI, DDL and every build pack part are served byte for byte, with
their
sha256, and downloadable withcurlso the model never retypes them. - Versioned like code. Released versions are immutable; a draft is separate;
what_changedtells a service exactly what moved since the version and the pack revision it was built from. - A human in the loop where it counts. For maintenance (RUN) teams, a change that crosses an escalation trigger is never applied: it becomes an issue for an architect, filed only after the user said yes.
You need Docker. The stack is the server, Qdrant and Ollama.
git clone https://github.com/shadowX4fox/solutions-architect-knowledge-base.git
cd solutions-architect-knowledge-base
export ARCHINDEX_INGEST_TOKEN=change-me-ci ARCHINDEX_MCP_TOKEN=change-me-ide
docker compose -f deploy/compose.yaml up -d
docker compose -f deploy/compose.yaml exec ollama ollama pull qwen3-embedding:0.6b
# publish the synthetic Order Desk example that ships with the tests
tar -czf - -C src/test/resources/bundles/order-desk/1.4.0 . | curl -H "Authorization: Bearer $ARCHINDEX_INGEST_TOKEN" \
-H "Content-Type: application/gzip" --data-binary @- "http://localhost:8080/bundles/order-desk/1.4.0?channel=released"Then connect your IDE to http://localhost:8080/mcp with the MCP token, and ask: "What talks to
Hub API in order-desk?"
For your own architecture, document it with the plugin and let CI publish each release: the plugin's
sa-delivery:architecture-index-export skill and its ci/publish-index.sh do the export and the upload.
The published image. CI publishes ghcr.io/shadowx4fox/alia-mcp-server for amd64 and arm64: edge from main,
the version and latest from a v* tag, and a sha-… tag for every build. It needs Ollama, and Qdrant unless
ARCHINDEX_STORE=file:
docker run -d -p 8080:8080 -v alia-data:/data --add-host=host.docker.internal:host-gateway \
-e ARCHINDEX_STORE=file -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
-e ARCHINDEX_INGEST_TOKEN=change-me-ci -e ARCHINDEX_MCP_TOKEN=change-me-ide ghcr.io/shadowx4fox/alia-mcp-server:edgeWithout Docker: ./mvnw package -DskipTests, ollama pull qwen3-embedding:0.6b, then
ARCHINDEX_STORE=file ARCHINDEX_INGEST_TOKEN=… java -jar target/architecture-index-server-*.jar — the file engine
needs no Qdrant (see Deploy).
VS Code, .vscode/mcp.json:
{ "servers": { "alia": { "type": "http", "url": "https://alia-mcp.internal/mcp",
"headers": { "Authorization": "Bearer ${input:alia-token}" } } },
"inputs": [ { "id": "alia-token", "type": "promptString", "description": "Alia MCP token", "password": true } ] }JetBrains: in the AI Assistant MCP settings, add a Streamable HTTP server with the same URL and header.
Claude Code: claude mcp add --transport http alia https://alia-mcp.internal/mcp --header "Authorization: Bearer $TOKEN".
A service repository binds itself to the architecture with .architecture.json, which get_build_instructions writes:
{ "server": "https://alia-mcp.internal/mcp", "project": "order-desk", "component": "hub-api", "architecture_version": "1.4.0" }Every capability is a tool, because every IDE client supports tools, while support for prompts and resources varies.
| Tool | Answers from | Use |
|---|---|---|
list_projects |
store | which architectures, versions, drafts and build packs the server holds |
get_architecture |
ledger | one version at a glance: systems, components, which have a build pack and why the others do not; support, the RUN-team policy |
get_component |
ledger | type, technology, internal name, its integrations, flows and budgets, build pack availability |
list_integrations |
ledger | the I-nn inventory, whole or naming one component (From/To as written, never a guessed direction) |
list_flows · get_flow |
ledger | flows by number, name or UC-nn, each phase with hops and latency budget |
list_decisions |
ledger | ADRs or TDRs, by status |
search_architecture |
vector index | reasons and explanations: dense + sparse fused by weighted RRF; each hit has file, lines, anchor, version |
get_chunk |
bundle | the whole text of a hit |
get_document |
bundle | a whole document (by file, ADR/TDR id or component) rebuilt from its chunks in line order |
get_build_pack |
pack | the index: parts (each with its sha256 and download_url; an ADR part also with scope, own or context, and via; a dependency part is the contract of the component at the other end of an interface, with its provider), ordered plan, interfaces both ways, flows, open questions |
get_build_instructions |
pack | BUILD.md: the .architecture.json binding, the rules, the plan and the done list |
get_build_part · get_asset |
pack | one part byte for byte (spec, asset:openapi.yaml, dependency:ledger-store/openapi.yaml, interface:a→b, adr:ADR-101 …), paged over 60,000 characters |
what_changed |
bundles | what changed for a component since the version and pack_revision it was built against, its providers' contracts (dependencies) and its technical debt included (the TDRs its descriptor names, never an action); a part is compared by its substance, so one that moved only its version stamp, link targets or debt table is restamped, never an action; action_required where code must move |
preview_escalation |
release, ledger | a change that crosses a trigger, or a question only the architect can answer that blocks the build, as the issue it would open ([ESC-?] …), with the commands that file it; writes nothing |
record_escalation |
— | the filed issue's URL, logged against its preview |
preview_tier0 |
release, ledger | a Tier 0 write (tdr-workaround, tdr-status, adr-proposed, doc-fix) requested, as the issue it would open ([T0-?] …, labels architecture-tier0, tier0:<kind>), with the commands that file it; the architecture repository writes the record. Writes nothing |
record_tier0 |
— | the filed request's URL, logged against its preview |
Versions: omitted or latest is the newest released version; draft is the unreleased draft. A component with no build
pack in the asked version is an error naming the reason and the versions that have one. The server never falls back to
another version on its own.
The plugin's ci/publish-index.sh calls this contract. Its pipeline secret SA_INDEX_TOKEN is ARCHINDEX_INGEST_TOKEN here.
| Request | Answer |
|---|---|
GET /bundles/{key}/before/{version} |
200 application/gzip: the newest released bundle older than version, which the export diffs against for changes.json · 404 none |
POST /bundles/{key}/{version}?channel=released|draft (gzipped tar) |
201 stored · 200 a released version held, the body naming the build packs added, replaced and unchanged, and ledger same or extended with the paths in ledger_added · 409 refused, reason chunks_differ, ledger_differs or packs_missing · 400 the URL does not match the bundle · 413 too large · 422 the bundle fails its check |
GET /bundles |
what is held, per slot, with the indexing state |
- A released version is immutable in what search serves. A second publish of it — the second phase of a release, when the regenerated handoff kit is committed — carries the same chunks, the same ledger and every build pack held. It may add packs and replace them, never drop one. Anything else is refused and what is held stays.
- A ledger may be extended, never changed. The plugin adds derived fields over time (e.g.
tdrs[].components), so a ledger that only adds keys to the objects of the one held, at any depth, is stored with the publish that carries the packs, and the 200 says"ledger": "extended"with the paths added. A changed value, or an array element added or removed, isledger_differs. A publish without packs keeps what is held. - A pack is replaced when its revision moved: the
sha256the manifest lists for itspack.json, which lists every part's hash.get_build_packreturns it aspack_revision. - The ledger is compared without the clock an export before plugin v8.150.1 wrote into it. Pin
SA_PLUGIN_REFso both publishes of a release run the same plugin. A re-run of the same job changes nothing. - Each replacement is kept under
{data}/bundles/{key}/revisions/{version}.json, sowhat_changedwithfrom_revisionlists the replacements since that revision, then the diff to the newer version. - A project has one draft; a release of the draft's version or a newer one removes it.
- Every bundle passes the same check as
export-index.js --checkbefore it is kept: every listed file with its sha256, nothing unlisted, every chunk's hash, every pack part. Extraction refuses links and paths that leave the bundle.
An agent saves a contract asset with curl, so the model never retypes it and git shows the contract diff.
get_build_pack lists a download_url on every part when ARCHINDEX_PUBLIC_URL is set.
| Request | Answer |
|---|---|
GET /packs/{key}/{version}/{slug}/{path}[?pack_revision=…] |
200 the file as stored, ETag: "<sha256>", X-Pack-Revision · 304 If-None-Match matched · 404 no such project, version, pack or part · 409 pack_revision is not the current one |
curl -fsSL -H "Authorization: Bearer $ALIA_TOKEN" -o architecture/openapi.yaml "$DOWNLOAD_URL?pack_revision=$PACK_REVISION"
sha256sum architecture/openapi.yaml{version}is a released version,latestordraft;{path}is the part'spathinpack.json, and nothing else in the bundle is served. It takes the MCP token, never the pipeline's.- Only the current revision is served. A 409 sends the agent back to
get_build_packandwhat_changed. - Without a download,
get_build_part's pages joined in order are the stored bytes: no page ends inside a surrogate pair, and nothing is trimmed or normalized.
- One Qdrant collection for every project. Each point carries a named dense vector (
dense, cosine, 1,024) and a named sparse vector (sparse, Qdrant'sidfmodifier), with keyword payload indexes onproject,slot,kind,components,adrs,content_hash,doc_languageandchunk_id. - A slot is a released version or the draft. The index keeps no text: a hit resolves against the bundle on disk.
- A chunk whose text the project has already embedded reuses its vector, and chunk ids hold across releases, so a release re-embeds only what it changed.
- Indexing runs on one background thread.
search_architecturereports a slot not ready yet, while the ledger and build-pack tools answer for it at once. - An engine returns two candidate lists, dense by cosine and sparse weighted by inverse document frequency over the
scope searched; the server fuses them, the same for every engine, by weighted reciprocal rank (k 60), equal scores
sharing a rank. It weighs the dense list 1 and the sparse list
ARCHINDEX_SPARSE_WEIGHT(0.3): at equal weight the exact-term list cost ordinary questions their best hit; at 0.3 identifiers still come first (eval/README.md). - Embedding prompts are the model's: Qwen3-Embedding takes an instruction on the query and nothing on documents.
- The context window Ollama reads (
ARCHINDEX_EMBEDDING_CONTEXT, 8192) is sent on every request: without it Ollama loads the model at 4096 and cuts a long chunk silently. - A collection created for one model refuses a model of another dimension. A model switch takes a new
QDRANT_COLLECTION.
A RUN team (support.mode is run in the release's manifest, plugin v10.3.0 and later) asks the released architecture
from the IDE. The four writes under support.tier0_writes need no architect, but they belong in the architecture
repository, not the service one: the agent requests them with preview_tier0 (see Tier 0 requests).
A change that crosses one of support.triggers is not applied: it becomes an escalation for a human architect, filed as an issue on the
architecture repository's git host. So does a question that blocks the build and only the architect can answer: a
contract with nothing to implement, an interface whose authentication is not documented, a Proposed decision the code
depends on. The agent sends the questions in questions, the trigger each one touches (an authentication or
authorization gap is security-iam; a gap no other trigger names is low-confidence) with its evidence (the build-pack
part and the line), and route tier-1. On a project with no archindex.escalation, it hands the user the same
questions, each with its part and line, for the architects. The flow mirrors the plugin's escalate.js:
preview_escalationchecks the request against the latest release only, never a draft, and only for a RUN-team project with a release. It validates the request asescalate.js validatedoes: an unknown trigger, a trigger with no evidence (low-confidenceaside) or an unknown route is refused. Components go under their canonical names. It returns the issue asescalate.jsrenders it, the release's open escalations, andfile_with: the commands for the project's host (gh,glab,az boardsor Gitea's API). Nothing is written.- The agent shows the user the issue. Only after a yes does it file it with the developer's own access. It first
checks the open escalation issues for the
preview_id, so a preview filed twice is found. record_escalationlogs the issue's URL against itspreview_id.
The server calls no git host and holds no token. It names where each project files (archindex.escalation.projects);
the caller never chooses. The issue is titled [ESC-?]: the plugin's escalate.js import writes the record into the
architecture repository and numbers it. The preview_id is the issue's fingerprint, carried in the body as a comment.
The log archindex.escalation has one line per preview (project, route, triggers, who asked) and one per filing (the
URL), joined on the preview_id — the count of escalations per team, with no store behind it.
From plugin v10.7.0 a RUN-team release lists its Tier 0 kinds (support.tier0_kinds, each with its label, its required
fields and whether it needs evidence) and the TDR status moves (support.tdr_moves); from v10.7.1 it also names the TDR
statuses and drivers (support.tdr_statuses, support.tdr_drivers) — a release exported with v10.7.0 is read with that
version's lists. A workaround recorded as a TDR
(tdr-workaround), a TDR status move (tdr-status), a project ADR in Proposed (adr-proposed) or a doc fix that
changes no fact (doc-fix) is requested, never made from the service repository:
preview_tier0checks the request against the latest release as the plugin'stier0.js preparewould:validate()with its own words (the kind, its fields, the evidence, a TDR id, a status, aYYYY-MM-DDdate, a file underdocs/, a line number), then every component by canonical name, a workaround's ADRs and a status move's TDR and arrow on the release's ledger. A move toWon't Fixis always refused: it is the architect's decision. The release may be behindmain, where Tier 0 writes merge between releases: a move refused from the released status but valid from one a Tier 0 write may have set since (reachable alongsupport.tdr_moves, never throughWon't Fix) is previewed, andnextsays what it assumes — the architecture repository checks it againstmain. A TDR or a project ADR a Tier 0 write created since the release is not on its ledger; when the project's draft (which a Tier 0 merge publishes) lists it, it is checked on the draft andnextsays so (TDR-003 is not in 1.5.1; it is Open in the draft of 2026-10-20; …). Without a draft listing it, it is refused as before. It returns the issue exactly astier0.js renderwrites it ([T0-?] …, labelsarchitecture-tier0andtier0:<kind>, the fields as a JSON block) andfile_with, the same commands as an escalation's.- The agent shows the user the issue and files it only after a yes.
record_tier0logs the URL against thepreview_id(logarchindex.tier0).
The architecture repository writes the record (architecture-tier0.yml), checking the request again; one that is no
Tier 0 write is refused there, with the reason. The body is the plugin's byte for byte, so it carries no fingerprint:
the agent finds a request filed already by its title and body.
A build no person attends — a CI job, a platform's build agent — declares "supervision": "unattended" in the service
repository's .architecture.json (plugin v10.10.0), and its agent passes supervision: "unattended" to both previews
("attended", the default, otherwise; any other value is refused). Nobody says yes, so:
preview_tier0renders the issue astier0.jsdoes for an unattended request: the labeltier0:<kind>only, neverarchitecture-tier0, and a footer saying a person reviews it and applies the label — that is the approval.file_withadds noarchitecture-tier0, and the agent never applies it. The architecture repository refuses the label from a bot or an account inSA_TIER0_AGENTSregardless.- A kind the architecture lists in
tier0.auto(support.tier0_auto, plugin v10.13.0) needs no person: the architecture repository keys it itself when an account inSA_TIER0_TRUSTED_AGENTSopened the issue. The agent still files it withtier0:<kind>only;preview_tier0renders the footertier0.jswrites for it. A move toResolvednames what resolved it, the build's pull requestowner/repo#Nor a commitowner/repo@sha, and is filed at once: the architecture repository writes it once that is merged into the default branch. The preview warns when the evidence names neither. preview_escalationrenders the same issue: an escalation writes nothing, so it is filed without a yes.- Either way,
file_withis followed with the build's own identity, and the issue is listed in the build's pull request, where a person reviews the work.
ARCHINDEX_STORE picks the engine behind search_architecture; only Ollama is needed besides it.
| Engine | Where the vectors live | At start | For |
|---|---|---|---|
qdrant |
the Qdrant collection | a released slot already held is ready | a shared server |
file |
one file, <data-dir>/index/vectors-<dimensions>.bin |
read from the file | one machine, local runs |
memory |
the process | everything re-embedded | tests and throwaway runs |
file and memory search in process by brute force. Every engine ranks the same: each returns its two candidate lists
and the server fuses them, and a contract suite (VectorIndexContract) holds each engine to the memory engine's ranking.
A new engine is a VectorIndex and a subclass of that suite.
docker compose -f deploy/compose.yaml up -d # Qdrant engine: server, qdrant, ollama
docker compose -f deploy/compose.yaml --profile file up -d server-file # file engine: no Qdrant
docker compose -f deploy/compose.yaml --profile scale up -d # plus a reader on 8081For a shared server, run the qdrant engine. One instance writes and any number read; all share the data directory (a
ReadWriteMany volume, or one volume on one host) and the Qdrant collection.
ARCHINDEX_ROLE=all (the writer, exactly one) |
ARCHINDEX_ROLE=reader (any number) |
|
|---|---|---|
POST /bundles |
publishes | 403: publish to the writer |
| indexing | embeds, writes to Qdrant, records each slot's chunk set | never embeds or writes; a slot is ready when the writer's record names its chunk set |
| bundles | writes a new directory per publish, then HEAD.json, then .generation |
reloads the projects that changed when .generation moves |
/mcp, /packs |
answers | answers |
- MCP is stateless. No session is held in memory, so any instance answers any call behind a load balancer.
- A bundle directory never changes once written. A replaced one stays for
ARCHINDEX_RETAIN_REPLACED(10 minutes), longer than a reader takes to reload, then the writer deletes it. - Search never sees half a slot. A re-index writes the new chunk set over the points held, then removes the rest.
- One writer. Publishes are rare, so the writer is a single replica: two writers on one data directory would
overwrite each other's
HEAD.json. - Ollama has no state. Run replicas behind their own load balancer, all with the same model.
Three URLs, each with a fixed target:
| URL | Set in | With a load balancer | Without one |
|---|---|---|---|
publish, /bundles |
CI: SA_INDEX_SERVER |
the writer | the writer's address; a reader answers 403 |
MCP, /mcp |
the IDE, and server in each .architecture.json |
the load balancer | any instance |
download base, ARCHINDEX_PUBLIC_URL |
each instance's environment | the load balancer | each instance's own address |
ARCHINDEX_PUBLIC_URL must resolve on the developer's machine, where curl runs — never a container hostname. Make
.architecture.json's server a stable DNS name you can repoint.
qwen3-embedding:0.6b is the default: 32K context (every chunk the export cuts fits), multilingual, Apache 2.0, 1,024
dimensions. On the retrieval eval it ties the other candidates on ordinary questions and leads on
answers deep in long chunks.
Ollama uses the GPU on its own: CUDA, or ROCm on AMD. On an AMD Radeon RX 9060 XT it embeds about 19 chunks a second; on a CPU, about 1. A release re-embeds only the chunks it changed. EmbeddingGemma 2 needs Ollama's MLX runtime, which the Linux package does not ship (checked with Ollama 0.40.4).
| Setting | Default | |
|---|---|---|
ARCHINDEX_INGEST_TOKEN |
unset (open, warned) | bearer token for /bundles/** |
ARCHINDEX_MCP_TOKEN |
unset (open, warned) | bearer token for /mcp and /packs/** |
ARCHINDEX_PUBLIC_URL |
unset (no download_url) |
the base the IDE reaches the server at, e.g. https://alia-mcp.internal |
ARCHINDEX_ICON |
Alia's escudo | the icon MCP clients show for the server (serverInfo.icons): a deployment's own URL or data: URI, or none |
ARCHINDEX_DATA_DIR |
data |
bundles on disk; shared by every instance |
ARCHINDEX_ROLE |
all |
all: publishes, indexes and answers · reader: answers only, follows the writer; needs qdrant |
ARCHINDEX_REFRESH_INTERVAL |
5s |
how often a reader looks for the writer's publishes |
ARCHINDEX_RETAIN_REPLACED |
10m |
how long a replaced bundle directory stays |
ARCHINDEX_STORE |
qdrant |
qdrant · file · memory |
ARCHINDEX_FILE_PATH |
<data-dir>/index/vectors-<dimensions>.bin |
the file engine's file |
ARCHINDEX_EMBEDDING_MODEL |
qwen3-embedding:0.6b |
Ollama tag; another model needs its prompts (ARCHINDEX_EMBEDDING_QUERYPREFIX, …_DOCUMENTPREFIX), its dimensions and a new collection |
ARCHINDEX_EMBEDDING_DIMENSIONS |
1024 |
the model's output size |
ARCHINDEX_EMBEDDING_CONTEXT |
8192 |
tokens Ollama reads per chunk |
ARCHINDEX_SPARSE_WEIGHT |
0.3 |
the sparse list's weight in the fusion |
OLLAMA_BASE_URL |
http://localhost:11434 |
|
QDRANT_HOST · QDRANT_GRPC_PORT · QDRANT_API_KEY · QDRANT_TLS · QDRANT_COLLECTION |
localhost · 6334 · – · false · architecture-qwen3-0.6b-1024 |
Where each project's escalations are filed, in application.yaml or the equivalent environment variables
(ARCHINDEX_ESCALATION_PROJECTS_0_PROJECT, …):
archindex:
escalation:
projects:
- project: order-desk
host: github # github | gitlab | gitea | azure
repository: acme/order-desk-architecture # GitLab: the project path · Azure DevOps: organization/project
server: github.acme.internal # unset: github.com, gitlab.com, dev.azure.com · Gitea: required
work-item-type: Issue # Azure DevOps onlyA project without an entry is not escalated through the server: the agent sends the user to the architects directly.
Spring Boot 4.1 · Spring AI 2.0 (MCP server, Ollama embedding model) · Qdrant Java client 1.19 · Jackson 3 · Java 21.
./mvnw test # every test; in-memory index and a stand-in embedding model, no services needed
QDRANT_TEST_HOST=localhost QDRANT_TEST_PORT=6334 ./mvnw test -Dtest=QdrantTest # the indexer against a real Qdrant
./mvnw package -DskipTests # target/architecture-index-server-*.jarCI (.github/workflows/ci.yml) runs every test on each push and pull request, builds the image and smoke-tests it: the
container starts, takes the Order Desk bundle and answers list_projects over MCP. Only then does it publish, from main
and from v* tags; a pull request builds without publishing.
src/test/resources/bundles/order-desk/ holds seven bundles of a synthetic project, Order Desk, exported by the plugin
from its own test project:
| Bundle | What it is |
|---|---|
1.3.0-search |
the release tag, before the handoff kit |
1.3.0 |
with the Hub API build pack |
1.4.0 |
contract, budget, technology and integration changes, its changes.json, an open escalation |
1.4.0-revised |
1.4.0 with the Hub API pack regenerated: its security obligations moved |
draft |
an unreleased draft |
1.4.0-stamped, 1.5.0-restamped |
a pair for what_changed by substance: Hub API parts that move only their version, links, line ends or debt table, and one case per JavaScript regex rule; changes.json and changes.md are the plugin's |
1.6.0-deps, 1.6.1-deps |
dependency contracts: Hub API carries Ledger Store's API and Web App's events; 1.6.1 changes the API and drops the events, whose part of the kit stays at 1.6.0; changes.json and changes.md are the plugin's |
src/test/resources/escalation/issues.json holds the escalation issues the plugin renders for four requests, the
awkward text included. src/test/resources/tier0/issues.json holds the Tier 0 issues tier0.js renders, one per kind
and one with every edge (backticks, pipes, padding, numbers, control characters), and what its validate() refuses.
| Test | Holds |
|---|---|
BundleTest |
the Java what_changed diff to the plugin's own changes.json; the store to the publishing contract |
EscalateTest |
the Java escalation issue to the plugin's, byte for byte |
EscalationTest |
the RUN-team policy gates and each host's filing commands |
Tier0Test |
the Java Tier 0 issue and refusals to the plugin's, byte for byte; JavaScript's numbers and key order; the checks on the ledger |
ServerTest |
every tool through the real MCP endpoint, publishing over HTTP with tokens, every part downloaded |
BuildPackTest |
a part's pages to its stored bytes |
ScaleTest |
a writer and a reader on one data directory and one index |
QdrantTest |
the indexer and a reader's view against a real Qdrant |
Regenerate the fixtures after a bundle schema change, or a change to the plugin's escalation or Tier 0 rendering:
SA_SKILLS_DIR=<solutions-architect-skills checkout> bun scripts/make-fixtures.ts src/test/resources/bundles/order-deskeval/ measures an embedding model the way the server ranks — hit@1 and hit@5 on ordinary questions,
answers deep in long chunks, and exact identifiers. Run it before changing the model, its prompts or the sparse weight.
| Item | State |
|---|---|
| Sign-in through an identity provider, permissions per project | planned: OIDC as the MCP authorization spec sets it, CI keeps the ingest token (#40); bearer tokens until then |
| A service template (skeleton, libraries, CI) | outside the architecture; BUILD.md points the agent to the organization's own |
| Retrieval eval on a real architecture | eval/ measures on a synthetic one; confirm the model and the sparse weight on yours |
| Bundles on blob storage (S3-compatible, Azure Blob) for Kubernetes | deferred until Kubernetes is the target (#12); a volume until then, ReadWriteMany for readers across nodes |
Pull requests are welcome. Bugs and ideas go to Issues.
./mvnw test must pass before a PR. A change is held to these rules — CLAUDE.md carries them in full:
- The plugin owns the bundle schema and the publishing contract. This server reads
sa-index/1and never redefines it. A schema change starts in the plugin, then the fixtures are regenerated here. - Ports stay ports.
BundleChecker,BundleDiffandEscalateare the plugin's own functions, field for field, each held to the plugin's output by a test. - Lists and relations come from the ledger, never from search. A build never goes through embeddings.
- Fixtures are synthetic. Never commit a bundle exported from a real organization's architecture.
- Commits are scoped Conventional Commits —
<type>(<scope>): <description>,!for a breaking change.
Report a vulnerability privately through GitHub's Report a vulnerability (Security → Advisories) rather than in a public issue, with what is affected, how to reproduce it and its impact.
The server is meant to run inside your network: set ARCHINDEX_INGEST_TOKEN and ARCHINDEX_MCP_TOKEN (it warns when
either is missing), keep them out of versioned files, and put TLS in front of it. It calls no external service besides
your Ollama and Qdrant.
MIT © 2026 shadowx4fox.
Alia's banner (docs/images/brand/server-hero.webp) and escudo (src/main/resources/alia/escudo.svg) are the companion
plugin's, MIT like the rest, and drawn as is: their rules are in the plugin's brand/alia/.
It is Alia's, never an organization's: a deployment that shows its own brand sets ARCHINDEX_ICON.
Dependencies, container images and the embedding model carry their own licenses: Spring Boot and Spring AI (Apache 2.0), the Qdrant client and Qdrant (Apache 2.0), Ollama (MIT), Qwen3-Embedding (Apache 2.0).
