matrx-seo 0.1.0__tar.gz
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.
- matrx_seo-0.1.0/.gitignore +271 -0
- matrx_seo-0.1.0/CLAUDE.md +32 -0
- matrx_seo-0.1.0/DEPLOY.md +165 -0
- matrx_seo-0.1.0/Dockerfile +20 -0
- matrx_seo-0.1.0/INDEPENDENCE.md +6 -0
- matrx_seo-0.1.0/PKG-INFO +25 -0
- matrx_seo-0.1.0/README.md +7 -0
- matrx_seo-0.1.0/deploy.sh +88 -0
- matrx_seo-0.1.0/matrx_seo/FEATURE.md +148 -0
- matrx_seo-0.1.0/matrx_seo/__init__.py +97 -0
- matrx_seo-0.1.0/matrx_seo/adapters.py +82 -0
- matrx_seo-0.1.0/matrx_seo/config.py +85 -0
- matrx_seo-0.1.0/matrx_seo/contracts.py +501 -0
- matrx_seo-0.1.0/matrx_seo/db/__init__.py +94 -0
- matrx_seo-0.1.0/matrx_seo/db/db_requirements.py +17 -0
- matrx_seo-0.1.0/matrx_seo/db/grants.yaml +28 -0
- matrx_seo-0.1.0/matrx_seo/db/models_host.py +85 -0
- matrx_seo-0.1.0/matrx_seo/db/models_seo.py +701 -0
- matrx_seo-0.1.0/matrx_seo/fakes.py +90 -0
- matrx_seo-0.1.0/matrx_seo/identity.py +51 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721095300_seo_foundation.sql +682 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721123000_seo_snapshot_and_binding_followup.sql +88 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721131500_seo_collection_tasks_and_leases.sql +97 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721134500_seo_collection_operation.sql +17 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721143000_seo_collection_receipt.sql +7 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721150000_seo_provider_call_ledger.sql +72 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721160000_search_performance_dimensions.sql +26 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721163327_remove_noncanonical_seo_project_schema.sql +107 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721170000_web_analytics_ga4_metrics.sql +15 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721170100_web_analytics_history_identity.sql +14 -0
- matrx_seo-0.1.0/matrx_seo/migrations/20260721190000_seo_canonical_rebuild.sql +479 -0
- matrx_seo-0.1.0/matrx_seo/orm_identity.py +244 -0
- matrx_seo-0.1.0/matrx_seo/orm_repository.py +612 -0
- matrx_seo-0.1.0/matrx_seo/pagespeed_history.py +197 -0
- matrx_seo-0.1.0/matrx_seo/providers/FEATURE.md +216 -0
- matrx_seo-0.1.0/matrx_seo/providers/__init__.py +101 -0
- matrx_seo-0.1.0/matrx_seo/providers/bing_webmaster.py +1004 -0
- matrx_seo-0.1.0/matrx_seo/providers/brave.py +472 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/__init__.py +24 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/adapter.py +277 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/client.py +883 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/contracts.py +115 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/operations.py +353 -0
- matrx_seo-0.1.0/matrx_seo/providers/dataforseo/transport.py +203 -0
- matrx_seo-0.1.0/matrx_seo/providers/ga4.py +973 -0
- matrx_seo-0.1.0/matrx_seo/providers/gsc.py +653 -0
- matrx_seo-0.1.0/matrx_seo/providers/pagespeed.py +586 -0
- matrx_seo-0.1.0/matrx_seo/providers/serpapi.py +704 -0
- matrx_seo-0.1.0/matrx_seo/rank_matching.py +89 -0
- matrx_seo-0.1.0/matrx_seo/repository.py +323 -0
- matrx_seo-0.1.0/matrx_seo/service.py +153 -0
- matrx_seo-0.1.0/matrx_seo/standalone/__init__.py +3 -0
- matrx_seo-0.1.0/matrx_seo/standalone/app.py +421 -0
- matrx_seo-0.1.0/pyproject.toml +42 -0
- matrx_seo-0.1.0/scripts/independence_gate.sh +72 -0
- matrx_seo-0.1.0/tests/conftest.py +126 -0
- matrx_seo-0.1.0/tests/fixtures/bing_webmaster_performance.json +24 -0
- matrx_seo-0.1.0/tests/fixtures/brave_web_rank_pages.json +51 -0
- matrx_seo-0.1.0/tests/fixtures/ga4/metadata.json +11 -0
- matrx_seo-0.1.0/tests/fixtures/ga4/report_page_1.json +52 -0
- matrx_seo-0.1.0/tests/fixtures/ga4/report_page_2.json +52 -0
- matrx_seo-0.1.0/tests/fixtures/gsc_search_analytics_pages.json +33 -0
- matrx_seo-0.1.0/tests/fixtures/pagespeed/mobile_full.json +138 -0
- matrx_seo-0.1.0/tests/fixtures/serpapi/google_rank_austin.json +76 -0
- matrx_seo-0.1.0/tests/test_bing_webmaster.py +468 -0
- matrx_seo-0.1.0/tests/test_bing_webmaster_live.py +6 -0
- matrx_seo-0.1.0/tests/test_brave_live.py +83 -0
- matrx_seo-0.1.0/tests/test_brave_rank_adapter.py +385 -0
- matrx_seo-0.1.0/tests/test_collection_service.py +502 -0
- matrx_seo-0.1.0/tests/test_dataforseo.py +895 -0
- matrx_seo-0.1.0/tests/test_dataforseo_live.py +49 -0
- matrx_seo-0.1.0/tests/test_ga4_live.py +6 -0
- matrx_seo-0.1.0/tests/test_ga4_provider.py +463 -0
- matrx_seo-0.1.0/tests/test_gsc_live.py +6 -0
- matrx_seo-0.1.0/tests/test_gsc_provider.py +408 -0
- matrx_seo-0.1.0/tests/test_independence.py +29 -0
- matrx_seo-0.1.0/tests/test_orm_repository_live.py +339 -0
- matrx_seo-0.1.0/tests/test_pagespeed_live.py +72 -0
- matrx_seo-0.1.0/tests/test_pagespeed_provider.py +460 -0
- matrx_seo-0.1.0/tests/test_rank_matching.py +50 -0
- matrx_seo-0.1.0/tests/test_serpapi_live.py +97 -0
- matrx_seo-0.1.0/tests/test_serpapi_provider.py +348 -0
- matrx_seo-0.1.0/tests/test_standalone_boot.py +89 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
*.pyc
|
|
2
|
+
secrets/
|
|
3
|
+
ignore/
|
|
4
|
+
temp/
|
|
5
|
+
logs/
|
|
6
|
+
# The broad `logs/` rule above is for RUNTIME log output, but it also matched
|
|
7
|
+
# the dashboard's SOURCE directory and silently swallowed an entire feature's
|
|
8
|
+
# files (only the pre-existing index.tsx stayed tracked), breaking the prod
|
|
9
|
+
# Docker build with "Could not resolve ./structured-tab". Re-include the source.
|
|
10
|
+
!apps/dashboard/src/features/logs/
|
|
11
|
+
!apps/dashboard/src/features/logs/**
|
|
12
|
+
todo
|
|
13
|
+
text_notes/
|
|
14
|
+
aidream/secrets/2.env
|
|
15
|
+
automation_matrix/matrix_processing/temp/*
|
|
16
|
+
cd
|
|
17
|
+
# Byte-compiled / optimized / DLL files
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*$py.class
|
|
21
|
+
|
|
22
|
+
# C extensions
|
|
23
|
+
*.so
|
|
24
|
+
.venv/
|
|
25
|
+
|
|
26
|
+
# Distribution / packaging
|
|
27
|
+
.Python
|
|
28
|
+
build/
|
|
29
|
+
develop-eggs/
|
|
30
|
+
dist/
|
|
31
|
+
downloads/
|
|
32
|
+
eggs/
|
|
33
|
+
.eggs/
|
|
34
|
+
lib/
|
|
35
|
+
lib64/
|
|
36
|
+
# The blanket lib/ rule above is from the standard Python .gitignore template
|
|
37
|
+
# and was silently swallowing TS source under the SPA `src/lib/` folders.
|
|
38
|
+
# Re-allow them explicitly so frontend builds don't ship without their lib layer.
|
|
39
|
+
!apps/dashboard/src/lib/
|
|
40
|
+
!apps/dashboard/src/lib/**
|
|
41
|
+
!apps/workflow-studio/src/lib/
|
|
42
|
+
!apps/workflow-studio/src/lib/**
|
|
43
|
+
parts/
|
|
44
|
+
sdist/
|
|
45
|
+
var/
|
|
46
|
+
wheels/
|
|
47
|
+
share/python-wheels/
|
|
48
|
+
*.egg-info/
|
|
49
|
+
.installed.cfg
|
|
50
|
+
*.egg
|
|
51
|
+
MANIFEST
|
|
52
|
+
|
|
53
|
+
# PyInstaller
|
|
54
|
+
# Usually these files are written by a python script from a template
|
|
55
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
56
|
+
*.manifest
|
|
57
|
+
*.spec
|
|
58
|
+
|
|
59
|
+
# Installer logs
|
|
60
|
+
pip-log.txt
|
|
61
|
+
pip-delete-this-directory.txt
|
|
62
|
+
|
|
63
|
+
# Unit test / coverage reports
|
|
64
|
+
ai/tests/clean_response.json
|
|
65
|
+
ai/tests/cx_storage_response.json
|
|
66
|
+
ai/tests/execution_test.py
|
|
67
|
+
ai/tests/final_response.json
|
|
68
|
+
htmlcov/
|
|
69
|
+
.tox/
|
|
70
|
+
.nox/
|
|
71
|
+
.coverage
|
|
72
|
+
.coverage.*
|
|
73
|
+
.cache
|
|
74
|
+
nosetests.xml
|
|
75
|
+
coverage.xml
|
|
76
|
+
*.cover
|
|
77
|
+
*.py,cover
|
|
78
|
+
.hypothesis/
|
|
79
|
+
.pytest_cache/
|
|
80
|
+
cover/
|
|
81
|
+
|
|
82
|
+
# Translations
|
|
83
|
+
*.mo
|
|
84
|
+
*.pot
|
|
85
|
+
|
|
86
|
+
# Django stuff:
|
|
87
|
+
*.log
|
|
88
|
+
local_settings.py
|
|
89
|
+
db.sqlite3
|
|
90
|
+
db.sqlite3-journal
|
|
91
|
+
|
|
92
|
+
# Flask stuff:
|
|
93
|
+
instance/
|
|
94
|
+
.webassets-cache
|
|
95
|
+
|
|
96
|
+
# Scrapy stuff:
|
|
97
|
+
.scrapy
|
|
98
|
+
|
|
99
|
+
# Sphinx documentation
|
|
100
|
+
docs/_build/
|
|
101
|
+
|
|
102
|
+
# PyBuilder
|
|
103
|
+
.pybuilder/
|
|
104
|
+
target/
|
|
105
|
+
|
|
106
|
+
# Jupyter Notebook
|
|
107
|
+
.ipynb_checkpoints
|
|
108
|
+
|
|
109
|
+
# IPython
|
|
110
|
+
profile_default/
|
|
111
|
+
ipython_config.py
|
|
112
|
+
|
|
113
|
+
# pyenv
|
|
114
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
115
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
116
|
+
# .python-version
|
|
117
|
+
|
|
118
|
+
# pipenv
|
|
119
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
120
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
121
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
122
|
+
# install all needed dependencies.
|
|
123
|
+
#Pipfile.lock
|
|
124
|
+
|
|
125
|
+
# poetry
|
|
126
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
127
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
128
|
+
# commonly ignored for libraries.
|
|
129
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
130
|
+
|
|
131
|
+
# pdm
|
|
132
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
133
|
+
#pdm.lock
|
|
134
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
135
|
+
# in version control.
|
|
136
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
137
|
+
.pdm.toml
|
|
138
|
+
|
|
139
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
140
|
+
__pypackages__/
|
|
141
|
+
|
|
142
|
+
# Celery stuff
|
|
143
|
+
celerybeat-schedule
|
|
144
|
+
celerybeat.pid
|
|
145
|
+
|
|
146
|
+
# SageMath parsed files
|
|
147
|
+
*.sage.py
|
|
148
|
+
|
|
149
|
+
# Environments
|
|
150
|
+
.env
|
|
151
|
+
.env_remote
|
|
152
|
+
.venv
|
|
153
|
+
env/
|
|
154
|
+
venv/
|
|
155
|
+
ENV/
|
|
156
|
+
env.bak/
|
|
157
|
+
venv.bak/
|
|
158
|
+
.env.armanonly
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# random armani files
|
|
179
|
+
/armani_dev/secrets/
|
|
180
|
+
/armani/
|
|
181
|
+
/_armani/
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# pytype static type analyzer
|
|
186
|
+
.pytype/
|
|
187
|
+
|
|
188
|
+
# Cython debug symbols
|
|
189
|
+
cython_debug/
|
|
190
|
+
|
|
191
|
+
.idea/
|
|
192
|
+
.vscode/
|
|
193
|
+
/node_modules/
|
|
194
|
+
|
|
195
|
+
# Frontend pnpm workspace (apps/) — node_modules at the workspace root and any
|
|
196
|
+
# member, plus Vite caches and build output. The unified lockfile (apps/pnpm-lock.yaml)
|
|
197
|
+
# IS committed; everything below is regenerated.
|
|
198
|
+
node_modules/
|
|
199
|
+
apps/**/.vite/
|
|
200
|
+
apps/**/dist/
|
|
201
|
+
.vite/
|
|
202
|
+
|
|
203
|
+
dump.rdb
|
|
204
|
+
|
|
205
|
+
frontend/
|
|
206
|
+
|
|
207
|
+
# AME Temp Files and directory structure
|
|
208
|
+
# Ignore all files in the temp directory and its subdirectories
|
|
209
|
+
/temp/**/*
|
|
210
|
+
/tmp/**/*
|
|
211
|
+
|
|
212
|
+
# Allow .gitkeep files to retain directory structure
|
|
213
|
+
!/temp/**/.gitkeep
|
|
214
|
+
!/tmp/**/.gitkeep
|
|
215
|
+
|
|
216
|
+
# Armani
|
|
217
|
+
.history*
|
|
218
|
+
.history/
|
|
219
|
+
local_data/
|
|
220
|
+
local_reports_data/
|
|
221
|
+
webscraper/quick_scrapes/temp/
|
|
222
|
+
automation_matrix/ai_apis/fireworks/_dev/*
|
|
223
|
+
automation_matrix/ai_apis/fireworks/_dev/fireworks_sample.py
|
|
224
|
+
*.pdf
|
|
225
|
+
*.flac
|
|
226
|
+
*.mp3
|
|
227
|
+
*.wav
|
|
228
|
+
miniconda.sh
|
|
229
|
+
/database/python_sql/temp_data/
|
|
230
|
+
.history*
|
|
231
|
+
.history/
|
|
232
|
+
.history/
|
|
233
|
+
|
|
234
|
+
_dev/
|
|
235
|
+
/_dev/
|
|
236
|
+
requirements_filtered.txt
|
|
237
|
+
|
|
238
|
+
# matrx-dev-tools backups
|
|
239
|
+
.env-backups/
|
|
240
|
+
# Matrx Ship config (contains API key)
|
|
241
|
+
.matrx-ship.json
|
|
242
|
+
|
|
243
|
+
# Matrx config (contains API keys)
|
|
244
|
+
.matrx.json
|
|
245
|
+
.matrx-tools.conf
|
|
246
|
+
|
|
247
|
+
# Claude Code local worktrees and per-user settings
|
|
248
|
+
.claude/worktrees/
|
|
249
|
+
.claude/settings.local.json
|
|
250
|
+
|
|
251
|
+
# Append-only snapshots from matrx_utils.update_history (unbounded; do not commit)
|
|
252
|
+
common/utils/data_in_code/data_history.json
|
|
253
|
+
packages/matrx-utils/matrx_utils/data_in_code/data_history.json
|
|
254
|
+
|
|
255
|
+
# Tool-dispatch debug logs — one file per server start, never committed
|
|
256
|
+
.matrx-debug/
|
|
257
|
+
|
|
258
|
+
# macOS Finder metadata
|
|
259
|
+
.DS_Store
|
|
260
|
+
**/.DS_Store
|
|
261
|
+
|
|
262
|
+
# Environment files
|
|
263
|
+
.env
|
|
264
|
+
.env.*
|
|
265
|
+
*.env
|
|
266
|
+
*.env.*
|
|
267
|
+
|
|
268
|
+
# Keep safe templates trackable
|
|
269
|
+
!.env.example
|
|
270
|
+
!.env.sample
|
|
271
|
+
!.env.template
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CLAUDE.md — matrx-seo
|
|
2
|
+
|
|
3
|
+
`matrx-seo` is the SEO vertical: provider/client layer + canonical ORM persistence over
|
|
4
|
+
the live `seo.*` schema. Read `matrx_seo/FEATURE.md` before changing this package — it
|
|
5
|
+
is the durable contract (repository semantics, identity names, association-only project
|
|
6
|
+
linkage, battery credentials).
|
|
7
|
+
|
|
8
|
+
Hard rules:
|
|
9
|
+
|
|
10
|
+
- Persistence uses canonical `web.site` / `web.page` ids and links keywords to
|
|
11
|
+
`workspace.projects` ONLY through `platform.associations`
|
|
12
|
+
(`orm_identity.link_keyword_to_project`). No table may contain `project_id`; no
|
|
13
|
+
feature-specific project membership or site/page binding table may exist.
|
|
14
|
+
- All DB access goes through matrx-orm models (`db/models_seo.py`, host mirrors in
|
|
15
|
+
`db/models_host.py`) — zero raw SQL.
|
|
16
|
+
- Credentials resolve through the matrx-orm secrets battery
|
|
17
|
+
(`config.battery_credential_resolver`); no environment credential fallback.
|
|
18
|
+
- Never import `aidream` or a repository-root module from `matrx_seo/` source
|
|
19
|
+
(tests may; only package source is boundary-checked).
|
|
20
|
+
|
|
21
|
+
The standalone service is REAL (`standalone/app.py::create_app`): matrx-connect
|
|
22
|
+
`AuthMiddleware` (Supabase JWTs only), boot gates (svc_seo pool from
|
|
23
|
+
`SUPABASE_MATRIX_*`, `assert_service_role()`, explicit
|
|
24
|
+
`CREDENTIALS_ENCRYPTION_KEY` round-trip, JWT material from
|
|
25
|
+
`SUPABASE_MATRIX_URL`/`SUPABASE_JWT_SECRET`), and thin JSON routes over ONE
|
|
26
|
+
`SeoCollectionService` (`/health`, `/health/ready`, `POST/GET /collections`,
|
|
27
|
+
`GET /collections/{run_id}`). Standalone provider registry =
|
|
28
|
+
`ADAPTER_FACTORIES` (brave / dataforseo / serpapi — the others need host client
|
|
29
|
+
factories). MCP tools were deliberately skipped (auth-stack cost — see
|
|
30
|
+
FEATURE.md). `scripts/independence_gate.sh` pins the boot contract in a bare
|
|
31
|
+
venv; `tests/test_standalone_boot.py` pins it in the suite. matrx-connect is a
|
|
32
|
+
`[standalone]`-extra dependency, not a core one.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Deploying matrx-seo (the SEO vertical service)
|
|
2
|
+
|
|
3
|
+
STATUS 2026-07-21: **artifacts ready, NOT yet deployed.** Everything below is
|
|
4
|
+
prepared; the first deploy needs three human actions (marked **[ARMAN]**) that
|
|
5
|
+
an agent cannot do: an AWS SSO login, a one-time PyPI Trusted Publisher, and
|
|
6
|
+
minting the `svc_seo` database password.
|
|
7
|
+
|
|
8
|
+
The service runs **on the same EC2 box as matrx-files** (`i-084f757c1e47d4efb`
|
|
9
|
+
/ `54.144.86.132`, Ship name `matrx-sandbox-host-dev`) — same pattern, same
|
|
10
|
+
management surface, different container/port/hostname.
|
|
11
|
+
|
|
12
|
+
| | matrx-files | matrx-seo |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| container | `matrx-files` | `matrx-seo` |
|
|
15
|
+
| port (loopback only) | `127.0.0.1:8080` | `127.0.0.1:8081` |
|
|
16
|
+
| env file (root 600) | `/etc/matrx-files.env` | `/etc/matrx-seo.env` |
|
|
17
|
+
| build dir on box | `/opt/matrx-files` | `/opt/matrx-seo` |
|
|
18
|
+
| public URL | `https://files.matrxserver.com` | `https://seo.matrxserver.com` |
|
|
19
|
+
| TLS | shared Caddy container (`matrx-files-tls`, host network, :443) | **same container, new site block** |
|
|
20
|
+
|
|
21
|
+
**One Caddy for the box.** Do NOT start a second TLS container — :443 is taken.
|
|
22
|
+
Add a `seo.matrxserver.com` block to the existing Caddyfile and reload.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## The database contract — there is ONE database
|
|
27
|
+
|
|
28
|
+
The service connects to **Matrx Main**, the same Postgres everything else uses,
|
|
29
|
+
through the **same env var names** (`SUPABASE_MATRIX_*`). There is no SEO
|
|
30
|
+
database and no SEO-specific connection variable.
|
|
31
|
+
|
|
32
|
+
What differs on THIS deployment is only the **credentials**: `SUPABASE_MATRIX_USER`
|
|
33
|
+
is `svc_seo`, a role that owns the `seo` schema and is granted exactly the
|
|
34
|
+
platform surfaces in [`matrx_seo/db/grants.yaml`](matrx_seo/db/grants.yaml).
|
|
35
|
+
The service asserts this at boot (`assert_service_role`) and **crashes if handed
|
|
36
|
+
a broad role** — that assertion is what makes the boundary real rather than
|
|
37
|
+
documentation.
|
|
38
|
+
|
|
39
|
+
### `/etc/matrx-seo.env` (root 600)
|
|
40
|
+
|
|
41
|
+
```ini
|
|
42
|
+
# The ONE database (identical names to aidream/scraper/files).
|
|
43
|
+
SUPABASE_MATRIX_HOST=<same as every other service>
|
|
44
|
+
SUPABASE_MATRIX_PORT=6543
|
|
45
|
+
SUPABASE_MATRIX_DATABASE_NAME=postgres
|
|
46
|
+
SUPABASE_MATRIX_USER=svc_seo # <- the ONLY per-deployment difference
|
|
47
|
+
SUPABASE_MATRIX_PASSWORD=<minted below> # <- svc_seo's password
|
|
48
|
+
|
|
49
|
+
# Supabase JWT verification (JWKS) — same value every service uses.
|
|
50
|
+
SUPABASE_MATRIX_URL=<same as every other service>
|
|
51
|
+
|
|
52
|
+
# The ONE platform encryption key (matrx-orm secrets battery). Same value as
|
|
53
|
+
# aidream/scraper — provider API keys are read straight from users.user_secrets.
|
|
54
|
+
CREDENTIALS_ENCRYPTION_KEY=<same as every other service>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every one of these already exists on the other servers. **Copy the values; do
|
|
58
|
+
not invent new ones.**
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## First deploy — the runbook
|
|
63
|
+
|
|
64
|
+
### 0. **[ARMAN]** One-time PyPI Trusted Publisher
|
|
65
|
+
`https://pypi.org/manage/account/publishing/` → pending publisher:
|
|
66
|
+
owner `AI-Matrix-Engine`, repo `aidream`, workflow `publish-package.yml`,
|
|
67
|
+
environment blank, project `matrx-seo`. (Same one-time step every workspace
|
|
68
|
+
package needed.)
|
|
69
|
+
|
|
70
|
+
### 1. **[ARMAN]** Mint the `svc_seo` login
|
|
71
|
+
The role exists and owns its schema, but is `NOLOGIN` with no password — it
|
|
72
|
+
cannot connect until you give it one. In the Supabase SQL editor:
|
|
73
|
+
|
|
74
|
+
```sql
|
|
75
|
+
ALTER ROLE svc_seo LOGIN PASSWORD '<a strong password you generate>';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Put that same password in `/etc/matrx-seo.env` as `SUPABASE_MATRIX_PASSWORD`.
|
|
79
|
+
Nobody needs to see it again — the drift guard never reads it, and the service
|
|
80
|
+
only proves *which role* it connected as.
|
|
81
|
+
|
|
82
|
+
> Keeping it `NOLOGIN` until deploy day is deliberate: the boundary exists in
|
|
83
|
+
> the database from the moment the schema did, but no credential existed to
|
|
84
|
+
> leak in the meantime.
|
|
85
|
+
|
|
86
|
+
### 2. **[ARMAN]** AWS SSO (the deploy script rides SSM)
|
|
87
|
+
```bash
|
|
88
|
+
aws sso login --profile AdministratorAccess-872515272894
|
|
89
|
+
ssh matrx-sandbox 'echo ok' # must print ok
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Publish the package
|
|
93
|
+
```bash
|
|
94
|
+
cd ~/code/aidream
|
|
95
|
+
git tag matrx-seo/v0.1.0 && git push origin refs/tags/matrx-seo/v0.1.0
|
|
96
|
+
# CI publishes to PyPI; confirm: https://pypi.org/project/matrx-seo/
|
|
97
|
+
```
|
|
98
|
+
matrx-orm must also be published at a version containing the secrets battery +
|
|
99
|
+
role-aware associations (tag `matrx-orm/v<version>` the same way) — the
|
|
100
|
+
Dockerfile resolves it from PyPI.
|
|
101
|
+
|
|
102
|
+
### 4. Put the env file on the box
|
|
103
|
+
```bash
|
|
104
|
+
scp /tmp/matrx-seo.env matrx-sandbox:/tmp/matrx-seo.env
|
|
105
|
+
ssh matrx-sandbox 'sudo mv /tmp/matrx-seo.env /etc/matrx-seo.env && sudo chown root:root /etc/matrx-seo.env && sudo chmod 600 /etc/matrx-seo.env'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 5. Deploy
|
|
109
|
+
```bash
|
|
110
|
+
cd ~/code/aidream/packages/matrx-seo
|
|
111
|
+
./deploy.sh 0.1.0
|
|
112
|
+
```
|
|
113
|
+
The script refuses to swap the live container unless the built image really
|
|
114
|
+
contains that version, and after the swap it requires readiness to report
|
|
115
|
+
`service_role: true` — i.e. it proves the service came up **inside its granted
|
|
116
|
+
boundary**.
|
|
117
|
+
|
|
118
|
+
### 6. TLS + DNS (first deploy only)
|
|
119
|
+
Add the site block to the box's shared Caddyfile (`/opt/matrx-files/Caddyfile`):
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
seo.matrxserver.com {
|
|
123
|
+
tls internal
|
|
124
|
+
reverse_proxy 127.0.0.1:8081
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
Then `ssh matrx-sandbox 'sudo docker restart matrx-files-tls'`.
|
|
128
|
+
|
|
129
|
+
Cloudflare: A record `seo.matrxserver.com` → `54.144.86.132`, **proxied**,
|
|
130
|
+
SSL mode Full (already set for the zone). Security group `sg-05a1b5a6163cd8ee6`
|
|
131
|
+
already allows 443 from Cloudflare ranges only — **no new SG rule**, and 8081
|
|
132
|
+
stays closed to the world (the app binds loopback).
|
|
133
|
+
|
|
134
|
+
### 7. Register in Ship
|
|
135
|
+
Already recorded in [`matrx-ship/SERVERS_AND_ROUTES.md`](/Users/armanisadeghi/code/matrx-ship/SERVERS_AND_ROUTES.md).
|
|
136
|
+
Drop the service descriptor on the box so the Manager UI lists it:
|
|
137
|
+
```bash
|
|
138
|
+
ssh matrx-sandbox 'sudo tee /opt/matrx-seo/service.json >/dev/null <<JSON
|
|
139
|
+
{"name":"matrx-seo","container":"matrx-seo","port":8081,
|
|
140
|
+
"public_url":"https://seo.matrxserver.com","health":"/health",
|
|
141
|
+
"readiness":"/health/ready","env_file":"/etc/matrx-seo.env",
|
|
142
|
+
"repo":"aidream/packages/matrx-seo"}
|
|
143
|
+
JSON'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Manage
|
|
149
|
+
|
|
150
|
+
`ssh matrx-sandbox`, or the Manager host-exec
|
|
151
|
+
(`POST /api/hosts/matrx-sandbox-host-dev/exec` → `sudo docker …`).
|
|
152
|
+
|
|
153
|
+
- App: `sudo docker {ps,logs,restart} matrx-seo`
|
|
154
|
+
- TLS: `sudo docker {logs,restart} matrx-files-tls` (shared)
|
|
155
|
+
- **Env changes need `docker rm` + `run`, not `restart`** — `--env-file` is read
|
|
156
|
+
only at run time. Re-running `./deploy.sh <version>` does this correctly.
|
|
157
|
+
- Rollback: `./deploy.sh <previous-version>`
|
|
158
|
+
|
|
159
|
+
## Health contract
|
|
160
|
+
|
|
161
|
+
- `GET /health` — liveness, unauthenticated.
|
|
162
|
+
- `GET /health/ready` — 503 until every boot gate passes; the body names the
|
|
163
|
+
failed component (`database` / `service_role` / `models` /
|
|
164
|
+
`credentials_encryption` / `service`).
|
|
165
|
+
- Every `/collections*` route requires a Supabase JWT (401 otherwise).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Matrx SEO — standalone vertical service image.
|
|
2
|
+
#
|
|
3
|
+
# Installs ENTIRELY from PyPI (every matrx-* dep is published; a bare
|
|
4
|
+
# `uv pip install matrx-seo[standalone]` is proven by the independence gate).
|
|
5
|
+
# Build from anywhere:
|
|
6
|
+
# docker build -t matrx-seo packages/matrx-seo
|
|
7
|
+
# Pin the version at build time for reproducible deploys:
|
|
8
|
+
# docker build --build-arg MATRX_SEO_VERSION==0.1.0 -t matrx-seo:0.1.0 packages/matrx-seo
|
|
9
|
+
#
|
|
10
|
+
# No system binaries: this service talks HTTP to SEO providers and Postgres to
|
|
11
|
+
# the ONE database. Nothing to render, nothing to OCR — keep the image small.
|
|
12
|
+
FROM python:3.13-slim
|
|
13
|
+
|
|
14
|
+
RUN pip install --no-cache-dir uv
|
|
15
|
+
|
|
16
|
+
ARG MATRX_SEO_VERSION=""
|
|
17
|
+
RUN uv pip install --system "matrx-seo[standalone]${MATRX_SEO_VERSION}"
|
|
18
|
+
|
|
19
|
+
EXPOSE 8081
|
|
20
|
+
CMD ["uvicorn", "matrx_seo.standalone.app:create_app", "--factory", "--host", "0.0.0.0", "--port", "8081"]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Package independence
|
|
2
|
+
|
|
3
|
+
This package follows `/Users/armanisadeghi/code/matrx-package-template/INDEPENDENCE.md`:
|
|
4
|
+
no host imports, explicit injected configuration, and package-owned contracts,
|
|
5
|
+
schema manifest, models, migrations, tests, and standalone boot surface.
|
|
6
|
+
|
matrx_seo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrx-seo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provider-neutral SEO collection contracts and canonical persistence.
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: httpx>=0.28
|
|
7
|
+
Requires-Dist: matrx-orm>=3.1
|
|
8
|
+
Requires-Dist: matrx-utils
|
|
9
|
+
Requires-Dist: pydantic>=2.12
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
13
|
+
Provides-Extra: standalone
|
|
14
|
+
Requires-Dist: fastapi>=0.115; extra == 'standalone'
|
|
15
|
+
Requires-Dist: matrx-connect; extra == 'standalone'
|
|
16
|
+
Requires-Dist: uvicorn>=0.34; extra == 'standalone'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# matrx-seo
|
|
20
|
+
|
|
21
|
+
Provider-neutral SEO provider clients, collection contracts, normalization, replay
|
|
22
|
+
fixtures, and canonical persistence for Matrx. Durable persistence is temporarily absent
|
|
23
|
+
while it is rebuilt on `platform.associations`; persistence remains part of this package's
|
|
24
|
+
required product boundary. See
|
|
25
|
+
[`matrx_seo/FEATURE.md`](matrx_seo/FEATURE.md) for the current contract.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# matrx-seo
|
|
2
|
+
|
|
3
|
+
Provider-neutral SEO provider clients, collection contracts, normalization, replay
|
|
4
|
+
fixtures, and canonical persistence for Matrx. Durable persistence is temporarily absent
|
|
5
|
+
while it is rebuilt on `platform.associations`; persistence remains part of this package's
|
|
6
|
+
required product boundary. See
|
|
7
|
+
[`matrx_seo/FEATURE.md`](matrx_seo/FEATURE.md) for the current contract.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deploy matrx-seo to the live EC2 box behind https://seo.matrxserver.com
|
|
3
|
+
#
|
|
4
|
+
# ./deploy.sh 0.1.0 # deploy a version (must already be on PyPI)
|
|
5
|
+
# ./deploy.sh 0.0.9 # rolling back is the same command
|
|
6
|
+
#
|
|
7
|
+
# Mirrors packages/matrx-files/deploy.sh (same box, same pattern) and REFUSES
|
|
8
|
+
# to touch the live container until the new image is proven:
|
|
9
|
+
# 1. checks PyPI actually has the version (tag push publishes it)
|
|
10
|
+
# 2. syncs THIS directory's canonical Dockerfile to the box (kills drift)
|
|
11
|
+
# 3. builds matrx-seo:<version> on the box
|
|
12
|
+
# 4. asserts the built image really contains <version> (aborts otherwise)
|
|
13
|
+
# 5. swaps the container (rm + run — NEVER docker restart: --env-file is
|
|
14
|
+
# read only at run time)
|
|
15
|
+
# 6. health-checks locally and through Cloudflare, and proves the service
|
|
16
|
+
# is inside its granted DB boundary (readiness reports service_role)
|
|
17
|
+
#
|
|
18
|
+
# Requires: `ssh matrx-sandbox` working (SSM per ~/.ssh/config; run
|
|
19
|
+
# `aws sso login --profile AdministratorAccess-872515272894` first if stale).
|
|
20
|
+
#
|
|
21
|
+
# The box needs /etc/matrx-seo.env (root 600) BEFORE the first deploy — see
|
|
22
|
+
# DEPLOY.md. It carries the ONE database's standard SUPABASE_MATRIX_* vars
|
|
23
|
+
# with USER/PASSWORD set to the svc_seo role, plus CREDENTIALS_ENCRYPTION_KEY.
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
VERSION="${1:?usage: deploy.sh <version, e.g. 0.1.0>}"
|
|
27
|
+
HOST="${MATRX_SEO_HOST:-matrx-sandbox}"
|
|
28
|
+
PUBLIC_BASE="https://seo.matrxserver.com"
|
|
29
|
+
PORT=8081
|
|
30
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
31
|
+
|
|
32
|
+
echo "==> [1/6] checking PyPI has matrx-seo==${VERSION}"
|
|
33
|
+
if ! curl -sf -m 15 "https://pypi.org/pypi/matrx-seo/${VERSION}/json" >/dev/null; then
|
|
34
|
+
echo "ERROR: matrx-seo==${VERSION} is not on PyPI."
|
|
35
|
+
echo " Publish first: cd aidream && git tag matrx-seo/v${VERSION} && git push origin refs/tags/matrx-seo/v${VERSION}"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "==> [2/6] syncing canonical Dockerfile to ${HOST}:/opt/matrx-seo/"
|
|
40
|
+
ssh "${HOST}" 'sudo mkdir -p /opt/matrx-seo'
|
|
41
|
+
scp -q "${DIR}/Dockerfile" "${HOST}:/tmp/matrx-seo.Dockerfile"
|
|
42
|
+
ssh "${HOST}" 'sudo mv /tmp/matrx-seo.Dockerfile /opt/matrx-seo/Dockerfile'
|
|
43
|
+
|
|
44
|
+
echo "==> [3/6] building matrx-seo:${VERSION} on ${HOST}"
|
|
45
|
+
ssh "${HOST}" "sudo docker build --build-arg MATRX_SEO_VERSION='==${VERSION}' -t 'matrx-seo:${VERSION}' /opt/matrx-seo"
|
|
46
|
+
|
|
47
|
+
echo "==> [4/6] verifying the image really contains ${VERSION}"
|
|
48
|
+
GOT="$(ssh "${HOST}" "sudo docker run --rm --entrypoint python 'matrx-seo:${VERSION}' -c 'import importlib.metadata as m; print(m.version(\"matrx-seo\"))'" | tr -d '[:space:]')"
|
|
49
|
+
if [ "${GOT}" != "${VERSION}" ]; then
|
|
50
|
+
echo "ERROR: built image contains matrx-seo ${GOT}, expected ${VERSION} — ABORTING; live container untouched."
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
echo " image verified: ${GOT}"
|
|
54
|
+
|
|
55
|
+
echo "==> [5/6] swapping the live container (rm + run)"
|
|
56
|
+
ssh "${HOST}" "sudo docker rm -f matrx-seo >/dev/null 2>&1 || true; sudo docker run -d --name matrx-seo --restart unless-stopped --env-file /etc/matrx-seo.env -p 127.0.0.1:${PORT}:${PORT} 'matrx-seo:${VERSION}'"
|
|
57
|
+
|
|
58
|
+
echo "==> [6/6] health checks"
|
|
59
|
+
HEALTH=""
|
|
60
|
+
for _ in $(seq 1 20); do
|
|
61
|
+
HEALTH="$(ssh "${HOST}" "curl -sf -m 5 http://127.0.0.1:${PORT}/health" 2>/dev/null || true)"
|
|
62
|
+
[ -n "${HEALTH}" ] && break
|
|
63
|
+
sleep 2
|
|
64
|
+
done
|
|
65
|
+
if [ -z "${HEALTH}" ]; then
|
|
66
|
+
echo "ERROR: health never came up on the box. Logs: ssh ${HOST} 'sudo docker logs --tail 50 matrx-seo'"
|
|
67
|
+
echo " Roll back: ./deploy.sh <previous-version>"
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
echo " box health: ${HEALTH}"
|
|
71
|
+
|
|
72
|
+
# Readiness proves the BOUNDARY: the service booted only because it connected
|
|
73
|
+
# as svc_seo and the encryption key round-tripped. A 503 here means a boot gate
|
|
74
|
+
# refused — read the body, it names the failed component.
|
|
75
|
+
READY="$(ssh "${HOST}" "curl -s -m 10 http://127.0.0.1:${PORT}/health/ready" 2>/dev/null || true)"
|
|
76
|
+
echo " box readiness: ${READY}"
|
|
77
|
+
case "${READY}" in
|
|
78
|
+
*'"service_role": true'*|*'"service_role":true'*) echo " boundary OK: connected as svc_seo" ;;
|
|
79
|
+
*) echo "ERROR: readiness does not confirm the svc_seo boundary — ABORTING the rollout check."
|
|
80
|
+
echo " Logs: ssh ${HOST} 'sudo docker logs --tail 50 matrx-seo'"
|
|
81
|
+
exit 1 ;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
echo " public health: $(curl -sf -m 10 "${PUBLIC_BASE}/health" || echo 'UNREACHABLE (DNS/Caddy not wired yet?)')"
|
|
85
|
+
ANON="$(curl -s -m 10 -o /dev/null -w '%{http_code}' "${PUBLIC_BASE}/collections?organization_id=00000000-0000-0000-0000-000000000000")"
|
|
86
|
+
echo " anon /collections -> HTTP ${ANON} (expect 401)"
|
|
87
|
+
|
|
88
|
+
echo "DONE: matrx-seo ${VERSION} is live at ${PUBLIC_BASE}. Rollback = ./deploy.sh <previous-version>"
|