tableau-cloud-audit 0.2.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.
Files changed (92) hide show
  1. tableau_cloud_audit-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +85 -0
  2. tableau_cloud_audit-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. tableau_cloud_audit-0.2.0/.github/ISSUE_TEMPLATE/scale_report.yml +69 -0
  4. tableau_cloud_audit-0.2.0/.github/workflows/ci.yml +49 -0
  5. tableau_cloud_audit-0.2.0/.github/workflows/release.yml +47 -0
  6. tableau_cloud_audit-0.2.0/.gitignore +25 -0
  7. tableau_cloud_audit-0.2.0/AI_GUIDE.md +278 -0
  8. tableau_cloud_audit-0.2.0/CHANGELOG.md +174 -0
  9. tableau_cloud_audit-0.2.0/CLAUDE.md +559 -0
  10. tableau_cloud_audit-0.2.0/CODE_OF_CONDUCT.md +129 -0
  11. tableau_cloud_audit-0.2.0/CONTRIBUTING.md +61 -0
  12. tableau_cloud_audit-0.2.0/LICENSE +202 -0
  13. tableau_cloud_audit-0.2.0/NOTICE +12 -0
  14. tableau_cloud_audit-0.2.0/PKG-INFO +221 -0
  15. tableau_cloud_audit-0.2.0/README.md +181 -0
  16. tableau_cloud_audit-0.2.0/SECURITY.md +67 -0
  17. tableau_cloud_audit-0.2.0/collector.toml.example +34 -0
  18. tableau_cloud_audit-0.2.0/docs/api-coverage.md +119 -0
  19. tableau_cloud_audit-0.2.0/docs/assets/demo.gif +0 -0
  20. tableau_cloud_audit-0.2.0/docs/assets/demo.tape +35 -0
  21. tableau_cloud_audit-0.2.0/docs/assets/query.gif +0 -0
  22. tableau_cloud_audit-0.2.0/docs/assets/query.tape +43 -0
  23. tableau_cloud_audit-0.2.0/docs/cli-reference.md +136 -0
  24. tableau_cloud_audit-0.2.0/docs/cookbook/activity-over-time.md +25 -0
  25. tableau_cloud_audit-0.2.0/docs/cookbook/all-users-grants.md +26 -0
  26. tableau_cloud_audit-0.2.0/docs/cookbook/certified-content.md +22 -0
  27. tableau_cloud_audit-0.2.0/docs/cookbook/embedded-credentials.md +24 -0
  28. tableau_cloud_audit-0.2.0/docs/cookbook/inactive-users.md +28 -0
  29. tableau_cloud_audit-0.2.0/docs/cookbook/index.md +42 -0
  30. tableau_cloud_audit-0.2.0/docs/cookbook/ownerless-content.md +25 -0
  31. tableau_cloud_audit-0.2.0/docs/cookbook/unused-content.md +29 -0
  32. tableau_cloud_audit-0.2.0/docs/getting-started.md +206 -0
  33. tableau_cloud_audit-0.2.0/docs/index.md +69 -0
  34. tableau_cloud_audit-0.2.0/docs/package-file-schema.md +122 -0
  35. tableau_cloud_audit-0.2.0/docs/runbook.md +136 -0
  36. tableau_cloud_audit-0.2.0/docs/troubleshooting.md +140 -0
  37. tableau_cloud_audit-0.2.0/docs/what-we-collect.md +149 -0
  38. tableau_cloud_audit-0.2.0/mkdocs.yml +64 -0
  39. tableau_cloud_audit-0.2.0/overrides/main.html +33 -0
  40. tableau_cloud_audit-0.2.0/pyproject.toml +79 -0
  41. tableau_cloud_audit-0.2.0/src/tca/__init__.py +7 -0
  42. tableau_cloud_audit-0.2.0/src/tca/cli.py +950 -0
  43. tableau_cloud_audit-0.2.0/src/tca/config.py +127 -0
  44. tableau_cloud_audit-0.2.0/src/tca/modules/__init__.py +0 -0
  45. tableau_cloud_audit-0.2.0/src/tca/modules/activity.py +118 -0
  46. tableau_cloud_audit-0.2.0/src/tca/modules/automation.py +33 -0
  47. tableau_cloud_audit-0.2.0/src/tca/modules/base.py +128 -0
  48. tableau_cloud_audit-0.2.0/src/tca/modules/content.py +79 -0
  49. tableau_cloud_audit-0.2.0/src/tca/modules/metadata.py +70 -0
  50. tableau_cloud_audit-0.2.0/src/tca/modules/permissions.py +114 -0
  51. tableau_cloud_audit-0.2.0/src/tca/modules/rest_core.py +52 -0
  52. tableau_cloud_audit-0.2.0/src/tca/normalize.py +588 -0
  53. tableau_cloud_audit-0.2.0/src/tca/pseudo/__init__.py +0 -0
  54. tableau_cloud_audit-0.2.0/src/tca/pseudo/manifest.py +478 -0
  55. tableau_cloud_audit-0.2.0/src/tca/pseudo/scrubber.py +259 -0
  56. tableau_cloud_audit-0.2.0/src/tca/sources/__init__.py +0 -0
  57. tableau_cloud_audit-0.2.0/src/tca/sources/metadata.py +73 -0
  58. tableau_cloud_audit-0.2.0/src/tca/sources/rest.py +107 -0
  59. tableau_cloud_audit-0.2.0/src/tca/sources/vds.py +39 -0
  60. tableau_cloud_audit-0.2.0/src/tca/storage/__init__.py +0 -0
  61. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/001_init.sql +80 -0
  62. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/002_event_history.sql +50 -0
  63. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/003_typed_state.sql +120 -0
  64. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/004_clear_views.sql +62 -0
  65. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/005_job_history.sql +41 -0
  66. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/006_vds_metadata_state.sql +185 -0
  67. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/007_current_view_semantics.sql +50 -0
  68. tableau_cloud_audit-0.2.0/src/tca/storage/migrations/008_state_current_only.sql +74 -0
  69. tableau_cloud_audit-0.2.0/src/tca/storage/writer.py +742 -0
  70. tableau_cloud_audit-0.2.0/src/tca/transport/__init__.py +0 -0
  71. tableau_cloud_audit-0.2.0/src/tca/transport/auth.py +45 -0
  72. tableau_cloud_audit-0.2.0/src/tca/transport/client.py +215 -0
  73. tableau_cloud_audit-0.2.0/tests/test_activity.py +267 -0
  74. tableau_cloud_audit-0.2.0/tests/test_automation.py +122 -0
  75. tableau_cloud_audit-0.2.0/tests/test_cli.py +281 -0
  76. tableau_cloud_audit-0.2.0/tests/test_config.py +55 -0
  77. tableau_cloud_audit-0.2.0/tests/test_content.py +174 -0
  78. tableau_cloud_audit-0.2.0/tests/test_docs.py +33 -0
  79. tableau_cloud_audit-0.2.0/tests/test_export.py +107 -0
  80. tableau_cloud_audit-0.2.0/tests/test_history.py +153 -0
  81. tableau_cloud_audit-0.2.0/tests/test_metadata.py +266 -0
  82. tableau_cloud_audit-0.2.0/tests/test_migrations.py +73 -0
  83. tableau_cloud_audit-0.2.0/tests/test_normalize.py +489 -0
  84. tableau_cloud_audit-0.2.0/tests/test_peek.py +103 -0
  85. tableau_cloud_audit-0.2.0/tests/test_permissions.py +169 -0
  86. tableau_cloud_audit-0.2.0/tests/test_pseudo.py +219 -0
  87. tableau_cloud_audit-0.2.0/tests/test_rest_core.py +111 -0
  88. tableau_cloud_audit-0.2.0/tests/test_resume.py +137 -0
  89. tableau_cloud_audit-0.2.0/tests/test_schema_contract.py +419 -0
  90. tableau_cloud_audit-0.2.0/tests/test_smoke.py +19 -0
  91. tableau_cloud_audit-0.2.0/tests/test_storage.py +163 -0
  92. tableau_cloud_audit-0.2.0/tests/test_transport.py +175 -0
