diff --git a/.obsidian/plugins/hoarder-sync/data.json b/.obsidian/plugins/hoarder-sync/data.json index 01f48aa..4e2c9b8 100644 --- a/.obsidian/plugins/hoarder-sync/data.json +++ b/.obsidian/plugins/hoarder-sync/data.json @@ -4,7 +4,7 @@ "syncFolder": "Hoarder", "attachmentsFolder": "Hoarder/attachments", "syncIntervalMinutes": 60, - "lastSyncTimestamp": 1778004999245, + "lastSyncTimestamp": 1778010108899, "updateExistingFiles": false, "excludeArchived": true, "onlyFavorites": false, diff --git a/99 Daily/2026-05-05.md b/99 Daily/2026-05-05.md index fb3fe4a..8befcff 100644 --- a/99 Daily/2026-05-05.md +++ b/99 Daily/2026-05-05.md @@ -210,3 +210,6 @@ tags: [daily] - 19:50 | `aimpress` - **Asked:** How to manage forgotten PVE server service credentials securely? - **Done:** Populated Vaultwarden with discovered services and credentials, decrypting password hashes using Kali tools. +- 21:03 (1min) | `memory-compiler` + - **Asked:** Set up knowledge compiler for personal wiki using Karpathy's LLM schema in Obsidian. + - **Done:** Created 3 concept articles documenting iCloud duplicates, username format, and SQLite boolean patterns. diff --git a/wiki/client-knowledge/3m.md b/wiki/client-knowledge/3m.md index 189ce85..f586912 100644 --- a/wiki/client-knowledge/3m.md +++ b/wiki/client-knowledge/3m.md @@ -60,6 +60,8 @@ Same platform used by H&M. See [[wiki/client-knowledge/hm|H&M client knowledge]] **Key quirk:** `sessionStorage` is used (not localStorage) — session is cleared on browser close. Users must log in again each browser session. This is intentional for security. +**User identity:** One2Edit usernames are email addresses in the format `FirstnameSurname@oliver.agency`. Example: Paul Johns → `PaulJohns@oliver.agency`. Look up users via `GET /api/users?clientId=` — do not construct usernames by guessing. See [[wiki/concepts/one2edit-username-format|one2edit-username-format]]. + --- ## Related diff --git a/wiki/concepts/icloud-space-2-duplicate-files.md b/wiki/concepts/icloud-space-2-duplicate-files.md new file mode 100644 index 0000000..d4c590b --- /dev/null +++ b/wiki/concepts/icloud-space-2-duplicate-files.md @@ -0,0 +1,49 @@ +--- +title: "iCloud Concurrent Write: ' 2.md' Duplicate Files" +aliases: [icloud-space-2, icloud-duplicate-files] +tags: [icloud, macos, concurrency, filesystem] +sources: [memory-compiler] +created: 2026-05-05 +updated: 2026-05-05 +--- + +# iCloud Concurrent Write: ' 2.md' Duplicate Files + +When two devices write to the same iCloud Drive file at the same time, iCloud resolves the conflict by keeping both versions. The second version gets a space-number suffix: `filename 2.md` (space before the number, not a hyphen or underscore). + +## Key Takeaways + +- Glob pattern to find duplicates: `* 2.md` (space before 2) +- Distinct from `.git` merge conflicts — this is iCloud's own conflict resolution +- Can silently accumulate: if not cleaned up, you may get `filename 2.md`, `filename 3.md`, etc. +- Most common in wiki/note systems where two machines flush to iCloud roughly simultaneously + +## When It Happens + +- Machine 1 is writing a file while Machine 2 is also writing the same file +- The memory-compiler multi-device workflow is a classic trigger: flush.py on Machine 2 writes the daily log; compile.py on Machine 1 also writes to wiki notes — both during the same ~21:00 window + +## Detection & Cleanup + +```bash +# Find all iCloud duplicate files (space-2 suffix) +find . -name "* 2.md" + +# Or with glob (zsh) +ls **/*\ 2.md +``` + +The memory-compiler runs a LaunchAgent dedup at **09:00** and **21:30** that removes these duplicates automatically. + +## Distinction from icloud-git-sync-conflict + +| Scenario | File produced | +|----------|--------------| +| iCloud concurrent write (any file) | `filename 2.md` | +| git conflict in iCloud-synced repo | `filename.md` with `<<<<<<` markers | + +See [[wiki/concepts/icloud-git-sync-conflict|icloud-git-sync-conflict]] for the git-specific variant. + +## Related + +- [[wiki/concepts/icloud-git-sync-conflict|icloud-git-sync-conflict]] — git conflict markers in iCloud-synced repos diff --git a/wiki/concepts/one2edit-username-format.md b/wiki/concepts/one2edit-username-format.md new file mode 100644 index 0000000..8442f56 --- /dev/null +++ b/wiki/concepts/one2edit-username-format.md @@ -0,0 +1,48 @@ +--- +title: "One2Edit Username Format" +aliases: [one2edit-username, one2edit-users-api] +tags: [one2edit, api, auth, 3m, hm] +sources: [01 Projects/3m-portal] +created: 2026-05-05 +updated: 2026-05-05 +--- + +# One2Edit Username Format + +One2Edit usernames are **email addresses**, not `firstname.lastname` handles. This trips up anyone guessing usernames from employee names. + +## Key Takeaways + +- Username format: `FirstnameSurname@oliver.agency` (no dot, no hyphen) +- Example: Paul Johns → `PaulJohns@oliver.agency` +- Do NOT guess `paul.johns` or `paul.johns@oliver.agency` — these do not exist +- Use the users API to look up valid usernames, don't guess + +## Users API + +``` +GET /api/users?clientId= +``` + +- Requires `clientId` parameter — the request will fail or return nothing without it +- Authenticate with service account (`portal@oliver.agency`) via the CORS proxy before calling +- Returns a list of users for the given client, each with their email-format username + +### Lookup Flow + +``` +1. Auth: POST /Api.php → externSessionId (service account) +2. Fetch: GET /api/users?clientId= → [{username, ...}, ...] +3. Use: pass username (email) to login/session endpoints +``` + +## Common Mistake + +> "I'll just try `paul.johns` or `p.johns@oliver.agency`" + +Neither works. The canonical username is the email without dots: `PaulJohns@oliver.agency`. Always look it up from the API rather than constructing it from a name. + +## Related + +- [[wiki/tech-patterns/one2edit-api|one2edit-api]] — full One2Edit API integration pattern +- [[wiki/client-knowledge/3m|3m]] — 3M OMG Portal where this pattern applies diff --git a/wiki/concepts/sqlite-not-null-as-boolean.md b/wiki/concepts/sqlite-not-null-as-boolean.md new file mode 100644 index 0000000..72bce4e --- /dev/null +++ b/wiki/concepts/sqlite-not-null-as-boolean.md @@ -0,0 +1,58 @@ +--- +title: "SQLite: IS NOT NULL AS Boolean" +aliases: [sqlite-not-null-boolean, sqlite-derived-boolean] +tags: [sqlite, sql, security, pattern] +sources: [sandbox] +created: 2026-05-05 +updated: 2026-05-05 +--- + +# SQLite: IS NOT NULL AS Boolean + +When a column contains sensitive data (e.g. a password hash), never select it directly to answer a yes/no question. Use `(column IS NOT NULL) AS flag` to return a derived boolean instead. + +## Key Takeaways + +- `(password_hash IS NOT NULL) AS has_password` → returns `1` or `0`, never the hash +- Hash never leaves the database layer — client gets the correct boolean +- Standard pattern for any column that must stay server-side + +## Pattern + +```sql +-- BAD: leaks the hash to the application layer +SELECT id, email, password_hash FROM users WHERE id = ?; + +-- GOOD: returns a boolean, hash stays in DB +SELECT id, email, (password_hash IS NOT NULL) AS has_password FROM users WHERE id = ?; +``` + +In SQLite, the expression evaluates to `1` (true) or `0` (false). Map to a JS/Python boolean at the application layer: + +```js +// Node.js / better-sqlite3 +const user = db.prepare(` + SELECT id, email, (password_hash IS NOT NULL) AS has_password + FROM users WHERE id = ? +`).get(userId); + +// SQLite returns 1/0; coerce to boolean +user.hasPassword = Boolean(user.has_password); +``` + +## Bug This Fixes + +The `userPublic()` helper was selecting `u.password_hash` directly. The result object included the raw hash — or, worse, `undefined` when the column was NULL — causing `hasPassword` to always evaluate to `false` (because the hash value was truthy but the field name wasn't mapped correctly). + +Fix: replace the direct column with `(password_hash IS NOT NULL) AS has_password`. + +## Generalisation + +The same pattern applies to any secret or large binary: +- `(api_key IS NOT NULL) AS has_api_key` +- `(avatar_blob IS NOT NULL) AS has_avatar` +- `(refresh_token IS NOT NULL) AS is_linked` + +## Related + +- [[wiki/concepts/fastapi-response-model-silent-field-strip|fastapi-response-model-silent-field-strip]] — similar idea: strip sensitive fields at the serialization layer diff --git a/wiki/log.md b/wiki/log.md index 5579a2b..93c5586 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -1,6 +1,11 @@ # Build Log +## [2026-05-05T21:00:00+01:00] compile | 2026-05-05.md (pass 2) +- Source: daily/2026-05-05.md +- Articles created: [[wiki/concepts/icloud-space-2-duplicate-files]], [[wiki/concepts/one2edit-username-format]], [[wiki/concepts/sqlite-not-null-as-boolean]] +- Articles updated: [[wiki/tech-patterns/one2edit-api]] (added Users API section + username=email gotcha); [[wiki/client-knowledge/3m]] (added One2Edit user identity note) + ## [2026-05-05T23:59:00+01:00] compile | 2026-05-05.md - Source: daily/2026-05-05.md - Articles created: [[wiki/concepts/vite-prebuilt-subpath-workaround]], [[wiki/concepts/llamaextract-data-none-gotcha]] diff --git a/wiki/tech-patterns/one2edit-api.md b/wiki/tech-patterns/one2edit-api.md index c7e3877..068ff34 100644 --- a/wiki/tech-patterns/one2edit-api.md +++ b/wiki/tech-patterns/one2edit-api.md @@ -53,7 +53,20 @@ Any client project built on the One2Edit platform (3M, H&M). - [[01 Projects/3m-portal/3M OMG Portal|3M OMG Portal]] — Full portal: CORS proxy + Node.js backend + embedded SDK - [[01 Projects/hm-o2e-tool/HM O2E Tool|H&M O2E Tool]] — Static tool: image relinking + document export (no proxy needed — called directly or via `python -m http.server`) +## Users API + +``` +GET /api/users?clientId= +``` + +- `clientId` is **required** — the request silently fails or returns nothing without it +- Auth with service account session before calling +- Returns user list with email-format usernames + +See [[wiki/concepts/one2edit-username-format|one2edit-username-format]] for full details. + ## Gotchas & Lessons +- **Username format is an email address** — `FirstnameSurname@oliver.agency` (e.g. `PaulJohns@oliver.agency`). Never guess `firstname.lastname` — it doesn't exist as a format - 301/302 redirects from One2Edit mean auth failure — the Node proxy converts them to 401 to prevent redirect loops in the browser - `sessionStorage` (not `localStorage`) — sessions clear on browser close, which is correct for this auth model - H&M O2E tool is static (no backend) — can run without a server for most operations