ecoportal-api 0.10.16 → 0.10.17
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.
Potentially problematic release.
This version of ecoportal-api might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.ai-assistance/.gitignore +2 -0
- data/.ai-assistance/bridge/.gitignore +10 -0
- data/.ai-assistance/bridge/CLAUDE.md +96 -0
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/capabilities/assumptions-log.md +23 -0
- data/.ai-assistance/scripts/bridge-inbox-check.sh +119 -0
- data/.ai-assistance/scripts/bridge-init.sh +86 -0
- data/.ai-assistance/scripts/confine-to-subtree.sh +58 -0
- data/.ai-assistance/scripts/dirty-tree-guard.sh +96 -0
- data/.ai-assistance/scripts/distill_procedural.py +602 -0
- data/.ai-assistance/scripts/log-mcp-access.sh +24 -0
- data/.ai-assistance/scripts/log-skill-usage.sh +79 -0
- data/.ai-assistance/scripts/log_mcp_access.py +158 -0
- data/.ai-assistance/scripts/observe-session.sh +13 -0
- data/.ai-assistance/scripts/observe_session.py +287 -0
- data/.ai-assistance/scripts/protect-host-paths.sh +135 -0
- data/.ai-assistance/scripts/scrub.py +1149 -0
- data/.ai-assistance/scripts/scrub.py.sha256 +6 -0
- data/.ai-assistance/scripts/surface-procedural.sh +9 -0
- data/.ai-assistance/scripts/surface_procedural.py +101 -0
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +519 -0
- data/.ai-assistance/skills/project-self-docs/SKILL.md +259 -0
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +378 -0
- data/.ai-assistance/standards-version.json +12 -0
- data/.ai-assistance/version.json +8 -0
- data/.claude/.gitignore +2 -0
- data/.claude/settings.json +128 -0
- data/CHANGELOG.md +8 -5
- data/CLAUDE.md +95 -71
- data/docs/self-docs/ARCHITECTURE.md +145 -0
- data/docs/self-docs/CHANGES.jsonl +7 -0
- data/docs/self-docs/COMPLIANCE.md +66 -0
- data/docs/self-docs/CONVENTIONS.md +74 -0
- data/docs/self-docs/INTEGRATIONS.md +62 -0
- data/docs/self-docs/OPERATIONS.md +64 -0
- data/docs/self-docs/OVERVIEW.md +61 -0
- data/docs/self-docs/STATUS.md +71 -0
- data/docs/self-docs/self-docs-index.json +51 -0
- data/docs/worklog.md +48 -0
- data/lib/ecoportal/api/common/client/with_retry.rb +6 -0
- data/lib/ecoportal/api/version.rb +1 -1
- metadata +40 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"defaultMode": "acceptEdits",
|
|
4
|
+
"allow": [
|
|
5
|
+
"Bash(bundle exec rspec *)",
|
|
6
|
+
"Bash(bundle exec rspec)",
|
|
7
|
+
"Bash(bundle exec rubocop *)",
|
|
8
|
+
"Bash(bundle exec rubocop)",
|
|
9
|
+
"Bash(git status)",
|
|
10
|
+
"Bash(git log *)",
|
|
11
|
+
"Bash(git diff *)",
|
|
12
|
+
"Bash(git diff)",
|
|
13
|
+
"Bash(git add *)",
|
|
14
|
+
"Bash(git commit *)",
|
|
15
|
+
"Bash(git branch *)",
|
|
16
|
+
"Bash(git checkout *)",
|
|
17
|
+
"Bash(git push -o merge_request.create*)",
|
|
18
|
+
"Bash(git push -u origin *)",
|
|
19
|
+
"Bash(git push origin HEAD*)",
|
|
20
|
+
"Bash(git push *)",
|
|
21
|
+
"Bash(bundle install)",
|
|
22
|
+
"Bash(bundle exec *)",
|
|
23
|
+
"Edit(.ai-assistance/**)",
|
|
24
|
+
"Read(.git/**)",
|
|
25
|
+
"WebFetch(domain:anthropic.com)",
|
|
26
|
+
"WebFetch(domain:docs.anthropic.com)"
|
|
27
|
+
],
|
|
28
|
+
"deny": [
|
|
29
|
+
"Bash(git push --force*)",
|
|
30
|
+
"Bash(git push * --force*)",
|
|
31
|
+
"Bash(git push --force-with-lease*)",
|
|
32
|
+
"Bash(git push * --force-with-lease*)",
|
|
33
|
+
"Bash(git push origin main*)",
|
|
34
|
+
"Bash(git push origin master*)",
|
|
35
|
+
"Bash(git push * origin main*)",
|
|
36
|
+
"Bash(git push * origin master*)",
|
|
37
|
+
"Bash(rm -rf *)",
|
|
38
|
+
"Bash(cat .env*)",
|
|
39
|
+
"Read(.env*)",
|
|
40
|
+
"Read(./secrets/**)",
|
|
41
|
+
"Edit(.env*)",
|
|
42
|
+
"Edit(//System/**)",
|
|
43
|
+
"Edit(//Library/**)",
|
|
44
|
+
"Edit(~/Library/**)",
|
|
45
|
+
"Edit(//usr/**)",
|
|
46
|
+
"Edit(//etc/**)",
|
|
47
|
+
"Edit(//bin/**)",
|
|
48
|
+
"Edit(//sbin/**)",
|
|
49
|
+
"Edit(//boot/**)",
|
|
50
|
+
"Edit(//c/Windows/**)",
|
|
51
|
+
"Edit(//c/Program Files/**)",
|
|
52
|
+
"Edit(//c/Program Files (x86)/**)",
|
|
53
|
+
"Read(~/.ssh/**)",
|
|
54
|
+
"Read(~/.aws/**)"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"hooks": {
|
|
58
|
+
"SessionStart": [
|
|
59
|
+
{
|
|
60
|
+
"matcher": "",
|
|
61
|
+
"hooks": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "bash .ai-assistance/scripts/bridge-init.sh 2>/dev/null || true"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "command",
|
|
68
|
+
"command": "bash .ai-assistance/scripts/bridge-inbox-check.sh 2>/dev/null || true"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "command",
|
|
72
|
+
"command": "bash .ai-assistance/scripts/surface-procedural.sh"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"type": "command",
|
|
76
|
+
"command": "bash .ai-assistance/scripts/dirty-tree-guard.sh 2>/dev/null || true"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"Stop": [
|
|
82
|
+
{
|
|
83
|
+
"matcher": "",
|
|
84
|
+
"hooks": [
|
|
85
|
+
{
|
|
86
|
+
"type": "command",
|
|
87
|
+
"command": "bash .ai-assistance/scripts/observe-session.sh"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "command",
|
|
91
|
+
"command": "bash .ai-assistance/scripts/dirty-tree-guard.sh 2>/dev/null || true"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"PreToolUse": [
|
|
97
|
+
{
|
|
98
|
+
"matcher": "Bash",
|
|
99
|
+
"hooks": [
|
|
100
|
+
{
|
|
101
|
+
"type": "command",
|
|
102
|
+
"command": "bash .ai-assistance/scripts/protect-host-paths.sh"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"matcher": "Skill",
|
|
108
|
+
"hooks": [
|
|
109
|
+
{
|
|
110
|
+
"type": "command",
|
|
111
|
+
"command": "bash .ai-assistance/scripts/log-skill-usage.sh 2>/dev/null || true"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"PostToolUse": [
|
|
117
|
+
{
|
|
118
|
+
"matcher": "mcp__.*",
|
|
119
|
+
"hooks": [
|
|
120
|
+
{
|
|
121
|
+
"type": "command",
|
|
122
|
+
"command": "bash .ai-assistance/scripts/log-mcp-access.sh 2>/dev/null || true"
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
}
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.10.17] - 2026-
|
|
5
|
+
## [0.10.17] - 2026-08-08
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
@@ -10,11 +10,14 @@ All notable changes to this project will be documented in this file.
|
|
|
10
10
|
|
|
11
11
|
### Fixed
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
- `WithRetry` never retried timeouts: `HTTP::TimeoutError` (and so `HTTP::ConnectTimeoutError`)
|
|
14
|
+
is a sibling of `HTTP::ConnectionError` in the `http` gem hierarchy, not a subclass — a
|
|
15
|
+
transient connect/read/write timeout propagated immediately and killed whole batches mid-loop
|
|
16
|
+
(live-confirmed on the Farmers native card, 2026-07-30). Added `HTTP::TimeoutError` to
|
|
17
|
+
`HANDLED_CONNECTION_ERRORS`; applies to all downstream gems that inherit this retry layer
|
|
18
|
+
(`ecoportal-api-v2`, `ecoportal-api-graphql`, `eco-helpers`).
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
## [0.10.16] - 2026-06-05
|
|
18
21
|
|
|
19
22
|
### Fixed
|
|
20
23
|
|
data/CLAUDE.md
CHANGED
|
@@ -1,71 +1,95 @@
|
|
|
1
|
-
# CLAUDE.md — ecoportal-api
|
|
2
|
-
|
|
3
|
-
AI agent instructions for this repository.
|
|
4
|
-
|
|
5
|
-
**Cross-cutting architecture context lives in `ecoportal-api-graphql` — see its `CLAUDE.md` and `.claude/` folder for the full dependency map, project history, and shared skills.**
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- `
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
# CLAUDE.md — ecoportal-api
|
|
2
|
+
|
|
3
|
+
AI agent instructions for this repository.
|
|
4
|
+
|
|
5
|
+
**Cross-cutting architecture context lives in `ecoportal-api-graphql` — see its `CLAUDE.md` and `.claude/` folder for the full dependency map, project history, and shared skills.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Read these first — every session
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
docs/worklog.md <- current state, blockers, open questions
|
|
13
|
+
.ai-assistance/local/paths.json <- local paths to ep-ai-standards + sibling repos (gitignored)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Repository Role
|
|
19
|
+
|
|
20
|
+
`ecoportal-api` is the **foundational REST API gem** in the EcoPortal Ruby stack. It provides authentication, HTTP client infrastructure, org context, and the `Ecoportal::API::Common` namespace that all downstream gems build on.
|
|
21
|
+
|
|
22
|
+
**Position in chain:**
|
|
23
|
+
```
|
|
24
|
+
ecoportal-api ← THIS REPO
|
|
25
|
+
↓
|
|
26
|
+
ecoportal-api-v2
|
|
27
|
+
↓
|
|
28
|
+
ecoportal-api-graphql
|
|
29
|
+
↓
|
|
30
|
+
eco-helpers
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Remote:** https://gitlab.ecoportal.co.nz/ecoportal/ecoportal-api.git
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Key Folder Layout
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
lib/ecoportal/api/
|
|
41
|
+
common/ Shared infrastructure (client, logging, response, batch, hash_diff)
|
|
42
|
+
client/ HTTP client — rate throttling, retries, timeouts, APM, error handling
|
|
43
|
+
errors/ Shared error classes
|
|
44
|
+
internal/ Internal API resources (people, permissions, schema, login providers)
|
|
45
|
+
v1/ REST API v1 resources (people, schema, jobs)
|
|
46
|
+
logger.rb Shared logger
|
|
47
|
+
version.rb Gem version
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Namespace
|
|
53
|
+
|
|
54
|
+
`Ecoportal::API` — specifically:
|
|
55
|
+
- `Ecoportal::API::Common` — shared base classes (consumed by `ecoportal-api-v2` and `ecoportal-api-graphql`)
|
|
56
|
+
- `Ecoportal::API::Internal` — internal API endpoints
|
|
57
|
+
- `Ecoportal::API::V1` — v1 REST endpoints
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Key Concerns
|
|
62
|
+
|
|
63
|
+
- `Common::Client` — base HTTP client with rate throttling, retry logic, timeouts, and APM integration. All downstream clients inherit or wrap this.
|
|
64
|
+
- `Common::BaseModel` / `Common::BaseClass` — base model classes. `ecoportal-api-v2` extends these heavily.
|
|
65
|
+
- Changes to `Common::` affect **all downstream gems** — treat as a public API.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Backwards Compatibility
|
|
70
|
+
|
|
71
|
+
Changes to `Ecoportal::API::Common::*` propagate to `ecoportal-api-v2`, `ecoportal-api-graphql`, and `eco-helpers`. Any breaking change must be coordinated across the chain.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Running Tests
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
bundle install
|
|
79
|
+
bundle exec rspec
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Token economy -- ENFORCED
|
|
85
|
+
|
|
86
|
+
This gem shares one account-wide weekly token ceiling with every other AI-enabled
|
|
87
|
+
repo. Keep sessions short and scoped -- end or reset one when a work item completes.
|
|
88
|
+
Any background/subagent work MUST start from a fresh context (never a fork) with a
|
|
89
|
+
self-contained task card naming an explicit model tier; never let a launched agent
|
|
90
|
+
silently inherit this session's model. Delegated agents COMMIT but MUST NOT push or
|
|
91
|
+
open MRs -- this session verifies independently, then pushes. Before briefing a task
|
|
92
|
+
that depends on an external API, probe its real limits (endpoint, window/page size,
|
|
93
|
+
pagination, scope) first -- do not let the agent discover them mid-task. Batch shell
|
|
94
|
+
commands; avoid per-command round-trips or polling loops.
|
|
95
|
+
Full standard: `standards/workflows/token-economy.md` in ep-ai-standards.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: "1.1"
|
|
3
|
+
repo: "ecoportal-api"
|
|
4
|
+
doc: architecture
|
|
5
|
+
last_generated: "2026-07-23"
|
|
6
|
+
source_head: "69bf8e3"
|
|
7
|
+
review_status: draft
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture -- ecoportal-api
|
|
11
|
+
|
|
12
|
+
**Scope:** High-level structure only. No `.ai-assistance/code/` code-specs exist yet in this repo
|
|
13
|
+
(scanner: `existing_context.code_specs` is empty) -- this section is derived directly from the
|
|
14
|
+
`lib/` tree, not from a code-specs layer.
|
|
15
|
+
|
|
16
|
+
## Top-level structure
|
|
17
|
+
|
|
18
|
+
(From the scanner's `top_level_dirs`.)
|
|
19
|
+
|
|
20
|
+
| Directory | Role |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `lib/` | The gem source (`Ecoportal::API` namespace: `common/`, `internal/`, `v1/`, `errors/`) |
|
|
23
|
+
| `spec/` | RSpec test suite, mirrors the `lib/` namespace layout |
|
|
24
|
+
| `bin/` | `bin/setup`, `bin/console` -- local dev helpers (from `README.md`) |
|
|
25
|
+
| `docs/` | This self-docs set + `docs/worklog.md` (session handoff log) |
|
|
26
|
+
| `.ai-assistance/` | AI tooling scaffold (skills, scripts, bridge, capabilities) -- most-touched
|
|
27
|
+
directory after `lib/` in the last 50 commits (scanner `activity_clusters`: `.ai-assistance` 27
|
|
28
|
+
touches vs `lib` 28) |
|
|
29
|
+
| `.claude/` | Committed Claude Code settings (`settings.json`: permissions + hooks) |
|
|
30
|
+
| `.ruby-lsp` | Ruby LSP cache/config directory |
|
|
31
|
+
|
|
32
|
+
## Key components
|
|
33
|
+
|
|
34
|
+
- `Ecoportal::API::Common` (`lib/ecoportal/api/common.rb` + `common/`) -- shared base classes and
|
|
35
|
+
infrastructure consumed by every downstream gem:
|
|
36
|
+
- `Common::Client` (`common/client.rb`) -- the HTTP client (wraps the `http` gem), with
|
|
37
|
+
sub-modules for rate throttling (`client/rate_throttling.rb`, `client/throughput*.rb`),
|
|
38
|
+
retries (`client/with_retry.rb`), timeouts (`client/time_out.rb`), error handling
|
|
39
|
+
(`client/error*.rb`), and optional Elastic APM reporting (`client/elastic_apm_integration.rb`).
|
|
40
|
+
- `Common::BaseModel` / `Common::BaseClass` (`common/base_model.rb`, `common/base_class.rb`) --
|
|
41
|
+
base model classes; `ecoportal-api-v2` extends these heavily (per `CLAUDE.md`).
|
|
42
|
+
- `Common::Response` / `Common::WrappedResponse`, `Common::BatchOperation` /
|
|
43
|
+
`Common::BatchResponse`, `Common::HashDiff`, `Common::Logging`, `Common::DocHelpers`.
|
|
44
|
+
- `Ecoportal::API::Internal` (`lib/ecoportal/api/internal/`) -- internal API resources: people,
|
|
45
|
+
person/person_details/person_schema(s), permissions, policy groups, login providers, preferences,
|
|
46
|
+
schema fields, account.
|
|
47
|
+
- `Ecoportal::API::V1` (`lib/ecoportal/api/v1/`) -- v1 REST resources: people, person(s),
|
|
48
|
+
person_schema(s), schema_field(s), and async jobs (`v1/job.rb`, `job/awaiter*`, `job/status.rb`).
|
|
49
|
+
- `Ecoportal::API::Errors` (`lib/ecoportal/api/errors/`) -- shared error base + timeout error
|
|
50
|
+
classes.
|
|
51
|
+
|
|
52
|
+
(Evidence: `CLAUDE.md` "Key Folder Layout"/"Key Concerns", cross-checked against the actual
|
|
53
|
+
`lib/` file listing.)
|
|
54
|
+
|
|
55
|
+
## How they fit
|
|
56
|
+
|
|
57
|
+
A consumer requires `ecoportal/api`, constructs a `Common::Client` with an API key/host/version,
|
|
58
|
+
and calls into `Internal::*` or `V1::*` resource classes, which use `Common::BaseModel`/
|
|
59
|
+
`Common::BaseClass` for their data shape and go through `Common::Client` for all HTTP traffic
|
|
60
|
+
(retry/throttle/timeout/APM are cross-cutting concerns applied at the client layer, not per
|
|
61
|
+
resource). `Common::Response`/`WrappedResponse` normalise API responses back into Ruby objects for
|
|
62
|
+
the resource classes.
|
|
63
|
+
|
|
64
|
+
## Entry points
|
|
65
|
+
|
|
66
|
+
- `lib/ecoportal/api.rb` -- the gem root; `require 'dotenv/load'` runs on require, so a `.env` file
|
|
67
|
+
in the consuming process is picked up automatically.
|
|
68
|
+
- `bin/console` -- interactive `pry`/IRB console for local experimentation (`README.md`).
|
|
69
|
+
- `bin/setup` -- installs dependencies for local development.
|
|
70
|
+
- There is no server/CLI/Lambda entry point -- this is a library, not a standalone service
|
|
71
|
+
(no `cdk.json`, `Dockerfile`, `manifest.json`, or `config/application.rb` found at the repo
|
|
72
|
+
root).
|
|
73
|
+
|
|
74
|
+
## Diagrams
|
|
75
|
+
|
|
76
|
+
Only one diagram category is evidenced in this repo. The CI/CD pipeline graph, the privacy
|
|
77
|
+
data-flow diagram, and the deployment-topology diagram are each OMITTED rather than fabricated,
|
|
78
|
+
per the rule "2 real diagrams beat 4 where two are invented" (here: 1 real diagram beats 4 where 3
|
|
79
|
+
would be invented):
|
|
80
|
+
|
|
81
|
+
- **CI/CD pipeline graph:** omitted -- no `.gitlab-ci.yml`, `Jenkinsfile`, or `.github/workflows/`
|
|
82
|
+
found in this repo (scanner `structure.ci_files` is empty). unknown -- needs owner input on
|
|
83
|
+
whether CI runs from an external/inherited GitLab CI template not present as a file in this
|
|
84
|
+
working tree.
|
|
85
|
+
- **Data-flow (privacy-relevant paths):** omitted -- no scrub/PII-transform module exists in
|
|
86
|
+
`lib/`; the gem's own `.ai-assistance/scripts/scrub.py` is AI-tooling-session scrubbing, not a
|
|
87
|
+
data path this gem's runtime code implements. See `COMPLIANCE.md`.
|
|
88
|
+
- **Deployment topology:** omitted -- no CDK/Terraform/Dockerfile/docker-compose file exists in
|
|
89
|
+
this repo (it is a library gem, not a deployed service).
|
|
90
|
+
|
|
91
|
+
### Component / layer diagram
|
|
92
|
+
|
|
93
|
+
The client (`Common::Client`) is the single HTTP boundary; everything above it is resource/model
|
|
94
|
+
code that never talks HTTP directly. APM reporting is an optional side-channel off the client,
|
|
95
|
+
gated by `ElasticAPM.running?` and a rescued `StandardError` (fails open to "APM disabled", not to
|
|
96
|
+
a crash) -- see `client/elastic_apm_integration.rb`.
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
flowchart TD
|
|
100
|
+
subgraph Consumers["Downstream gems (ecoportal-api-v2, ecoportal-api-graphql, eco-helpers)"]
|
|
101
|
+
C[Consumer code]
|
|
102
|
+
end
|
|
103
|
+
subgraph Resources["Ecoportal::API::Internal / V1 resources"]
|
|
104
|
+
R[Internal::* and V1::* resource classes]
|
|
105
|
+
end
|
|
106
|
+
subgraph CommonLayer["Ecoportal::API::Common"]
|
|
107
|
+
BM[BaseModel / BaseClass]
|
|
108
|
+
CL[Client]
|
|
109
|
+
RT[RateThrottling / Throughput]
|
|
110
|
+
WR[WithRetry]
|
|
111
|
+
TO[TimeOut]
|
|
112
|
+
RESP[Response / WrappedResponse]
|
|
113
|
+
end
|
|
114
|
+
EXT[["ecoPortal API (live.ecoportal.com)"]]
|
|
115
|
+
APM[["Elastic APM (Elastic Cloud, optional)"]]
|
|
116
|
+
|
|
117
|
+
C --> R
|
|
118
|
+
R --> BM
|
|
119
|
+
R --> CL
|
|
120
|
+
CL --> RT
|
|
121
|
+
CL --> WR
|
|
122
|
+
CL --> TO
|
|
123
|
+
CL --> RESP
|
|
124
|
+
CL -->|HTTPS, api_key| EXT
|
|
125
|
+
CL -.->|error telemetry, opt-in| APM
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Sources: `lib/ecoportal/api/common/client.rb`, `lib/ecoportal/api/common/client/*.rb`,
|
|
129
|
+
`lib/ecoportal/api/common/base_model.rb`, `lib/ecoportal/api/internal/`, `lib/ecoportal/api/v1/`,
|
|
130
|
+
`CLAUDE.md`.
|
|
131
|
+
|
|
132
|
+
## Build / test tooling
|
|
133
|
+
|
|
134
|
+
- Build: Bundler/RubyGems (`Rakefile` -- `bundler/gem_tasks`; `bundle exec rake install` /
|
|
135
|
+
`rake release` per `README.md`).
|
|
136
|
+
- Tests: RSpec (`Rakefile` -- `RSpec::Core::RakeTask`; run via `bundle exec rspec` per `CLAUDE.md`
|
|
137
|
+
and `README.md`'s `rake spec`). Lint: RuboCop (`RuboCop::RakeTask`, `.rubocop.yml`). Docs: YARD
|
|
138
|
+
(`YARD::Rake::YardocTask`, `.yardopts`). `rake` (no args) runs RuboCop then RSpec.
|
|
139
|
+
- CI: none found in this repo (scanner `structure.ci_files` is empty; see "Diagrams" above).
|
|
140
|
+
|
|
141
|
+
## Links to detailed docs
|
|
142
|
+
|
|
143
|
+
None yet -- `.ai-assistance/code/` has no code-specs for this repo (scanner:
|
|
144
|
+
`existing_context.code_specs` is empty). Running the `code-specs` skill would give this section
|
|
145
|
+
real per-area links instead of this note.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/OVERVIEW.md", "change": "created", "from_hash": null, "to_hash": "a819fa9d200c7ab7"}
|
|
2
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/ARCHITECTURE.md", "change": "created", "from_hash": null, "to_hash": "49a7df216c5bdc07"}
|
|
3
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/CONVENTIONS.md", "change": "created", "from_hash": null, "to_hash": "f45075d3f3ca043c"}
|
|
4
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/INTEGRATIONS.md", "change": "created", "from_hash": null, "to_hash": "b5cb184b9e829040"}
|
|
5
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/STATUS.md", "change": "created", "from_hash": null, "to_hash": "147657c19c13c34e"}
|
|
6
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/COMPLIANCE.md", "change": "created", "from_hash": null, "to_hash": "802e24afbd20f486"}
|
|
7
|
+
{"ts": "2026-07-23T10:50:08Z", "file": "docs/self-docs/OPERATIONS.md", "change": "created", "from_hash": null, "to_hash": "475c09e0348a9187"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: "1.1"
|
|
3
|
+
repo: "ecoportal-api"
|
|
4
|
+
doc: compliance
|
|
5
|
+
last_generated: "2026-07-23"
|
|
6
|
+
source_head: "69bf8e3"
|
|
7
|
+
review_status: draft # audit answers MUST come from 'reviewed' docs only -- a human sets this
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Compliance -- ecoportal-api
|
|
11
|
+
|
|
12
|
+
**Purpose:** the audit / privacy / ISO-27001 surface for this project. It lets ROVO (over the EP Projects
|
|
13
|
+
Register) answer compliance questions across the fleet. **Trust rule:** everything here separates
|
|
14
|
+
EVIDENCE (a fact or link) from INTENT (planned); a reviewer must set `review_status: reviewed` before an
|
|
15
|
+
answer built on this doc is used for audit or re-certification -- a confident answer on unreviewed content
|
|
16
|
+
would mislead an auditor.
|
|
17
|
+
|
|
18
|
+
## ISO 27001
|
|
19
|
+
|
|
20
|
+
| Control area | Status (evidence / planned) | Evidence link |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Access control (credential handling) | EVIDENCE (partial) | `api_key` passed at `Client.new` call site, sent as `X-ApiKey` header (`lib/ecoportal/api/common/client.rb`); the key's storage/rotation is the CONSUMER's responsibility, not this gem's -- this gem does not persist credentials |
|
|
23
|
+
| Secure engineering (lint/test gate) | EVIDENCE (partial) | `.rubocop.yml`, RSpec suite in `spec/`, `rake` default task runs RuboCop then RSpec (`Rakefile`) -- but no CI file enforces this automatically pre-merge (see `ARCHITECTURE.md` Diagrams section); PLANNED/unknown whether merge is gated on these passing |
|
|
24
|
+
| Dependency management | EVIDENCE | `ecoportal-api.gemspec` pins runtime deps with version constraints; `Gemfile.lock` exists but is gitignored (`.gitignore`: "it's a gem, ignore the lockfile") |
|
|
25
|
+
| Third-party telemetry vendor review | unknown -- needs owner input | Elastic APM is used (see below); whether it has been through a formal vendor/DPA review is not evidenced in this repo |
|
|
26
|
+
| Incident/error monitoring | EVIDENCE (partial) | `ElasticApmIntegration` reports `UnexpectedServerError`s only, fails open/silent on APM-unavailable (`client/elastic_apm_integration.rb`) |
|
|
27
|
+
|
|
28
|
+
## Data handled
|
|
29
|
+
|
|
30
|
+
| Data class (none/team/customer/PII) | What / where | Treatment + retention | Exposed? (how) |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| Customer/employee data (potentially PII) | Person records (`Internal::Person`, `V1::Person`, schema fields) -- this gem transports them between the consuming application and the ecoPortal API; it does not persist them itself (no database/store in this repo) | In-memory only within this gem's process lifetime; retention is entirely the CONSUMER application's responsibility, not this gem's | Not exposed by this gem directly -- exposure depends on what the consuming application does with the returned model objects. unknown -- needs owner input on which consumers log/persist full response bodies |
|
|
33
|
+
| APM telemetry | Error class/status metadata sent to Elastic Cloud on unexpected server errors (`transaction_sample_rate: 0.1`) | Elastic Cloud-side retention -- unknown -- needs owner input (governed by ecoPortal's Elastic Cloud account settings, not this repo) | Sent to Elastic Cloud (`https://<account>.apm.<region>.aws.cloud.es.io`), see Vendors below |
|
|
34
|
+
|
|
35
|
+
## Third-party vendors in the data path
|
|
36
|
+
|
|
37
|
+
| Vendor | What data | Disclosed to end users? | Notes |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| Elastic (Elastic Cloud / Elastic APM) | Error/transaction telemetry, `transaction_sample_rate: 0.1`, only for unexpected server error responses; scoped by `ELASTIC_APM_*` env vars, defaults to `ap-southeast-2` region | unknown -- needs owner input | Optional: disables itself silently (`rescue StandardError`) if unavailable or unconfigured -- does not fail the host application (`elastic_apm_integration.rb`) |
|
|
40
|
+
| RubyGems.org | Gem package (source code, no runtime customer data) at release time | N/A -- public package registry | `rubygems_mfa_required: true` set in gemspec |
|
|
41
|
+
|
|
42
|
+
No other outbound third-party vendor call was found in `lib/` (only `http`, `dotenv`,
|
|
43
|
+
`elastic-apm`, `rate_throttle_client` are runtime dependencies per the gemspec).
|
|
44
|
+
|
|
45
|
+
## PII / data-leak controls
|
|
46
|
+
|
|
47
|
+
- No PII-scrubbing module exists in this gem's runtime code (`lib/`) -- this gem is a thin HTTP
|
|
48
|
+
client/model layer; it does not implement scrubbing because it is not itself an ingestion/storage
|
|
49
|
+
system. Responsibility for PII handling sits with the consuming application. This is an
|
|
50
|
+
observation of current reality, not a judgement that it should change -- unknown -- needs owner
|
|
51
|
+
input on whether that responsibility split is the intended design or a gap.
|
|
52
|
+
- `.ai-assistance/scripts/scrub.py` exists in this repo, but it scrubs AI-tooling session
|
|
53
|
+
artefacts (per the ecoPortal AI-standards convention), not this gem's runtime data path -- do not
|
|
54
|
+
conflate the two.
|
|
55
|
+
- Credential handling: the `api_key` is passed in by the caller at construction time and is not
|
|
56
|
+
logged by default (`Common::Logging` was not audited line-by-line in this run for redaction
|
|
57
|
+
behaviour) -- unknown -- needs owner input on whether `api_key` can ever land in a log line.
|
|
58
|
+
|
|
59
|
+
## AI-generated content
|
|
60
|
+
|
|
61
|
+
- Externally-exposed AI content: no -- this is a code library with no output surface that presents
|
|
62
|
+
AI-generated content to end users.
|
|
63
|
+
- Marked as AI-generated: N/A.
|
|
64
|
+
- Review process: N/A. This self-docs set ITSELF is AI-generated (by the `project-self-docs`
|
|
65
|
+
skill) and carries `review_status: draft` until a human reviews it -- see this file's own
|
|
66
|
+
frontmatter.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: "1.1"
|
|
3
|
+
repo: "ecoportal-api"
|
|
4
|
+
doc: conventions
|
|
5
|
+
last_generated: "2026-07-23"
|
|
6
|
+
source_head: "69bf8e3"
|
|
7
|
+
review_status: draft
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Conventions -- ecoportal-api
|
|
11
|
+
|
|
12
|
+
**Source of truth:** `.rubocop.yml` (root, plus `spec/.rubocop.yml` for the spec directory),
|
|
13
|
+
`.editorconfig` not found, `.solargraph.yml`/`.ruby-lsp/` (editor tooling, not style rules). No
|
|
14
|
+
`ai-discovery` `conventions.md` exists yet in `.ai-assistance/local/` (scanner:
|
|
15
|
+
`existing_context.conventions` is `null`) -- this doc is derived directly from `.rubocop.yml` and
|
|
16
|
+
observed git history instead.
|
|
17
|
+
|
|
18
|
+
## Code style
|
|
19
|
+
|
|
20
|
+
(From `.rubocop.yml`, root.)
|
|
21
|
+
|
|
22
|
+
- Target Ruby version: 3.2 (`AllCops.TargetRubyVersion: 3.2`; gemspec requires `>= 3.2.2`)
|
|
23
|
+
- `NewCops: enable` -- new RuboCop cops are opted in by default
|
|
24
|
+
- Notable size limits: `Metrics/ClassLength` max 500, `Metrics/ModuleLength` max 300,
|
|
25
|
+
`Metrics/MethodLength` max 50, `Metrics/BlockLength` max 50 (heredoc/array/method_call each
|
|
26
|
+
count as one), `Metrics/AbcSize` max 30, `Metrics/ParameterLists` max 5,
|
|
27
|
+
`Metrics/CyclomaticComplexity`/`Metrics/PerceivedComplexity` max 30
|
|
28
|
+
- `Style/HashSyntax`: `no_mixed_keys`, shorthand allowed either way
|
|
29
|
+
- `Style/ClassAndModuleChildren`: disabled (compact nested-namespace style, e.g.
|
|
30
|
+
`class Ecoportal::API::Foo`, is allowed alongside nested `module`/`class` blocks)
|
|
31
|
+
- Indentation/quotes/line-length: unknown -- needs owner input; not asserted by `.rubocop.yml`'s
|
|
32
|
+
cop list read so far and no `.editorconfig`/`.rubocop.yml` `Layout/*` overrides were inspected
|
|
33
|
+
beyond what is quoted above -- do not infer from a single file without checking further.
|
|
34
|
+
|
|
35
|
+
## Branch naming
|
|
36
|
+
|
|
37
|
+
Observed from the local + remote branch list (`git branch -a`): `feat/<slug>`, `fix/<slug>`,
|
|
38
|
+
`chore/<slug>`, `auto/<slug>` prefixes (e.g. `feat/deploy-project-self-docs`,
|
|
39
|
+
`fix/migration-0013-scrub-settings-hygiene`, `chore/ai-standards-alignment-1.9.1`,
|
|
40
|
+
`auto/claude-md-startup-reads`). This matches the `type/slug` convention used across the
|
|
41
|
+
ecoPortal AI-tooling fleet (see `ep-ai-standards` branching model). Numeric-only branches also
|
|
42
|
+
exist from pre-AI-tooling history (e.g. `remotes/origin/19-ecoportal-api-common-basemodel-...`) --
|
|
43
|
+
treat those as legacy, not the current convention.
|
|
44
|
+
|
|
45
|
+
## Commit message style
|
|
46
|
+
|
|
47
|
+
Observed from `git log --oneline -20`: `type: message` or `type(scope): message`, e.g.
|
|
48
|
+
`fix: migration 0013 -- canonical scrub.py + kit refresh + settings hygiene`,
|
|
49
|
+
`chore(ai): confirm alignment with ep-ai-standards v1.9.1`,
|
|
50
|
+
`feat(ai): deploy project-self-docs skill (self-documentation, run in-repo)`. Feature-branch work
|
|
51
|
+
merges via GitLab merge commits (`Merge branch '<branch>' into 'master'`).
|
|
52
|
+
|
|
53
|
+
## Test conventions
|
|
54
|
+
|
|
55
|
+
- Runner: RSpec (`bundle exec rspec`, `.rspec` config file, `.rspec_status` present for
|
|
56
|
+
failure-tracking -- gitignored per `.gitignore`).
|
|
57
|
+
- Placement: `spec/` mirrors the `lib/` namespace (e.g. `lib/ecoportal/api/internal/person.rb` ->
|
|
58
|
+
`spec/ecoportal/api/internal/person_*_spec.rb`); fixture JSON files sit alongside their specs
|
|
59
|
+
(e.g. `spec/ecoportal/api/internal/person_employee.json`).
|
|
60
|
+
- Coverage expectations: unknown -- needs owner input; no coverage tool/threshold config
|
|
61
|
+
(e.g. SimpleCov) was found in the files inspected.
|
|
62
|
+
- `.rubocop.yml` in `spec/` scopes lint rules separately for specs (file present but not read in
|
|
63
|
+
detail here -- unknown -- needs owner input on spec-specific cop overrides).
|
|
64
|
+
|
|
65
|
+
## Project-specific rules
|
|
66
|
+
|
|
67
|
+
From `CLAUDE.md`:
|
|
68
|
+
|
|
69
|
+
- Changes to `Ecoportal::API::Common::*` are treated as a public API -- they propagate to
|
|
70
|
+
`ecoportal-api-v2`, `ecoportal-api-graphql`, and `eco-helpers`; any breaking change must be
|
|
71
|
+
coordinated across that chain, not made unilaterally in this repo.
|
|
72
|
+
- Cross-cutting architecture/dependency context for the wider gem chain lives in
|
|
73
|
+
`ecoportal-api-graphql`'s `CLAUDE.md` and `.claude/` folder, not duplicated here.
|
|
74
|
+
- `docs/worklog.md` is the required first read every AI session (session handoff log).
|