@meada/meada 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /node_modules
2
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,559 @@
1
+ # Changelog
2
+
3
+ All notable changes to Meada are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-30
10
+
11
+ The first release: the trunk of the roadmap (ADR 0003) and every spec merged since, with
12
+ identity automation (ADR 0016, #319) and named workspaces (ADR 0020) landing on the release day.
13
+ The trunk's detailed entries are in [docs/releases/0.1.0.md](https://github.com/ClaudioMascaro/meada/blob/v0.1.0/docs/releases/0.1.0.md);
14
+ the two later specs are listed below in full. Prebuilt binaries cover Linux and macOS on x86_64
15
+ and arm64 and Windows on x86_64; Windows arm64 waits for a toolchain that builds it.
16
+
17
+ ### Added
18
+
19
+ - TypeScript and JavaScript indexing with tree-sitter (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`,
20
+ `.cjs`): definitions, imports (relative paths, `index` resolution, `tsconfig.json` `paths`)
21
+ and calls, each with a level (`exact`, `inferred` or `ambiguous`). A call through a local
22
+ binding never resolves `exact` to the definition it hides.
23
+ - Python language pack (spec #24): definitions, imports (relative, packages, `import *` with
24
+ `__all__`, source roots from `pyproject.toml`) and calls, with receivers resolved through
25
+ `self`, annotations, constructor calls and the method resolution order. The main guard and
26
+ `[project.scripts]` targets are program entry points.
27
+ - Go language pack (spec #25), with no Go toolchain: functions, methods, structs and interfaces;
28
+ imports through the module path of the owning `go.mod` or a local `replace`; one scope per
29
+ package directory; build constraints for a fixed `linux/amd64` target; method calls through
30
+ the receiver types the source writes; `func main` of each `package main` as an entry point.
31
+ - Rust language pack (spec #30), with no Rust toolchain: crates built from their roots through
32
+ `mod` declarations; `use` declarations with renames, globs and `pub use` re-exports; path and
33
+ method calls resolved through the module tree and written types, a type from outside the
34
+ repository staying opaque; only the default configuration of a crate, for a fixed `x86_64`
35
+ Linux target and its default features; `fn main` of each binary target as an entry point.
36
+ - Java language pack (issues #84, #85, #91), with no JDK or build tool: package-qualified types
37
+ and signatures, overloads narrowed by argument count, package and static imports, calls
38
+ through fields, parameters, locals, return types and supertypes, read per class `family`, and
39
+ `public static void main` as an entry point.
40
+ - Kotlin language pack (issue #95), with no Kotlin compiler: `.kt` and `.kts` files with classes,
41
+ objects, companions, extension functions and properties; single, aliased and star imports;
42
+ calls `exact` when the argument count leaves one function; Kotlin and Java files of one
43
+ repository call each other.
44
+ - C# language pack (issue #107), with no .NET SDK or MSBuild: `.cs` files grouped into projects
45
+ by SDK-style `.csproj` files, namespace-qualified and `partial` types, `using` in all its forms
46
+ (`global`, `static`, aliases, a project's `<Using>` items and implicit usings), what referenced
47
+ projects pass on and the `Directory.Build.props` files MSBuild imports.
48
+ - Swift language pack (spec #29, #117), with no Swift toolchain or Xcode: each SwiftPM target is
49
+ a module; symbols named with their argument labels and extension members under the extended
50
+ type; calls narrowed by labels, `exact` on a receiver of written type; a `@main` type and a
51
+ `main.swift` start a process.
52
+ - HTTP contracts across repositories. TypeScript `fetch` consumers (client wrappers and base URLs
53
+ from the environment included) link to Next.js app router and Express providers; every link
54
+ carries a level and a reason, and a call whose URL cannot be read is an `unresolved consumer`,
55
+ never linked.
56
+ - HTTP providers in every other pack: FastAPI, Flask and Django URLconfs; `net/http`, gin, echo,
57
+ chi and gorilla/mux; axum, actix-web and Rocket; Spring MVC and JAX-RS; Ktor; ASP.NET Core
58
+ controllers and minimal APIs; Vapor and Hummingbird. Routers and groups are followed through
59
+ assignments, arguments, return values and imports, so a route is served under every prefix
60
+ around it; a prefix or mount Meada cannot read serves nothing.
61
+ - HTTP consumers in every other pack: `requests`, `httpx` and `aiohttp`; `net/http` and resty;
62
+ reqwest and ureq; RestTemplate, WebClient, `HttpRequest`, OkHttp and the `declarative client`s
63
+ OpenFeign and Retrofit (Java and Kotlin); the Ktor client; `HttpClient`, typed clients and
64
+ Refit; `URLSession`, Alamofire and AsyncHTTPClient. URLs fold from literals, constants,
65
+ concatenation and format strings, and a `client wrapper` is read at each of its call sites.
66
+ - Base URLs from the environment and configuration in every pack (`os.Getenv`, `std::env::var`,
67
+ `System.getenv`, `Environment.GetEnvironmentVariable`, `ProcessInfo`), Spring `@Value` with
68
+ `application.properties` and `application.yml`, and C# configuration with `appsettings.json`,
69
+ mapped to a `peer` through `base_url_env`. Meada never reads the value. Cross-language fixtures
70
+ link each language with TypeScript.
71
+ - Channels in Python, Go, Rust, Java, Kotlin and C#: Kafka, RabbitMQ, Amazon SQS, NATS, Redis
72
+ streams, JMS and Azure Service Bus clients are a `producer` or a consumer of a `channel`, linked
73
+ to the same channel in other languages, with the new broker kinds `jms` and `servicebus`. A
74
+ call counts only when its client traces back to the library; a name Meada cannot evaluate is
75
+ an `unresolved side`.
76
+ - Packages as contracts across seven `package ecosystem`s: npm (spec #22), PyPI, Go (spec #25),
77
+ Cargo (spec #30), Maven and Gradle (spec #26), NuGet (spec #28) and SwiftPM (spec #29).
78
+ A dependency on a workspace package links to it with its range flag, an import of the package
79
+ crosses into the provider's public declarations, `search` takes the kind `package`, and
80
+ `context` on an exported declaration lists its consumers in other repositories.
81
+ - Shape drift (spec #23, ADR 0009): each field a TypeScript or JavaScript consumer reads from a
82
+ response that the linked route does not return. Provider shapes come from an OpenAPI
83
+ `provider document`, a zod schema or the returned object literals, also through packages
84
+ (issue #175); consumer shapes from reads, casts and type annotations. `trace`, `context`,
85
+ `search` (kind `drift`), `meada status` and `meada export` show it.
86
+ - Communities and processes (spec #31): groups of symbols that call each other, found with a
87
+ deterministic Leiden and named after their most common directory, and `entry point`s (`route`,
88
+ `channel`, `export`, `program`) whose call tree `trace` renders from `process::<identity>`
89
+ across repositories. `search` takes the kinds `community` and `process`, and `context` adds
90
+ a symbol's community and the processes it belongs to.
91
+ - The query surface: an MCP server over stdio with three read-only tools, `search`, `context` and
92
+ `trace`, answering compact English lines under a token budget; `context` names the span's
93
+ range on its first line and cuts the span text first. `search` with kind `duplicate` lists the
94
+ symbols whose text is the same in two or more places of the workspace.
95
+ - The `meada` command (`index`, `status`, `export` with byte-reproducible JSON, `mcp`), messages
96
+ in Portuguese or English after `LANG`, `meada.toml` with `[[peer]]` and `[serve]`, workspace
97
+ discovery through the `origin` remote, and a per-repository SQLite index in `.meada/`
98
+ refreshed on every query from the files changed on disk. `meada status` points to the new
99
+ `inference` issue form when a repository holds a shape drift or an `inferred` link.
100
+ - Server mode (spec #259, ADR 0011): `meada mcp --listen` serves the three tools over MCP
101
+ streamable HTTP, one endpoint per repository, from the mirrors a `workspace manifest` lists;
102
+ indexes are refreshed by command, never by the server, and a reverse proxy admits the clients.
103
+ - Cloud modules (ADR 0012), cargo features off by default and all enabled by `cloud`: the shared
104
+ Valkey connection; the `snapshot store`, where `meada snapshot publish` uploads a `generation`
105
+ with BLAKE3-checked bundles and `meada snapshot pull` extracts it as hostile input; serving
106
+ generations with `--generations`, pinned at `/<identity>@<generation>`; the generation pointer
107
+ and retained generations in Valkey; and the `refresh lock` with its `fence`, never swapping a
108
+ `node` to a lower one.
109
+ - Operations on generations (ADR 0014): `meada snapshot pointer set`, `retain` and `drop` and
110
+ `meada snapshot fence recover` (#299, #300), a named lock holder, and the `heartbeat` each
111
+ `node` writes to Valkey after every pull round.
112
+ - OIDC admission (ADR 0012, feature `oidc`): the server becomes an OAuth 2.1 resource server
113
+ that checks a bearer JWT against the issuer's JWKS and bounds every answer to the identities
114
+ its claims, or its groups through `[oidc.groups]`, grant (#290, #291).
115
+ - GitHub App credentials (ADR 0013, feature `github-app`): `meada github-app credential` is git's
116
+ credential helper for the indexing job, minting installation tokens cached in Valkey and
117
+ skipping throttled installations.
118
+ - Workspace versions (spec #293, ADR 0014): with `[workspace] source = "store"`, the
119
+ `[[repository]]` entries live as immutable documents in the snapshot store, managed with
120
+ `meada workspace import`, `list`, `show`, `apply` (under the refresh lock, rolling back by
121
+ applying an older one) and `pull`.
122
+ - The management `console` (spec #293, ADR 0014, feature `ui`): `meada ui --listen` serves a
123
+ static Vite, React and TypeScript front end and a JSON API, with OIDC sign-in by authorization
124
+ code with PKCE S256, `viewer` and `operator` roles, operational pages for fleet, store,
125
+ repositories, GitHub App and admission, a query playground, code graph views, and operator
126
+ writes with one audit line each, in Brazilian Portuguese and English. The workspace editor
127
+ and version history (issue #310) validate entries and diff versions in the browser through
128
+ the shared WebAssembly core.
129
+ - Team ownership (ADR 0015): a workspace entry may name its `owner` team, and a `team operator`
130
+ may save and apply changes to its teams' entries alone; any other change is refused naming
131
+ each entry and its reason. The editor marks refused changes as they are typed, and the
132
+ console documentation gains a Teams section. The whole is additive, a minor change.
133
+ - Distribution: a cargo-dist release workflow building binaries for Linux, macOS and Windows on
134
+ x86_64 and arm64 with a shell installer, and an npm package `@meada/meada` (spec #32) staged
135
+ with each release for `npx @meada/meada`; multi-arch OCI images, default and `-cloud` (issue
136
+ #279), with the console's static files in the cloud image (issue #313); a Helm chart (#281);
137
+ Docker Compose examples (#280); and opt-in console profiles for both (issue #314).
138
+ - Documentation and community: README, CONTRIBUTING, code of conduct, security policy and issue
139
+ and PR templates in Portuguese, English and Spanish; a CI workflow; a multilingual
140
+ documentation site (issue #320); ADR 0020, recording the direction of spec S5.
141
+ - Every language pack and every manifest and configuration reader has a hostile-input probe
142
+ held to a time and size budget.
143
+ - Validation and operation of the named-workspace flow (#347, spec S5): a failure test that forces
144
+ the role check, the fetch/index and the local publication to fail in turn and shows the current
145
+ generation stays queryable, eligibility stays fail closed and a revoked grant never revives
146
+ (only a later complete permissions set restores it); the upgrade procedure (migrate to schema 8,
147
+ upgrade every binary before the first refresh that records a context, then configure), the
148
+ failure table and the acceptance map on the named-workspaces page in Brazilian Portuguese,
149
+ English and Spanish; `[catalog]` and `[workspaces]` in the Compose bootstrap examples; and the
150
+ Helm values `modules.workspaces` with `values-workspaces.yaml`, which put those sections (and
151
+ the `[ui]` block the job needs) in the console's and the job's bootstrap and never a node's.
152
+ The measurement against a real GitHub organization (#338) stays open and does not gate S5. No
153
+ code, default dependency or golden changed.
154
+ - The proposal server (#346, ADR 0022, feature `proposals`, enabled by `cloud`): `meada workspaces
155
+ mcp --listen <addr> --workspace <bootstrap>` with `[proposals]` (`public_url`, `audience`)
156
+ serves CLI agents four read-only MCP tools over the console's own named-workspace functions:
157
+ `catalog` (the repositories the caller may select), `workspaces` and `show` (the workspaces it
158
+ may see, with their `seq`) and `propose`, which validates manifest text as a new workspace or a
159
+ change and returns the canonical manifest, the diff against the base, diagnostics, each named
160
+ identity's eligibility, the refusals the console would give, the effective configuration,
161
+ `applicable` and the base, storing nothing. Identity, login, grant and role come from a bearer
162
+ token for `[proposals] audience`, which no node or console accepts and which accepts no
163
+ node's or console's token; no tool takes an argument naming an actor, a role or a grant, and
164
+ repository hints from an agent's own GitHub read MCP change no result. Every Postgres session
165
+ of the server is read-only, it calls no GitHub, node or identity provider API and writes no
166
+ audit record, store object or Valkey key. Inputs and answers are bounded (a body of 80 KiB, a
167
+ manifest of 64 KiB, an answer of 192 KiB, 32 calls in flight). The node's MCP server, the
168
+ console and the default build are unchanged.
169
+ - Guided authoring of named workspaces (#345, ADR 0021 "As built (#345)", feature `workspaces`):
170
+ the console edits the manifest of a workspace the subject operates through form controls
171
+ (repositories from the catalog, serve settings, resources, peers and environment-variable
172
+ names) that are one document with the manifest text, both going through `meada-core`'s
173
+ WebAssembly. It shows each entry's eligibility, ingestion and effective configuration in text,
174
+ the diff against the stored version, and freezes the reviewed payload until the person
175
+ confirms; server refusals (`entries_refused`, `stale_base`, a lost operator role) are shown as
176
+ worded and the catalog is read again. `namedDiff` and `namedChanged` join the WebAssembly
177
+ exports. Viewers get no mutation control. No server route changes; nothing changes without
178
+ `[workspaces]`.
179
+ - Context-specific configuration of named workspaces (#344, ADR 0021 contract 6, feature
180
+ `workspaces`): `GET /api/v1/workspaces/<id>/effective[?version=]` shows, per entry the caller
181
+ reads, where its configuration comes from (the repository's own `meada.toml` wins whole, else
182
+ the workspace's `serve`, `resource` and `peer` blocks, else nothing), the blocks that are
183
+ ignored, whether the repository is indexed, pending ingestion or excluded, and diagnostics
184
+ (`ignored_by_repository`, `unknown_until_ingested`, `excluded_no_sponsor`, `peer_missing`,
185
+ `documents_not_indexed`). `POST /api/v1/workspaces/effective` shows the same view for manifest
186
+ text before it is saved and stores nothing; creation, save and apply answers carry the same
187
+ `diagnostics`, so an ignored declaration never looks plainly applied. Two workspaces that
188
+ share a repository keep their own peers, resources and serve hosts: the configuration is applied
189
+ when answering and never enters a bundle, so a change of it re-indexes nothing and a pinned
190
+ generation keeps what it recorded. The decision is `meada_core::effective`, also exported to
191
+ WebAssembly (`namedEffective`). The console's workspace page shows the view in text and has a
192
+ text editor for the manifest of a workspace the subject operates. Nothing changes without
193
+ `[workspaces]`.
194
+ - The version lifecycle of named workspaces (#343, ADR 0021 contract 4, feature `workspaces`):
195
+ `POST /api/v1/workspaces/<id>/versions` saves a version without moving anything (an operator
196
+ submits the entries it reads and the version stored keeps the ones it cannot read; the
197
+ platform administrator submits the whole manifest), `.../apply` applies a stored version (a
198
+ version that was current before is a rollback), `.../rename`, `.../delete` (versions and
199
+ history are kept) and `.../operators` (the platform administrator delegates; a workspace keeps
200
+ at least one operator). Every write carries the `base` it read: a stale base, a revoked role, a
201
+ refused entry or a version of another workspace is refused and changes nothing, and the
202
+ transaction rechecks authority, base and the eligibility of what the operation adds or changes
203
+ against the newest permissions set. Only an operator of the workspace or the platform
204
+ administrator writes; a viewer is a 403 and a caller who cannot see the workspace gets the 404
205
+ of an unknown id. `GET .../versions`, `.../versions/<version>?against=` (with a diff) and
206
+ `.../history` read them under the same discovery rule. Each operation is one audit record
207
+ (`workspaces.save`, `.apply`, `.rollback`, `.rename`, `.delete`, `.operators`) with the
208
+ workspace and base. `meada workspaces list|show|versions|diff|create|save|apply|rename|delete|operators` are
209
+ the command-line twins (the platform administrator, `cli` audit records). Migration 8 adds
210
+ `workspace_versions.entries`: run `meada postgres migrate` before upgrading the binary. Without
211
+ `[workspaces]` nothing changes.
212
+ - The first named workspace of spec S5 (#342, ADR 0021 contracts 2 to 5, cargo feature
213
+ `workspaces`): `[workspaces]` (`mirrors`, an absolute root; `max_workspaces`, default 200, 1 to
214
+ 2000; `max_entries`, default 100, 1 to 500) needs `[ui]` with `operations = true`,
215
+ `[oidc.github]`, `[catalog]` and `[workspace] source = "store"`. An admitted subject whose
216
+ GitHub login holds an accepted role on every selected repository, directly or through a group,
217
+ creates a workspace with `POST /api/v1/workspaces`; `GET /api/v1/workspaces` and
218
+ `GET /api/v1/workspaces/<id>` list and show it to whoever's own grant reads every one of its
219
+ repositories (anyone else gets the one 404 of an unknown id), with `state` (`applied` or
220
+ `live`) and each entry's `ingestion` (`pending`, `ready` or `excluded`), never ready before a
221
+ generation records it. The creator operates it; group peers only view. The
222
+ decision is taken in one Postgres transaction against the newest permissions set (migration 7:
223
+ `workspaces`, `workspace_operators`, `workspace_versions`, `workspace_history` and a `context`
224
+ column on `permission_exclusions`; run `meada postgres migrate`); the version document is the
225
+ shared store's `workspaces/<version id>.json`. `meada workspace pull` also writes the contexts
226
+ file, `meada github-app roles` applies the sponsor rule (an entry no operator of its workspace
227
+ holds an accepted role on is left out before the fetch, `no_sponsor`, which is not a failed
228
+ fetch), and the repositories only named workspaces select are fetched, indexed and bundled once
229
+ at `<mirrors>/<identity>` (`meada workspace show --pulled` lists them). A generation records them
230
+ as `members` and the named workspaces as `contexts` (ids and digests in the generation id; a
231
+ deployment without named workspaces keeps every id), and a node serves
232
+ `/-/workspaces/<id>/<identity>` only to a token that reads every repository of the context (the
233
+ first segment `-` is reserved); the console's playground gains `workspace` and
234
+ `GET /api/v1/workspaces/<id>/repositories/<identity>/export`. The console has the workspaces
235
+ page (list, detail, creation from the catalog) and WebAssembly exports for the shared
236
+ rules. Save, apply, rename, delete and delegation (#343), per-context configuration (#344),
237
+ guided authoring (#345) and proposals (#346) are not built. Without `[workspaces]` every
238
+ process behaves as before; upgrade every binary, nodes included, before the first refresh that
239
+ records a context.
240
+ - The repository catalog of spec S5 (#341, ADR 0021 contract 1, cargo feature `workspaces`,
241
+ part of `cloud`): `[catalog]` (`allow` patterns on `[oidc.github] host`, `max_repositories`
242
+ 1 to 5000 with no default) makes `meada github-app roles` list, besides the workspace's own
243
+ repositories, those the Apps' installations cover (`GET /installation/repositories`, every page,
244
+ only for owners a pattern names) and `allow` admits, into the same permissions set with a
245
+ `source` on each repository (Postgres migration 6; run `meada postgres migrate`). A failed or
246
+ throttled listing, a body that is not the documented object or a candidate set above the cap
247
+ fails the run and writes nothing. The console gains `GET /api/v1/catalog` (bounded, opaque
248
+ pagination, `limit`, `after`, `q`) and `POST /api/v1/catalog/check`, answering only the
249
+ repositories the caller's login holds an accepted role on, direct or group-derived alike, with
250
+ the states `ready` and `pending` (`no_set`, `stale_set`, `missing_login`) and `unavailable`
251
+ without a reason, plus the repository catalog page; App coverage, group names in a token and
252
+ agent metadata alone grant nothing, and discovery creates no workspace, ACL or index. Without
253
+ `[catalog]` every process behaves as before. The cost of expanded coverage against a real
254
+ organization is not measured (#338 does not gate it).
255
+ - Spec S5 consolidated (#340, documentation only; nothing is implemented): ADR 0020 now holds
256
+ the accepted domain decision (platform administrator, workspace operator and inherited viewer,
257
+ creation eligibility from accepted repository roles, the frozen Y/A/B matrix, hidden discovery
258
+ for partial readers, revocation), ADR 0021 the contracts (catalog, selected entries, shared
259
+ ingestion membership, version lifecycle, query selection, effective configuration,
260
+ deterministic ids, migration and the slice graph #341 to #347) and ADR 0022 the authenticated
261
+ stateless proposal server. The glossary gains the terms, ADRs 0002, 0012, 0014, 0015 and 0016
262
+ carry dated amendments, `tests/fixtures/named-workspaces.json` is the Y/A/B table with a
263
+ `meada-core` test that checks it against the rules, and the named workspaces and roadmap pages
264
+ are updated in three languages. No serialized role, route, id, golden or default build changes.
265
+ - Identity automation for enterprise deployments (ADR 0016, spec #319), in the entries below: the
266
+ `postgres` and `directory` features and the `[postgres]`, `[directory]`, `[oidc] type`,
267
+ `[oidc.entra]` and `[oidc.github]` sections; `meada postgres migrate`, `meada github-app roles`
268
+ and `meada workspace show --pulled`; an entry's `added_by` and the `unattributed` and
269
+ `added_by_change` reasons of `entries_refused`; the session's `login`, `github_host` and
270
+ `github_grant`, the token check's `sources`; the audit and `GET /api/v1/audit`; the SCIM endpoint
271
+ under `/scim/v2/`, the `deprovisioned` refusal, the owner picker, `owner_missing` and `excluded`.
272
+ Under Spec R1's stability rule every one of them is additive: a minor change. Without the new
273
+ sections every process behaves as before, and the default build, the CLI help, the MCP surface
274
+ and every export and trace golden are unchanged.
275
+ - The identity automation documentation, in three languages: an enterprise identity guide (the
276
+ data layer, the audit, the provider type, the GitHub grant, additions under the provider, the
277
+ directory, Entra overage and Known ceilings), recipes for Entra ID, Okta and Keycloak, the
278
+ roadmap and diagrams with S4 delivered, and the architecture map's Known ceilings gaining the
279
+ login length and the audit's growth. S5 (#339), named workspaces and authoring through the UI or
280
+ MCP, stays planned.
281
+ - The Compose example gains `compose.postgres.yaml` (a Postgres 15 with TLS and the three
282
+ recommended roles, and a one-shot `meada postgres migrate` step that finishes before the job,
283
+ the console and the nodes), a `fetch-pulled.sh` for the job's refresh under `[oidc.github]`, and
284
+ commented `[postgres]`, `[oidc.github]`, `[directory]` and `[oidc.entra]` examples in the
285
+ bootstraps. The Helm chart gains `modules.postgres`, `modules.githubApp`, `modules.oidc.github`,
286
+ `modules.oidc.type` and `entra`, `modules.directory`, a migrate hook Job with pods that wait for
287
+ the schema, an example Postgres (`postgres.enabled`) and `values-identity.yaml`; the default
288
+ values render nothing of Postgres.
289
+ - `[oidc.github]`, the GitHub grant provider's table (ADR 0016): `host` (default `github.com`, a
290
+ lowercase host name), `login_claim` (required), `roles` (a non-empty array of distinct role
291
+ names of 1 to 100 characters with no control character, compared exactly with GitHub's
292
+ `role_name`; default `["read", "triage", "write", "maintain", "admin"]`) and `max_age_seconds`
293
+ (required, at least 60). It needs `[postgres]` and a build with `postgres`, each error naming
294
+ them; beside it `[oidc]` needs neither `identities_claim` nor `groups_claim`.
295
+ - The GitHub grant at admission (ADR 0016): nodes and the console with `[oidc.github]` keep the
296
+ newest permissions set in memory, read every 60 seconds without admission ever waiting on
297
+ Postgres, and a token reads the union of its claims grant and what its `login_claim` login (1
298
+ to 39 ASCII letters, digits, `-` and `_`, compared in lowercase) holds an accepted role on,
299
+ everywhere the grant is read: a node's admission, routing, answers and 404 lists, and every
300
+ bounded view of the console, the team operator role, the entry authorization and the token
301
+ check. The GitHub part grants for at most `max_age_seconds` past the set's `finished` on
302
+ Postgres's clock, then nothing, the claims grant unaffected; a machine token's `*` never
303
+ depends on Postgres. A node starts and serves without Postgres, and names a schema version it
304
+ does not know once on stderr. Every row read is checked as hostile input.
305
+ - `GET /api/v1/session` gains, with `[oidc.github]`, `login` (when the token has a valid one),
306
+ `github_host` and `github_grant` (`current`, `expired` or `unread`); `grant` is the union.
307
+ `POST /api/v1/admission/check` gains `sources`: `{"claims": "*" | [identities], "github":
308
+ [identities]}`, each sorted, from the console's cache. `GET /api/v1/admission` gains `github`:
309
+ the grant provider's settings, the grant's state and the newest set's id, `finished`, age, `max_age_seconds`,
310
+ repositories, rows and exclusions. The admission and session pages show them, and audit
311
+ records carry the subject's login.
312
+ - Additions under the GitHub grant provider (ADR 0015, ADR 0016): a team operator may add, for
313
+ its team, an entry of a repository on `[oidc.github] host` that its grant does not read yet.
314
+ The console's save sets the addition's `added_by` to the saver's login whatever the submitted
315
+ text says (and clears it on additions off the host), and the role check fetches the entry only
316
+ while that login holds an accepted role. Two refusal reasons join `entries_refused`, in
317
+ precedence order `outside_grant`, `unattributed` (an addition on the host without `added_by`,
318
+ or by a subject without a login), `ownerless`, `owner_change`, `added_by_change` (an edit
319
+ changing, clearing or setting `added_by`) and `not_your_team`. A save that adds an identity
320
+ the workspace already holds outside the grant is refused `outside_grant`, naming it as
321
+ submitted. The global operator's saves and the CLI keep `added_by` as written.
322
+ `authorize_entries` and `compose_entries` of `meada-core`, and their WebAssembly exports, take
323
+ the subject's login and the GitHub grant provider's host (none without the GitHub grant provider, answering as before),
324
+ and the editor marks the new reasons as it marks the others, in pt-BR and English, and shows
325
+ each entry's `added_by`.
326
+ - `excluded`: with `[oidc.github]`, `GET /api/v1/workspace` and `GET
327
+ /api/v1/workspace/versions/<id>` mark `excluded: true` on each entry of the version the newest
328
+ permissions set checked whose addition it left out, for those who see the entry; the field is
329
+ absent otherwise. The versions page and the editor mark those entries with the reason that the
330
+ adder holds no accepted role.
331
+ - `meada github-app roles --workspace <bootstrap manifest>` (features `github-app`, `postgres`
332
+ and `oidc`), the job's role check at every refresh: for each identity of the workspace on
333
+ `[oidc.github] host` it lists `GET /repos/{owner}/{repo}/collaborators?affiliation=all&per_page=100`,
334
+ every page, through the installation ADR 0013's selection picks, and replaces the permissions
335
+ set in Postgres in one transaction (what GitHub showed of each repository, its rows, every
336
+ collaborator listed with its `role_name`, and its exclusions with their reason), refused when a set with a higher `MEADA_REFRESH_FENCE` exists. With
337
+ `source = "store"`, every addition whose `added_by` holds no accepted role is left out:
338
+ written to `<index_dir>/workspace/excluded`, and `meada index`, `status`, `export` and
339
+ `snapshot publish` treat it as absent from the workspace, refusing to run, naming the role
340
+ check, when the file is missing or names another version. A 403 or 429 passes to the next
341
+ installation; every one throttled, a 5xx, a timeout, an answer past the size cap or a
342
+ `Link` header that leaves the repository's listing fails the run, which writes nothing. A
343
+ 404 or an owner no installation covers gives a warning and no rows. No App permission beyond
344
+ Metadata (read) is needed. The job's refresh becomes `workspace pull`, `github-app roles`,
345
+ the fetch of what `show --pulled` lists, `index` and `snapshot publish`.
346
+ - `meada workspace show --pulled --workspace <bootstrap manifest>` prints the version `meada
347
+ workspace pull` left on disk in the `[[repository]]` shape, without the entries the role
348
+ check excluded, for the fetch script; with a version argument it is a usage error.
349
+
350
+ - `added_by`, an optional key of a workspace version's `[[repository]]` entries beside `repo`,
351
+ `path` and `owner` (ADR 0016): the GitHub login that added the entry, 1 to 39 characters of
352
+ lowercase ASCII letters, digits, `-` and `_`. It is imported, stored, shown (`meada workspace
353
+ show` prints it right after `owner`) and pulled like `owner`, joins the version id and no
354
+ generation or bundle id, and nothing reads it to decide anything yet. An entry without it keeps
355
+ its version id. A bad value is an error naming the file, the line and the key, in a
356
+ bootstrap manifest's own `[[repository]]` entries (`source = "file"`) the key is an error
357
+ naming `source = "store"`, and a repository's `meada.toml` still rejects it as unknown.
358
+ - `[oidc] type` names the identity provider that issues the deployment's tokens (ADR 0016):
359
+ `generic` (the default), `entra`, `okta` or `keycloak`. None changes the admission, the grant or
360
+ any answer, and a bootstrap manifest without the key works as before; the console's `GET
361
+ /api/v1/admission` gains `"type"`, which the admission page shows beside the issuer. Any other
362
+ value, or one that is not a string, is an error naming the file, the line, the key and the four
363
+ values.
364
+ - `[oidc.entra] overage = "directory"` (ADR 0016): a Microsoft Entra ID token in more groups than
365
+ it can carry (`_claim_names` mapping `groups_claim` to a source, no `groups`) gets its groups
366
+ from the directory the identity provider pushes through the console's SCIM endpoint: the
367
+ `group_attribute` values of the groups whose members include the active user whose
368
+ `user_attribute` equals the token's `user_claim`. They replace the missing claim everywhere
369
+ `groups_claim` is read: a node's grant through `[oidc.groups]`, and the console's grant,
370
+ `[ui] operator_groups`, the team operator's teams, the session's `groups` and the token check.
371
+ It needs `[directory]` and `groups_claim`, each error naming the file, the line and the key.
372
+ Nodes and the console look a token up once at admission and keep the answer five minutes, per
373
+ `user_claim` value; a failed lookup is not kept, and with Postgres unreachable the token has no
374
+ groups, as before, with one stderr line. Meada never calls the source's endpoint. The console
375
+ still refuses a deactivated or deleted provisioned user as `deprovisioned` before any cached
376
+ answer is used. `overage = "ignore"` keeps today's behavior.
377
+ - `[oidc.entra]`, the settings only Entra ID needs, allowed under `type = "entra"` alone, with one
378
+ key: `overage = "ignore"` (the default), a token in overage having no groups, as before. Any
379
+ other value, and an unknown key, is an error naming the file, the line and the key.
380
+ - The data layer (ADR 0016): the `postgres` cargo feature, off by default and enabled by `cloud`,
381
+ and the bootstrap manifest's `[postgres]` section, which needs no other section. It names the
382
+ connection URL only through `url_env` (default `MEADA_POSTGRES_URL`) or `url_file`, plus
383
+ `ca_file`, `schema` (default `meada`), `pool_size`, `connect_timeout_seconds` and
384
+ `statement_timeout_seconds`. The URL is `postgres://` or `postgresql://` with a TCP host and no
385
+ query parameter (`sslmode` included); an error names the key, never the URL. A host outside
386
+ loopback always connects over TLS with its name verified, against `ca_file` alone when set,
387
+ else the system roots; a loopback host connects in plain TCP unless `ca_file` is set. Every
388
+ table lives in the schema `[postgres]` names. `[postgres]` in a build without the feature is
389
+ the error naming it. Postgres 15 and later.
390
+ - `meada postgres migrate --workspace <bootstrap manifest>` creates the schema when absent and
391
+ applies each migration up to the binary's version, each in its own transaction, under an
392
+ advisory lock keyed by the schema, so two runs serialize; it prints the version reached and
393
+ never lowers one. It ships migration 1, the schema's version. Every later connection refuses a
394
+ version the binary does not know: a lower one (an absent schema included) names `meada
395
+ postgres migrate`, a higher one names both versions.
396
+ - The directory (ADR 0016): the `directory` cargo feature, implying `postgres` and enabled by
397
+ `cloud`, and the bootstrap manifest's `[directory]` section, which needs `[postgres]` and
398
+ `[oidc]`: `token_env` (default `MEADA_SCIM_TOKEN`) or `token_file`, `user_claim`,
399
+ `user_attribute` (`userName` or `externalId`) and `group_attribute` (`displayName`, the
400
+ default, or `externalId`). Only the console reads the token, at start: shorter than 32
401
+ characters, unset or unreadable, it refuses to start naming the key, never the value.
402
+ `[directory]` in a build without the feature is the error naming it.
403
+ - The SCIM 2.0 endpoint: with `[ui]` and `[directory]` the console serves `/scim/v2/`, where
404
+ Entra ID's and Okta's provisioning create, read, filter, page, replace, patch and delete users
405
+ and groups, stored in the data layer (migration 3) as the provider sent them, never with
406
+ `password`. It admits only its own bearer token (no console token, and no request carrying
407
+ `Origin`), bounds a body at 1 MiB and a resource at 64 KiB, answers RFC 7644's errors with
408
+ their `scimType` and no echoed value, a 503 with Postgres unreachable, and the
409
+ `/ServiceProviderConfig`, `/ResourceTypes` and `/Schemas` documents. A deleted user leaves
410
+ every group and a revocation marker until it is provisioned again, active. Each write leaves
411
+ one stderr line, `scim: <method> <resource> <id>: <status>`.
412
+ - Deprovisioning in the console (ADR 0016): with `[directory]`, the token's `user_claim` is looked
413
+ up in the directory by `user_attribute` (`userName` ignoring case, `externalId` exact). A user
414
+ found inactive, or deleted and not provisioned active since, gets a 403 `deprovisioned` on every
415
+ route but `GET /api/v1/config`, whatever its role, and the console shows a deactivated-account
416
+ page. A user never provisioned is admitted. With the directory unreadable, reads are admitted
417
+ and a write is a 503 `audit_unavailable` before any audit insert, with one stderr line that
418
+ names the directory and never the claim. Migration 4 adds the directory's state row (the schema
419
+ version is now 4); a directory that had already lost its last group before the upgrade starts
420
+ with no group ever provisioned, so nothing is flagged until a group is provisioned again.
421
+ - The owner picker: `GET /api/v1/directory/groups?prefix=<text>`, for the global operator alone,
422
+ lists at most 100 group names starting with the prefix (compared exactly, sorted), and the
423
+ workspace editor offers them while the caret is inside an `owner = "..."` string.
424
+ - `owner_missing`: `GET /api/v1/workspace` and `workspace/versions/<id>` add `"owner_missing":
425
+ true` to an entry whose owner names no directory group, once a group was ever provisioned; the
426
+ versions page and the editor say so in text. `GET /api/v1/admission` gains the directory's
427
+ users and groups counts and the last SCIM write, which the admission page shows.
428
+ - The audit (ADR 0016): with `[postgres]`, every console write (`workspace.save`,
429
+ `workspace.apply`, `pointer.set`, `pointer.retain`, `pointer.drop`, `fence.recover`) and every
430
+ CLI twin of one (`meada workspace import` and `apply`, `meada snapshot pointer set`, `retain`
431
+ and `drop`, `meada snapshot fence recover`) leaves a record in `<schema>.audit` (migration 2):
432
+ who (subject, role, a team operator's owners), what (action and arguments), when (Postgres's
433
+ clock) and the outcome. The record is written as `pending` before the write touches Valkey or
434
+ the store, so a write whose record cannot be written is refused and changes nothing: the
435
+ console answers 503 `audit_unavailable`, the CLI exits 1 naming Postgres. Role refusals and
436
+ refusals under the lock are recorded too. The console starts with Postgres unreachable (reads
437
+ are served) and refuses to start on a schema version it does not know, as each CLI twin exits
438
+ 1. The global operator reads the records, newest first, through `GET
439
+ /api/v1/audit?before=<id>&limit=<n>` and the console's new audit page. Without `[postgres]`
440
+ every write behaves as before, and the stderr lines are unchanged in both cases.
441
+ - `meada status --workspace` with `[postgres]` prints `postgres: reachable, schema <name>
442
+ version <n>` or `postgres: unreachable (<reason>)`, a version refusal included, without
443
+ changing its exit code.
444
+
445
+ ### Changed
446
+
447
+ - The repository is a Cargo workspace (ADR 0014, #296, #297): the library crate `meada-core`
448
+ holds the manifest schema, the generation and version ids, the export's types, the query
449
+ results and shape drift, builds for WebAssembly for the console, and is published to
450
+ crates.io before `meada`. Every golden and message of the default build is unchanged.
451
+ - A refresh publishes the whole workspace or nothing (ADR 0012, #316), and a workspace of any
452
+ number of repositories opens and answers (the limit of eleven is gone).
453
+ - `context` and `trace` show the overloads of one method as one set, and a call that decorates
454
+ a definition (an annotation, an attribute, a decorator, a route attribute) is listed under
455
+ `annotations:` in `context` instead of among the callees.
456
+ - A Kotlin `HttpClient` held through more locals than `LOCAL_DEPTH` is now a consumer.
457
+ - The `decisions` `decider`'s default model is now `typesafe/jev-1.14`; `MEADA_DECIDER_MODEL`
458
+ still overrides it.
459
+ - The coding standards class each string table a language pack holds, and a test fails on an
460
+ unlisted one.
461
+
462
+ ### Fixed
463
+
464
+ - `meada mcp` answers about the client's first MCP root and falls back to its own directory
465
+ without roots (#192); a `roots/list` that times out after 5 s or fails answers about the last
466
+ root, or returns a tool error, instead of hanging or answering about the wrong tree (#282,
467
+ #285).
468
+ - TypeScript and JavaScript resolution: a call with type arguments under `await`, the `paths` of
469
+ a nested `tsconfig.json`, functions defined by assignment (`exports.y =`, prototypes, object
470
+ literal methods) and a name two `export *` give differently.
471
+ - Python resolution: nested `pyproject.toml` package roots, star imports and names bound again
472
+ (the last binding decides, `del` and a grown or computed `__all__` included).
473
+ - Java and Kotlin resolution: annotations written in full, method annotations through a base
474
+ from outside the repository, calls on another call's result, secondary constructors,
475
+ `expect` and `actual` classes, star imports, varargs, Java locals followed into their value,
476
+ separate field and method namespaces, and Maven or Gradle coordinates named by a property.
477
+ Ambiguous or unresolved OpenFeign and Retrofit methods are `open item`s that `meada decide`
478
+ resolves.
479
+ - C# resolution: types of enclosing namespaces, `internal` types, a qualified name two projects
480
+ declare, library names written in full, member reads between calls and overloads across
481
+ projects; `obj/` and `bin/` stay out of the index.
482
+ - Swift resolution: a module per package with `@testable import`, `typealias` receivers,
483
+ implicit members, capitalized function calls, outside member reads as `opaque outside`, Vapor
484
+ middleware groups and `if let` environment reads.
485
+ - Rust resolution: calls on another call's result, types brought by a glob import, a `mod` any
486
+ test build compiles, and broker clients read by path.
487
+ - A class whose outside bases are all platform types with `known outside members` resolves
488
+ calls to its own members at `exact` (ADR 0019).
489
+ - Spring and Retrofit contracts: an AMQP publish of a repository class, `@Value` fields beside
490
+ same-named locals, Gradle source sets outside `src/main`, Retrofit path rules, and `context`
491
+ on a shared artifact's class listing its calls in other repositories.
492
+ - What gets indexed: `node_modules`, `Pods`, Carthage `Checkouts` and minified files stay out,
493
+ `.meadaignore` is read like a `.gitignore`, `Package@swift-<version>.swift` is a manifest, a
494
+ large generated C# file is read whole (or named by `meada status`), and an invalid or
495
+ oversized config no longer stops indexing.
496
+ - Hostile input: crafted chains of re-exports, receivers, includes and static imports no longer
497
+ overflow the stack; folded values stop at a budget of 2^16 steps per file or 256 levels; a
498
+ manifest past 1 MiB and a provider document past 16 MiB are read capped, and a Spring YAML
499
+ past its key budget reads as empty.
500
+ - Performance: method chains, files of thousands of names, imports or definitions, Java local
501
+ chains, deeply nested Rust blocks and large C# project files no longer take time quadratic
502
+ or worse in their size, and a class family near 128 classes resolves the same whatever the
503
+ reading order.
504
+ - Console and fences: manifest groups and validation errors respect the viewer's grant, PKCE
505
+ redirects keep the issuer's query parameters, and fence recovery shares the fleet reader's
506
+ Valkey clock and heartbeat checks.
507
+ - Rust item macros (#398): the braced body of a `macro_rules!` of the repository whose first arm
508
+ takes `$($x:item)*` and writes each item back behind outer attributes only (tokio's
509
+ `cfg_rt! { pub mod runtime; }`) is read as items of the module invoking it: its functions,
510
+ types, `impl` blocks, `use` and `mod` declarations and the calls inside them, with spans into
511
+ the file. A `mod` in it reaches its file, and the `#[cfg(..)]` attributes the arm writes gate
512
+ every item under the crate's default features. A name defined several times qualifies only when
513
+ all definitions agree; any other macro stays unread. A refresh parses a file again when what it
514
+ invokes changes elsewhere, and equals a cold index.
515
+
516
+ ### Measured
517
+
518
+ The maintainer ran this release on the private dogfood set on 2026-10-14 (ADR 0003).
519
+
520
+ Links. One consumer repository calls the provider from four sites in its current code (ADR 0003
521
+ counted five); all four link to the right route at `inferred`, through `base_url_env`. The other
522
+ consumer repository's three calls do not link: its client receives `fetch` as a parameter and
523
+ takes the URL from a destructured object, two patterns this release does not read (#157, #158).
524
+
525
+ Cost, on the provider repository at the 2026-10-11 commit nearest to 633 files (634 TypeScript
526
+ and JavaScript files), median of three runs on a 16-thread Linux laptop:
527
+
528
+ | Metric | Target | Result |
529
+ | --- | --- | --- |
530
+ | Cold index time | ≤ 7 s | 1.2 s |
531
+ | Cold index size | ≤ 37 MB | 21.8 MB |
532
+ | Single-file reindex | ≤ 1 s | 0.24 s |
533
+ | Tool list | ≤ 1k tokens | 454 tokens |
534
+ | Default answer | ≤ 2k tokens | 1,834 tokens, the largest of 11 answers |
535
+
536
+ At today's 664 files: 1.3 s, 23.6 MB and 0.26 s. Tokens count 4 bytes each, as the answer budget
537
+ does. These numbers gate 1.0, not this release.
538
+
539
+ Communities and processes (spec #31) were measured on 2026-10-17 on the same repository, at the
540
+ same commit and at that day's 692 files. The runs used release builds of `main` just before and
541
+ just after the spec, each a median of three runs on a 16-thread Linux laptop with 7.2 GiB of
542
+ RAM. MB here means 10^6 bytes.
543
+
544
+ | Metric | Target | 634 files, before → after | 692 files, before → after |
545
+ | --- | --- | --- | --- |
546
+ | Cold index time | ≤ 7 s | 1.81 s → 1.80 s | 2.20 s → 2.12 s |
547
+ | Cold index size | ≤ 37 MB | 22.7 MB → 23.0 MB | 26.8 MB → 27.1 MB |
548
+ | Single-file reindex | ≤ 1 s | 0.74 s → 0.76 s | 0.89 s → 0.92 s |
549
+ | Tool list | ≤ 1k tokens | | 620 tokens |
550
+ | Default answer | ≤ 2k tokens | | 1,996 tokens, the largest of 11 answers |
551
+
552
+ The tool list counts the whole `tools/list` result as compact JSON. Among the 11 answers are
553
+ `context` on the three most-called symbols, each with its community and process membership,
554
+ and `trace` from the two longest processes. The spec adds about 0.3 MB and 0.03 s to a reindex.
555
+ The rise of the single-file reindex since 2026-10-14 predates it (specs #22 and #23), and at
556
+ 692 files it is close to its target.
557
+
558
+ [Unreleased]: https://github.com/ClaudioMascaro/meada/compare/v0.1.0...HEAD
559
+ [0.1.0]: https://github.com/ClaudioMascaro/meada/releases/tag/v0.1.0