Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
85920ba
feat(gateway): add Result<T> type with tests
rsalus Jan 27, 2026
46fcdb9
feat(gateway): add Error and ErrorType with tests
rsalus Jan 27, 2026
0c6bf86
feat(gateway): add ErrorFactory with tests
rsalus Jan 27, 2026
1529ca0
feat(gateway): add FhirErrors with tests
rsalus Jan 27, 2026
5eaf99b
feat(gateway): add EpicFhirOptions with tests
rsalus Jan 27, 2026
7413fdb
feat(gateway): add IntelligenceOptions with tests
rsalus Jan 27, 2026
24065e5
feat(gateway): add IHttpClientProvider interface
rsalus Jan 27, 2026
8b1d2be
feat(gateway): add ResiliencyOptions with tests
rsalus Jan 27, 2026
e28e304
feat(gateway): add HttpClientProvider with token caching
rsalus Jan 27, 2026
6a82622
feat(gateway): add ServiceCollectionExtensions with resilience
rsalus Jan 27, 2026
f19aa08
feat(gateway): add IFhirSerializer and FhirSerializer with tests
rsalus Jan 27, 2026
f0f83d4
refactor(gateway): update EpicFhirContext to use IFhirSerializer
rsalus Jan 27, 2026
77edb22
Merge branch 'feature/008-fhir-serializer' into feature/013-service-m…
rsalus Jan 27, 2026
c25a0eb
chore: merge all infrastructure branches for service migration
rsalus Jan 27, 2026
106431a
fix: resolve merge conflicts in configuration and serializer types
rsalus Jan 27, 2026
2590112
feat(gateway): update IEpicFhirClient to return Result<T>
rsalus Jan 27, 2026
45a494d
feat(gateway): update EpicFhirClient implementation for Result<T>
rsalus Jan 27, 2026
040b12f
feat(gateway): update service interfaces to return Result<T>
rsalus Jan 27, 2026
62190ba
feat(gateway): update endpoint handlers for Result<T>
rsalus Jan 27, 2026
9430931
feat(gateway): consolidate DI registration in ServiceCollectionExtens…
rsalus Jan 27, 2026
4424e8f
Merge feature/013-service-migration (gateway FHIR refactor)
rsalus Jan 27, 2026
24beee1
refactor(gateway): vendor-agnostic naming and architecture cleanup
rsalus Jan 27, 2026
cbd1855
refactor: remove CDS/vendor-specific code and create stubs
rsalus Jan 27, 2026
c5d7cf2
fix(intelligence): wrap long line for ruff E501 compliance
rsalus Jan 27, 2026
13e6963
fix: code style
rsalus Jan 27, 2026
5be600e
chore: regenerate schemas from Intelligence OpenAPI spec
rsalus Jan 27, 2026
127038f
fix: address PR review feedback
rsalus Jan 27, 2026
c79a93f
test(intelligence): add tests for stub implementations
rsalus Jan 27, 2026
6759ccb
fix: address additional PR review feedback (round 2)
rsalus Jan 27, 2026
db26427
feat: add GitHub Project automation for PRs and issues
rsalus Jan 27, 2026
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
50 changes: 50 additions & 0 deletions .dockerignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
# ===========================================================================
# Root .dockerignore for monorepo Docker builds
# ===========================================================================

# Git
.git/
.gitignore

# Node modules (will be installed fresh in container)
**/node_modules/

# Python virtual environments
**/.venv/
**/__pycache__/
**/*.pyc

# IDE
.idea/
.vscode/
*.swp
*.swo

# Test artifacts
**/coverage/
**/.vitest/
**/.pytest_cache/

# Build artifacts we don't need
**/*.log
**/npm-debug.log*

# OS files
.DS_Store
Thumbs.db

# Documentation (not needed in containers)
docs/

# Orchestration (not needed in app containers)
orchestration/

# Temp files
*.txt
*.tmp

# Keep these (explicitly)
!apps/dashboard/dist/
!apps/dashboard/nginx.conf
!shared/types/dist/
!shared/validation/dist/
262 changes: 262 additions & 0 deletions .github/workflows/project-automation.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -205,6 +205,268 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# ============================================================
# PROJECT: Add PRs to Project #4 with workstream assignment
# ============================================================
add-pr-to-project:
if: |
github.event_name == 'pull_request' &&
github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- name: Checkout for file detection
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Add PR to project
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const pr = context.payload.pull_request;

// Get project and field info
const projectResult = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
projectV2(number: $number) {
id
fields(first: 20) {
nodes {
... on ProjectV2SingleSelectField {
id
name
options { id name }
}
}
}
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
number: parseInt(process.env.PROJECT_NUMBER)
});

const project = projectResult.repository.projectV2;
if (!project) {
console.log(`Project #${process.env.PROJECT_NUMBER} not found`);
return;
}

const workstreamField = project.fields.nodes.find(f => f.name === 'Workstream');

// Add PR to project
const addItemMutation = `
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
item { id }
}
}
`;

const addResult = await github.graphql(addItemMutation, {
projectId: project.id,
contentId: pr.node_id
});

const itemId = addResult.addProjectV2ItemById.item.id;
console.log(`Added PR #${pr.number} to project, item ID: ${itemId}`);

// Determine workstream based on changed files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
});

const touchesGateway = files.some(f => f.filename.startsWith('apps/gateway/'));
const touchesIntelligence = files.some(f => f.filename.startsWith('apps/intelligence/'));

