Repository navigation
Development - #111
Merged
Merged
Development#111
Conversation
…eded [#37] Addresses the review on #43. collector-rbac is deleted. It existed to let clickstack's collector scrape cAdvisor through the API server, which needs nodes/proxy -- and the vendored chart defines no ServiceAccount, so the grant went to `default` and every pod in observability inherited it: ClickHouse, MongoDB and HyperDX included. `get` on nodes/proxy reaches any kubelet endpoint, exec among them. Narrowing the binding was the other option offered. Removing the scrape is better because the scrape was already redundant: the otel-agent DaemonSet reads the same kubelets directly, and emits the k8s.* names the dashboards query rather than the container_* names nothing looked at. The duplication was also the source of 21.5 million metric rows a day, which is what forced ClickHouse to 8Gi. otel-agent loses nodes/proxy too. Its receiver dials the kubelet directly on :10250 and never uses the API server proxy, so the verb was unused -- inherited by copying the scrape that did need it. nodes/stats, nodes, pods and namespaces remain, which is what kubeletstats and its metadata lookup actually use. The chart now follows CONVENTIONS.md: it depends on common, takes labels from common.labels and common.selectorLabels so every resource carries part-of: oan, and has a CHANGELOG and a README. The selector renders the same two labels as before -- common.name is .Chart.Name without a nameOverride -- so this is safe on a running DaemonSet, whose selector is immutable. Also corrects a comment claiming extra_metadata_labels attaches limits and requests. It does not; only container.id is set. adapter-service parses its routing instead of grepping it. The trailing-slash guard tested lines starting `url:` and ending `/`, missing a quoted url, a list-item form and a trailing comment -- each a slash that reaches the router. It now checks routingRules[].target.url through fromYaml, fails when the document has no rules at all, and names `routing.config` rather than the chart file when that is where the routing came from.
feat: turn on signing, signature and extended schema validation
…griNet/discovery-service#45] Load tests for the OAN network layer, driven by JMeter, reporting throughput, latency percentiles, error rate and per-pod CPU and memory. make data make publish|discover|select URL=... THE DATA IS REAL. Fetched from Agmarknet with credentials that live only in the environment -- nothing about the endpoints or the credentials is in this repository. 4,172 markets across 36 states, of which 9 have a commodity mapping at all: Maharashtra, Tamil Nadu, Madhya Pradesh, Uttar Pradesh, Karnataka, Chhattisgarh, Meghalaya, Andhra Pradesh and Bihar. That gives 19,135 real market-commodity pairs. ONE RESOURCE IS ONE MARKET-COMMODITY PAIR, not a market carrying every commodity it trades. Markets trade between 1 and 80 commodities, median 4, so the alternative made every resource a different size and every discover response a different size with it. Paired, resource size holds within 2%. targetResources sets the total -- 100,000 -- and the result lands on it exactly, each state taking a share proportional to its real pairs. The manifest records realPairs alongside it, so the x5.2 stretch is visible rather than implied. Catalogs are derived from the markets that survive filtering, so refetching with a different set of states needs no config change and a state with no usable market never becomes an empty catalog. DISCOVER QUERIES ARE DISTINCT AND UNIFORM. Each carries a JSONPath filter and a spatial constraint together, never one alone. Centres are placed continuously along the line between two markets either side of a state border, so the supply of distinct queries is unbounded rather than one per border pair. The extent is solved rather than guessed: that commodity's resources are sorted by distance and the circle's edge goes between the last one wanted and the first not. 20,000 queries, 20,000 distinct, 67-83 matches around a target of 75, every one spanning at least two states. Select requests name a published resource, so they measure the provider path rather than its rejection path. PAYLOADS ARE NOT COMMITTED: 100k resources and 20k queries is about 150 MB and this repository is public. data/CHECKSUMS holds a digest per directory and `make verify-data` regenerates and compares -- the same guarantee at 296 bytes. Proven by wiping all three directories and rebuilding. Traps handled, each found by running it rather than by reading it: a ${__P()} does not evaluate inside a JMeter doubleProp or boolProp; a throughput of 0 is an infinite wait rather than no limit; `jmeter --version` leads with a date banner; and Agmarknet answers 400 when it simply has no data, which killed an all-India fetch at Delhi. Nothing environment-specific is committed -- the target is a URL argument.
…ry-service#45] `kubectl top pod` with no --namespace reads whatever the kubeconfig context points at, usually `default`, which holds nothing. The samples file came out with a header and no rows, and the report showed an empty CPU table for a run that was otherwise fine. It matters more here than most: this deployment puts each component in its own namespace -- consumer-adapter, network-adapter, provider-adapter, discovery, registry, postgres -- so no single namespace covers a request path. --all-namespaces prepends a column, so every field shifts one right. Reading the old three would have taken the namespace as the pod name and the pod name as the CPU figure: garbage that still parses. Rows are now recorded as namespace/pod, which the report groups on unchanged and which disambiguates two namespaces holding a pod of the same name. Verified against the cluster: 90 rows in 12 seconds, covering all three adapters, discovery, both Postgres instances and the registry.
dependency-check took `jmeter --version | head -1`, which on a real install
printed
jmeter WARN StatusConsoleListener The use of package scanning to ...
JMeter leads with a log4j plugin-scanning warning on some installs and an
ASCII banner on others, so the first line is rarely the version. The same
trap was already fixed in tools/run-benchmark.sh and missed here.
Verified against output shaped like the VM's: reports 5.6.3.
All three plans carried
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">${__P(loops,-1)}</stringProp>
and with continue_forever=true JMeter IGNORES the loop count. Each thread
loops until the test is stopped. A positive LOOPS also sets scheduler=false,
so DURATION is not bounding it either -- the run has no end condition at all
and sails past the requested count until killed by hand.
Caught on a 6x167 run that should have sent 1,002 requests and was still
climbing at 1,207. It means every LOOPS run so far ended when someone
pressed Ctrl-C, not when the work was done, and "one full pass" was never
one full pass.
A loops value of -1 still means forever, so DURATION-bounded runs are
unaffected.
Verified by parsing all three plans: continue_forever=false, loops still
read from the property. Not executed here -- JMeter is not installed on this
machine, and the next run on the load VM is the real test.
A .jtl is CSV and JMeter writes the assertion failure into failureMessage, so a failed request spans seven physical lines where a successful one spans a single line. Counting lines counted every failure about seven times, and every figure built on that count was wrong in the direction that flatters the run: more requests over the same wall time read as higher throughput, and the same failures over a larger denominator read as a lower error rate. On the first 100k publish pass that reported 4,676 requests at 2.45/s with 12% errors, where JMeter's own summariser said 1,302 at 0.7/s with 43%. The percentiles were dragged down too, by feeding 1,866 values into a set that holds 1,304. Records are now rejoined on quote parity, which is the actual CSV rule, rather than by matching a timestamp at the start of a line. Runs without failures are unaffected: all seven discover rows report exactly as before, and 10,000 requests for 20 threads x 500 loops is now arithmetic rather than coincidence.
Select could not be reproduced from this file. The mock upstream ships in this directory and was never mentioned, and the three deployment facts that have to agree with it were written down nowhere: the registry's upstream pointing at the mock, the provider's auth set to none, and a mapping tag that still exists. The trap that costs the most time is now stated too -- the seed only creates rows it does not already have, so editing a value changes nothing until the row is deleted. Four things were also wrong or out of date: - the scenario table routed select through the network adapter. It does not go there; the consumer adapter hands straight to the provider. - CPU and memory were described as coming from the harness sampling kubectl top, with nothing in the dashboard. SAMPLE defaults to none, so a plain run records neither, and where an OpenTelemetry agent runs the dashboard has them and keeps them after the run ends. - resources.csv was listed as something every run produces. It appears only when SAMPLE is set. - the limit labels read as optional detail. Nothing reads them back, so leaving them unset means the run cannot say what hardware produced it, which is what happened to every run in the first matrix.
The file opened with commands and buried the folder tree in the middle, so a reader met `make publish` before knowing what had to exist for it to work. It now follows the order the work happens in: what is here, what must be in place, build the data, run, read the results, reference. Layout moves to the top. "What you need" and the select prerequisites merge into one Prerequisites section, and a part is added that was missing entirely -- what the deployed stack has to provide. That last part matters because nothing environment-specific is committed: the ingress can be anything, so long as it routes /publish to the provider adapter and /discover and /select to the consumer, and `URL=` names that host with no path. Per-pod CPU and memory is listed as good to have rather than required, since without metrics-server or a collector a run still measures throughput, latency and errors. Commands split by when they are used: building payloads under Prepare the data, the run commands and settings under Run a benchmark, and fetching runs back and tidying up under After a run. Heading levels are normalised so nested sections stop reading as top-level ones. No content dropped -- every make target and every setting in the table survives the move.
Thirty-three settings in one flat list, in no order, and several rows named a thing rather than describing it. The seven limit pairs said only "the consumer adapter", which identifies the service and never mentions that the setting records rather than sets. SAMPLE and ON_SAMPLE_ERROR listed their values without saying what they select. ARGS said "extra flags" without naming --record-host, the only one anybody passes. They are now six tables grouped by what you are deciding: where to send it, how hard to push, what to call it and what it ran against, what to sample, where output goes, and fetching metadata. Every row stands on its own, so nobody has to read the paragraph underneath to learn that a limit variable changes nothing in the cluster. Every setting survives the regrouping.
16% fewer words and 88 of the remaining lines are table rows, which is the shape reference material should have. Nothing was removed that anybody needs: every make target and every setting survives. What went was justification rather than instruction. The data section said the same thing twice under two headings and is now one. The run example listed all twenty-six options at once, which reads as a wall rather than a starting point; it is now the minimum command followed by one realistic run taken from an actual matrix row. MandiPrice kept its numbers and lost the reasoning behind them. The select prerequisites became a table, since three things that must each equal something is a table and not three paragraphs. Also removes a duplicated sentence the earlier restructure left behind at the top of section 4.
…phs [#45] The rendering was off because the file had two h1s -- Benchmark at the top and Reference in the middle -- so a reference heading drew the eye harder than the steps above it. There is now one h1, sections at h2 and subsections at h3, with Refreshing the data and Known quirks demoted to bold labels rather than a fourth level. Also fixes a numbering hole the earlier merge left: 3.2 existed with no 3.1. Paragraphs that were really lists are now lists -- what the deployed stack needs, what is not committed, what make data produces, and the reasoning behind the market-commodity pairing. Nothing removed, only reshaped, since a requirement in the middle of a sentence is a requirement people miss.
Two things were wrong rather than merely long. mock-upstream/ was described in prose but missing from the directory tree above it, and the link pointing at it resolved to nothing, because numbering the sections had changed the anchor. Both internal links now resolve. The rest was the file repeating itself. The limit variables were explained twice in the same section, CONFIG three times across two, and Refreshing the data restated section 3.1 command for command. The tail of 4.1 also described what a run writes -- resources.csv, the per-pod table, the labelled-against-deployed comparison -- which is section 5's subject, so it moves there. Also drops a bold "Tidy up." stranded under a heading called Tidying up, and settles on one URL style; the last example still said http://<host>:<port> where everything else says https://<host>. 487 lines from 519, with every make target and every setting intact.
Publish is bound by contention rather than by hardware: every request takes the catalog's row lock, loads everything already stored in it, and rebuilds the filter documents for the whole catalog. With nine shared catalogs the requests queue, and the wait is what crosses the adapter's 30-second timeout. BENCH_FRESH_IDS=1 measures the same work without that. Before each request goes out, a token unique to it is appended to every `catalog:` and `resource:` id in the payload, so the request takes a lock nobody wants, merges into an empty catalog, and rebuilds only its own hundred resources. The difference between the two runs is the cost of sharing a catalog. Applied to the id prefixes rather than to whole ids, which keeps the document consistent: publishDirectives[].catalogId still names its own catalog and an offer's resourceIds still name resources in the same request. Verified over two rewrites of one payload -- no catalog or resource id in common, directives still matching, context.action untouched. Off unless the variable is set, so every existing command behaves as it did. It needs no new payloads either: the rewrite happens to the body already being read, beside the transaction and message ids.
…ount [#45] Two things a reader of a report could not previously work out. The fresh-catalog rewrite was reached through BENCH_FRESH_IDS in the environment. It decides what publish measures -- on, every request gets a catalog of its own and there is no contention; off, requests queue for the catalogs already stored -- yet both produced identical run.env and identical reports. Two runs measuring opposite things were indistinguishable afterwards. It is now --fresh-ids on run-benchmark.sh and FRESH_IDS= in the Makefile, carried to the plan as -JfreshIds, written to run.env, and stated in the report. The plan compares it against the allowed values rather than testing it for truth: the property is always set, and "no" is a true value in Groovy, so the old `if (fresh)` would have turned it on for every run. LOOPS is counted per thread, so 10 threads and 501 loops is 5,010 requests, not 501. The corpus is 1,002 payload files and the list restarts when a run outlasts it, so that run publishes the set five times over. The README says both, and the report now prints the total alongside the loop count.
feat: add a benchmark harness for publish, discover and select
OpenBao v2.6.3 from https://openbao.github.io/openbao-helm, committed whole and byte-identical to the upstream tarball, as kong and cert-manager are. OAN's settings arrive as a values file, not as edits to the chart.
- openbao.dev.yaml: one server on raft storage, auto-unsealed by the static seal from a Kubernetes Secret (no cloud KMS, no manual unseal), audit log to its own volume, injector off since ESO delivers secrets. - openbao-cluster-secret-store: the ClusterSecretStore ESO reads, logging in with Kubernetes auth as its own ServiceAccount. Replaces asm-cluster-secret-store. - oan-secrets: asm-secrets pointed at the openbao store. Same 12 secrets, same keys, same namespaces. - openbao-configure.sh: one-time init, KV v2 at secret/, Kubernetes auth, and a read-only policy on oan/<env>/* for ESO. Re-runnable. The asm-* charts stay until a separate cleanup PR.
asm-to-openbao.sh copies every oan/<env>/<name> entry value for value -- the values are baked into Postgres roles, Keycloak's stored client secret and the adapter keys the registry holds, so they must not be regenerated. secrets-snapshot.sh fingerprints every managed Secret (hashes, never values) so the switch can be proven with an empty diff.
README gains the bring-up, verification and migration steps; CONVENTIONS and the registry README describe the OpenBao flow. adapter-service's config comments change too, so it and registry get PATCH bumps with changelog entries.
…es [#166] - openbao-configure.sh: an unreachable OpenBao (pod not yet running, wrong kube context) was read as "not initialised" and triggered an init. It now retries for a minute and then stops with an error. Init output goes to a temp file moved into place only on success, so a failed init no longer leaves an empty file that every re-run refuses to overwrite. - asm-to-openbao.sh: the aws CLI's stderr was captured into the value, so any warning on a successful read failed the JSON check for every entry. It is now kept separate and shown only on failure. - asm-to-openbao.sh: a failed OpenBao write aborted the run under set -e, leaving it half-copied with no record. Each failure is now reported, the rest still copy, and the script exits non-zero at the end.
CONVENTIONS.md: no chart carries an oan- prefix; the organisation is in the part-of: oan label, not in names. The chart directory, its name, and every reference in the docs and scripts change. Rendered output differs only in the chart name label, so the in-place upgrade of the asm-secrets release works as before -- its Argo CD app now points at charts/openbao-secrets.
OpenBao refuses any request no audit device can record, and nothing rotates the audit file, so a full audit volume would stop every request -- ESO's reads and admin logins alike. A second device writes to stdout, so a request fails only if both fail, and the audit trail reaches the cluster's log pipeline. Reproduced on a 1 MiB audit volume: file-only answers 500 once it is full; file plus stdout keeps serving.
…166] ESO logs in to OpenBao as its own ServiceAccount whichever namespace an ExternalSecret is in, so the OpenBao policy cannot tell namespaces apart. An ExternalSecret in any namespace could read postgres-superuser. The new allowedNamespaces value (default external-secrets, where openbao-secrets is installed) renders as the store's spec.conditions, and the chart refuses to render with it empty.
ADR-0002 records why the tiers moved to /consumer/, /network/ and /provider/, and how the paths callers see stay what ADR-0001 promised: the edge rewrites /discover onto /network/ and the transaction actions onto /provider/, so the mount names are ours and nobody else carries them. NETWORK_BASE_URL goes back to no path. The chart banned snippet, rewrite and regex annotations outright, because one could reach /_internal or /publish. That ban also forbids the mapping this needs. It is replaced by a check on the rewrite TARGET, refusing only /publish and /consumer -- the two mounts that take unsigned calls. Anything else is the deployment's business, so a mount added later needs no chart release. No ingress controller is named: Kong, ingress-nginx and ALB spell a rewrite differently and the annotations come from values. quick-start does the same job in nginx, but on the individual proxy host rather than server_proxy.conf: that file is included in every server block and the upstream differs per host -- in multi mode /select belongs to provider-adapter and /discover to network-adapter, which no shared rule can express. The shared file keeps what is true everywhere, denying every mount by name. Appendix B carries the per-host rule. Verified: the descriptor advertises /discover again, the adapter answers 401 on /network/discover and /provider/select, 404 on /discover, and the chart accepts a rewrite onto /network/ while refusing one onto /publish.
A rewrite is an annotation on the Ingress and holds one target, so the paths sharing an object must share a destination. Single mode serves /discover from the /network/ mount and /select from /provider/, which are two -- and the chart could only ever emit one Ingress. ingress.routes takes a list and emits one Ingress each, with its own annotations and tls. ingress.annotations/hosts/tls still work and still produce exactly one, so nothing already deployed changes. The guard runs per route rather than once: a path that is not a literal /<action>, or a rewrite targeting /publish or /consumer, fails the render wherever it appears. Exactly one route carries the tls block for a hostname. A TLS Secret is namespaced and cert-manager orders per Ingress, so a second block is a second ACME order for a name already covered -- against a limit of five failed authorizations per hostname per hour. Kong terminates on SNI.
The guard refused any rewrite whose value mentioned publish or consumer, which also refused /network/publish -- the mount that serves the publish action BEHIND validateSign and checkCrossNetworkRequester. The word was never the problem; the mount is. It now works out where a request lands -- the rewrite if there is one, else the path unchanged -- and refuses only when the first segment is /publish or /consumer. So /publish rewritten onto /network/publish is allowed, and /publish with no rewrite is not, which is the case that matters: without one it reaches the mount that signs on behalf of its caller and checks nothing. That makes a public /publish safe to expose. The caller has to sign for itself, with a key the registry publishes. Anything that cannot sign reaches the unsigned mount by port-forward, which is the only way it should be reachable.
The charts had no crawler at all, so a Helm-deployed network could answer another network's crawl but never run one of its own. The network role now carries the catalogcrawler block, with the settings an operator flips coming from values.federation and the rest from placeholders the init container already substitutes -- the signing key and key id are the same ones that sign everything else, so what we send is signed as us. Off by default, and off builds no schedule and never dials: a cluster that federates with nobody stays quiet rather than logging a refused connection on an interval. Answering another network's crawl is unaffected either way; that is the /network/ mount's job. No database. dbDsn is required only by staticIndexUrls, networks or publishPipelines, none of which this uses. A crawled catalog is published to this adapter's own /network/publish, the same path a local publish takes, so nothing leaves the process to do it.
Nothing in the charts served .well-known, so a cluster could neither tell another network who it was nor read anyone else's answer. Federation existed only in quick-start. A Job signs the descriptor, JWKS and schema packs from this network's registry entry and writes them into a ConfigMap; a small server mounts that ConfigMap and answers for exactly those three paths. Kong routes to it. The Job mounts the SAME Secret the adapter reads. oanfed refuses to sign with a key the registry does not publish for this network, so sharing it is what guarantees the descriptor names the key that signs traffic -- a mismatch there surfaces much later, as another network failing to verify anything we send. The private key exists only inside the Job; what reaches the ConfigMap is public. Each run publishes the next revision, read from what the ConfigMap already holds. oanfed refuses a revision that does not increase on the one served, and so does every network holding the old one, so the number cannot come from a value in git that a re-run would repeat. Admissions are values, one Job each, so who we agreed to talk to is reviewable rather than a command somebody ran once. They need no key: admission reads the other network's public documents and writes a row in our registry. oanfed's open_registry imported quick-start's setup.py and read .env, which a Job has neither of. It now takes REGISTRY_URL and BECKN_NETWORK_ID from the environment when both are set and falls back to setup.py otherwise, so the same file runs in both places. The server holds nothing secret and serves nothing else: 404 for any other path, and the Ingress template refuses one outside /.well-known/.
The chart's Participant schema was the pre-federation one. role was a single string, so one participant could not serve several layers -- which is what single-adapter mode is -- and there was no federationRole, revision or projectionTtl at all, so `oanfed admit` would have been refused by the registry it writes to. The federation chart could not have worked against it. SchemaRegistry's schemaUrl pattern allowed only raw.githubusercontent, where a crawled catalog carries the published openagrinet.github.io ref in its @context; a pack URL in that form was refused. Both are now the files quick-start runs, which is where the federation work was proven. ProviderSchema was already identical.
adapterKeys pointed each tier at its own key Secret, from when each tier was its own participant. A network has ONE identity: another network admits a network, not three adapters, and the Network Descriptor names one id and one keyId -- so a tier signing with a different key signs with one that descriptor never mentions, and nothing the other side holds can verify it. All three now default to network-adapter-keys. They remain three settings because a deployment that seeded three participants before the modes merged keeps working by pointing each tier at its own Secret, until those identities are retired.
quick-start dropped Keycloak and reaches the registry without a token; the charts still required one. Same stack, two behaviours. authenticationEnabled already existed but did nothing useful: the chart still demanded keycloak.url and an admin client secret, and still emitted every sunbird_sso_* and OAUTH2_* variable, so turning it off meant standing up a Keycloak nothing talked to. Those requirements and that env block are now conditional on it. registry-seed mints a token to write. It now mints one only when the registry checks one, and omits the Authorization header otherwise -- the seed document carries authenticationEnabled so the Job and the registry cannot disagree. keycloak.url and seedUser.passwordSecret are required only while it is true. What kept the registry private was never the token. It has no Ingress and its own namespace, and Sunbird RC uses POST for reads as well as writes, so no method rule would separate them if it ever were routed. Verified: with auth on the chart emits the same 8 Keycloak variables as before; with it off, none, and neither chart asks for a Keycloak value.
…ok [#72] Choosing what an agreement covers meant opening their schema-packs.json in a browser and copying URLs back into PACKS. `make verify` reported a COUNT and the URL to go and read -- the one command whose job is to tell you about them. verify now lists them. admit with no PACKS lists what that direction can cover and refuses, rather than saying only that one is required -- and the list is theirs for `source`, ours for `requester`, because those are different questions. PACKS=all takes every pack they publish TODAY. A snapshot, not a standing agreement: one that silently widened when they added a pack would not be much of an agreement, so taking in a new one is the same command again. Naming a pack the other side does not publish is still refused.
A requester agreement took its candidates from our own SchemaRegistry rows. A row exists the moment a provider is registered; the other network only learns of it when host-descriptor republishes. So we could agree to give a capability that is not in the document they read -- something they have no way to know to ask for. Measured on a live stack: register a provider, do not republish, and the registry holds two packs where the published document holds one. Both directions now read a DOCUMENT rather than a registry: theirs when we read from them, ours when they ask us. Agreeing to give an unpublished pack is refused, and republishing is what makes it available -- which is the same order the other network sees it in. admit takes --out for where host-descriptor wrote .well-known, and refuses with an explanation when it is absent rather than quietly falling back. The test fixture served one .well-known as both our document and the peer's. They are separate directories now, because the two lists are different questions and conflating them is what hid this.
] A requester agreement covers the packs in the document the other network reads. The admit Job mounted only the scripts, so it had no .well-known at all and would have refused every requester admission -- the right refusal for the wrong reason. It mounts the ConfigMap the publish Job writes, at the path it passes as --out. Optional, so a missing document is oanfed naming the command to run rather than a pod stuck Pending with the reason two layers away; publish is an earlier hook weight, so a normal upgrade does both in order.
Comments across this branch ran to ten, twelve, thirty-five lines where two were allowed. The reasoning belongs in a doc, not above a function, and a newcomer should not have to be told how the rest of the system works before a comment makes sense. Nothing about behaviour changes. Both charts lint, every values file renders, the 38 oanfed tests pass, and a live adapter was re-rendered from the trimmed templates and came back with no restarts. What is left over two lines is mostly a worked example -- an ingress route, an admissions entry, a make command -- where the shape is the explanation.
The split put quick-start files here. They belong on the quick-start branch.
The path split left quick-start files here; they belong on the quick-start branch. charts/federation keeps its own copy of oanfed.py.
Replaying onto fix/agmarknet-token-auth dropped four things that branch had added: the 0.8.0 bump, its changelog, Grievance in the single-adapter step list, and a pack URL pattern that allows a dotted ref. The pattern is now both widenings rather than either.
release-0.2.0 is where the networkId one-or-many and discover pagination live; the beckn tag predates both.
nginx-unprivileged keeps its temp paths and its pid under /tmp. The emptyDirs were at /var/cache/nginx and /var/run, where the privileged image keeps them, so with a read-only root nginx could not start: mkdir() "/tmp/proxy_temp" failed (30: Read-only file system).
A secret volume is owned by root and the Job runs as 101, so defaultMode 0400 left the key unreadable: PermissionError on /keys/signingPrivateKey. fsGroup gives the file a group the container is in, and 0440 lets that group read it.
oanfed appends /api/v1, the adapter's own registry.url already carries it, and the comment said to match the adapter -- so the value was copied across and Sunbird answered 404 reading "api" as a schema name.
The consumer routed discover there and nothing else, so a publish arriving at the consumer had no rule and errored before it reached the mount that answers it.
Only services that export OTLP themselves (network adapter, discovery) reached HyperDX; everything else logged to stdout and nothing collected it. A filelog receiver now reads /var/log/pods on each node, k8sattributes names the owning workload, and that becomes service.name. Chart 0.2.0.
…er and /publish routes
…charts feat: a federation chart and its ingress
manjudr
approved these changes
Nov 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings release-0.2.0 up to date with development.