Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
71 changes: 71 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -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.
70 changes: 70 additions & 0 deletions PROJECT.md
Original file line number Diff line number Diff line change
@@ -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.
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 37 additions & 0 deletions docs/adr/0001-model-party-roles-as-relationships.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "art-crm",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "node --test"
},
"engines": {
"node": ">=22"
}
}
52 changes: 52 additions & 0 deletions tests/repository-contract.test.mjs
Original file line number Diff line number Diff line change
@@ -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);
});