let workstreamValue = null;
if (touchesGateway && !touchesIntelligence) {
workstreamValue = 'Gateway (.NET)';
} else if (touchesIntelligence && !touchesGateway) {
workstreamValue = 'Intelligence (Python)';
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// If both or neither, leave unset

if (workstreamValue && workstreamField) {
const option = workstreamField.options.find(o => o.name === workstreamValue);
if (option) {
const updateFieldMutation = `
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}) {
projectV2Item { id }
}
}
`;

await github.graphql(updateFieldMutation, {
projectId: project.id,
itemId: itemId,
fieldId: workstreamField.id,
optionId: option.id
});

console.log(`Set Workstream to: ${workstreamValue}`);
}
}

# ============================================================
# PROJECT: Add issues to Project #4 with priority and workstream
# ============================================================
add-issue-to-project:
if: github.event_name == 'issues' && github.event.action == 'opened'
needs: auto-triage
runs-on: ubuntu-latest
steps:
- name: Add issue to project
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const issue = context.payload.issue;

// Get project and field info
const projectResult = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
projectV2(number: $number) {
id
fields(first: 20) {
nodes {
... on ProjectV2SingleSelectField {
id
name
options { id name }
}
}
}
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
number: parseInt(process.env.PROJECT_NUMBER)
});

const project = projectResult.repository.projectV2;
if (!project) {
console.log(`Project #${process.env.PROJECT_NUMBER} not found`);
return;
}

const priorityField = project.fields.nodes.find(f => f.name === 'Priority');
const workstreamField = project.fields.nodes.find(f => f.name === 'Workstream');

// Add issue to project
const addItemMutation = `
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
item { id }
}
}
`;

const addResult = await github.graphql(addItemMutation, {
projectId: project.id,
contentId: issue.node_id
});

const itemId = addResult.addProjectV2ItemById.item.id;
console.log(`Added issue #${issue.number} to project, item ID: ${itemId}`);

// Re-fetch issue to get labels (auto-triage may have added them)
const { data: freshIssue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number
});

const labels = freshIssue.labels.map(l => l.name);

// Set Priority based on labels
if (priorityField) {
let priorityValue = 'Medium'; // Default
if (labels.includes('priority:high')) {
priorityValue = 'High';
} else if (labels.includes('priority:low')) {
priorityValue = 'Low';
}

const option = priorityField.options.find(o => o.name === priorityValue);
if (option) {
const updateFieldMutation = `
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}) {
projectV2Item { id }
}
}
`;

await github.graphql(updateFieldMutation, {
projectId: project.id,
itemId: itemId,
fieldId: priorityField.id,
optionId: option.id
});

console.log(`Set Priority to: ${priorityValue}`);
}
}

// Set Workstream based on scope labels
if (workstreamField) {
let workstreamValue = null;
if (labels.includes('scope:gateway')) {
workstreamValue = 'Gateway (.NET)';
} else if (labels.includes('scope:intelligence')) {
workstreamValue = 'Intelligence (Python)';
}

if (workstreamValue) {
const option = workstreamField.options.find(o => o.name === workstreamValue);
if (option) {
const updateFieldMutation = `
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}) {
projectV2Item { id }
}
}
`;

await github.graphql(updateFieldMutation, {
projectId: project.id,
itemId: itemId,
fieldId: workstreamField.id,
optionId: option.id
});

console.log(`Set Workstream to: ${workstreamValue}`);
}
}
}

# ============================================================
# RELEASE AUTOMATION: Generate changelog and release
# ============================================================
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,4 @@ assets/pdf-templates/*.pdf

# Workflow state (session-specific)
docs/workflow-state/
.worktrees/
7 changes: 5 additions & 2 deletions apps/dashboard/Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,10 +11,13 @@ FROM node:22-alpine AS builder
WORKDIR /app

# Copy package files first for layer caching
COPY package.json package-lock.json* ./
COPY package.json ./

# Install dependencies
RUN npm ci
# Note: Using npm install since this Dockerfile builds in isolation without
# the monorepo's package-lock.json. For reproducible CI/CD builds, use
# Dockerfile.build from the monorepo root instead.
RUN npm install

# Copy source and build
COPY . ./
Expand Down
55 changes: 55 additions & 0 deletions apps/dashboard/Dockerfile.build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# ===========================================================================
# AuthScript Dashboard - Full Build Dockerfile
# For CI/CD when pre-built assets aren't available
#
# Build from monorepo root:
# docker build -f apps/dashboard/Dockerfile.build -t authscript-dashboard .
# ===========================================================================

# ---------------------------------------------------------------------------
# Stage 1: Build
# ---------------------------------------------------------------------------
FROM docker.io/library/node:22-alpine AS builder

WORKDIR /app

# Copy workspace config and lock file
COPY package.json package-lock.json ./

# Copy shared packages
COPY shared/types ./shared/types
COPY shared/validation ./shared/validation

# Copy dashboard package.json for dependency resolution
COPY apps/dashboard/package.json ./apps/dashboard/

# Install all workspace dependencies with cache mount
RUN --mount=type=cache,target=/root/.npm \
npm ci --workspace=apps/dashboard --workspace=shared/types --workspace=shared/validation

# Build shared packages first
RUN npm run build -w shared/types && npm run build -w shared/validation

# Copy dashboard source
COPY apps/dashboard ./apps/dashboard

# Build dashboard
RUN npm run build -w apps/dashboard

# ---------------------------------------------------------------------------
# Stage 2: Runtime with nginx
# ---------------------------------------------------------------------------
FROM docker.io/library/nginx:1.27-alpine

# Copy built assets
COPY --from=builder /app/apps/dashboard/dist /usr/share/nginx/html

# Copy nginx configuration
COPY apps/dashboard/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1

CMD ["nginx", "-g", "daemon off;"]
Loading
Loading