@@ -0,0 +1,85 @@
1
+ name: Bug report
2
+ description: Something went wrong or behaved unexpectedly.
3
+ title: "[bug] "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for reporting! A few structured details make bugs much quicker to fix.
10
+
11
+ **Privacy:** the safest way to hand us context is `tca diagnostics --markdown` —
12
+ PII-free by design. If you paste an error or traceback, glance over it first and
13
+ redact anything site-specific: the collector masks the PAT and pseudonymises
14
+ identities, but a raw Python traceback can still echo payload fragments.
15
+ - type: textarea
16
+ id: what-happened
17
+ attributes:
18
+ label: What happened
19
+ description: A clear description of the bug — what you expected versus what actually happened.
20
+ placeholder: "tca collect crashed on the permissions module; I expected it to finish or skip the item."
21
+ validations:
22
+ required: true
23
+ - type: dropdown
24
+ id: command
25
+ attributes:
26
+ label: Which command?
27
+ options:
28
+ - init
29
+ - verify
30
+ - collect
31
+ - summary
32
+ - runs
33
+ - diagnostics
34
+ - peek
35
+ - resolve
36
+ - export
37
+ - other / not sure
38
+ validations:
39
+ required: false
40
+ - type: textarea
41
+ id: repro
42
+ attributes:
43
+ label: Steps to reproduce
44
+ description: What you ran, in order — the exact command(s), but NEVER the PAT secret.
45
+ placeholder: |
46
+ 1. tca collect
47
+ 2. ...
48
+ render: shell
49
+ validations:
50
+ required: false
51
+ - type: textarea
52
+ id: error
53
+ attributes:
54
+ label: Error output / traceback
55
+ description: Paste the message or traceback. Redact anything site-specific first.
56
+ render: text
57
+ validations:
58
+ required: false
59
+ - type: textarea
60
+ id: diagnostics
61
+ attributes:
62
+ label: "`tca diagnostics --markdown` output"
63
+ description: Run `tca diagnostics --markdown` and paste it — PII-free by design; gives us versions + scale at a glance.
64
+ placeholder: |
65
+ ## tca diagnostics
66
+ _Safe to share — no names, e-mails, LUIDs or site identity._
67
+ ...
68
+ render: markdown
69
+ validations:
70
+ required: false
71
+ - type: input
72
+ id: version
73
+ attributes:
74
+ label: Collector version
75
+ description: From `tca --version` (skip if you pasted the diagnostics output above).
76
+ placeholder: "0.1.0"
77
+ validations:
78
+ required: false
79
+ - type: checkboxes
80
+ id: privacy-ack
81
+ attributes:
82
+ label: Privacy check
83
+ options:
84
+ - label: I've reviewed what I'm pasting — it contains no real names, e-mails, user LUIDs or my PAT secret.
85
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/simboli/tableau-cloud-audit/blob/main/SECURITY.md
5
+ about: Please report security issues privately — do not open a public issue. See SECURITY.md.
@@ -0,0 +1,69 @@
1
+ name: Scale report
2
+ description: Tell us how the collector ran on your Tableau Cloud site — size, timing, anything that broke.
3
+ title: "[scale] "
4
+ labels: ["scale-report"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for running **tableau-cloud-audit** on a real site! Reports from larger
10
+ estates are how we harden it beyond the tiny dev sandbox it is built on.
11
+
12
+ **Privacy first — the easy way to share numbers is `tca diagnostics --markdown`.**
13
+ Its output is PII-free *by construction*: no names, e-mails, LUIDs or your site
14
+ identity, and it passes a final self-gate before printing. Paste it below and you
15
+ are done. (Prefer not to? Skip it and just fill in the rough scale.)
16
+ - type: textarea
17
+ id: diagnostics
18
+ attributes:
19
+ label: "`tca diagnostics --markdown` output"
20
+ description: Run `tca diagnostics --markdown` and paste the whole report here.
21
+ placeholder: |
22
+ ## tca diagnostics
23
+ _Safe to share — no names, e-mails, LUIDs or site identity._
24
+ ...
25
+ render: markdown
26
+ validations:
27
+ required: false
28
+ - type: dropdown
29
+ id: outcome
30
+ attributes:
31
+ label: Did the collection complete?
32
+ options:
33
+ - "Yes — tca collect finished (status ok)"
34
+ - "Partial — it stopped and was resumable"
35
+ - "No — it failed"
36
+ - "Haven't run a full collect yet"
37
+ validations:
38
+ required: true
39
+ - type: input
40
+ id: scale
41
+ attributes:
42
+ label: Rough site scale
43
+ description: Ballpark is fine — useful especially if you skipped the diagnostics output.
44
+ placeholder: "~4000 workbooks, ~600 datasources, ~1500 users, ~200 projects"
45
+ validations:
46
+ required: false
47
+ - type: textarea
48
+ id: observations
49
+ attributes:
50
+ label: What happened
51
+ description: Timing, memory, rate-limiting, errors — anything that felt slow or broke. Which module was the slow/failing one, if you noticed.
52
+ placeholder: "collect took ~40 min; the permissions module dominated; one 429 backoff; ..."
53
+ validations:
54
+ required: false
55
+ - type: input
56
+ id: version
57
+ attributes:
58
+ label: Collector version
59
+ description: From `tca --version` (also shown in the diagnostics output).
60
+ placeholder: "0.1.0"
61
+ validations:
62
+ required: false
63
+ - type: checkboxes
64
+ id: privacy-ack
65
+ attributes:
66
+ label: Privacy check
67
+ options:
68
+ - label: I've reviewed what I'm pasting — it contains no real names, e-mails or user LUIDs. (The `tca diagnostics` output is safe by design; this only matters for any free text I added myself.)
69
+ required: true
@@ -0,0 +1,49 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ cache: pip
21
+ - name: Install
22
+ run: pip install -e ".[dev]"
23
+ - name: Lint (ruff)
24
+ run: |
25
+ ruff check .
26
+ ruff format --check .
27
+ - name: Type check (mypy)
28
+ run: mypy
29
+ - name: Tests (incl. the schema contract — the additive-only gate)
30
+ run: pytest -q
31
+
32
+ docs:
33
+ # Publishes the MkDocs site to GitHub Pages on every push to main.
34
+ # One-time repo setup: Settings -> Pages -> deploy from `gh-pages` branch.
35
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
36
+ needs: test
37
+ runs-on: ubuntu-latest
38
+ permissions:
39
+ contents: write
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.13"
45
+ cache: pip
46
+ - name: Install docs toolchain
47
+ run: pip install -e ".[docs]"
48
+ - name: Build and deploy
49
+ run: mkdocs gh-deploy --force
@@ -0,0 +1,47 @@
1
+ name: Release to PyPI
2
+
3
+ # Publishes via PyPI Trusted Publishing (OIDC) — no tokens stored anywhere.
4
+ # One-time PyPI setup: pypi.org -> Account -> Publishing -> Add pending
5
+ # publisher (project: tableau-cloud-audit, owner: simboli, repo:
6
+ # tableau-cloud-audit, workflow: release.yml, environment: pypi).
7
+
8
+ on:
9
+ release:
10
+ types: [published]
11
+ workflow_dispatch: {}
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.13"
21
+ - name: Build sdist and wheel
22
+ run: |
23
+ pip install build
24
+ python -m build
25
+ - name: Sanity check
26
+ run: |
27
+ pip install twine
28
+ twine check dist/*
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment:
38
+ name: pypi
39
+ url: https://pypi.org/p/tableau-cloud-audit
40
+ permissions:
41
+ id-token: write # OIDC: this IS the credential
42
+ steps:
43
+ - uses: actions/download-artifact@v4
44
+ with:
45
+ name: dist
46
+ path: dist/
47
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+
9
+ # Tooling caches
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # Site-specific local files — never commit client data or config
15
+ *.duckdb
16
+ *.duckdb.wal
17
+ *.duckdb.sha256
18
+ collector.toml
19
+ .env
20
+
21
+ # OS / editor
22
+ .DS_Store
23
+ .idea/
24
+ .vscode/
25
+ site/
@@ -0,0 +1,278 @@
1
+ <!--
2
+ This file is written FOR AN AI ASSISTANT.
3
+ If you are a human: hand this file to Claude (or any capable LLM) and then
4
+ ask your question about tableau-cloud-audit. It gives the assistant enough
5
+ grounding to answer accurately instead of guessing.
6
+ If you are an AI assistant: read this whole file before answering. It is
7
+ the ground truth about this project's purpose, boundaries, architecture,
8
+ and the invariants you must never violate in any suggestion you make.
9
+ -->
10
+
11
+ # AI assistant guide to `tableau-cloud-audit`
12
+
13
+ You are helping someone understand, use, or extend **tableau-cloud-audit**
14
+ (CLI name: `tca`). This document is your briefing. Read it fully, then answer
15
+ the user's actual question. When the repository is available to you, prefer
16
+ reading the real code over reciting this file — this is orientation, the code
17
+ is truth. When it is not available, this file is your best grounding; say so
18
+ if you are asked about something it does not cover, rather than inventing it.
19
+
20
+ Answer in the user's language even though this guide is in English.
21
+
22
+ ---
23
+
24
+ ## 1. What this project is (in one paragraph)
25
+
26
+ `tableau-cloud-audit` is an **open-source, client-executed collector** for
27
+ Tableau Cloud. An administrator runs it with a Personal Access Token (PAT);
28
+ it reads their Tableau Cloud site through the REST API, the VizQL Data Service
29
+ (Admin Insights), and the Metadata API (GraphQL), and writes everything into
30
+ **one local DuckDB file** — pseudonymised by construction, optionally
31
+ encrypted at rest. That file is the deliverable: a complete, queryable
32
+ snapshot of a Tableau Cloud estate (users, groups, content, permissions,
33
+ activity history, lineage) that outlives Tableau's own ~90-day retention.
34
+
35
+ Tagline the project uses: *"your entire Tableau Cloud estate in one DuckDB
36
+ file."*
37
+
38
+ ## 2. What this project is NOT (this matters — do not cross it)
39
+
40
+ This repository is **only the collector**. It extracts and stores; it does
41
+ **not analyze, score, rank, or evaluate** anything.
42
+
43
+ The analysis engine — effective-permission resolution, usage tiering,
44
+ zombie/dormancy scoring, duplicate-metric clustering, findings generation, the
45
+ report deliverables — is **proprietary and lives in a separate private
46
+ repository**. It is the commercial half of the product; this collector is the
47
+ free, trust-building half and the funnel to it.
48
+
49
+ **The moat rule, which governs every suggestion you make:** if a change would
50
+ teach a reader *how a Tableau site gets evaluated* — any logic that turns raw
51
+ facts into a judgment (tiers, scores, "this is a problem", effective
52
+ permissions, risk levels) — **it does not belong in this repo.** Mechanical
53
+ transformations (renaming a field, parsing a timestamp, flattening a list,
54
+ deduplicating on a natural key) are fine and welcome. Interpretation is not.
55
+
56
+ If a user asks you to add analysis/scoring/tiering here, do not just do it —
57
+ explain that it belongs in the private engine and offer the mechanical,
58
+ in-scope alternative instead. The query cookbook in the docs is allowed to go
59
+ *teaser-deep* (show a stale-content query) but never *moat-deep* (never
60
+ compute effective permissions or a dormancy score).
61
+
62
+ ## 3. The privacy model (the other hard invariant)
63
+
64
+ Privacy is **structural, not best-effort**. Internalize this; it constrains
65
+ almost every code suggestion.
66
+
67
+ - **Pseudonymisation is a mandatory gate on the write path.** Nothing reaches
68
+ storage without passing through `Scrubber.scrub()`. Real user identities
69
+ (LUID, username, full name, e-mail, external auth id) are replaced with a
70
+ stable pseudonym `U-####` *before* the payload is written.
71
+ - **The single reviewable PII manifest** (`src/tca/pseudo/manifest.py`) is the
72
+ privacy contract. It declares, per endpoint, *where* personal fields live.
73
+ It is I/O-contract metadata, not business logic. Every endpoint the
74
+ collector calls **must** be registered there — an unregistered endpoint's
75
+ payload is refused, never written.
76
+ - **The identity vault** (`identity.map` table) is the ONLY place real
77
+ identities exist, keyed by pseudonym. It lives inside the same DuckDB file.
78
+ - **The safety net**: after scrubbing, a blocking check rejects any payload
79
+ where an e-mail-shaped string or a known user LUID survived. A gap in the
80
+ manifest fails loudly — it never leaks silently.
81
+ - **Three minimization mechanisms** beyond pseudonymisation: credential
82
+ usernames are redacted to `[redacted]`; VDS and GraphQL sources request only
83
+ a curated column/field list (identity columns are never even fetched);
84
+ e-mail-only sources are reverse-looked-up to `U-####` (unknown → `[redacted]`).
85
+ - **The export boundary**: `tca export` produces the ONLY file allowed to
86
+ leave the client machine. It is a copy with the `identity` schema **dropped**
87
+ — so real identities are structurally absent, not merely filtered — plus a
88
+ re-run of the safety net and a SHA-256 sidecar. The original file never
89
+ leaves the machine.
90
+
91
+ When you suggest adding a new data source, the FIRST thing to check is: does
92
+ it carry identity, and is it in the manifest? Never propose writing a payload
93
+ that bypasses the scrubber.
94
+
95
+ ## 4. Architecture — the shape of the code
96
+
97
+ Repository layout (Python package `tca`, installed as CLI `tca`):
98
+
99
+ ```
100
+ src/tca/
101
+ cli.py # Typer app: init, verify, collect, summary, peek, resolve, export
102
+ config.py # pydantic config: collector.toml + TCA_* env vars
103
+ normalize.py # mechanical raw → typed `state` tables (runs after each collect)
104
+ transport/ # HTTP layer (NOT named http/ to avoid stdlib clash)
105
+ client.py # httpx wrapper: auth header, retry/backoff, pagination, silent re-auth
106
+ auth.py # PAT sign-in
107
+ sources/ # dumb endpoint wrappers, typed responses (no logic)
108
+ rest.py vds.py metadata.py
109
+ modules/ # one extraction domain each; fetch → scrub → land
110
+ base.py # Module protocol + RunContext + ModuleStats
111
+ rest_core.py content.py automation.py permissions.py activity.py metadata.py
112
+ pseudo/
113
+ manifest.py # THE PII manifest (privacy contract) — REST, VDS, GraphQL sections
114
+ scrubber.py # the mandatory scrub gate + safety net + identity upserts
115
+ storage/
116
+ writer.py # PackageStore: the DuckDB connection, migrations, history, export
117
+ migrations/ # numbered, additive-only SQL (001…007); released files are FROZEN
118
+ tests/ # pytest, one file per module; ~98 tests
119
+ docs/ # PUBLIC docs only (MkDocs Material site)
120
+ ```
121
+
122
+ **The data flow of one `tca collect`:**
123
+
124
+ 1. Sign in (PAT → session token + site LUID).
125
+ 2. For each selected module, in dependency order: fetch pages from a source,
126
+ pass each through `Scrubber.scrub()`, and `land()` it into
127
+ `raw.api_responses` (one row = one HTTP response page, already scrubbed).
128
+ Every page commits immediately.
129
+ 3. History accumulation: TS Events → `history.events`, Job Performance →
130
+ `history.job_runs` (deduplicated on a natural key, cumulative across runs —
131
+ this is what outlives Tableau's retention window).
132
+ 4. `normalize.py` rebuilds the typed `state.*` tables from the raw pages of
133
+ this run (delete + insert; raw stays the source of truth).
134
+
135
+ **The package-file schema (current version v0.7)** has these DuckDB schemas:
136
+
137
+ - `meta` — logbook: `file_info`, `collection_runs`, `schema_migrations`,
138
+ `event_coverage`, and views (`v_latest_run`, `v_endpoint_latest_run`,
139
+ `v_event_gaps`).
140
+ - `raw` — `api_responses`: the source of truth, scrubbed JSON payloads.
141
+ - `state` — convenient typed tables, one row per object, rebuilt each run,
142
+ with `v_*_current` views (each following the latest run that actually
143
+ collected that data — see §7 "known subtleties").
144
+ - `history` — `events`, `job_runs`: append-only accumulators.
145
+ - `identity` — `map`: the vault (the only real identities; absent from exports).
146
+ - `clear` — read-time views re-joining `state` with `identity.map` so the
147
+ local user sees real names (`tca peek` reads these; impossible on an export).
148
+
149
+ ## 5. The CLI surface (7 commands)
150
+
151
+ Secrets are environment variables ONLY, never flags or files:
152
+ `TCA_PAT_SECRET` (the PAT), `TCA_DB_KEY` (optional encryption passphrase).
153
+
154
+ | Command | Purpose |
155
+ |---|---|
156
+ | `tca init` | Wizard → `collector.toml` + empty package file |
157
+ | `tca verify` | Pre-flight: config, secret, sign-in, Admin Insights/VDS, Metadata API, file. Collects nothing |
158
+ | `tca collect` | The main run: fetch → pseudonymise → land → normalize. `--modules/-m`, `--resume` |
159
+ | `tca summary` | What the file holds: runs, pages, vault size, history window, coverage gaps |
160
+ | `tca peek VIEW` | Browse the latest snapshot WITH real identities (local only): `users`, `members`, `content`, `rules` |
161
+ | `tca resolve U-####` | One pseudonym → full identity, from the local vault |
162
+ | `tca export [OUT]` | The shareable copy: everything except `identity`, verified, + SHA-256 |
163
+
164
+ Modules for `collect` (default set = all six):
165
+ `rest_core` (users, groups, membership), `content` (projects, workbooks,
166
+ views, datasources, connections), `automation` (refresh tasks, jobs,
167
+ subscriptions), `permissions` (project + default-template + per-item rules),
168
+ `activity` (Admin Insights via VDS: TS Events, TS Users, Site Content, Tokens,
169
+ Job Performance, Groups, Permissions, Subscriptions, Viz Load Times),
170
+ `metadata` (Metadata API GraphQL: fields, calculated-field formulas, lineage,
171
+ sheet field usage).
172
+
173
+ Exit codes: `0` success · `1` actionable user error (clear message, no
174
+ traceback) · `130` Ctrl-C (the run is resumable).
175
+
176
+ ## 6. Conventions and stack (respect these in any code you write)
177
+
178
+ - **Language**: conversation with the maintainer happens in Italian;
179
+ **everything that ships in the repo is English only** — code, identifiers,
180
+ comments, docstrings, commit messages, CLI output, docs.
181
+ - **Stack**: Python ≥3.11 (developed on 3.13). Runtime deps deliberately short
182
+ — `httpx`, `duckdb` (≥1.4, for native encryption), `typer`, `pydantic`,
183
+ `rich`. Own HTTP client, NOT `tableauserverclient`. Standard `venv`.
184
+ License **Apache-2.0**.
185
+ - **Tooling**: `ruff` (format + lint, line length 100), `mypy` (strict; types
186
+ on all public signatures), `pytest` (one test file per module). CI runs
187
+ ruff + mypy + the full suite on Python 3.11 and 3.13; **the schema contract
188
+ test gates every merge**.
189
+ - **Docstrings** only on the public API surface (CLI commands, module entry
190
+ points). No comments on obvious internal code. A comment states a constraint
191
+ the code can't show — never narrates what the next line does.
192
+ - **Error handling**: fail loud. No silent `except: pass`. Network/auth errors
193
+ propagate with a clear `rich`-formatted message. The PAT is never logged or
194
+ printed, including in error output.
195
+ - **Commits**: Conventional Commits (`feat:`, `fix:`, `docs:`, `test:` …).
196
+ **The maintainer commits personally** — an AI should propose the commit
197
+ message, not run `git commit`, unless explicitly asked.
198
+ - **Schema migrations are additive-only and released files are FROZEN.** Never
199
+ edit `001`…`007`; a new need gets a new numbered file. Tables/columns/views
200
+ never disappear or change type — the analyst's engine depends on this
201
+ guarantee, enforced by `tests/test_schema_contract.py`. When the schema
202
+ grows, update the golden snapshot in that test in the same change.
203
+ - **Docs discipline**: `docs/api-coverage.md` (endpoint status) and
204
+ `docs/package-file-schema.md` / `docs/what-we-collect.md` are kept in sync
205
+ with the code in the same change that alters an endpoint or the schema;
206
+ drift is caught by `tests/test_docs.py`. Only PUBLIC docs live in `docs/` —
207
+ no business/IP material.
208
+
209
+ ## 7. Known subtleties (so you don't "rediscover" or break them)
210
+
211
+ - **The package file is long-lived and reused run after run.** It IS the
212
+ archive: deleting it regenerates pseudonyms and breaks cross-run
213
+ comparability. History accumulates in it.
214
+ - **`raw.api_responses` is the resume checkpoint.** A unit is
215
+ `(run_id, endpoint, entity_luid, page)`; resume skips already-landed units.
216
+ History accumulation reads *back from raw*, so a crash between landing and
217
+ accumulating heals on re-run.
218
+ - **`v_*_current` semantics (v0.7)**: each `state.v_*_current` view follows the
219
+ latest ok run that *actually collected that data* (via
220
+ `meta.v_endpoint_latest_run`), NOT the single latest run. This is so a
221
+ partial run (e.g. `-m rest_core`) refreshes only users and doesn't empty the
222
+ permissions/content views. An empty listing still lands a page, so
223
+ genuinely-empty data stays empty (no stale ghosts).
224
+ - **`EXPORT_SAFE_VIEWS`** in `writer.py` recreates identity-free convenience
225
+ views inside the export and must stay in sync with migrations 002/003/007.
226
+ The `clear.*` views must NEVER be added there (they touch identity).
227
+ - **Encryption is opt-in but recommended**: if `TCA_DB_KEY` is unset the file
228
+ is written unencrypted with a clear warning (maintainer's decision — no hard
229
+ error, no interactive prompt). An encrypted file can't be opened with a bare
230
+ `duckdb file.duckdb`; use the CLI or the documented ATTACH+key incantation.
231
+ - **Live-testing detail**: `pod` is the bare pod name (`10ax`), not a URL; the
232
+ site `contentUrl` may differ from the display name (dashes stripped). Both
233
+ are documented failure modes.
234
+ - **Tests are hermetic**: HTTP is mocked with `pytest-httpx`; no test touches a
235
+ real Tableau site. When you add a source, add a test in the same style.
236
+
237
+ ## 8. Project status and roadmap (as of mid-2026)
238
+
239
+ The collector is **feature-complete for its MVP scope and verified live**
240
+ against a real Tableau Cloud sandbox. CI is green; the docs site is published.
241
+ Schema is at v0.7. ~98 tests pass.
242
+
243
+ What is deliberately deferred or out of scope (do not "helpfully" build these
244
+ without discussing scope first): the proprietary analysis engine (§2); typed
245
+ state for VDS sources beyond what exists; several backlog REST endpoints
246
+ (flows, virtual connections, data alerts, webhooks, site settings — see
247
+ `docs/api-coverage.md` for the tracker with statuses); a Parquet export escape
248
+ hatch; optional group pseudonymisation (`G-####`).
249
+
250
+ Known honest gaps that are NOT code problems (useful context if the user asks
251
+ "what would make this successful"): it has only been run against one small
252
+ site (scale behaviour on a 10k-user estate is unproven); the free tool lacks a
253
+ one-command "wow" artifact (a teaser `tca report` is the highest-leverage idea
254
+ on the table); distribution/adoption and the first paid assessment are the
255
+ real business risks, not the code; Windows packaging (the typical Tableau
256
+ admin persona) is still via `pip`/`uv` rather than a signed installer.
257
+
258
+ ## 9. How to be genuinely useful here
259
+
260
+ - **Ground your answers in the actual code when you can see it.** File paths in
261
+ this guide are stable pointers; open them.
262
+ - **Honor the two invariants above all**: the moat rule (§2) and the privacy
263
+ model (§3). If a request would violate either, say so plainly and offer the
264
+ in-scope alternative — that is more helpful than a compliant-but-wrong patch.
265
+ - **Match the house style** (§6): English in the repo, short deps, fail loud,
266
+ additive-only schema, propose commits rather than making them.
267
+ - **When unsure, say so.** This project values honesty over confident guessing;
268
+ the maintainer explicitly prefers "I don't know, here's how to find out" to a
269
+ fabricated answer. If this guide and the code disagree, the code wins — and
270
+ point out the drift so the docs can be fixed.
271
+ - **Keep public/private separation sacred**: never suggest committing
272
+ business logic, analysis heuristics, or IP material to this public repo.
273
+
274
+ ---
275
+
276
+ *This guide describes the repository's intent and invariants; the code is the
277
+ final authority. If you spot a contradiction between this file and the code,
278
+ that is a bug in this file — mention it.*