From 96921c86ecdc40384cae852e364974658652d91b Mon Sep 17 00:00:00 2001 From: Reid Surmeier Date: Fri, 31 Jul 2026 01:22:24 -0400 Subject: [PATCH] Document the planned CRM domain --- .github/workflows/validate.yml | 23 ++++++ AGENTS.md | 38 ++++++++++ CONTEXT.md | 71 +++++++++++++++++++ PROJECT.md | 70 ++++++++++++++++++ README.md | 43 ++++++++++- ...0001-model-party-roles-as-relationships.md | 37 ++++++++++ package-lock.json | 15 ++++ package.json | 12 ++++ tests/repository-contract.test.mjs | 52 ++++++++++++++ 9 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 AGENTS.md create mode 100644 CONTEXT.md create mode 100644 PROJECT.md create mode 100644 docs/adr/0001-model-party-roles-as-relationships.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/repository-contract.test.mjs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..5bbc3fd --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,23 @@ +name: Validate + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm test diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fbe9547 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,38 @@ +# Agent guide + +Read `PROJECT.md`, `CONTEXT.md`, and the accepted ADRs before changing this +repository. It is a planned product scaffold, not a working CRM. + +## Commands + +```bash +npm ci +npm test +git diff --check +``` + +Run all three commands before claiming the current documentation contract works. +Once implementation begins, add its lint, type, integration, persistence, and +export checks here before treating them as required gates. + +## Operating rules + +- Work test-first in one tracer-bullet workflow at a time. +- Preserve the factual distinction between planned and implemented behavior. +- Use the domain terms in `CONTEXT.md`; update that document and an ADR when a + tested workflow disproves the current language. +- Keep Party identity separate from Party Roles. +- Keep an authored Work separate from a physical Inventory Object. +- Use temporary synthetic fixtures in tests. Never commit real contact, + collector, artist, sales, provenance, financial, or location data. +- Do not add credentials, `.env` files, raw secret scans, or provider + responses. +- Do not deploy, enable Pages, or import production data as a side effect of + tests. + +## First implementation slice + +The next slice is Inventory Intake: Party → Work → Inventory Object. Do not +start with dashboards, sales funnels, websites, or bulk imports. Earn those +surfaces from tested operational workflows and explicit data-custody +decisions. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..87d7816 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,71 @@ +# Art CRM domain + +## Language + +**Party**: +A person or organization known to the gallery. +_Avoid_: Contact type, customer + +**Party Role**: +A time-aware relationship a Party holds in a gallery workflow, such as artist, +collector, consignor, consignee, gallery, advisor, or vendor. +_Avoid_: Permanent user type, identity + +**Contact Channel**: +A Party's address, email, telephone number, or other communication route, +together with its use and consent state. +_Avoid_: Contact + +**Work**: +An authored artistic work independent of any particular physical object, +edition item, custody state, or sale. +_Avoid_: Inventory item, SKU + +**Inventory Object**: +One identifiable physical object or edition-specific item through which a Work +is held, located, consigned, exhibited, or sold. +_Avoid_: Work, product + +**Consignment**: +A time-bounded agreement assigning custody or authority to offer one or more +Inventory Objects without silently changing ownership. +_Avoid_: Sale, location + +**Sale**: +A recorded transfer involving one or more Inventory Objects, participating +Parties, terms, and a completed or cancelled state. +_Avoid_: Lead, consignment + +**Exhibition**: +A time-bounded presentation that includes Inventory Objects without implying +ownership transfer, availability, or custody. +_Avoid_: Category, sale + +**Inventory Intake**: +The first controlled workflow that establishes a Party, Work, and Inventory +Object with validated identity, status, and location. +_Avoid_: Import, product creation + +## Relationships + +- A **Party** may hold several **Party Roles**, concurrently or over time. +- A **Party** may have several **Contact Channels**. +- A **Party** in an artist **Party Role** may create one or more **Works**. +- A **Work** may have one or more **Inventory Objects**. +- A **Consignment** relates participating **Parties** to one or more + **Inventory Objects** for a bounded period. +- A **Sale** relates participating **Parties** and transferred **Inventory + Objects** without changing the identity of their **Works**. +- An **Exhibition** includes **Inventory Objects** for a bounded period. +- **Inventory Intake** establishes the minimum identities needed before later + workflows can refer to them safely. + +## Open questions + +- Whether the first user is a solo artist, a gallery team, or both. +- Whether an edition should be modeled between Work and Inventory Object. +- Which provenance, valuation, tax, currency, and jurisdiction fields are + required. +- Which Party Roles and status vocabularies need controlled extension. +- What access, audit-log, retention, consent, export, and backup guarantees are + necessary before admitting real data. diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..4289a08 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,70 @@ +# Art CRM + +## Goal + +Give a gallery or artist a trustworthy operational record of relationships, +artworks, physical inventory, consignments, sales, and exhibitions without +locking those records inside an opaque workflow. + +## Current state + +- Lifecycle: planned. +- Canonical repository: `ReidSurmeier/art-crm`. +- Canonical checkout: `/home/reidsurmeier/src/art-crm`. +- No application code, data schema, or deployment exists. +- The sole original commit contained a one-sentence README and broad + Node/Python ignore rules. +- GitHub currently reports a public repository on `main`, with no Pages site + and no Actions workflows. +- The repository was created after a gallery-CRM market review, but no durable + requirements or implementation plan were committed with it. + +## Decisions + +- Model an actor as a Party and its changing gallery relationships as Party + Roles; do not make artist, collector, gallery, or vendor permanent contact + types. +- Distinguish an authored Work from each physical or edition-specific + Inventory Object. +- Use tracer-bullet workflows to choose technical architecture from observed + needs instead of selecting a full application stack at scaffold time. +- Keep deployment ownership `none` until a real application surface and data + custody model exist. + +## Boundaries + +- Do not claim contacts, inventory, consignments, sales, or exhibitions are + implemented until their behavior has executable tests. +- Do not import real personal, financial, sales, provenance, or location data + without explicit access, retention, backup, export, and privacy decisions. +- Do not collapse a Work into a physical Inventory Object; editions and + multiples require separate identities. +- Do not encode a Party's role as its permanent identity. +- Do not publish or deploy the scaffold as a product. + +## Completion criteria for the first slice + +- A Party can be created without assigning it a permanent business type. +- A Work can identify its creating Party. +- An Inventory Object can identify its Work, current status, and current + location. +- Invalid or ambiguous records fail closed. +- The complete slice is covered by tests and has a documented local + persistence boundary. +- Export and backup behavior are specified before real records are admitted. + +## Exact next action + +Implement the Inventory Intake tracer bullet test-first: create one Party, one +Work, and one Inventory Object; validate their identities and relationships; +then choose the smallest local persistence adapter that preserves an explicit +export path. + +## Last verified + +2026-07-31: GitHub contained one initial commit (`7fd26da`) with only +`.gitignore` and `README.md`; Pages returned no site and the repository had no +Actions workflows or known runtime owner. The missing original canonical +checkout was restored additively at `/home/reidsurmeier/src/art-crm` and +registered in Orca. No feature or deployment claim was inferred from the +repository description or earlier market research. diff --git a/README.md b/README.md index b67eabb..b52e0bf 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,42 @@ -# art-crm +# Art CRM -Gallery/artist CRM for contacts, inventory, consignments, sales, and exhibitions. +A planned gallery and artist relationship-management system for contacts, +inventory, consignments, sales, and exhibitions. + +This repository is currently a documented product scaffold. It does not yet +contain application code, a data schema, production data, or a deployment. +The documentation establishes the product boundary and the first testable +implementation slice without presenting planned capabilities as working +features. + +## Work locally + +Requirements: Node.js 22 or later. The repository currently has no runtime +dependencies. + +```bash +npm ci +npm test +``` + +## Repository map + +- `PROJECT.md` records lifecycle, boundaries, evidence, and the exact next + action. +- `AGENTS.md` defines safe operating and verification rules. +- `CONTEXT.md` defines the initial gallery-operations language. +- `docs/adr/` records decisions that should survive implementation choices. +- `tests/` protects the scaffold's documentation and truthfulness contract. +- `.github/workflows/validate.yml` validates pull requests and `main` without + deploying the scaffold. + +## Current boundary + +The first implementation target is an Inventory Intake tracer bullet: record a +Party, an authored Work, and one physical Inventory Object with explicit +identity and status. Persistence, user interface, authentication, and hosting +choices remain open until that slice is designed test-first. + +No deployment exists. Do not enter real collector, artist, sales, provenance, +or financial data until access, backup, export, and privacy requirements are +decided and tested. diff --git a/docs/adr/0001-model-party-roles-as-relationships.md b/docs/adr/0001-model-party-roles-as-relationships.md new file mode 100644 index 0000000..49383d4 --- /dev/null +++ b/docs/adr/0001-model-party-roles-as-relationships.md @@ -0,0 +1,37 @@ +# ADR 0001: Model Party Roles as relationships + +- Status: Accepted +- Date: 2026-07-31 + +## Context + +The planned CRM covers artists, collectors, galleries, consignors, consignees, +advisors, vendors, and other participants. One person or organization may hold +several of those roles, and those roles can change between transactions and +over time. + +Making each role a permanent contact type would duplicate Parties, lose +history, and force later workflows to choose one identity where several +relationships are true. The product also needs to distinguish an authored Work +from each physical or edition-specific Inventory Object that is located, +consigned, exhibited, or sold. + +## Decision + +Represent every person or organization as a Party. Represent artist, +collector, gallery, consignor, consignee, advisor, vendor, and similar +classifications as Party Roles attached through explicit relationships. + +Represent an authored Work independently from its Inventory Objects. The first +implementation slice must prove Party → Work → Inventory Object before adding +consignment, sale, exhibition, interface, or deployment concerns. + +## Consequences + +- A Party can participate in several workflows without duplicate identities. +- Role history can become time-aware without changing Party identity. +- Editions and multiples can gain object-level status, location, custody, and + transaction history. +- Queries require relationship traversal rather than one fixed type column. +- The Inventory Intake tracer bullet becomes the architecture-driving next + step. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7f540ba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15 @@ +{ + "name": "art-crm", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "art-crm", + "version": "0.0.0", + "engines": { + "node": ">=22" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..64d9fe3 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "art-crm", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "node --test" + }, + "engines": { + "node": ">=22" + } +} diff --git a/tests/repository-contract.test.mjs b/tests/repository-contract.test.mjs new file mode 100644 index 0000000..865cff1 --- /dev/null +++ b/tests/repository-contract.test.mjs @@ -0,0 +1,52 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +const root = new URL("../", import.meta.url); + +async function read(path) { + return readFile(new URL(path, root), "utf8"); +} + +test("the planned project carries the complete documentation contract", async () => { + for (const path of [ + "README.md", + "PROJECT.md", + "AGENTS.md", + "CONTEXT.md", + "docs/adr/0001-model-party-roles-as-relationships.md", + ]) { + assert.ok((await read(path)).trim(), `${path} must exist and be non-empty`); + } +}); + +test("project status does not overstate the scaffold", async () => { + const project = await read("PROJECT.md"); + + assert.match(project, /Lifecycle: planned/); + assert.match(project, /No application code, data schema, or deployment exists/); + assert.match(project, /Inventory Intake tracer bullet/); +}); + +test("the domain separates works, objects, and party roles", async () => { + const context = await read("CONTEXT.md"); + + assert.match(context, /\*\*Party\*\*/); + assert.match(context, /\*\*Party Role\*\*/); + assert.match(context, /\*\*Work\*\*/); + assert.match(context, /\*\*Inventory Object\*\*/); + assert.match(context, /A \*\*Party\*\* may hold several \*\*Party Roles\*\*/); + assert.match(context, /A \*\*Work\*\* may have one or more \*\*Inventory Objects\*\*/); +}); + +test("GitHub validates the scaffold without deploying it", async () => { + const workflow = await read(".github/workflows/validate.yml"); + + assert.match(workflow, /pull_request:/); + assert.match(workflow, /branches: \[main\]/); + assert.match(workflow, /actions\/checkout@v7/); + assert.match(workflow, /actions\/setup-node@v7/); + assert.match(workflow, /npm ci/); + assert.match(workflow, /npm test/); + assert.doesNotMatch(workflow, /deploy|pages/i); +});